Skip to content

fix(onboarding): restore secure organization creation - #7650

Merged
bokelley merged 1 commit into
mainfrom
conductor/p0-restore-organization-onboarding
Sep 22, 2026
Merged

bokelley merged 1 commit into
mainfrom
conductor/p0-restore-organization-onboarding

Conversation

@bokelley

@bokelley bokelley commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Restores safe self-service organization creation after the containment introduced in #7538:

  • enables new personal workspaces and genuinely new corporate organizations
  • binds the first owner to the exact authenticated WorkOS credential and current provider identity state
  • keeps prospect adoption fail-closed with a distinct response
  • keeps invitation acceptance/request-to-join surfaces contained and replaces active UI actions with owner-invite/support guidance

Security model

  • No authority is inferred from email strings, canonical person IDs, linked sibling identities, or historical memberships.
  • Corporate creation requires the exact authenticated credential, current provider email verification, and current verified-domain authority.
  • The local immutable identity snapshot, exact credential binding, authorization epoch, consent, marketing preference, organization, domain, membership, and audit effects finalize atomically.
  • Provider calls use bounded clients and stable provider idempotency/external IDs; database locks are not held across WorkOS calls.
  • A durable onboarding-operation ledger, leased recovery worker, compensation intent, and fencing handle retries, timeouts, ambiguous persistence, expired workers, and provider/local reconciliation.
  • Active leases cannot be stolen by a changed request. Transient database failures after provider writes remain retryable rather than triggering unsafe compensation.
  • The exact-credential authorization lock primitive is shared with membership mutations to preserve lock ordering.
  • API keys and M2M identities are explicitly rejected from first-owner onboarding.
  • Caller-supplied tier remains ignored; Stripe webhooks remain the only tier authority.

Contained flows

  • Prospect adoption returns a distinct fail-closed response; genuinely new organization creation remains available.
  • Request-to-join and invitation acceptance are not exposed as active public mutations. The onboarding UI directs users to an organization owner or support.
  • UI idempotency keys persist across reloads and are released only after confirmed terminal failures; retryable/reconciliation states retain the key.

Verification

Focused integration matrix against a clean PostgreSQL test database:

  • 346/346 tests passed across 8 files and 18 suites
  • prospect containment: 3
  • implicit-onboarding containment and credential swap/link cases: 61
  • invitation approval containment: 8
  • member-agent bootstrap containment: 6
  • membership mutation races: 65
  • membership mutations: 176
  • onboarding restoration, route indexes, and reconciliation alerting: 25
  • caller-controlled tier rejection: 2

The restoration suite covers API/M2M rejection, duplicate concurrent submissions, provider timeout/retry, current provider emailVerified and domain changes, expired-lease compensation fencing, ambiguous database persistence after provider writes, transient database lock retries, changed concurrent requests, and test-row cleanup.

Fresh database migration and validation:

  • all 581 migrations applied from scratch, including migration 606
  • migration validation: 3/3 passed

The mandatory commit hook ran the single authoritative fresh full precommit/typecheck after the focused matrix and staged diff checks:

  • canonical vectors: 31/31
  • governance JWS: 1/1
  • root Vitest: 1,079/1,079
  • repository lint tests: 18/18
  • server unit: 11,018 passed, 31 skipped across 666 files
  • TypeScript and fixed-trace rollout typecheck passed
  • git diff --check passed

Direct Codex review completed with no remaining actionable findings. Security review has no open Must Fix or Should Fix items.

Release and rollback notes

  • No protocol changeset is required; this is application/UI/migration-only work.
  • Migration 606 is additive. If application rollback is required, the prior code remains compatible with the new ledger table and will return the original global onboarding containment response.
  • Do not drop the ledger table during rollback; retain it for reconciliation/audit and deploy the previous application image through the normal Fly.io workflow.
  • After deployment, verify new personal/corporate creation is available while prospect adoption, invitation acceptance, API-key, and M2M attempts remain fail-closed.

@bokelley
bokelley force-pushed the conductor/p0-restore-organization-onboarding branch from 098ccb9 to f868b69 Compare September 22, 2026 22:00

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Comment (human reviewer recommended)

Comment — 1 medium finding; a human should look.

The reviewer examined the org-onboarding restoration: the ~1470-line organization-bootstrap.ts rewrite, migration 606 (production-facing, runs on startup), the reconciliation job, and the onboarding UI. Verified the durable ledger, lease-vs-provider-timeout safety (5s/no-retry enforcement client), credential fences, agreement-version agreement, and fail-closed containment. No changeset is required (app/UI/migration-only surface, out of protocol scope). No critical/high findings.

Medium findings

  • server/src/services/organization-bootstrap.ts — manual_reconciliation strands created WorkOS orgs with no alerting path and no worker re-visit (durable-state async failure hidden without operator visibility).

