Uh oh!
There was an error while loading. Please reload this page.
test(rest): census what an absent execution context means at all 72 resolveExecCtx consumers - #13213
Merged
Merged
Conversation
…esolveExecCtx consumers Every row is DRIVEN — the mounted handler is called under two wirings of one instrument (an absent context, and an entitled one) and the decision is read off the response, never off the grep shape. The site reached by each driven route is read off the stack, so the route-to-site correspondence is observed rather than transcribed. Measured: - all 52 bare sites read an absent context as the ANONYMOUS SUBJECT and refuse 401 UNAUTHENTICATED, each with a same-instrument positive control; - the metadata umbrella refuses the same on every non-carve-out /meta route; - with the umbrella isolated, six inner sites do not refuse on their own reading (single-guarded) while six do (double-guarded); - the public-audience carve-out — the one production path where a swallowed context reaches a consumer — is fail-CLOSED at the item read and the tree, and fail-CLOSED by FILTERING at the list, where a 200 with an empty body is the refusal. Two corrections to the thread's counts, both re-derived from the tree: the caught/bare split is 20/52, not 16/52 (four sites spell the catch on the continuation line and a single-line grep misses them, which is why 16+52 does not reach 72); and all 52 bare sites are guarded by enforceAuth on the very next line while none of the 20 caught ones is — the inverse of the reason the thread gave for doing the bare half first. No behaviour change: one new test file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TvqBFLRzXdSPcbusDoED9k
Contributor
📓 Docs Drift CheckNothing 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
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Aug 29, 2026
os-trump
marked this pull request as ready for review
August 29, 2026 12:33
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#13160
A per-site census of what an absent execution context means at every
resolveExecCtxconsumer inpackages/rest/src/rest-server.ts. Measurement only — this PR changes no behaviour and repairs nothing. One new test file,packages/rest/src/execctx-consumer-census.test.ts.⛔ It does not generalise PR #13153's packages-door result. That door is measured there; every row below is measured here, independently.
How each row was produced
Every classification is driven, never read off the grep shape. The mounted handler is called under two wirings of one instrument — an absent context, and an entitled one — and the decision is read off the response. The site line reached by each driven route is read off the stack, so the route-to-site correspondence is observed rather than transcribed.
⭐ Every "this site denies" row stands beside a same-instrument row that is served. 30+ of the driven routes answer a real 2xx to an entitled caller on the same boot, and the audience gate answers 403 PERMISSION_DENIED to an entitled caller holding the wrong permission set — so the 401s are decisions, not a stuck needle.
Two corrections to the thread's numbers, re-derived from the tree
1. The caught/bare split is 20 / 52, not 16 / 52. The thread's own table does not add to 72, and this is why: four sites spell their
.catch(() => undefined)on the continuation line, invisible to a single-line grep. 16 same-line + 4 continuation-line = 20 locally caught; 72 − 20 = 52 bare. The 52 was right; the 16 was the undercount. Both totals the card offered as controls hold: 72 invocation sites, 89 mentions.2. ⭐ The sequencing rationale is inverted — the bare half is the guarded half. Measured, mechanically, on all 72:
The bare sites are bare because the shared anonymous floor is the very next statement. The locally-caught sites carry their own
.catchbecause they sit outside that floor and each must decide for itself. The card's reasoning — "a reader at one of those lines sees an ordinaryawaitand the swallow is 500 lines away" — is true about the swallow, but the reader also sees the guard on the next line. The surprise, if there is one, lives in the 20, not the 52. (The 52 were still done first, as dispatched; they are simply the uniform half.)The census
Supplier (measured, not assumed). A faulting resolve fulfils with an absent context rather than rejecting, so a local
.catchhas nothing to catch on that path. Positive control: a healthy resolve fulfils with the context. The dispatch's line numbers for that method are stale by +31 — it isrest-server.ts:1853–:2046onorigin/main(f951e945), not:1822–:2015, and the wrapper's second net is:1521, not~:1490. #13153's own 31-line comment block is the shift. The substance is unchanged.The 52 bare sites — all 52 driven, none inferred. An absent context is the anonymous subject: every one refuses 401
UNAUTHENTICATEDthrough the shared floor, and reaches no downstream service. Fail-CLOSED.The umbrella (the metadata surface). One wrapper guards every
/metaendpoint. An absent context is refused 401 there on every route outside the carve-out below.With the umbrella isolated — a counterfactual, the same "isolate the floor to read the clause underneath" move #13153 used — the inner sites split in two:
FORBIDDEN, capability clause) — double-guarded_drafts,_migrate-stored, meta item PUT, meta item DELETE, publish, rollback/layers,/audit,/published, the object masker⭐ The one production path where an absent context reaches a consumer.
isPublicAudienceReaddeliberately lets an anonymous GET of the book/doc list, the book/doc item read and the book tree past the anonymous floor, so the ADR-0046 §6.7 audience gate decides instead. Measured across four audiences:UNAUTHENTICATEDorgUNAUTHENTICATEDpublicUNAUTHENTICATEDPERMISSION_DENIEDFail-CLOSED, and the gate reads an absent context as unauthenticated rather than as authenticated-holding-nothing — those are two different answers and it picks the first. The tree route answers the same way.
200with an empty body is the refusal there and200with the item is the allow, so status alone reads it wrong. Asserted on the body.Both were hit during this work and both are written into the file, because each produced a confident wrong answer first:
getMetaItemanswers the declared envelope and the handler unwraps it once. A fixture returning the bare document leaves the unwrapped value absent, so every downstream gate guarded on it is skipped — including the audience gate — and the raw envelope is still served 200. That reads precisely as "the gate did not run for an anonymous caller". It was caught only because the sibling tree route refused on the same boot.What is NOT measured here
rest-server.ts:1521) is mounted by the package registrar, not by this server's own route registration, so it is outside this file's route table. Measured inpackage-door-execctx-fault-reading.test.ts(PR test(rest): measure what a swallowed execution context reads as at the packages door #13153) — fail-CLOSED, two ablation legs, both rival readings falsified. Recorded as deferred to that file, ⛔ never as "assumed closed". Every other one of the 72 is driven here.check:test-completeness— NOT MEASURED locally: it grades a saved test log, and its own text says the no-argument invocation is not a red and there is nothing to fix.No fail-open found
⛔ No site in this census reads an absent execution context as "no gate applies". Nothing here is escalated as a security card.
Ablations — the census can go wrong
Both mutated after the implementation was committed, both confirmed on disk by blob hash before the run, both restored and the restore confirmed by blob equality plus an empty
git diff HEAD. The mutation changed the outcome in each case, which is itself the proof that the resolution path is the source and not a stale build.enforceAuthguard from a single bare site: 2 red — the 52-of-52 invariant, and the driven leg withGET /api/v1/data/:object under an absent context: expected 200 to be 401. The census names the exact door.Verification
Union re-run on the final commit
7c31930e.packages/rest: 156 test files, 2580 tests passed;typecheckexit 0. The new file is inside the test tsc program (--listFiles, 1 hit) — not a green over source nothing read.check:authz-resolver— the authorization-relevant gate for this file — its own verdict line: "single shared authorization resolver intact; both entry points delegate."node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackfrom the real change set (1 path), plus the convention-triggered set: all green —cross-package-test-inputs,dispatcher-error-vocabulary,logger-receiver-detach,objectql-double-limit,page-declaration-shape,published-files,slot-lookup,test-source-alias,type-source-resolution,dual-build-cjs-loads,query-options-erasure,engine-double-contract,where-matcher,type-check-coverage,type-check-debt,route-envelope,nul-bytes,ci-filter-parity,comment-mask-adoption,keyed-text-bounds,plugin-teardown-shape,shard-attestation,undeclared-dep-imports,affected-docs,drift-comment.check:type-check-debt --re-measureandcheck:dual-build-cjs-loadsboth refused on an unbuilt worktree (exit 3 / prerequisite, not a red). The workspace closure was built and both then ran green — "30 ledger entries re-measured, none above its recorded number" and "102 published require entry points across 66 packages load".No changeset: this PR publishes nothing from any package.
skip-changesetapplied.Generated by Claude Code