How tied together they are today
1DeployableOne Worker, one wrangler config, one deploy lock, one 128-binding budget.
246Shared lib filesreached by both sides (284 OS-only, 28 public-only)
103Direct cross-importslib files imported by public code and reachable from the OS
662 / 274Import edgesOS into shared set / public into shared set
~40Public files that import OS codethe real coupling to cut
~100Side-effect edgesfunnel calls Slack, Zoom, WhatsApp, journeys in-process
The heaviest shared files (imports from public / from OS)
| File | Public | OS | Role |
|---|---|---|---|
lib/env.ts | 21 | 116 | One typed Worker env |
app/db.ts | 14 | 42 | DynamoDB layer |
lib/pg.ts | 3 | 50 | Neon client |
lib/slack.ts | 5 | 18 | All Slack cards |
lib/guards.ts | 11 | 12 | Request guards |
lib/apply-review.ts | 8 | 9 | Human review flow |
lib/crm.ts | 7 | 10 | CRM switchboard |
lib/tracking.ts | 7 | 7 | Attribution |
flowchart LR
subgraph NOW["Today: one Worker"]
P1["Public funnel"] --- S1["Shared lib: env, db, pg, crm, slack"]
O1["ThybridOS"] --- S1
P1 -.->|"in-process calls"| O1
end
Target: two repos, one core package
flowchart TD
subgraph PUB["thybrid-landing repo (Thybrid)"]
F["app/apply, book, ve, pages"]
FA["api: apply, booker, booking, otp, phone, ev, rec"]
H["hot-worker, media-worker, leads-worker"]
end
subgraph CORE["packages/core (shared, versioned)"]
C["env, db (DynamoDB), pg, crm, guards, token, tracking, types"]
end
subgraph OSR["thybridos repo (ThybridOS)"]
OA["apps/os: admin.thybrid.com"]
OD["packages/domains: booking, journey, calls, social, ..."]
OW["workers: sweep, crm-sync, jobs, mcp"]
end
PUB --> CORE
OSR --> CORE
FA -->|"signed events: application, booking, cancel, reschedule"| OA
thybridos/
apps/os/ Next.js 16 + OpenNext, admin.thybrid.com
app/(admin)/... today's app/admin-v2 pages and server actions
app/api/{admin,cron,slack,stripe,zoom,whatsapp,meta,call-bot,
call-transcript,v1,ses,email,marketing,reports}
middleware.ts admin host, CSP, fetch-site
packages/core/ env, db (DynamoDB), pg, crm, guards, token, tracking, types
packages/domains/ booking-native, journey, automations, calls, call-bot,
zoom-native, social, connections, command-centre,
personal-notify, privacy, exports, sync, ask, ai,
email-campaigns, invariants, weekly-digest, webhooks, recorder
workers/ sweep, crm-sync, jobs, mcp
db/migrations/ 0001 to 0033 plus pending/
infra/ Terraform: DynamoDB, S3, Lambda ads-sync
docs/ tests/
The public repo keeps app/apply, book, ve, b, confirm, reschedule, the marketing pages, api/{apply,booker,booking,otp,phone,ev,rec} and hot-worker, plus a thin client of the core package.
Integration contract between the two
| Seam | Rule |
|---|---|
DynamoDB thybrid-applications | Funnel writes rows (create, draft, booking fields, events). The OS reads and updates status, notes, outcome and payment. Needs a written row schema and the existing optimistic-write rules. Triage status stays in data.triageStatus. |
| Neon CRM | The OS owns schema and migrations. The funnel gets a narrow database role (contact upsert, appointment create, slots read) instead of the owner role. |
| KV switches | The OS writes settings:*, the public site reads. Fail-open behaviour is kept. Either share the namespace or split by prefix. |
| Events | Funnel posts signed events to POST /api/ingest/{application,booking,cancel,reschedule} on the OS, or uses a Cloudflare Queue. Precedents exist: the sweep Worker's service binding and the call-score queue. |
| Links | OS to funnel for /j/ and /prepare links through SITE_ORIGIN. |
| Provider webhooks | Stripe, Slack, Zoom, WhatsApp, Meta, Resend and SES point only at the OS. Each vendor URL change is a human-gated step. |
| Shared secrets to align | Cron secret, reply token, reschedule secret, transcript link secret, short-link secret, admin session secret. Split the Secrets Manager manifest so the public Worker no longer holds Zoom, Stripe, Slack signing, Resend inbound or Meta DM secrets. |
What has to be cut or duplicated
- Host and routes. Stop serving admin.thybrid.com from the
thybridWorker. Move the custom domain, the Access policy and the middleware admin branch to the new Worker. Old/adminredirects stay on the public site. - Core package. Move env typing, db, pg, crm, guards, token, tracking, throttle and applicant helpers into one internal workspace package. Copying instead would drift (env.ts alone is imported 137 times).
- Side effects. Public booking and apply routes call Slack cards, journey emit, call-bot, personal-notify, WhatsApp and Zoom in-process. Replace with an outbound event. This is the biggest cut.
- OS imports in public code. About 40 public files import from
admin-v2(call-confirm, call-hold, team-booking, crm-native, wa-senders, apply-review-deps, booking-native hours and google-connect, privacy erase). Relocate those helpers to the core package first. - Booking engine.
booking-nativemust stay callable from the public site. Either keep it in core with the funnel reading slots (hot-worker already isolates that), or expose slots and book from the OS. - Crons. The sweep Worker's service binding and URL variables re-target the OS Worker. The sweep route itself reads DynamoDB and expires unbooked applications, so it stays in the OS.
- Deploy tooling.
scripts/deploy.mjs, the worker guard, env-bake allowlist and binding-count guard are single-target. They need a second target. - Satellites.
crm-sync,mcp,jobsandcron-workermove with the OS.leads,adset-analyzer,media,hot, the static sites,ads-syncandalarm-slackstay independent.reports-worker(Command Centre) overlaps OS Home and needs a keep-or-retire decision.
Suggested order of work
flowchart LR A["1. Relocate the 40 OS helpers used by public code"] --> B["2. Extract packages/core"] B --> C["3. Event contract for funnel side effects"] C --> D["4. Stand up OS Worker on dev, own KV prefix and secrets"] D --> E["5. Move webhooks and crons, vendor by vendor"] E --> F["6. Cut admin host over, remove OS branches from public middleware"] F --> G["7. Apply row-level security, then multi-tenant"]
Steps 1 to 3 change no deployed behaviour and can ship from the current repo. Steps 4 to 6 each need the owner's word before anything goes live.
Risks
- The import counts treat any import as a dependency, so barrel files make the shared set look larger than it is. Dynamic imports and
scripts/were not analysed. - Two copies of the code is the failure the team already hit once (a stale tree overwrote production). The split must leave exactly one deployable per host.
- The dev Worker writes to production data. The OS dev environment needs its own data rules from day one.
- Vendor webhook changes (Stripe, Slack, Zoom, Meta, Resend) are manual and the moment of highest outage risk. Do them one at a time with the old route kept as a forwarder.
- The deploy lock and 128-binding cap are on the main Worker today. Splitting removes pressure on the cap but doubles the release process.