Skip to content

Prove self-host setup integrity, forged creators, switcher states and SSO admission on the image - #85

Open
shuv1337 wants to merge 4 commits into
feat/selfhost-multi-org-uifrom
feat/selfhost-multi-org-evidence
Open

shuv1337 wants to merge 4 commits into
feat/selfhost-multi-org-uifrom
feat/selfhost-multi-org-evidence

Conversation

@shuv1337

@shuv1337 shuv1337 commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Why

Self-host multi-organization support (#81 to #84) is on hold until executed evidence and an operational gate exist. This PR adds the scenarios that were missing and records where the retained runs live. It changes no product code: the diff against #84 is e2e only. #81 to #84 keep their heads.

What changed

  • self-host-setup-integrity.spec.ts: five callers racing to set up a fresh instance leave one owner, organization and root record ([200, 403, 403, 403, 403]); a setup that fails at its last write (a database trigger on member) leaves no rows, and a retry succeeds.

  • self-host-organization-creation.spec.ts: a body naming the owner's userId does not change the creator (admin 403, member 403, anonymous 401); the owner's API key and MCP OAuth token, each first shown valid where it was issued, get 401 on create and on capabilities.

  • self-host-organization-switcher.spec.ts: no create action while the capability answer is pending or answered 503; a reload with the server answering shows it.

  • docker-release-sso-admission.spec.ts (released-image suite): runs the native image with SSO against the synthetic OpenID provider. A sidecar owns the container network and forwards the provider's HTTPS address, so the image trusts it through NODE_EXTRA_CA_CERTS. A verified, approved address without a membership, an unverified address and an unapproved domain are refused with their codes and no session; an invited member who connected SSO signs in and is refused once removed.

  • scripts/inspect-self-host-root.ts: the offline root-record gate. It loads an executor-host export archive copy into an in-memory PGlite and compares it with the baseline the operator recorded before the upgrade, { "version": 1, "organizationId", "ownerUserIds": [...] }. It resolves PGlite from the repository's exact 0.5.8 pin and refuses any other version before running it, and requires Postgres 18. It passes only for exactly one root record, id = 'root', naming the recorded organization, whose owners are exactly the recorded set, with the journal recording 1 root_organization. Exit codes: 0 pass, 1 gate failed, 2 invalid invocation or baseline, 3 archive or PGlite unusable. It prints a fixed report without rows, SQL errors or stack traces. It replaces a rehearsal script that exited 0 on a failed gate, accepted one owner and imported PGlite from a fixed path.

  • --rollout pr81 on the same script adds the Let self-host serve several organizations while creation stays off #81 rollout gate: exactly one organization, and private_hosted_migrations (9 entries) and private_self_host_migrations (1 entry) exactly as 7c49225 declares them, nothing missing, extra or renamed. The entries were derived from that commit's migrateProductSteps records and Effect's <id>_<name> key parsing.

  • self-host-root-inspection.spec.ts (bun run e2e:root-inspection): runs the gate as a separate process over synthetic PGlite archives. Two recorded owners pass in any order. An omitted or added owner, an admin recorded as owner, an unrecorded admin,owner, a duplicate owner row, a missing, duplicate, renamed or other-organization root record, a missing organization, and a missing or wrong journal fail with exit 1. Malformed baselines and invocations exit 2. A missing or non-archive file, a Postgres 17 data directory, missing PGlite and PGlite 0.4.0 exit 3. Every case checks that stderr is empty and that neither stream has the session token planted in the archive, SQL text or stack frames. bun.lock is unchanged.

Evidence

Retained on the author's machine under executor-evidence/multi-org-2026-11-05/; each run records head, tree, status --porcelain, dirty diff, command, exit, log and results. Credential-bearing exports and volume backups are not part of it.

Run Source Result
Clean #81 baseline (Bun host) 7c4922535596a6fe86938a8acbe0078107ce2bca, empty status 24/24 passed
Supplemental #81 (adds setup-integrity as a dirty diff) same head 26/26 passed
Whole stack (Bun host) 689f8bdd9695a038125d4af8ba2e5ec91fccf947 40/40 passed
SSO admission on the #81 native image sha256:367abe95…08ed8 test source 75565d7420d64ad35d3bb92cec2b51cf451c7be5 2/2 passed
Same refusal scenario on the 411a959 image sha256:b95d4619…92fd4 same fails as expected: an uninvited address is admitted

Each new scenario was also run against a deliberately broken product and failed: setup without its guard ([200, 503, 503, 503, 503]), setup outside its transaction (rows survive), creation allowed for everyone (admin spoof 200), a switcher that offers creation unless told no (shown while pending).

Offline gate and restore, on disposable local volumes with images built from the exact commits:

  • Full-volume backup of a stopped 411a959 volume (product data and Motel), restored into clean volumes; the restored data digest matched the backup every time.
  • Let self-host serve several organizations while creation stays off #81 on a restored copy, then executor-host export from a stopped copy and inspection of a separate copy with PGlite 0.5.8 loadDataDir: exactly one selfHostRoot row (id = root) naming the recorded pre-upgrade organization, the recorded owner as its only owner, journal 1 root_organization.
  • Rollback by full-volume restore: 411a959 starts on the restored pre-upgrade volume with its restored keys; owner and member identities unchanged; no root table.
  • Restoring the post-upgrade backup keeps the gate passing.
  • 411a959 refuses owner and member (403) once a second organization exists; Let self-host serve several organizations while creation stays off #81 on that same volume signs both back in.

Root gate with the #81 rollout profile (c4331c50c22474d0d12c96c6e880682c78087858, clean tree): synthetic suite 5/5 passed. The rollout group passes a one-organization archive with the full pinned journals, and fails two organizations, a missing, extra or renamed hosted entry, no hosted journal, an extra self-host entry and a missing self-host entry; the general gate still accepts two organizations. Three scratch mutations (organization count not enforced, journal read only at migration_id = 1, unexpected entries ignored) each fail it. Earlier, at 4b144f658081d0639967182b4fa2c905ae4d2119: synthetic suite 4/4 passed. Two scratch mutations (exit 0 on failure; ignore unrecorded owners) each fail it. On real archives restored from the full-volume backups and exported with the matching images, the gate exits 0 for the #81-upgraded volume, 1 for the pre-upgrade volume (no root table, no journal) and 1 for a baseline with an extra owner; stderr empty each time.

bun run check: exit 0. Not run: the Cloud target for the isolation specs, and per-PR runs for #82 and #83 (not required for the #81 rollout).

self-host-root-inspection.spec.ts
self-host-setup-integrity.spec.ts
self-host-organization-creation.spec.ts
self-host-organization-switcher.spec.ts

shuv1337 and others added 4 commits November 5, 2026 16:07
…switcher's unknown answer

Five callers racing to set up a fresh instance leave one owner, organization
and root record. A setup that fails at its last write leaves no rows and a
retry succeeds. Creation ignores a body naming the owner and refuses the
owner's own API key and MCP OAuth token, each shown valid where it was
issued. The switcher offers no creation while the capability answer is
pending or failed, and recovers on reload.

Co-authored-by: Cursor <cursoragent@cursor.com>
The released-image suite gains two scenarios that run the native image with
SSO configured against the synthetic OpenID provider. A sidecar owns the
container network and forwards the provider's HTTPS address, so the image
trusts it through NODE_EXTRA_CA_CERTS. A verified, approved address without
a membership, an unverified address and an unapproved domain are refused
with their codes and no session or membership. An invited member who
connected SSO signs in, and is refused once removed.

Co-authored-by: Cursor <cursoragent@cursor.com>
scripts/inspect-self-host-root.ts loads an executor-host export archive into
an in-memory PGlite and compares it with the identity the operator recorded
before the upgrade: one organization ID and its full set of owners. It
resolves PGlite from the repository, refuses any version but the product's
0.5.8 before running it, and requires Postgres 18. It passes only when the
archive has exactly one root record, id root, naming that organization, the
organization's owners are exactly the recorded set, and the journal records
1 root_organization. It exits 1 when the gate fails, 2 for an invalid
invocation or baseline and 3 when the archive or PGlite cannot be used, and
prints a fixed report without rows, SQL errors or stack traces.

e2e:root-inspection runs it as a separate process over synthetic PGlite
archives for each pass, failure, baseline and error case.

Co-authored-by: Cursor <cursoragent@cursor.com>
--rollout pr81 adds two requirements to the general checks: the archive
holds exactly one organization, and private_hosted_migrations and
private_self_host_migrations are exactly the journals
7c49225 writes, with nothing missing,
extra or renamed. The report now counts organizations in every mode and,
for the rollout, lists missing pinned entries and counts unexpected ones.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

1 participant