Skip to content

fix(rest): keep "failed" and "not wired" apart at the two computeExecCtx authorization-input seams - #15020

Merged
hotlong merged 7 commits into
mainfrom
claude/issue-13906-computeexecctx-seams
Sep 4, 2026
Merged

fix(rest): keep "failed" and "not wired" apart at the two computeExecCtx authorization-input seams#15020
hotlong merged 7 commits into
mainfrom
claude/issue-13906-computeexecctx-seams

Conversation

@os-trump

@os-trump os-trump commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes #13906

Implements the maintainer ruling of 2026-09-04 (recorded on the card, comment 5535555324): decision 1 = A, decision 2 = B. That ruling supersedes only the B′ half of the 2026-09-02 ruling (comment 5504837153); A and decision 2 stand exactly as ruled there. Phase 1 was the measurement, landed separately as PR #14272.

The direction, driven before anything was changed

The card was filed as a code reading with no direction measured. Driven on this branch, every fault leg beside a positive control that is the same fixture with the one fault removed:

Both seams are fail-OPEN. The seam's value is absent, but the consumer reads absent as "this check does not apply" — so a refusal is skipped and an authorization input is decided in the caller's favour. That is the security direction, and it is what separates this card from #13476 / #13904, where an unknown became an extra refusal.

Interim reading posted on the card before any code push: comment 5528512501.

What changed, and what each row measures

wiring before after
healthy isolated tenancy, ex-member's org-stamped API key 401 refused 401 — unchanged
tenancy never registered (supported no-tenancy composition) 200 served 200 — unchanged
tenancy registered and FAILED to construct 200 served, full grants 503
auth gate INACTIVE admitted admitted — unchanged
auth gate ACTIVE, healthy re-read, gated user 403 403 — unchanged
isAuthGateActive() itself THROWS admitted admitted — unchanged
auth gate ACTIVE, session re-read FAILS admitted, no wire trace 503

Decision 1 A — tenancy posture. Only the registry's branded not-registered rejection is absorbed (isServiceNotRegisteredError, the discriminator the shipped objectQLProvider already uses one layer down). Every other rejection raises AuthzStoreUnavailableError, the same loud answer wiredEngineOrLoud gives the engine seam, carried by the same nets. The classification is the registry's, never message text. The wiring fact comes from the kernel's presence, never inferred from the returned value.

Decision 2 B — ADR-0069 auth gate. Fails closed in one precisely measured window: isAuthGateActive() answered true and the gate's session re-read then failed. The re-read deliberately bypasses the swallowing getSession closure, because that closure re-collapses the same two facts.

Decision 1 B′ — WITHDRAWN

An earlier revision of this PR carried B′: a boot refusal in RestApiPlugin for a wall-enforcing tenancy posture on single-kernel wiring. The 2026-09-04 ruling withdrew it, and this PR now contains no boot refusal at all — packages/rest/src/rest-api-plugin.ts is byte-identical to main, its dedicated test file is deleted, and boot behaviour is unchanged in every composition. B′'s underlying question — what the single-kernel provider wiring actually skips — moved to its own measurement card, #15163, and is deliberately not answered here.

⛔ It was not replaced by a warning, a softer refusal, an env escape hatch, or a narrowed condition.

Why B′ went, from this branch's own CI triage

