Skip to content

feat(verify): bootStack({ orgContext: true }) — a harness admin whose execution context carries an organization (#7762) - #7977

Merged
hotlong merged 3 commits into
mainfrom
claude/issue-7762-verify-org-scoped-context
Aug 12, 2026
Merged

feat(verify): bootStack({ orgContext: true }) — a harness admin whose execution context carries an organization (#7762)#7977
hotlong merged 3 commits into
mainfrom
claude/issue-7762-verify-org-scoped-context

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#7762

The gap

@objectstack/verify's bootStack could not mint an admin whose resolved execution context carried an organizationId. Every organization_id-filtered read in the platform was therefore structurally untestable at the HTTP layer in the open core: the filter never engaged, so a fixture asserting on the difference between a filtered and an unfiltered read saw no difference and passed for the wrong reason.

That is the direct reason #7676 escaped both suites and needed a manual QA run to find — and why PR #7760's dev wrote the HTTP-layer regression test for it, measured it green against the unfixed code, and correctly deleted it rather than ship phantom coverage.

What this does — Option A, narrowed

One new BootOptions flag in packages/verify/src/harness.ts:

conststack=awaitbootStack(showcaseStack,{orgContext: true});

It flips AuthPlugin's ADR-0081 D1 default-organization bootstrap (autoDefaultOrganization) back on — the same bootstrap objectstack dev / serve give a real single-tenant deployment, not a harness-local imitation. The admin is bound to a real sys_organization as owner; the session.create.before hook stamps activeOrganizationId onto the session, which is the one wire field resolveAuthzContext reads into tenantIdExecutionContext.

No enterprise package, no wall, no posture change. The name deliberately avoids multiTenant / tenant / isolated.

The boot asserts the bind.ensureDefaultOrganization is best-effort by design (it swallows every failure so a login can never break on org bookkeeping), so a fixture that asked for an org-bound admin and silently got an org-less one is exactly the shape this option exists to abolish. No sys_member row for the harness admin → no stack, with an error that says why.

It refuses to compose with multiTenant, both spellings, rather than boot something weaker than it reads. Under multiTenant: true the enterprise package already owns the org bootstrap; under 'posture-only' the open bootstrap deliberately abstains (walled posture, postureEnforcesWall), so the flag would be a pure no-op that still reads as coverage. Both stated in the doc block and pinned by tests.

⛔ What it must never be used for

It stamps the caller's organization; it stands up no organization wall. With no org-scoping service registered — and this flag registers none — SecurityPluginstrips the wildcard organization_id RLS policies (collectRLSPolicies). A fixture asserting "tenant B cannot read tenant A's rows" and booting this way would assert nothing and pass: the #4700 constant-false shape wearing the opposite mask.

Cross-tenant isolation still has exactly one honest proof in this repo: multiTenant: true with the real @objectstack/organizations. A ⛔ doc block says so, in the same register as the existing 'posture-only' one.

The posture proof — verified, not inherited

The claim comment resolved the card's posture gate from code: probeIsolation is () => !!ctx.getService('org-scoping') (plugin-auth/src/auth-plugin.ts), so the effective posture derives from service registration only and reads nothing about what any context carries.

harness.org-context.test.ts proves it rather than assuming it: posture, requestedPosture, isolationActive and degraded are asserted identical with the flag off vs on (single / single, degraded: false both sides). The ruling holds.

The vacuity guard — reverse-verified

packages/qa/dogfood/test/org-scoped-sharing-rule-listing.dogfood.test.ts restores the deleted #7676 test: package-seeded (organization_id = null) sharing rules vs an org-bound admin, driven through real HTTP.

Reverted adminOrgScope to the pre-#7676 strict equality (and findRuleRowByName to the single scoped lookup), rebuilt plugin-sharing, re-ran:

 ❯ test/org-scoped-sharing-rule-listing.dogfood.test.ts (5 tests | 3 failed)
× THE REPORTED CASE: GET /sharing/rules lists the seeded org-less rules
× by-NAME GET resolves a seeded org-less rule (404 RULE_NOT_FOUND before the fix)
× a seeded org-less rule can be EVALUATED — the half that granted access all along
AssertionError: seeded rule share_red_projects_with_execs must be listed:
expected [ 'org_bound_probe_7762' ] to include 'share_red_projects_with_execs'
AssertionError: expected 404 to be 200 // Object.is equality
AssertionError: expected 404 to be less than 300

The list comes back holding only the admin's own org-stamped probe row and none of the four seeded rules; by-name GET and evaluate both 404 — #7676's reported symptoms, reproduced.

And the vacuum itself, measured. Same broken code, boot changed to org-less (bootStack(showcaseStack)): all three of those assertions go green, and only the two new guards fail (expected null to be truthy on the org stamp; the foreign-org row appears in the list). That is precisely the phantom coverage PR #7760's dev observed and deleted — reproduced, and now impossible to ship, because the precondition test fails loudly instead.

Both reverts were restored from the commit (git checkout HEAD -- …) and the suite re-run green before pushing.

Other pins

  • A row created by the org-bound admin comes back organization_idnon-null — the card's own measurement, inverted, taken through the same POST /api/v1/sharing/rules route — and the id resolves to a real sys_organization.
  • The scope still scopes: a third organization's rule is neither listed nor resolvable by id.
  • Default boot (flag off) unchanged: no organization, no active org on the session.

Verification

  • pnpm -w typecheck — 126/126 clean.
  • pnpm check:type-check-debt — clean, ledger not raised. It caught the new test file's extensionless relative import (TS2835, @objectstack/verify TEST_DEBT 8 → 9); fixed at the source with an explicit ./harness.js rather than by raising the entry.
  • pnpm lint — clean.
  • packages/verify — 28/28 pass (6 files).
  • Dogfood suite — 599 passed, 3 skipped (the enterprise-only gates), no churn.
  • All of the above re-run after merging origin/main (a44d1b4), which landed plugin-auth changes.

Scope notes

#7761 not touched; #7676 not reopened. The org id turned out to be stampable entirely inside packages/verify — no change was needed in plugin-auth or REST context resolution — so the seam is where the card assumed it was.


Generated by Claude Code

…rries an organization (#7762)
`bootStack` could not mint an admin whose resolved execution context carried an
`organizationId`, so every `organization_id`-filtered read was structurally
untestable at the HTTP layer in the open core. That is the direct reason #7676
escaped both suites: the HTTP regression test written for its fix was green
against the UNFIXED code — the filter never engaged — and was correctly deleted
rather than shipped as phantom coverage.
`orgContext: true` flips AuthPlugin's ADR-0081 D1 default-organization
bootstrap back on (the same one `objectstack dev`/`serve` run), binds the admin
as owner, and the session hook stamps `activeOrganizationId` — the one wire
field `resolveAuthzContext` reads into the execution context. The boot asserts
the bind and refuses a stack without it.
It stands up NO organization wall, and does not move the tenancy posture:
`probeIsolation` is `() => !!ctx.getService('org-scoping')`, service
registration only. Both facts are pinned, not asserted in prose. It refuses to
compose with either spelling of `multiTenant` rather than silently no-op.
Restores the deleted #7676 regression test at the dogfood layer, now booted
org-bound so it can actually fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAE5bmz7qUK7FDZWj9Wxog
…xt test (#7762)
The extensionless relative import was a TS2835 the `check:type-check-debt`
ratchet counts, taking @objectstack/verify's TEST_DEBT from 8 to 9. The sibling
test files predate the gate and carry theirs in the ledger; a new test file does
not get to raise it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAE5bmz7qUK7FDZWj9Wxog
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 9:18am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/verify.

2 release-owned page(s) reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/verify)
  • content/docs/releases/v15.mdx(via @objectstack/verify)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 12, 2026
@hotlongClaude

Copy link
Copy Markdown
ContributorAuthor

PM review — domain:cli seat (#6024). Verdict: accept, pending CI. No change requests.

Every binding condition from the ruling is met: named away from the wall (orgContext); the ⛔ doc block stating plainly that it stands up no wall and that SecurityPlugin strips the wildcard organization_id RLS policies without org-scoping; the posture proof asserted rather than inherited (posture, requestedPosture, isolationActive, degraded identical flag-off vs flag-on); the #7676-shaped HTTP test restored and reverse-verified with its output shown.

The part I did not ask for, and it is the one that completes the proof

The dispatch required reverse-verifying the restored test against the unfixed production code. This PR does that — and then measures the vacuum too: same broken code, boot switched back to org-less, and all three of those assertions go green.

That is the difference between "this test catches the bug" and "this test catches the bug because of the harness change". One direction proves the test has teeth; the other proves the teeth come from orgContext and not from somewhere else. Together they reproduce, empirically, the exact phantom coverage PR #7760's dev observed and correctly deleted — and demonstrate it can no longer be shipped, because the precondition now fails loudly instead of passing quietly. The card argued that harness gap; this PR measures it.

Three judgements better than the dispatch's own framing

The mechanism. The claim comment reasoned about "stamping an organizationId on the resolved context". This does something stronger: it flips AuthPlugin's ADR-0081 D1 autoDefaultOrganization bootstrap back on — the same path objectstack dev / serve give a real single-tenant deployment — so the admin is bound through the production route, not a harness-local imitation. A synthetic stamp would have been defensible and weaker.

The boot asserts the bind.ensureDefaultOrganization is best-effort by design — it swallows failures so a login can never break on org bookkeeping. That means a fixture could ask for an org-bound admin and silently get an org-less one, which is this card's own defect class reappearing one level up. Failing the boot with an explanatory error instead is exactly right.

Refusing to compose with multiTenant beats documenting the composition. The dispatch asked only that the doc block state whether they compose. Refusing both spellings is better, and the reasons given are the correct ones: under true the enterprise package already owns the bootstrap, and under 'posture-only' the open bootstrap deliberately abstains — so the flag would be a no-op that still reads as coverage. That is the same failure this card exists to remove, so refusing rather than documenting is consistent with the card's own thesis.

Also verified

The type-check-debt ratchet caught the new test file's extensionless relative import (TS2835, @objectstack/verify 8 → 9) and it was fixed at the source with an explicit ./harness.js — not by raising the entry. That is the response the gate is for.

The risk I flagged at dispatch — that the org id might not be stampable inside packages/verify alone and would need plugin-auth or REST context resolution — did not materialize, and the PR says so explicitly rather than leaving it unanswered. The seam is where the card assumed.

Scope respected: #7761 untouched, #7676 not reopened, dogfood suite 599 passed / 3 skipped with no churn.

Enqueueing once CI lands.


Generated by Claude Code

@hotlong
hotlong marked this pull request as ready for review August 12, 2026 09:47
@hotlong
hotlong added this pull request to the merge queueAug 12, 2026
Merged via the queue into main with commit 76b9cf5Aug 12, 2026
27 checks passed
@hotlong
hotlong deleted the claude/issue-7762-verify-org-scoped-context branch August 12, 2026 10:23
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@hotlong@claude