Platform Bus V1.0.0 shipped in June 2026: DarDev replaced the Node sync-worker integration service on dardev-vps with a standalone Go binary at platform/bus, listening on port 3100 with the same HTTP surface area. DarDev Ops—the Next.js console at ops.dardev.net—stayed TypeScript. Twenty CRM, Listmonk, and Stalwart were untouched. The migration was a runtime swap behind frozen contracts, not a big-bang rewrite of every caller.
We abandoned the earlier SW-1 plan to migrate Node to TypeScript inside sync-worker. Go won for a single static binary, smaller Swarm images, and alignment with company backend standards for security-critical paths like bearer auth, webhook HMAC, and SSH allowlist execution. Track progress in ops/tracker/platform-go.json; gate checks live in docs/runbooks/PLATFORM-GO-GATE.md. Companion guides: frozen-http-contracts-strangler-migration for the method, crm-orchestrates-bus-executes for why callers did not change, multi-product-saas-one-core for domain split.
Two-domain architecture
- Domain A — ops/ TypeScript Next.js: UI, RBAC, manifest spine, runsOn ops connectors
- Domain B — platform/bus/ Go: integration bus, webhooks, mail jobs, news API, SSH bridge
- Third-party — vendor images: Twenty, Listmonk, Stalwart unchanged
- Coupling — HTTP only via bus-proxy.ts; Ops never imported sync-worker modules
Ops proxies authenticated operator actions to the bus. CRM workflows POST to mail.dardev.net API paths. Cron hits daily digest endpoints. None of those callers needed to know the implementation language flipped from Node to Go—only that status codes and JSON shapes stayed stable.
Prerequisites we met before cutover
Ops Maturity V1.0.0 milestones M16 through M22 completed first—state, security gate, scrapers, metering, multi-mail, Swarm topology. M17 security gate enforces bearer tokens and webhook verification fail-closed. Manifest bus.routes entries plus validate-bus-parity.mjs kept handler keys aligned. packages/contracts/ held a fifty-route catalog regenerated by ops-validate bus-sync-catalog.
Milestone map P0 through P5
- P0 PG-S1 — Contracts, docs, platform-go tracker bootstrap
- P1 PG-S2 — go.mod, auth middleware, Dockerfile, core health routes
- P2 PG-S3 — Strangler wave 1: core-webhooks, ops-intel, listmonk groups
- P3 PG-S4 — Strangler wave 2: news, news-articles, daily, mail-jobs, services-outreach
- P4 PG-S5 — Strangler wave 3: server-discover SSH, site routes, team-alert, connector-writes
- P5 PG-S6 — Production cutover: all fifty routes on Go, sync-worker archived
Each milestone had gate validation: go run ./cmd/ops-validate platform-go-status --gate P2 style checks, plus go test ./... inside platform/bus. Golden tests hit the same URLs against Node baseline then Go implementation until responses matched.
Route groups at a glance
Thirteen groups span two core health endpoints, high-risk webhooks and services-outreach, daily digest SMTP, mail-jobs batch sender, eleven news-articles CRUD and publish paths, ops-intel mail provider probes, server-discover SSH executor, listmonk proxy routes, and connector-writes. Riskiest ports were webhook auth, SSH allowlist, and news SQLite export—exactly why we wanted Go's simpler concurrency model and static binary deploy.
Go module layout
platform/bus/cmd/bus/main.go boots the server. internal/config reads the same env keys as legacy sync-worker for compatibility. internal/auth implements bearer and webhook HMAC from M17 rules. internal/registry maps handler keys from the catalog. Handlers split per route group packages: news with modernc.org/sqlite pure Go driver, mail with SMTP job queue, ssh with allowlist executor. Dockerfile produces roughly fifteen to twenty-five megabyte image versus Node alpine plus native sqlite build chain.
Docker and Swarm impact
During P1 through P4 dev, optional compose profile platform-bus-go could run Go on alternate port beside Node. Production P5 flipped docker-compose.yml default service to platform-bus with sync-worker alias for env compatibility. Swarm manager runs bus plus ops; worker runs mail stack—lighter manager images matter when pulling on every deploy. Legacy Node tree moved to archive/sync-worker-legacy/ with profile for emergency rollback only.
V1.0.0 done criteria
- All P0 through P5 milestones done in platform-go.json
- platform-bus default in compose with sync-worker service alias
- platform-go-status --validate returns complete true
- Production dardev-vps rebuilt with Go bus on :3100
- Fifty golden routes plus M17 curl smoke plus ops remote smoke green
Validation timestamp landed 2026-06-08. DarDev Ops operators noticed faster cold starts and smaller image pulls—not a UI change. CRM twenty workflows, daily cron, and news publish pipeline continued without manifest edits.
What we explicitly did not do
No rewrite of Ops or Twenty or Listmonk. No public URL path changes. No manifest handler key renames. No forcing Hesabi cloud to call dardev-vps bus. Env names SYNC_WORKER_SECRET and SYNC_WORKER_URL kept working; PLATFORM_BUS_URL alias optional later. That restraint is the strangler pattern described in frozen-http-contracts-strangler-migration.
Operator impact
Deploy scripts and Fabric flows still rsync repo and compose up platform-bus. Logs say platform-bus instead of sync-worker. Troubleshooting mail jobs or news export reads Go structured logs. Security reviews focus on internal/auth and internal/ssh packages. DarDev Ops Configure tab still shows connector capabilities—it orchestrates, never embeds SMTP truth per ops-orchestrates-never-configures-mail discipline.
If you operate DarDev infrastructure, treat Platform Bus V1.0.0 as the current integration spine. Node sync-worker is legacy reference only. New routes must land in packages/contracts first, then Go handlers, then manifest bus.routes—a contract-first pipeline that scales as we add solutions without another language migration panic.
Lessons for the next migration
Invest early in catalog generation and parity scripts—manual route spreadsheets rot. Keep env var aliases one release longer than you think necessary. Run side-by-side on alternate ports only where dual memory cost is acceptable; dardev-vps single-node cutover was fine with golden tests green. Document archived paths so new engineers do not import legacy sync-worker by mistake.
Platform Bus V1.0.0 is not the end of platform-go tracker work—future routes for agent mail, license heartbeat, and scraper metering will extend the same catalog discipline. The shipped story is proof the strangler pattern works on a live CRM-and-mail stack serving real outreach, not a greenfield demo.
Memory and CPU on dardev-vps manager node dropped measurably after removing Node sqlite native builds from the hot path—secondary benefit beyond image size. Swarm deploy windows shortened when pulling platform-bus compared to sync-worker layers. These ops wins matter at our scale even before counting engineer preference for Go static analysis tooling.
Twenty CRM MCP and workflow HTTP actions were regression-tested against Go handlers before P5—not because MCP requires Go, but because outreach is revenue-adjacent for 20.tn and services. Any bus migration that breaks send-one is a P0 rollback regardless of image size wins.
Is sync-worker still running in production?
No. platform-bus Go service is default on dardev-vps port 3100. Legacy Node code lives in archive/sync-worker-legacy/ for reference.
Did DarDev Ops get rewritten in Go?
No. Ops stays TypeScript Next.js. Only Domain B integration bus moved to Go behind HTTP proxy.
How many HTTP routes migrated?
Fifty routes in thirteen groups, cataloged in packages/contracts/bus-route-catalog.json.
Why abandon Node-to-TypeScript SW-1?
Strangler rewrite to Go delivered smaller images and matched company backend direction without rewriting Ops callers.
How do I verify bus health after deploy?
Run platform-go-status --validate, golden route tests, and ops remote smoke documented in PLATFORM-GO-GATE.md.