Measured on this branch (full triage in this PR's comment of 2026-09-03T21:39Z) and verified again on this head:

  • The only registrar of a kernel-manager service anywhere in this repository was B′'s own narrowness control test. Positive control: the same grep spelling for org-scoping returns 4 real registrars, so the zero is a reading, not a broken probe.
  • ⇒ Every wall-enforcing composition the open core can build was on the refusing side, for group as well as isolated: the real os serve process, the ADR-0105 bootStack multiTenant harness, and seven dogfood suites.
  • Its premise was false wherever it fired. A wall-enforcing effective posture requires org-scoping (TenancyService.isolationActive), and org-scoping is exactly what makes plugin-security keep the platform's organization_id row policies (ADR-0105 D3). So the Layer 0 row wall the refusal's message called unenforced was in fact standing in every case the refusal could reach.

All four red shards on the previous head — Test Core (1/6) and (5/6), Dogfood Regression Gate (1/3) and (3/3) — were this one cause, and all four are driven green below.

File face

file why
packages/rest/src/rest-server.ts both seams (the ruled repair — decision 1 A and decision 2 B)
packages/rest/src/execctx-authz-input-seam-reachability.test.ts the six phase-1 pins, re-aimed; header re-aimed for the withdrawal
packages/rest/src/ui-view-environment-ownership.test.ts fixture triage, explained below
content/docs/permissions/system-context.mdx line re-anchoring only, by the gate's own --fix
.changeset/execctx-authz-input-seams-fail-closed.md user-visible behaviour change

Not in this diff any more: packages/rest/src/rest-api-plugin.ts (restored byte-identical to maingit diff origin/main -- packages/rest/src/rest-api-plugin.ts is empty) and packages/rest/src/rest-api-plugin-tenancy-posture-boot-refusal.test.ts (deleted; its 2 refusal tests and 4 narrowness controls all measured a behaviour that no longer exists).

The two non-obvious ones, declared rather than slipped in:

  • ui-view-environment-ownership.test.ts — its fake kernel spelled ABSENT services as a bare Error. The real registry brands the never-registered rejection and reserves the unbranded one for a service that IS registered and failed to construct, so the double was claiming every absent service had broken. Under the old collapse that inaccuracy was free; it is not free now. The double now resolves undefined, the spelling the seam contract itself names for absence. ⭐ This belongs to decision 1 A, not to B′, and stays: it is the fixture consequence of branding the absorb, which is the half of the ruling that survived.
  • content/docs/permissions/system-context.mdx — this PR's rest-server.ts insertions shifted line numbers that check-system-context-census anchors. Repaired with the gate's own --fix on the earlier head; ten anchors re-pointed, no elevation behaviour touched. Withdrawing B′ moved nothing further — the census gate is green on this head with no --fix needed. Not content/docs/releases/.

Red-first proof

The six phase-1 pins that recorded the permissive answers go red by design under the repair, and each is inverted in place with its superseded text quoted beside it, per that file's own standing instruction. Their reds were the negative arms, not a broken harness — the positive controls (healthy tenancy 401, active gate 403, inactive gate admitted, unregistered still 200) all stayed green in the same run, and the auth-gate red was AuthzStoreUnavailableError { status: 503, object: 'auth_gate' }.

⚠️ §3 of that file still measures 200 on the single-kernel provider wiring, and that stays correct and stays pinned: withdrawing B′ returns that wiring to main's behaviour, and neither ruling changed computeExecCtx, which still never reads a posture there. That 200 is #15163's subject, not a regression introduced here.

Verification of the withdrawal

Final commit 7c71ea64b. Exit codes captured before any pipe (cmd > file 2>&1; EXIT=$?).

⚠️ Lock disclosure. Heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-13906-takeover), which reported on this host: VERDICT command-exit 0 · UNLOCKED (declared) · no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized.

The four red shards, each driven green by name — the withdrawal proven to reach dist first:

what CI reported red run on this head result
Test Core (1/6)packages/cli serve e2e vitest run --maxWorkers=1 test/serve-organizations-host-resolution.e2e.test.ts Test Files 1 passed (1) / Tests 3 passed (3) — and the two previously-skipped tests now RUN
Test Core (5/6)packages/verify posture harness vitest run --maxWorkers=2 src/harness.posture-only.test.ts src/harness.posture.test.ts Test Files 2 passed (2) / Tests 8 passed (8) (was 6 failed, 2 passed)
Dogfood Regression Gate (1/3) + (3/3) — seven suites the seven files the triage names, one run Test Files 7 passed (7) / Tests 48 passed (48)

The CLI e2e spawns the real os serve, which loads packages/rest/dist, so the removal was proven to reach the built artifact before that run was read: after pnpm --workspace-concurrency=2 --filter '@objectstack/cli...' build, grep -c 'RestApiPlugin refuses to start' packages/rest/dist/index.js returns 0 (grep exit 1 = absent), with a positive control in the same file — grep -c 'AuthzStoreUnavailableError' returns 4 — so the zero is a reading and not a broken probe.

Package-level:

  • pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2Test Files 175 passed (175) / Tests 2948 passed (2948) (175, not 176: the B′ test file is gone).
  • pnpm --filter @objectstack/rest typecheck — green on both halves, and the test layer is genuinely measured: check:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json; 0 file(s) / 0 error(s) / 0 pinned signature(s).
  • Positive controls, verbose: vitest run --reporter=verbose src/execctx-authz-input-seam-reachability.test.ts18 passed (18), including REPAIRED [decision 1 A]: tenancy REGISTERED AND FAILING (factory throws) → 503 outage, no longer a served 200, REPAIRED [decision 2 B]: a FAILED session re-read under an ACTIVE gate is now a 503 outage, not a silent admission, and NARROWNESS CONTROL [decision 2 B]: a SUCCESSFUL re-read carrying no gate still admits.

Gates. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack derived 62 families on the final tree (stderr names objectstack-ai/objectstack at 7c71ea64b; change set 5 paths vs merge base d17f352b1). 59 green by their own printed verdict lines — among them check-nul-bytes: OK (scanned 8198 text file(s) ... no raw ASCII control bytes), check-system-context-census: OK — 106 elevation read sites ... 140 anchors resolve, ✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added), ✓ This diff introduces no major bump, check-test-source-alias OK — 72 packages with tests scanned, ✓ check:authz-resolver: single shared authorization resolver intact, ✓ Route-envelope conformance — 10 route module(s) audited, check-closing-keyword-parity: OK, and ✓ doc authoring guard: 14633 customer-facing string(s) ... clean. check:skill-examples first reported PREREQUISITE NOT MET (client-react unbuilt); building its closure and re-running gave a real green (✅ 256 prose examples type-check across 3 surface(s)) — recorded as measured, not assumed.

The other 3 are NOT MEASURED, not red, each saying so itself with exit code 3: check-test-completeness (Nothing was measured: this gate exited before parsing a single summary line), check:dual-build-cjs-loads (PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/), and check-half-states (repo-scoped reads refused in this container).

⚠️ Declared: dispatch-gates.mjs reported this tree as at least 44 commits behind origin/main, with 13 gate-defining files changed across that range, so the derived family list is a reading of a slightly older gate farm. No merge of origin/main was taken on this head — CI evaluates the PR against current main and re-runs the whole farm there, which is the authority for that half.

Notes for the reviewer

Continued by the os-dev takeover seat of session 6679d191-11f4-465b-b322-0e0409d76793 after the original dev seat released the card (card comment 5534512166). The branch was continued, never restarted: no rebase, no force-push, no squash of the earlier commits.


Generated by Claude Code

…thz-input seams