Gate walk (top-down): Row 1 (critical/high) — none. Row 2 — gated_paths false. Row 3 — no deletions in high-risk reasons; migration 606 is (added), the rest (modified). Row 4 — the single medium concerns durable-state/async recovery, not schema/data-loss/infra category. Row 5 — high_risk true with (modified) files, but this requires a medium finding; the medium here is on organization-bootstrap.ts, which is NOT among the high-risk-matched files (job-definitions.ts, migration 606, member-agents.ts, organizations.ts), so row 5 does not fire. Rows 6/7 — no prior decision, no team gate. Row 8 — fewer than three mediums.

With a single medium finding this would fall through to row 9 (approve). However review_decision is unknown/none and the medium (stranded WorkOS orgs with no alerting/re-visit on a durable async path) warrants a human eye before merge; surfacing as comment rather than blocking.

@bokelley
bokelley force-pushed the conductor/p0-restore-organization-onboarding branch from f868b69 to 53ee5a6 Compare September 22, 2026 22:35

Copy link
Copy Markdown
Contributor Author

Addressed the Ladon medium finding in 53ee5a6: manual-reconciliation operations are now revisited by the scheduled recovery job and surfaced as durable, high-priority Addie escalations with concurrency-safe deduplication. The bounded scan excludes operations that already have an active escalation so newer rows cannot starve; integration coverage proves two divergent operations are both surfaced across bounded scans. The exact-credential authority fence remains fail-closed and these cases are never auto-adopted.

aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Sep 22, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean pass, no blocking findings.

The reviewer examined the exact-credential first-owner onboarding restoration: the durable operation ledger (migration 606), the leased recovery worker, and the compensation/reconciliation saga in organization-bootstrap.ts, plus the contained invitation/join/claim surfaces and onboarding.html idempotency changes. Verified fail-closed data-loss guards (no provider deletion when a local org exists), atomic local commit gated on lease+status, exact-credential binding that never infers authority from canonical/linked identity, and bounded provider I/O with lease fencing on the reconciliation job. This is an app/UI/migration-only change with no protocol surface touched, so correctly no changeset.

Decision-rule walkthrough:

  • No critical/high findings (rows 1 skipped).
  • gated_paths is false (row 2 skipped).
  • high_risk is true, but the reasons are (modified)/(added) only — no (deleted) (row 3 skipped).
  • No medium findings, so no data-loss/schema/infra medium (row 4) and no high-risk-modified + medium (row 5).
  • Prior decision was comment, not escalate (row 6 skipped).
  • No no-auto-approve team match (row 7 skipped).
  • Zero medium findings, well under three (row 8 skipped).
  • Falls through to row 9 → approve.

Migration 606 runs automatically on startup and was judged as production-facing; the reviewer found no data-loss or destructive concerns. New files matching high-risk globs (addie job, migration) are normal scaffolding and not escalation-worthy on the flag alone.

@bokelley
bokelley force-pushed the conductor/p0-restore-organization-onboarding branch from 53ee5a6 to 15c9d0f Compare September 22, 2026 23:03

Copy link
Copy Markdown
Contributor Author

Resolved fresh aggregate CodeQL high alert from check-run 106968350988 structurally in 15c9d0f: caller-supplied clientIdempotencyKey no longer controls whether the authorization-sensitive replay lookup executes. Both keyed and fallback lookups now run unconditionally; SQL NULL gating preserves keyed/unkeyed replay semantics. Post-fix verification: focused onboarding/containment/invitation matrix 346/346, full mandatory hook 11,018 passed + 31 skipped across 666 server files, and TypeScript/fixed-trace typecheck passed.

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean subsequent pass on PR #7650.

This PR restores exact-credential first-owner organization onboarding as a durable WorkOS+Postgres saga. The reviewer confirmed: ownership binds to the exact authenticated credential (API-key/M2M/impersonator rejected), provider effects are recoverable by stable external ID, and lease-fencing, compensation, CHECK-constraint transitions, and idempotency all hold, backed by thorough integration coverage.

No Critical/High/Medium findings. Only two non-triggerable Low/defense-in-depth notes (agreements version int-array cast, stale-request UX corner) were raised as non-gating follow-ups.

Decision-table walk:

  • Row 1 (critical/high): no such findings.
  • Row 2 (gated_paths): false — N/A.
  • Row 3 (deleted high-risk path): high_risk is true but all high-risk reasons are (added) or (modified), no (deleted).
  • Row 4 (medium data-loss/schema/infra): no medium findings.
  • Row 5 (high_risk modified + medium): no medium findings.
  • Row 6 (sticky escalate): prior decision was approve, not escalate — N/A.
  • Row 7 (no-auto-approve team): no team match.
  • Row 8 (≥3 medium): zero medium findings.
  • Row 9: approve.

The migration (606) and new addie job are new/modified high-risk files, but with no medium-or-higher findings the modifications are presumed safe and new files matching high-risk globs are normal scaffolding — not escalation-worthy on the flag alone. This is a net improvement consistent with the prior approve.

@bokelley
bokelley merged commit 0f45120 into main Sep 22, 2026
33 checks passed
@bokelley
bokelley deleted the conductor/p0-restore-organization-onboarding branch September 22, 2026 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants