Skip to content

test(rest): drive the two authorization-input absorb seams in computeExecCtx — phase-1 measurement (#13906) - #14272

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-13906-execctx-authz-seams
Sep 1, 2026
Merged

os-justin merged 2 commits into
mainfrom
claude/issue-13906-execctx-authz-seams

Conversation

@os-justin

@os-justin os-justin commented Sep 1, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #13906

What this PR is

Phase-1 MEASUREMENT delivery: one new test file (packages/rest/src/execctx-authz-input-seam-reachability.test.ts), zero production changes — rest-server.ts is not touched, per the dispatch's read-only order. It turns the card's code reading into driven readings, with a positive control beside every fault leg. Pins on permissive answers are measurements, not endorsements: whether any seam should change is the phase-2 ruling, deliberately not taken here. #13906 stays open on merge: the measured defect is unrepaired and phase 2 awaits the maintainer's ruling — which is why the first line above says Part of rather than a closing keyword.

Measured answers to the four dispatch questions (driven at b93d4fd49f)

Q1 — the permissive path IS reachable, driven. With a real ObjectKernel on the kernelManager path: a healthy isolated tenancy service makes the Layer 0 organization_membership_ended refusal fire for an ex-member's org-stamped API key (401 on the wire; the resolver names the reason — that is the positive control that the refusal APPLIES to this fixture). Change ONE condition — register the tenancy service as a factory that throws, the registry's own "registered and failed to construct" rejection (#13905 class) — and the same key is served 200 with full grants. The refusal was skipped, not inapplicable: the resolver-level leg shows accessible_org_ids in hand and lacking the stamped org while the principal is admitted. "Registered and failed" and "never registered" answer byte-identically at the door. The sibling posture-conditional refusal (organization_required, org-less key under isolated) is skipped the same way — measured in the same section. Note: api-key.ts documents absent-posture-admit as a deliberate decision for the org-less refusal; what that comment does not distinguish is "no tenancy service" from "the tenancy service failed" — both arrive as the same absent posture.

Q2 — the card's sharper claim HOLDS, and it is the normal state of the shipped single-kernel wiring. The local kernel in computeExecCtx is assigned only on kernelManager branches (section 0 pins this mechanically from source). Driven: ONE real kernel with a healthy, wall-enforcing tenancy service behind a RECORDING factory, wired both ways. Via kernelManager: 401 refusal, tenancy factory invoked. Via the shipped provider wiring (byte-faithful to the post-#14250 rest-api-plugin.ts providers): 200 for the same ex-member key, and the tenancy factory was never invoked — invocation count 0. On that path the posture is undefined before any failure can occur; no failure is even required.

Q3 — a failed probe and an inactive gate are indistinguishable, at the context field and at the consumer. Positive control: an ACTIVE gate with a healthy session re-read blocks the gated user — enforceAuth answers 403 with the gate's code and message. An inactive gate, a THROWING isAuthGateActive probe, and a FAILED session re-read under an active gate (transient class: identity read succeeds, the gate's second read rejects — the fixture counts the reads, so the gate is proven to have probed) all produce authGate: undefined on the assembled ExecutionContext and the identical not-blocked observable, asserted deep-equal against the inactive leg. The gated user whose session policy says must be blocked is not blocked, and the wire carries no trace.

Q4 — the criterion controls. In-file: the enumeration criterion flags the two named seams (known positives) and never flags wiredEngineOrLoud (the known kept-apart site). Historical control, run outside vitest with the same criterion over git show trees: at the pre-#13910 tree (836a29c27a~1) it reports 10 absorb site lines in computeExecCtx and flags the provider engine seam — the known positive that #13476 was filed to repair; at today's HEAD it reports 9, the provider engine line is gone and wiredEngineOrLoud is present — exactly reproducing the card's own census (10 before / 9 after / 1 loud after).

Premise re-derivation (ZONE 3)

Both seams are still live and still collapse on today's tree after the three 09-01 rest-server.ts merges (#13811, #13910, #14120) and after #13904's provider repair (#14250). #13910 repaired the ENGINE seam beside them; #14250 narrowed the provider absorb to the branded not-registered rejection. Neither reaches the tenancy or auth-gate seams. One repair-relevant fact for phase 2: the tenancy seam's blanket catch now absorbs the #13905 branded not-registered rejection and unbranded construction failures indistinguishably, although the discriminator exists — isServiceNotRegisteredError is exported and the shipped objectql provider already uses it one layer down.

Out of scope, not entered

The auth-service seams and the getSession swallow (the CONTEXT-LOST degrade recorded on #13255, which 404s in this repo as the routing comment warned), and the settings/localization seam.

Verification

  • pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/execctx-authz-input-seam-reachability.test.ts — "Test Files 1 passed (1) · Tests 17 passed (17)".
  • Full affected-package suite at the same tree: pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 — "Test Files 167 passed (167) · Tests 2802 passed (2802)".
  • pnpm --filter @objectstack/rest typecheck — tsc clean plus "check:test-typecheck: OK — test layer compiles under tsconfig.test.json" (the build tsconfig excludes tests, so the test-layer half is the one that measures this file).
  • Gate union derived with node scripts/pm/dispatch-gates.mjs, re-derived after the last commit at b93d4fd49f (stderr names this repo and commit): 26 families both times, comm reconciliation empty in both directions. 25 of 26 green with their own verdict lines — including check:objectql-double-limit ("301 doubles graded, bound applied" — it caught the first fixture being limit-blind; repaired by holding the bound by presence) and check:where-matcher ("325 matchers, refusing conformer"), check:engine-double-contract, check:type-check-debt ("27 ledger entries re-measured, none above recorded"), check:nul-bytes (standard any-edit family, additionally run). scripts/check-test-completeness.mjs exits 3 locally and its own text says to record it as NOT MEASURED outside CI (it parses a saved turbo test log CI supplies).
  • Dogfood suite: not owed — this diff does not touch rest-server.ts (the dispatch names that suite for diffs that do).
  • Changeset: none — tests-only diff, skip-changeset applied per the closed list.

Session: https://claude.ai/code/session_015YPiiDdw96RGS25WLctCQP

Generated by Claude Code

…teExecCtx

Phase-1 MEASUREMENT — no production code changed. Drives, with positive
controls beside every fault leg:

- tenancy posture (kernelManager path): a healthy isolated posture refuses an
  ex-member org-stamped API key (organization_membership_ended, 401 on the
  wire); a tenancy service that is REGISTERED AND FAILS to construct skips the
  refusal and the same key is served 200 — the permissive path is reachable.
  "Registered and failed" and "never registered" answer byte-identically.
- tenancy posture (single-kernel provider path): the local `kernel` is only
  assigned on kernelManager branches, so the posture probe is never issued —
  a healthy, wall-enforcing tenancy service is NEVER INVOKED (recorded), and
  the same deployment facts answer 401 via kernelManager and 200 via the
  shipped provider wiring. The always-undefined claim holds as the normal
  state of that wiring.
- ADR-0069 auth gate: an active gate blocks the gated user (403 + gate code);
  an inactive gate, a throwing probe, and a failed session re-read are
  byte-indistinguishable at the ExecutionContext field and at enforceAuth.
- Section 0 pins the enumeration criterion in-file (flags the two seams,
  never wiredEngineOrLoud); the historical control (same criterion flagging
  the provider engine seam pre-13910, 10 sites before / 9 after) is recorded
  on the card.

Pins are measurements, not endorsements.

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

The fixture find double now holds the caller's limit bound by presence and
filters through one hand-written matcher that supports equality and the $in
shape the shared resolver actually issues, refusing every other shape loudly.
check:objectql-double-limit graded the new double (301 graded, bound applied)
and check:where-matcher counts the matcher among the refusing conformers.

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

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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 — 0 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 6eb8e3cc5022c4b1d0007962220444f4f947036d → packageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants