Second client instance — implementation plan
Status: Planned — reviewed and rebaselined; P0 blocked on kwiss's decisions
Author: Fable (PLAN second client instance)
Date: 2026-09-03 (rebaselined 2026-09-04 and again 2026-09-07 after two rounds of dual adversarial review)
Repo: north-os
Related: specs/2026-09-03-second-client-instance-design (the spec this executes; conflict ids C-n and test ids T-n refer to it) · specs/2026-07-29-zero-downtime-deploy-design · packages/db/MIGRATIONS.md
The dates are real. Today is 2026-09-04. The instance must be usable so the client's admin can be invited, onboard, connect OneDrive firm-wide and start ingesting; the firm is in real use from 2026-10-15. The weeks between were meant as deliberate bug-finding slack and are not to be spent finishing this plan.
What changed on 2026-09-04. Two independent adversarial reviews rejected the first version of this plan. Both concluded 09-20 was unreachable as scheduled; both found work on the critical path that the plan did not contain. The largest is C-17: a firm that does not use Clio cannot finish onboarding at all, so it never reaches the step where OneDrive is connected. Two others are external dependencies with lead times North does not control (the client's Entra tenant consent, a second email sending domain). One phase — P2's parameterization of the deploy shell — was found to be the wrong shape entirely, because deploy/preprod/install.sh seeds its database from the development database and deliberately runs NODE_ENV=development with the insecure cipher. This version is rebaselined around those facts. Spec §8 records every corrected claim.
What changed on 2026-09-07. A second review round (Fable 5.1 + GPT-6-Astra) on the rewritten version found the 09-04 fixes incomplete in three places: the scheduler-login split does not close the privileged path because the runtime login is also a member of the table-owning role forma; "no service references the owner DSN" cannot be implemented because every worker requires it; and "reuse the deploy machinery verbatim" does not work because bluegreen.sh pins heynorth.dev in production mode. It also sized C-17 down to one prop plus a persisted choice, moved the RLS enumeration tests off the client's critical path, withdrew the second-sending-domain dependency (heynorth.dev is North's domain, not HSE's), and added two connector findings that matter more than most of P3: the callback never checks which Microsoft tenant authorized it, and a delegated token sees the connecting account's corpus, not the firm's. Spec §8.1 records all of it.
0. The date, stated honestly
The first version scheduled the separate-deployment path to land exactly on 09-20, with no fallback trigger, while the spec simultaneously claimed the ingestion date was never at risk. Both cannot be true, and the second is now known to be false (C-17 blocks either topology). So:
- 09-20 is reachable only for a preprod-grade instance — own host, own cluster, own Redis, own bucket, real production build, but backups, a second sending-domain reputation history and any deploy-tooling generalization deferred. That is decision §7.3 in the spec, and it is the difference between a date that holds and one that does not.
- A conjunctive readiness gate on 2026-09-12. On 09-12, all of the following must be true or the onboarding start moves: the client's Entra consent granted with all scopes and one authorization completed; the expected M365 tenant id recorded; P1 merged; a host exists and instance 2 has booted once in production mode with KMS; P3's bootstrap role hygiene in place; a real email delivered from instance 2. Any one missing moves the date (the round-one wording said "and", which was a logic error — each prerequisite blocks P4 independently). If moved, 09-27 is the target, conditional on the same gate re-passing by 09-19 — not an automatic promise. Choosing the shared-deployment fallback recovers no time: C-17, the consent and the tenant pinning all bind on both topologies.
- Where things stand on 09-07: no host has been provisioned, no P1 or P3 branch exists, spec §7.1–7.3 are still "recommendation", and the consent request has not gone to the client. Every day of P0 that has not started is a day the gate is already behind.
What is needed for the second client, and what is not
- P0 external dependencies + decisions — needed; critical path; the only work whose clock North does not own.
- P1 onboarding without a practice-management system (C-17) — needed on both topologies; critical path; application work, not infrastructure.
- P2 instance bootstrap on a dedicated host — needed (separate topology); critical path.
- P3 credential hygiene at bootstrap + T4/T7/T8 — needed; the RLS enumeration tests (T1/T3) move to October as HSE hygiene and shared-topology insurance.
- P4 OneDrive end to end on a synthetic firm — needed; gates the ingestion start.
- P5 production readiness — a minimum backup + restore proof and the KMS restart proof move into P2, before the admin is admitted (spec §7.7, recommended); the richer automation, dashboards and provider-capacity work stay before 10-15.
- P6 client onboarding — the client's own work, from the date decision §0.
- P7 shared-deployment prerequisites — only if the fallback is taken.
- P8 self-service posture and follow-ups — not needed for client 2 at all; explicitly out of this cycle.
Ordering rule. Nothing in P5, P7 or P8 may block P1, P2 or P4. And P8 does not start on 09-20: the slack between onboarding and 10-15 is for finding bugs in a live client's instance, which is the entire reason it exists.
P0 — External dependencies and the decisions they depend on
2026-09-04 → 09-08. Owner: kwiss (decisions and client-facing asks), then an omp worker (config). Blocks P2 and P4.
- Decisions §7.1, §7.2, §7.3, §7.4, §7.5, §7.7, §7.10 of the spec. Everything below assumes: separate deployment, dedicated host, and an explicit answer on environment grade.
- Microsoft tenant readiness (C-20, C-29, C-30) — start today. Name the client's Entra administrator; decide shared or dedicated app registration (§7.5); send one consent request carrying every scope the product will ever ask that tenant for — OneDrive's
Files.ReadWrite.All,Sites.ReadWrite.Alland Outlook'sMail.ReadWrite,Mail.Send,User.Read— and prove it by completing one authorization. Record the client's M365 tenant id; it becomes instance 2 configuration that the connector enforces (P1). Ask for the corpus-location inventory (which drives, which SharePoint libraries) and a durable connecting account with read access to all of them, plus who owns that account when someone leaves — the delegated token sees what that account sees, nothing more. Target: consent and tenant id by 09-10. The redirect URI is derived fromBETTER_AUTH_URLand the Graph webhook URLs are supplied per subscription request, so the registration work is the URIs; the slow work is the client's IT. - Transactional mail (C-19, corrected). Reuse North's
heynorth.devsending domain with instance 2's own Resend credentials — no new domain, no DNS wait. What must happen:RESEND_API_KEYrequired andAPP_DEV_MAIL_OUTBOX_DIRrefused whenNODE_ENV=production(today the first is optional and the second overrides Resend even with a key);RESEND_NEWSLETTER_FROMset explicitly; and the gate is a real email received in an Outlook inbox whose link points at instance 2. - Host, DNS, TLS. Subdomain is
<firm>.northos.com(decided 09-07); create the record; confirm TLS issues. Verify by body comparison against a random hostname — the wildcard blocks answer 200 for anything. - Its own R2 bucket with a bucket-scoped API token (C-11, C-31), its own Scaleway KMS key, its own Sentry project, its own Resend credentials, its own secrets file. Not
north-os-dev, not a copy of the dev environment, and no shared SaaS project that would receive both firms' payloads. - Deploy path (decided 09-07): a bounded instance-2 script, no blue/green. #354/#362 merged and not yet deployed on instance 1, and
bluegreen.sh:25-29pinsheynorth.devin production mode — so instance 2 does not reuse that loop. A ~50-linedeploy.sh(fetch, build, migrate, restart, health) plus a matching rollback to the previous release directory — run by the deploy bot through thedeploy-preprodskill with an explicit instance argument, on kwiss's word, never by hand. The skill and the bot's prompt learn the instance name in P2; a deploy that does not name one refuses. P2 verifies first install, one further release and one rollback, all through the bot.
Done when: the subdomain answers over TLS with a placeholder and a distinguishable body; the client's tenant has consented to all scopes, one authorization has completed and the tenant id is recorded; the corpus inventory and connecting account are named; a real email has been received from instance 2; the bucket, scoped token, KMS key and Sentry project exist; and the deploy-path decision is written down.
P1 — Onboarding without a practice-management system (C-17) + tenant pinning (C-29)
2026-09-08 → 09-10. Owner: claude opus worker (onboarding UI, flow, copy) for C-17; omp worker (connector) for C-29. Critical path, on either topology. Round two sized C-17 down from five days to roughly one: Skip already exists behind a prop, the advance route has no Clio check, and the settings page already exists.
Today: signup lands the organization at onboarding_step="invitations"; the order is invitations → matters → vault → apps; the matters step enables Continue only when Clio is authorized and a matter is tracked or sync_mode="everything", with Skip hidden; and the vault step — where OneDrive is connected — redirects away unless the organization already reached onboarding_step="vault". A OneDrive-first firm is therefore stuck before it can connect anything, and cannot invite its own people either.
- Decided (spec §7.4, amended 2026-09-07): the matters step shows Skip (
matters/page.tsx:171, drophideSkipconditionally) on an explicit, persisted choice — "we don't use a practice-management system" — not on the absence of a Clio connector, which a fresh Clio firm also has at that moment. The choice lives on the organization (one column, one migration; the schema wave is this one, nobody else runsdb:generate). Back/forward navigation inapps/web/lib/onboarding.ts:6-12,25-35,64-74is fixed-sequence and must respect the skip both ways. - The settings return path is the existing Clio settings page (
(settings)/connections/clio); P1 only has to make sure a completed no-system firm can reach it and connect later without re-entering onboarding.onboarding_stepstays the single source of truth for every step page's redirect. - C-29, tenant pinning: instance configuration carries the client's expected M365 tenant id;
connector-onedrive/src/auth.tscompletion (and reconnect) refuses a bundle whosetenant_id_m365does not match. On instance 1 the value is HSE's tenant. Small, and it closes the wrong-account path that every other test in this plan would miss.
Acceptance, stated as flows, each pinned in packages/e2e/tests/onboarding.spec.ts (which today works around the wall by advancing the step in the DB — that workaround goes): (1) a synthetic admin admitted by signup code, choosing "no practice-management system", reaches the vault step, connects OneDrive firm-wide, selects sources, starts ingestion, and can invite a colleague; (2) a fresh firm that does not make that choice sees today's Continue-only matters step, unchanged; (3) a completed no-system firm connects Clio from settings later. Plus (4) a wrong-tenant Microsoft authorization is rejected.
Gates: bun run lint, bun run typecheck, apps/web tests, packages/connector-onedrive tests, the e2e onboarding guard.
P2 — Instance 2 bootstrap on a dedicated host
2026-09-08 → 09-12 (first production boot), → 09-16 (second release, rollback, backup and KMS proofs). Owner: omp worker (shell, systemd, provisioning), with kwiss's word for each step that touches a live host and the deploy bot performing it. Critical path. Round two's forecast, which I share: this is the phase most likely to slip, because it is the first NODE_ENV=production + Scaleway KMS boot of this stack anywhere and every step needs a human approval.
This replaces the first version's "lift instance identity into a per-instance config file". That approach assumed deploy/preprod/install.sh was a general installer whose logic could stay byte-for-byte equivalent. It is not: it requires the dev workspace and the dev app-postgres container, it creates its database by pg_dumping the development database, it copies the development environment minus an override list, and it deliberately runs NODE_ENV=development with the insecure in-memory cipher. Generalizing it would either import dev and HSE data into a paying client's instance or require exactly the semantic surgery the approach was chosen to avoid. On a dedicated host there is nothing to generalize for — no port collision, no name collision — so:
- Reuse the existing internal names, paths and ports verbatim on the new host. The 383 literals stop being a problem the moment nothing else on the machine answers to them.
- Write a small production bootstrap (a script plus a runbook, not a framework): create an empty database; create the logins first and pin who does what —
north_owner(non-superuser, assumesforma) runs migrations soformaowns the tables,north_appis grantedapp_role+sync_roleonly,north_schedulerholdsscheduler_role; runDB_APP_LOGIN_USER=north_app bun --filter=@workspace/db provision(it throws without that variable) withASSUMABLE_ROLESreduced accordingly; write an explicit allowlist of secrets rather than copying an env file; setNODE_ENV=productionwith real Scaleway KMS; point at instance 2's own R2 bucket, its own Redis service and its own public URLs. The superuser DSN exists for migrations and break-glass and is referenced by no unit; every service's owner pool usesnorth_owner, and the chassis'sDATABASE_URLfallback for that pool is refused in production. - KMS rehearsal, before any client credential exists: encrypt a synthetic connector credential, restart the fleet, decrypt it, rotate the key once.
runtime-cipher.ts:111-114throws in production without KMS; the Scaleway cipher has only unit tests behind it. - Minimum recovery, before the admin is admitted (spec §7.7): an encrypted
pg_dumpto a named destination on a timer, one restore actually performed into a scratch database and the app booted against it. Not the full backup subsystem — that stays in P5 — but the first mechanism that turns "total data loss" into "a bad afternoon". - Its own Redis service (C-22), not a logical db.
clicky.eventsandnorthos:thread-activityare global channel names that ignore the database number. - Its own Postgres cluster (C-23), which a dedicated host gives for free. Roles and memberships are cluster-global; a second database in the existing cluster shares the entire privilege model.
- The bounded
deploy.shand its rollback (P0 decision). Own bare repo and release directories; a deploy that does not name its instance refuses. The deploy bot addresses instances by name from day one (thedeploy-preprodskill gains the argument; the bot's standing prompt names both). C-15's per-instance changelog baseline is deferred to P5; a wrong-instance deploy is never acceptable. - One alert on
/api/health/deep, on the single Better Stack path, proven to fire. Dashboards (C-14) are October. - Deliberately deferred: unifying instance 1 and instance 2 onto shared tooling. That is a refactor for after 10-15, with two live instances as its test, and it must not sit on this critical path.
Verification: every service's boot probe reports its expected role and app_role is NOBYPASSRLS on the new database; pg_auth_members for north_app lists exactly app_role and sync_role; the front door is green on /api/health/deep compared by body, not status; first install, a second release and one rollback performed; KMS and restore proofs recorded; and T8 — instance 2's credentials cannot connect to instance 1's database, Redis, bucket, KMS key or internal APIs (a two-line check on a localhost-bound host). Do not deploy two instances concurrently; two simultaneous production builds are unmeasured.
Gates: deploy/preprod/bluegreen-sim.test.sh and deploy-build-isolation.test.sh still pass for instance 1 (this plan does not touch its shell, so a failure means something unintended moved), plus scripts/preprod-install-env.test.ts, scripts/monitoring-config.test.ts, scripts/preprod-caddy-ports.test.ts.
P3 — Credential hygiene and the tests that guard instance 2
2026-09-08 → 09-12. Owner: omp worker (provisioning, tests). Parallel to P1/P2. Narrowed twice: round one dropped T2/T6 to October; round two moved T1/T3 there too, because on the recommended topology they guard cross-org reads inside one database — and the database with 51 organizations and a real corpus is heynorth.dev, not instance 2. What stays is what a fresh instance gets for free at bootstrap plus the tests whose failure would mean a real hole on instance 2.
- Credential graph (spec §7.6, decided 09-07: instance 2 only, at bootstrap). The P2 bootstrap creates three logins —
north_appgrantedapp_role+sync_roleonly,north_scheduler→scheduler_role,north_owner→forma(non-superuser, runs migrations soformaowns the tables) — and pointsWORKER_SCHEDULER_DATABASE_URLand the services'OWNER_DATABASE_URLat the last two. Provisioning takes the assumable-role list from configuration rather than the hardcoded four, and the chassis refuses theDATABASE_URLfallback for the owner pool in production. Instance 1 is not touched before 10-15; its sequenced change is in P8. - T4 — privileged reachability. A connection made with a runtime credential cannot read firm A's rows by any role it can assume, enumerating
pg_auth_membersrather than a hardcoded list. Fails today twice (scheduler_role: 263chat_threadrows across 2 orgs;forma: owner bypass onworking_profile_answer). Goes green only when the runtime login's memberships are exactlyapp_role+sync_role. Do not touch FORCE: it constrains neither path and would breakworking_profile_answer's deliberate design. T4b: no service unit references the superuser DSN. - T7 — checkpointer thread authorization. Every graph entry point resolves its
thread_idthrough the org-scoped thread helper, and every writer ofagent_job.thread_id(the gateway dispatch route,apps/clicky-gateway/src/dispatch.ts) proves the thread belongs to the job's organization —clicky-run-loop.tsitself is fine, it loads the job under RLS. Fix the stale file reference incheckpointer.ts:19in the same change. - T8 — cross-instance negative + wrong-tenant rejection. The first half in P2; the tenant half from P1's C-29 change, asserted here.
- C-4 —
matter_hidden. AddpgPolicy()+.enableRLS()inpackages/db/src/schema/matter.ts; the migration rides P1's schema wave (one agent runsdb:generate). Read the generated SQL line by line: 64 hand-writtenFORCE ROW LEVEL SECURITYstatements across 25 migrations are invisible to the TS schema. - Moved to October (P8): T1 (schema invariant — three lists: full store inventory, RLS-covered subset with per-command
USING/WITH CHECKassertions, compensating-control subset with a written control each; non-FK stores registered, not discovered), T3 (no-GUC fail-closed, scoped to the covered subset), T2, T6. They are HSE hygiene and shared-topology insurance, and they are still worth doing.
Mutation discipline is part of the deliverable. T4, T7 and T8 must each be shown to go red when the invariant they guard is broken — grant a membership back, hand a graph an unauthorized thread id, authorize from the wrong tenant — and that demonstration goes in the PR body. Two versions of this plan have now specified a T4 that would have passed on day one while a hole stayed open; that is the failure this rule exists to prevent.
Gates: bun run lint, bun run typecheck, bun run test from packages/db/ (its script is Vitest; the earlier bun --filter=@workspace/db test wording was wrong), plus the tests of any package whose export surface moved.
P4 — OneDrive end to end on a synthetic firm
2026-09-13 → 09-17. Owner: claude opus worker (flows, ui-test) plus omp worker (connectors, subscriptions). Gates the ingestion start. This is the first run of the firm-wide OneDrive path on this codebase — the only OneDrive connector in the live database is HSE's, revoked — so budget for finding things.
Against a synthetic organization on instance 2, in the client's consented tenant, with the client's real corpus inventory in hand:
- Admission by a single-use, email-bound signup code; a real email received from instance 2's sending domain; sign-in completed. (Note the plan's earlier wording alternated between "an invitation-shaped path" and a signup code — they are not interchangeable: an invitation needs an organization to already exist, and the first admin's does not. The signup code is the path.)
- The no-Clio onboarding route from P1, all the way to the vault step.
- Firm-wide OneDrive connect end to end on a fresh organization, using the client's designated connecting account; and a wrong-tenant authorization refused (C-29).
- Drive and folder discovery compared against the client's corpus inventory (C-30) — every named library discovered, and one deliberately inaccessible library reported rather than silently skipped (today
drives.ts:75-79swallows 403s); the ingest-source picker, including the overlap guard against a lawyer's personal drafts location. - Sources actually activated and ingestion actually started — selection alone leaves rows at
status="pending", and the vault step can be passed without starting anything. - First ingestion reaching
knowledge_sourcesandknowledge_chunks, with a retrieval that returns the ingested material. - Knowledge tools appear only once the corpus is real and disappear on revoke, asserted on both surfaces — product toolbelt and MCP catalogue (non-negotiable #6). Per C-28 the implemented gate admits on connector
status="authorized"alone; the acceptance criterion here is corpus health, and the gap between the two is a finding to report, not to paper over. - Subscription lifecycle (C-24, narrowed). Creation; the OneDrive route wired to the existing Outlook lifecycle dispatcher (
outlook-lifecycle.ts:33-80) soreauthorizationRequiredis handled; an expired row leavingactive(today three rows sitactivepast expiry on a revoked connector); a forced renewal — move the synthetic subscription'sexpires_atinside the 7-day horizon and run one worker tick, asserting Graph-side and local expirations advance; and the dropped-webhook scenario: with notifications blocked, the scheduled rewalk (scheduled-rewalk.ts, hourly, 6-hour staleness) picks up a changed file within the stated bound. The freshness contract P4 asserts is "≤ 6 h without webhooks, minutes with them" — not "webhooks or nothing". - That the NetDocuments firm/personal exclusivity rules do not refuse a legitimate firm-wide OneDrive connection.
Stop condition, stated in advance: if firm-wide connect fails, if the connecting account cannot see the inventoried corpus, or if revoked access (reauthorizationRequired) is not surfaced, the onboarding date moves. A lapsed subscription alone is bounded by the rewalk and is not a stop; lost access that nobody is told about is.
P5 — Production readiness
2026-09-17 → 10-10. Owner: omp worker plus kwiss's word for anything touching a live host. Needed before 10-15. The minimum backup/restore and KMS proofs moved to P2; this is the rest.
- Backups (C-21) as a subsystem — and for both instances. HSE's live data has no copy anywhere today (verified 09-07: no timer, no cron,
archive_mode = off, no pgbackrest/wal-g, no backup directory; Scaleway Elastic Metal does not snapshot local disks on its own). Beyond P2's minimum: an RPO and RTO someone has agreed to, a stated database/R2 consistency rule, key handling for the backup encryption, retention, a written restore runbook, and a restore rehearsal on instance 1. - Dashboards and the remaining alert routes on instance 2 (C-14), on the single Better Stack path.
- Provider capacity before the client's first crawl (C-27): confirm whether the Anthropic and Voyage accounts are shared with HSE's live traffic and what their account-level limits are, and decide whether B's ingestion gets its own keys. This must be answered before P6's ingestion starts, whatever else in P5 slips.
- C-15 — per-instance changelog baseline in the gate; and instance 2's blue/green once it is in real use.
P6 — Client onboarding
From 09-20 if the 09-12 gate passed in full; otherwise from 09-27, conditional on the gate re-passing by 09-19. Owner: kwiss with the client's admin.
Generate one email-bound, short-expiry signup code for the admin (bun run scripts/gen-signup-code.ts --email …). They create the firm and name their own domain, then invite their own people from inside. Then connect OneDrive firm-wide, pick ingest sources, and start ingestion. Between onboarding and 10-15 the instance is watched for bugs — which is the reason the slack exists and the reason nothing in P8 should be consuming it.
P7 — Shared-deployment prerequisites (only if the fallback is taken)
Not on the critical path in the recommended topology, and note that taking the fallback does not recover the date: C-17 and the Microsoft consent both bind on either topology. If §7.1 nonetheless chooses a second organization inside heynorth.dev, these become P2-priority and must land before the client's admin exists.
- C-1 — resolver unification + T5. Every surface named in the comment at
apps/web/lib/session-resolver.ts:243-260converted to the shared resolver, and a user with two memberships and no active organization refused everywhere rather than given an arbitrary firm. Convert callers together with the pages they redirect to, or a stale-firm bug becomes a redirect loop — the lesson NOS-238 already paid for. - C-6 — Metabase scoping. Per-firm dashboards, or an accepted written decision that North's analytics view spans clients.
- C-5 — the three HSE-pinned environment variables reviewed for a two-firm deployment.
- C-25 — worker contexts narrowed so user-job workers do not hold owner and scheduler handles they never use; and an inventory of
SECURITY DEFINERfunctions and views that bypass row security. - T1, T3, T2, T6 in full (the enumeration and two-firm tests), and
newsletter_*scoped (C-32). - Every one of the spec's §1.3 uncovered stores accepted in writing as a two-firm risk, since none of them is closed by application RLS.
P8 — Deferred: self-service posture and follow-ups
Not in this cycle, and explicitly not on 09-20. The first version scheduled the self-service work 09-20 → 09-24, inside the bug-finding slack it insisted must stay empty.
- Self-service posture —
APP_SELF_SERVICE = off | code | open, defaultcode, one middleware on the signup router, invitations and auto-join never gated, theofflanding page, and the guard tests of spec §4.6. It is an env var and onesignupRoute.use("*"), and it will still be that in November. Nothing about client 2 needs it: their admin arrives by signup code, which is thecodeposture that is already the default. - C-12 rate limiting on the three public signup routes — a hard prerequisite before
openis ever switched on, and worthwhile anyway on a scanner-probed public subdomain. - C-13 global uniqueness for a claimed email domain.
- C-26 the eval merge gate's single-firm corpus — the one hardcoded HSE organization id that is not merely dev tooling, because it is wired into a gate named in CLAUDE.md.
- C-9 Polaris host as first-class configuration or a per-instance build. Until then Polaris is out of scope for instance 2, and that must be said to the client rather than discovered.
- T1, T3, T2 and T6 — the schema invariant (three lists, per-command assertions), no-GUC fail-closed on the covered subset, the two-firm read test folded behind T1's enumeration, and the per-namespace storage key parsers with a boot-time bucket marker. HSE hygiene; still worth doing.
- C-25 — worker contexts narrowed so user-job workers do not hold owner and scheduler handles they never use, and the
SECURITY DEFINERinventory. - The remaining hardcoded HSE organization ids in
scripts/and eval harnesses defaulted from an environment variable, so an engineer cannot run the wrong firm's corpus by pasting a command. - Instance 1 credential graph — the same three-login split as instance 2, sequenced: create logins → rewrite env → deploy →
REVOKE scheduler_role, forma FROM north_app. HSE hygiene; needs kwiss's word at each step. - Sender domain — move
RESEND_FROM/RESEND_NEWSLETTER_FROMtonorthos.comfor both instances once the app lives there. - Blue/green for instance 2, generalizing the post-#354 machinery with two live instances as the test.
Risks, with the honest ones first
- The client's Entra consent is not North's to schedule. It is a law-firm IT process with an unbounded lead time, and nothing in P4 can run without it. Response: it is the first item of P0, targeted at 09-10, and it is half of the 09-12 decision trigger.
- The client's corpus layout and connecting account are unknown (C-30). A delegated token sees what one account sees; if the firm's documents are spread across libraries that account cannot reach, "connected" means little. Response: the inventory and the account are P0 asks, and P4 compares against them.
- C-17 is small but its condition is subtle. Skipping on "no Clio connector" would silently change onboarding for a future Clio firm. Response: an explicit persisted choice, three flows pinned in e2e.
- Instance 1 keeps its current credential graph until October.
SET ROLE scheduler_role/formafromnorth_appstays possible on heynorth.dev, one firm, as today. Response: accepted explicitly (spec §7.6); P8 carries the sequenced change; T4 on instance 2 enumerates memberships so it cannot be satisfied by a shorter list than the real one. - P2 is the first production boot of this stack anywhere. KMS,
NODE_ENV=production, the post-#354 blue/green code, per-step approvals. Response: it gets the calendar's slack (through 09-16), a KMS rehearsal, and a deploy-path decision taken in P0 rather than discovered mid-phase. - Backups still do not exist for HSE. Response: P5 covers both instances, and it is scheduled before 10-15 rather than budgeted as a clause.
- Two concurrent production builds on this box — never measured, and the deploy path builds at full parallelism on 24 cores already at load 5–8. Response: sequential deploys only until measured. A dedicated host removes this entirely.
- Upstream provider rate limits during the first full ingestion (C-27). Response: measured in P5 before the client's ingestion, not discovered during it.
- A North account ends up in both firms. Convenient for support, and it is exactly what arms C-1 on a shared deployment — and, on a separate one, the C-29 wrong-tenant path if that operator also holds an HSE Microsoft account. Response: decision §7.9 before any such account is created, and tenant pinning in P1.
Who does what
Per docs/WORKFLOW.md § Who does what: P2, P3, P5, the C-29 connector change in P1 and the connector half of P4 go to an omp worker (schema, provisioning, shell, systemd, config, connectors). P1's onboarding change and P4's flow verification go to a claude opus worker (onboarding UI, flows, UX copy). Specs, plans, prompts, reviews, gates and git stay with the orchestrator. Only one agent touches the schema, in the P1 wave (the skip choice column and matter_hidden's policy ride the same migration). Every phase gets its own dual adversarial review before merge — a fresh Fable session and an omp reviewer at gpt-5.6-sol high — with an explicit instruction to audit what each diff deletes, and, for P1, to check that HSE's own onboarding path did not move.