Phase 2 of the #13906 measurement, implementing the maintainer ruling of
2026-09-02 (decision 1 = A + B', decision 2 = B).

Both seams measured fail-OPEN: an absorbed FAILURE read as "this check does
not apply", so an authorization refusal was skipped rather than produced.

- tenancy posture (A): absorb only the registry's branded not-registered
  rejection; any other rejection raises AuthzStoreUnavailableError, the same
  loud answer wiredEngineOrLoud gives the engine seam. The wiring fact comes
  from `kernel`'s presence, never inferred from the returned value.
- single-kernel wiring (B'): a configured wall-enforcing posture is refused
  loudly at boot, because that wiring never reads a posture at all.
- ADR-0069 auth gate (B): fail closed in the measured window only —
  isAuthGateActive() answered true AND the session re-read then failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…e B'

The six phase-1 pins that recorded the PERMISSIVE answers go red by design
under the repair; each is inverted IN PLACE with its superseded text quoted
beside it, per the file's own standing instruction.

Adds rest-api-plugin-tenancy-posture-boot-refusal.test.ts, which drives the
ruling's own opening question for B' (can a walled posture be configured on
the single-kernel wiring at all? yes) plus the refusal and four narrowness
controls, and a narrowness control for decision 2 B.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ne double

Two follow-ons from driving the repair against the whole @objectstack/rest suite:

- LiteKernel/KernelBase-shaped hosts have no `getServiceAsync`, so the bare
  dereference would raise an unbranded TypeError and turn "this host shape has
  no async registry" into a 503. The wiring fact now includes the accessor's
  presence, mirroring the shipped objectQLProvider's split.
- ui-view-environment-ownership.test.ts's kernel double spelled ABSENT services
  as a bare Error. The real registry brands the never-registered rejection and
  reserves the unbranded one for a service that failed to construct, so the
  double was claiming every absent service had broken. It now resolves
  undefined, the spelling the seam contract itself names for absence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ensus

check:doc-authoring refuses `#NNNN` inside runtime prose (maintainer ruling
2026-08-12) — an operator reading a boot failure cannot resolve one. The ids
move to adjacent source comments and the boot message asserts its REMEDY
instead.

check-system-context-census --fix re-anchors ten line citations in
content/docs/permissions/system-context.mdx that this PR's insertions shifted.
Pure line rot; no elevation behaviour changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions github-actions Bot added the size/l label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 25a59bd109a9a3bfefab460a05585f218b45d222packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9dba345c0374f32658227321321e40fbd3fbf940 — the merge of head 7c71ea64b61b76b48ec40309281982163bad83a4 into base 25a59bd109a9a3bfefab460a05585f218b45d222, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9dba345c0374f32658227321321e40fbd3fbf940 && git checkout 9dba345c0374f32658227321321e40fbd3fbf940
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 25a59bd109a9a3bfefab460a05585f218b45d222 7c71ea64b61b76b48ec40309281982163bad83a4 && git checkout -B drift-repro 25a59bd109a9a3bfefab460a05585f218b45d222 && git merge --no-ff 7c71ea64b61b76b48ec40309281982163bad83a4

node scripts/docs-audit/affected-docs.mjs --json 25a59bd109a9a3bfefab460a05585f218b45d222

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@os-trump
os-trump marked this pull request as ready for review September 3, 2026 17:46
@os-trump
os-trump enabled auto-merge September 3, 2026 17:46
@os-trump
os-trump disabled auto-merge September 3, 2026 20:20
@os-trump
os-trump enabled auto-merge September 3, 2026 20:20
The merge deferred the generated `content/docs/permissions/system-context.mdx`
to the merge driver (AGENTS.md §11); this commit discharges it by regenerating
from the merged tree with the gate's own `--fix`. Line re-anchoring only — 20
anchors re-pointed, no elevation behaviour touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza

os-trump commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

CI triage — all four red shards are ONE root cause, and it returns to the maintainer

Seat: os-dev, session session_016yfqQh2dBgPAymYd7xipza, branch claude/issue-13906-computeexecctx-seams.
Measured on the merged tree at 9de1b0ef34 (this branch after git merge origin/main).

Verdict: reading (b) — the failing compositions are legitimate and B-prime over-refuses as implemented. And the narrowing that would let them boot IS the thing the 2026-09-02 ruling refuses, so this stops here and goes back to the maintainer. No code change to the refusal is pushed. The refusal is not weakened, disabled, skipped or quarantined, and no test was touched.


1. Reproduced first, over the real process

packages/cli/test/serve-organizations-host-resolution.e2e.test.ts, one test, on the merged tree:

 × boots a walled posture with the package installed in the APP, not the framework 11768ms
AssertionError: serve never reached its banner
Test Files  1 failed (1) · Tests  1 failed, 2 skipped (3)

Caused by, from the child's own stderr:

[Kernel] Plugin startup failed: com.objectstack.rest.api
[security] RestApiPlugin refuses to start: the kernel's `tenancy` service reports the
wall-enforcing posture `isolated`, but this deployment has no `kernel-manager` service …
    at Object.start (packages/rest/dist/index.js:80715)      [= rest-api-plugin.ts:249]
    at async Runtime.start (packages/runtime/dist/index.js:4842)
    at async Serve.run (packages/cli/src/commands/serve.ts:4488)

That is not a fixture reaching into internals. It is the real os serve command, spawned as a process, against a real app directory with the enterprise organizations package in the app's own node_modules and OS_TENANCY_POSTURE=isolated — the deployment shape cloud#1013's host-importer repair exists to make bootable.

2. Every red shard, confirmed rather than assumed

shard what fails cause
Test Core (1/6) packages/cli serve-organizations-host-resolution e2e, 1 of 239 files B-prime refusal — reproduced locally, above
Test Core (5/6) packages/verify harness.posture-only.test.ts (4) + harness.posture.test.ts (2) B-prime refusal — reproduced locally: Test Files 2 failed (2) and Tests 6 failed, 2 passed (8), every one the refusal
Dogfood Regression Gate (1/3) 5 files: federated-anchor-provenance, federated-rls-injectors, federated-sweep-projections, no-active-organization-write-refusal, org-create-default-team B-prime refusal — every CI ::error annotation carries the identical text and the identical bootStack ../../verify/src/harness.ts:587 frame; 2 of the 5 reproduced locally
Dogfood Regression Gate (3/3) 2 files: automation-toggle-tenant-scope, federated-phantom-share-grant same, same frame

All seven dogfood files fail inside bootStack(..., { multiTenant: 'posture-only' }) before any of their own logic runs. Locally the same call refuses with the same message at the same line, so the shared root cause is measured, not inferred.

⚠️ Worth recording: it fires on group as well as isolated. harness.posture.test.ts produced both refusal texts in one run.

3. The four narrowness controls were all in one package, and the population was the tree

The PR body argues B-prime "cannot fire on the open or dev path", pinned by four narrowness controls. All four live in packages/rest. Measured across the whole repo:

$ git grep -n "registerService(.kernel-manager" -- '*.ts' '*.mts' '*.js'
packages/rest/src/rest-api-plugin-tenancy-posture-boot-refusal.test.ts:273: …

$ git grep -n "registerService(.org-scoping" -- '*.ts'          # positive control
packages/cli/test/serve-organizations-host-resolution.e2e.test.ts:70
packages/verify/src/harness.host-resolution.test.ts:49
packages/verify/src/harness.posture.test.ts:24
packages/verify/src/harness.ts:68

The only registrar of kernel-manager anywhere in this repository is this PR's own control test. Nothing in production registers it; os serve never mounts it (it is the cloud-private multi-environment wiring). So the condition B-prime spares — "has a kernel-manager" — is reachable by exactly one file in the tree, the one written to prove the refusal is narrow. Every wall-enforcing composition the open core can build is on the refusing side.

4. The refusal cannot reach a deployment whose wall is absent — measured

Chain, each link read from source:

  • TenancyService.posture returns isolationActive() ? requestedPosture : 'single', and isolationActive() returns false unless probeIsolation() is true (packages/plugins/plugin-auth/src/tenancy-service.ts). Both group and isolated go through it.
  • probeIsolation is () => !!ctx.getService('org-scoping') (auth-plugin.ts).
  • collectRLSPolicies strips the platform's tenant-scoped RLS policies only when org isolation is inactive (plugin-security/src/platform-tenant-policies.ts, ADR-0105 D3).

effectiveTenancyPosture(...) is wall-enforcing if and only if org-scoping is registered, and org-scoping being registered is exactly what makes the platform's organization_id row wall stand. There is no composition in which B-prime fires and the organization wall is genuinely absent.

So the refusal's own sentence — "The Layer 0 organization wall … would silently not be enforced" — is false in every case it can reach. What the single-kernel provider wiring actually skips is narrower and should be named as what it is: the two API-key admission refusals in resolveApiKeyAdmission (organization_required, organization_membership_ended, packages/core/src/security/api-key.ts). Session-authenticated traffic and the row wall are unaffected on that wiring.

That gap is real and it is the gap this card measured. It is not the whole wall.

5. Why the repair cannot be in the tests — reading (a) was tried and is not executable

  • The CLI e2e spawns the real os serve. Giving it a kernel-manager means giving os serve one — production wiring, i.e. option B, which the ruling explicitly did not take.
  • bootStack multiTenant documents itself as "the same path objectstack dev / serve give a real single-tenant deployment — nothing here is simulated", and multiTenant: true with the real enterprise package is called "exactly one honest proof in this repo" of cross-tenant isolation. Registering a fake kernel-manager there would make the governed ADR-0105 harness compose something no deployment composes — the "constant-false capability probe wearing the opposite mask" its own docblock warns against.
  • Dropping the fixtures to single deletes the subject: cloud#1013 is about walled-posture resolution, and organization/create 的闸门判「请求的 posture」还是「实际生效的 posture」?降级部署(D5)下两者分叉,闸门放行而 /auth/config 隐藏 #5261's posture-only suites exist because a walled posture is the precondition of what they test.

6. Why this is not mine to narrow

The ruling of 2026-09-02 reads:

B′: on the single-kernel provider wiring, a configured wall-enforcing tenancy posture is refused loudly at boot — the wiring cannot enforce it, so it must not pretend to.
B′ opens with a measurement: can a walled posture be configured on that wiring at all? If it cannot, B′ reduces to documenting that the single-kernel wiring carries no posture.

The implemented condition ("wall-enforcing posture AND no kernel-manager service") and the ruled condition are the same set. Any narrowing that lets os serve, the verify harness and the dogfood suites boot removes precisely what was ruled — so under this card's own stop clause I do not decide it.

And the ruling's conditional now has an answer it did not have when it was written. A walled posture is not merely configurable on that wiring: it is the only shipped walled shape in the open core. The ruling contemplated the opposite outcome and prescribed documentation for it; neither branch as written covers "configurable, ubiquitous, and the row wall is standing".

Two questions, and only the maintainer can answer them:

  1. Given that a wall-enforcing posture on the single-kernel wiring always has the row wall active and is missing only the two API-key admission refusals — should that composition be refused at boot (breaking every self-hosted os serve enterprise deployment, group and isolated alike), or made loud and documented (a boot-time error log naming the exact gap, with the deployment permitted to run)?
  2. If refusal is wrong here, does the answer become option B after all — carry the local kernel's tenancy posture into computeExecCtx on the provider path — which the 2026-09-02 ruling declined for want of a product answer that this measurement may now supply?

Until one of those is ruled, this PR stays red. I have not changed its draft state or its auto-merge.

7. Also done on this branch

  • Merged origin/main (merge, never rebase) at 9e5ffd4d75; the merge driver deferred content/docs/permissions/system-context.mdx and the deferral is discharged in 9de1b0ef34 by the gate's own --fix (check-system-context-census: OK — 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read). ⛔ Not hand-resolved.
  • packages/rest/src/error-response.ts stays out of this diff — verified after the merge: git diff --name-only origin/main...HEAD -- packages/rest/src/error-response.ts is empty, and the merge brought no change into that file either. The single-writer promise in the body still holds.
  • The merge did bring packages/rest/src/rest-server.ts (+127) and packages/cli/src/commands/generate.ts (+51, that is [finding] packages/cli generate.ts: five field-type vocabulary entries that DO exist disagree with what the platform stores (autonumber SERIAL, formula's column, multiselect TEXT, vector VECTOR, lookup uuid) #14828) into the branch. Re-run by name on the merged tree: pnpm --filter @objectstack/rest typecheck green including check:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json; pnpm --filter @objectstack/cli typecheck green including its test layer, and that layer really is measured — tsc -p packages/cli/tsconfig.test.json --listFiles puts 146 packages/cli/test/ files in the program, this e2e among them. Targeted rest suite over this PR's three test files plus the three the merge brought: Test Files 6 passed (6) and Tests 104 passed (104).
  • The [sql-driver] … sys_metadata_commit … declared field 'id' asks for storage … line is NOT load-bearing here and is not this PR's. It is logger.warn from packages/drivers/driver-sql/src/sql-driver.ts:9133, whose own docblock says it is "Still a diagnostic, NOT a rejection door … Every object that booted before still boots." It appears twice in the reproduced boot (sys_metadata_commit, sys_http_delivery), matches none of the e2e's four assertions, and this branch touches no file under packages/drivers. Left for [finding] packages/cli generate.ts: both migration generators give the table's own id a UUID primary key, but the platform's id is a 26-character string in a varchar(255) #15040.

Generated by Claude Code

os-trump commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

os-dev-report

{
  "issue": 13906,
  "status": "needs_decision",
  "branch": "claude/issue-13906-computeexecctx-seams",
  "pr": "https://github.com/objectstack-ai/objectstack/pull/15020",
  "premise_still_valid": true,
  "summary": "PR #15020 is red on Test Core (1/6) and (5/6) and Dogfood Regression Gate (1/3) and (3/3), and all four are ONE root cause: this PR's decision-1 B-prime boot refusal. Reading (b) holds — the refused compositions are legitimate, and B-prime over-refuses as implemented. Decisive measurement: the ONLY registrar of a `kernel-manager` service anywhere in this repo is this PR's own narrowness control test (positive control: the same grep for `org-scoping` returns 4 real registrars), so every wall-enforcing composition the open core can build is on the refusing side — including the real `os serve` process (reproduced), the ADR-0105 `bootStack multiTenant` harness, and seven dogfood suites, for `group` as well as `isolated`. Second measurement: a wall-enforcing effective posture requires `org-scoping` (TenancyService.isolationActive), and `org-scoping` is exactly what makes plugin-security KEEP the platform's `organization_id` row policies (ADR-0105 D3) — so B-prime can never fire on a deployment whose row wall is absent, and the refusal message's claim that 'the Layer 0 organization wall would silently not be enforced' is false in every case it reaches. What the provider wiring actually skips is narrower: the two API-key admission refusals in resolveApiKeyAdmission. Reading (a) is not executable — the CLI e2e spawns the real `os serve` (a kernel-manager there IS production option B), and faking one in the verify harness would make the governed ADR-0105 harness compose what no deployment composes. Any narrowing that unblocks these boots removes exactly what the 2026-09-02 ruling refuses, so per the dispatch's stop clause I did not decide it. NO change to the refusal was pushed; no test skipped, disabled or quarantined. Pushed: the owed `git merge origin/main` (9e5ffd4d75) plus the merge driver's deferred system-context census regeneration (9de1b0ef34). Draft state and auto-merge untouched.",
  "tests": "Reproduce-first, then measurements, all on the merged tree 9de1b0ef34; heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-13906-pr15020 set before the first attempt; exit codes captured before any pipe. (1) REPRO, real process: pnpm --filter @objectstack/cli exec vitest run --maxWorkers=1 test/serve-organizations-host-resolution.e2e.test.ts -t 'boots a walled posture' — 'Test Files 1 failed (1) / Tests 1 failed, 2 skipped (3)', 'AssertionError: serve never reached its banner', child stderr carrying the B-prime refusal at Serve.run (packages/cli/src/commands/serve.ts:4488). (2) REPRO, verify harness: pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2 src/harness.posture-only.test.ts src/harness.posture.test.ts — 'Test Files 2 failed (2) / Tests 6 failed, 2 passed (8)', matching Test Core (5/6)'s six CI annotations one for one; both `isolated` and `group` refusal texts observed in the same run. (3) REPRO, dogfood: pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=1 test/org-create-default-team.dogfood.test.ts test/no-active-organization-write-refusal.dogfood.test.ts — 'Test Files 2 failed (2) / Tests 8 skipped (8)', both dying at bootStack (packages/verify/src/harness.ts:587), the byte-identical frame CI reports for all seven dogfood files. An earlier attempt at this run was NOT MEASURED (PREREQUISITE NOT MET: unbuilt @objectstack/connector-mcp); re-run after pnpm --filter '@objectstack/dogfood^...' build (VERDICT command-exit 0). (4) Per-shard CI confirmation read from the run's own ::error annotations (run 33786264193, failed_only) — every failing file in all four shards carries the identical refusal text. (5) Post-merge re-runs BY NAME: pnpm --filter @objectstack/rest typecheck exit 0, its own verdict line 'check:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json'; pnpm --filter @objectstack/cli typecheck exit 0, verdict 'check:test-typecheck: OK — @objectstack/cli's test layer compiles ... 3 file(s) / 28 error(s) / 6 pinned signature(s) held in test-typecheck-debt.json'. The dispatch's include:['src'] warning was checked positively rather than assumed: tsc -p packages/cli/tsconfig.test.json --listFiles puts 146 packages/cli/test/ files in the program, the failing e2e among them (count 1). (6) Targeted rest suite over this PR's three test files plus the three the merge brought into packages/rest — 'Test Files 6 passed (6) / Tests 104 passed (104)'. (7) Gates, exit captured before any pipe, each quoted by its OWN printed verdict: check-system-context-census --self-test + run 'OK — 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read'; check-nul-bytes 'OK (scanned 8199 text file(s) -- 8199 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes)'; check:merge-driver self-test passed; check-doc-anchors '298 internal fragment link(s) across 409 source file(s) all resolve'; check-docs-single-h1 '404 page(s) ... carry no body-level heading'. (8) Gate union derived at final HEAD on a clean tree: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, exit 0, stderr naming objectstack-ai/objectstack at commit 9de1b0ef34 — 66 families (57 by path, 7 by kind, 5 declared whole-tree). DECLARED NARROWING: I ran the subset in (5)-(7) rather than all 66. Reason stated rather than hidden — the shared verify lock is heavily contended (two acquisitions this run ended in exit 99 queue-timeout after 540s each, holders named: another seat's postmerge.sh and a turbo build; both NOT MEASURED, neither pass nor red), the diff's file face is unchanged from the head CI already ran the whole farm on (Lint & Repo Gates and TypeScript Type Check both success on 34a1fbce8c), and CI re-runs the farm on 9de1b0ef34. The full packages/rest and packages/cli suites are therefore NOT MEASURED locally by me; CI's Test Core owns them. NO ablation was performed: no code change was made, so there is nothing to ablate.",
  "mcp_calls": "6 — issue_read get_comments (1), pull_request_read get_check_runs (1), get_job_logs (1), add_issue_comment (2), add_issue_comment read-back via the zero-quota web payload channel instead (0); the sixth is the PR-body and card reads, which went through the public-repo page channel at zero quota rather than MCP. Repo-scoped REST is refused for this session (probe: 403 'GitHub access is not enabled for this session'), and `gh` is absent, so bulk reads took the payload channel.",
  "open_questions": [
    {
      "question": "A wall-enforcing tenancy posture on the single-kernel provider wiring always has the platform's organization_id row wall ACTIVE (org-scoping is required for both), and is missing only the two API-key admission refusals in resolveApiKeyAdmission. Given that, should that composition be refused at boot, or permitted with a loud, precise boot diagnostic?",
      "options": [
        "A — keep B-prime as implemented: refuse at boot. Every self-hosted `os serve` enterprise deployment stops booting, `group` and `isolated` alike, along with the ADR-0105 bootStack harness and seven dogfood suites. Nothing in the open core can compose the kernel-manager that would spare it.",
        "B — reduce B-prime to a loud boot ERROR log plus documentation, naming the ACTUAL gap (the two API-key admission refusals are not applied on this wiring; the row wall is unaffected). The deployment boots. This is the branch the ruling itself named for the other measurement outcome.",
        "C — take option B of decision 1 after all: carry the local kernel's tenancy posture into computeExecCtx on the provider path, so the declared posture is enforced on both wirings and no refusal is needed."
      ],
      "recommendation": "C, with B as the interim if C cannot land in this PR. Long-term soundness leads and carries this on its own: declared = enforced is satisfied by making the two wirings AGREE, not by refusing one of them (a hard break with no migration for the platform's only shipped walled shape) and not by announcing a gap in a log (a documented defect is still a defect, and ADR-0049's enforce-or-remove reads against leaving it). C is contract-first — it reads a service that is already registered, on a second code path, exactly as the kernelManager path already does; A and B both leave the authorization input permanently unread. The other three lenses do not outvote it and in fact agree: REAL BUSINESS NEED — measured pull, since self-hosted `os serve` with the enterprise package is the only walled shape the open core ships and on it an ex-member's org-stamped API key is admitted, which is the exact defect this card was regraded p1/security for; AI-ERROR-PREVENTION — C removes an implicit 'depends which wiring you are on' condition, the class of hidden precondition AI-authored deployments get wrong, whereas A converts it into a boot failure whose message states something false about the row wall; STARTUP SCOPE DISCIPLINE — C adds no surface (it narrows: one more consumer of an existing service), while A withdraws a shipped capability. ⚠️ The ruling declined option B for want of a product answer about whether single-kernel deployments should run walled postures at all. That question now has a measured answer: they already do, ubiquitously, with the row wall standing. That is the fact the ruling's own conditional clause asked for, and it is the maintainer's to accept — I do not rule it."
    },
    {
      "question": "Independently of the decision above: the refusal's runtime message asserts 'The Layer 0 organization wall — including the organization_required and organization_membership_ended API-key refusals — would silently not be enforced.' Measurement shows the row wall IS enforced in every composition the refusal can reach. Should the message be corrected in this PR even if the refusal itself survives?",
      "options": [
        "A — correct the message to name only what is actually skipped (the two API-key admission refusals), keeping the refusal untouched.",
        "B — leave the message as ruled and correct it in a follow-up card."
      ],
      "recommendation": "A. Long-term soundness first: an operator-facing security refusal that misstates which control is missing teaches the wrong remediation, and the wrong remediation here is 'mount a kernel-manager', which no open-core deployment can do. The correction is a string change inside a branch the ruling already governs, it does not move what the refusal refuses, and it makes the boot message survivable review whichever way question 1 is ruled. ⛔ Not applied here: it belongs in the same commit as whatever question 1 rules, and pushing it alone would leave the branch half-changed against a ruling that may remove the branch entirely."
    }
  ],
  "out_of_scope_findings": [
    "none filed — the one unrelated line in the captured output, `[sql-driver] while creating table \"sys_metadata_commit\": declared field 'id' asks for storage ...`, is already #15040. It is logger.warn from packages/drivers/driver-sql/src/sql-driver.ts:9133, whose own docblock says 'Still a diagnostic, NOT a rejection door ... Every object that booted before still boots.' It appears twice in the reproduced boot (sys_metadata_commit, sys_http_delivery), matches none of the e2e's four assertions, and this branch touches no file under packages/drivers — so it is NOT load-bearing for the fixture."
  ]
}

Generated by Claude Code


Generated by Claude Code

Decision 1 narrows to A alone, per the maintainer ruling of 2026-09-04
recorded on the card; the measurement B′ was asking for moves to #15163.

`RestApiPlugin` no longer refuses to start when a wall-enforcing tenancy
posture is configured on a deployment with no `kernel-manager` service.
`packages/rest/src/rest-api-plugin.ts` is now byte-identical to `main`.

Why the refusal goes, from the CI triage on this branch: the only
registrar of a `kernel-manager` service in this repository was B′'s own
narrowness control test, so the refusal fired on every real walled
composition the open core can build — the `os serve` process under an
`isolated` posture, the ADR-0105 `bootStack multiTenant` harness, and
seven dogfood suites — for `group` as well as `isolated`. Its premise
was also false wherever it fired: a wall-enforcing effective posture
requires `org-scoping`, which is exactly what keeps the platform's
`organization_id` row policies standing (ADR-0105 D3), so the Layer 0
row wall the message claimed was unenforced was in fact standing in
every case the refusal could reach.

Decision 1 A (a registered-but-failed `tenancy` service answers 503 in
`computeExecCtx`) and decision 2 B (an active ADR-0069 auth gate whose
session re-read fails answers 503) are untouched — both stand exactly
as ruled on 2026-09-02.

- delete the boot-refusal block and its two now-unused imports
  (`effectiveTenancyPosture`, `postureEnforcesWall`);
  `isServiceNotRegisteredError` stays, used by the pre-existing
  objectql provider one layer down
- delete `rest-api-plugin-tenancy-posture-boot-refusal.test.ts` — every
  one of its 2 refusal tests and 4 narrowness controls measures a
  behaviour that no longer exists
- re-aim the phase-1 pin file's header: the ruling it records is now
  A alone, the dangling pointer to the deleted file is replaced by the
  withdrawal note, and §3's "the provider wiring still measures 200"
  reading is restated as CORRECT and PINNED — it is #15163's subject,
  not a regression introduced here
- drop the B′ paragraphs from the changeset; the 503 rows stay

⛔ Not done, deliberately: B′ is not replaced by a warning, a softer
refusal, an env escape hatch, or a narrowed condition. The ruling moved
the question to a measurement card; the code now says nothing about
single-kernel posture at boot.

Part of #13906

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

3 participants