Repository navigation
test(rest): drive the #13906 fail-closed window against the auth-gate allow-list (#15021 measurement) - #16658
Merged
Conversation
… allow-list Measurement only — no behaviour change, and `rest-server.ts` is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
…th-gate-allowlist-window
…urement Both are the grading inputs triage named: whether the window latches across requests, and whether the gate's own re-read can fail while identity resolution stayed healthy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
…ation path The reachability qualifier the fork needs: §2 proves the seam is path-blind, this proves the refusal reaches no remediation route on this door today. Red here means that qualifier is gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
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): |
os-sales
marked this pull request as ready for review
September 7, 2026 16:51
os-sales
enabled auto-merge
September 7, 2026 16:52
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…ommits that decided them (objectstack-ai#20775) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the twelfth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/triggers/trigger-schedule/src/**` and nothing else. By the seat's claim (`5903462246`), it is the largest package in the lane that no in-flight work holds, now that objectstack-ai#20599's PR objectstack-ai#20746 (which edited `time-relative-trigger.ts`) has landed. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 11 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR objectstack-ai#20737 as `4dfff176b`, PR objectstack-ai#20742 as `697845d19`, PR objectstack-ai#20757 as `cba417a8f`). That is **32 sites on 32 lines in 6 files, covering 2 numbers**: - 18 census sites (every census site this package has); - 14 sites in test comments, which the census defers; - no site the gate's grammar cannot see (the package has none, see Acceptance notes). Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **2 distinct shas**. Neither number has an ADR or ruling record of its own (a grep of `docs/adr/`, `scripts/adr-anchors/` and the rest of `docs/` for both numbers finds nothing, and no ADR records the acting-organization decision or the driver-memory per-call refusal), so both anchors are commits, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (32 lines out, 32 in, over 6 files), so no line citation into these files moves. Every one of the 32 changed lines carried a dead citation; there is no reflow line. No code token moves (see the guard below). **No citation number is added.** The only tracker number on an added line is the live `objectstack-ai#8844`, on the line it already stood on. Added minus removed is negative for the two dead numbers and zero for every other number, and no number is new to the diff. No PR number is the citation on an added line. 4 dead sites are left on purpose: three `describe` titles, and one comment that quotes one of those titles verbatim (see the list below). One more file: a `patch` changeset for `@objectstack/trigger-schedule`, because the rewritten prose ships (see Changeset below). ## Census: `trigger-schedule`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/triggers/trigger-schedule/`. Each run counts as a reading only because its board frontier equals the newest issue or pull-request number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | trigger-schedule sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `cba417a8f`, run 2026-09-30T03:30:14Z to 03:33:24Z | enumerated, 186 pages, frontier objectstack-ai#20769 (newest objectstack-ai#20769 before and after) | 823 | **18** | 18 | 2 | 2 | | after | head `226be8050`, run 03:37:59Z to 03:41:09Z | enumerated, 186 pages, frontier objectstack-ai#20769 (newest objectstack-ai#20769 before and after) | 805 | **0** | 0 | 0 | 0 | The before count matches the seat's census and A1 (18 sites: `objectstack-ai#16659` ×17 and `objectstack-ai#16589` ×1, in `schedule-trigger.ts` ×5 and `time-relative-trigger.ts` ×13). A1 noted that PR objectstack-ai#20746 edited `time-relative-trigger.ts` today; the before count above is taken on the base that already holds that edit. The whole-repo drop is 18, exactly this diff's census sites. The `resolves` tally is 33,038 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `226be8050`; the head `14314f49c` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `trigger-schedule/src` (14 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction, 36,840 rows) and did not report it. Every number this package cites is covered by one or the other, so no number needed a separate read to be judged; the two dead numbers were also read one by one on the issues endpoint, and each answers 404. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `cba417a8f` | 159 | **36** | 18 | 15 | 0 | 3 | | after, `226be8050` | 127 | **4** | 0 | 1 | 0 | 3 | Its src-comment column equals the census's 18, which is the control on the second instrument. The 123 live citations are the same in both readings, and the drop of 32 citations is exactly the rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 162 occurrences and 36 dead before, 130 and 4 after. Beyond the gate's grammar it sees 3 tokens, none a tracker reference: the maintainer decision-batch ordinals `batch objectstack-ai#13`, `objectstack-ai#116` and `objectstack-ai#118`, which the gate's `NON_CITATION_HEADS` excuses by design. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (21 lines blame to the anchor itself; for the other 11, `merge-base --is-ancestor` of anchor and blamed commit exits 0). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#16659` | 34/6 | 30/4 | `ecdfc9411` (PR objectstack-ai#17334): a time-triggered flow (`schedule` or `time_relative`) declares its acting organization on its start node as `config.organization`; the engine lifts it onto the binding; both time triggers refuse to bind a flow that declares none, naming it at `error` and THROWING so the engine records the refusal instead of reporting the flow bound; the run carries the declared organization as `tenantId`; and the time-relative sweep's own query carries it too, so the sweep SELECTS inside that organization (the review finding F2 its diff names), with a store that cannot honour the scope reported at `error` and an object the engine exempts from scoping disclosed at bind. Its body names `objectstack-ai#16659` twice (the three consequences pinned on both drivers, and the proof registered), and its diff names it on 65 added lines. The anchor the spec stage (`0f6dcac5e`) and the lint stage (`f29c83db1`) already give the same number | | `objectstack-ai#16589` | 2/2 | 2/0 | `555a89cbd` (PR objectstack-ai#17005): `driver-memory` gains a third seam, `assertCallNotTenantScoped`, called first in every driver door that accepts `DriverOptions`, which REFUSES a call the engine tenant-scoped instead of discarding the scope and answering every organization's rows; row-level isolation is deliberately not implemented. Its message does not carry the number, but its own diff writes the mechanism the two lines describe and names `objectstack-ai#16589` 30 times (the `[objectstack-ai#16589] Seam 3` markers and the guard's docblock), so it is the commit that decided it. New to the sweep | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 2), and both are ancestors of the base (`merge-base --is-ancestor`, exit 0 for both; control leg: stage 1's landing `422db788a` exit 0; reverse leg, base against `ecdfc9411`, exit 1; the history is complete, `--is-shallow-repository` false, 15,160 commits; each anchor lies deeper than the control, 1,616 and 1,814 commits behind the base). Each of the 2 numbers answers 404 on the issues endpoint, which serves pull requests too. Independently, the package's own shipped `CHANGELOG.md` pairs `ecdfc94` with `objectstack-ai#16659` (line 149) and `assertCallNotTenantScoped` with `objectstack-ai#16589` (line 238). ## Wordings to check - **Tag swaps in brackets or parentheses.** 「[objectstack-ai#16659]」 became 「[commit ecdfc94]」 on 18 lines, 「(objectstack-ai#16659)」 became 「(commit ecdfc94)」 at `schedule-trigger.ts:251`, `:372` and `time-relative-trigger.ts:50`, and 「(objectstack-ai#16589)」 became 「(commit 555a89c)」 at `time-relative-trigger.ts:615` and `time-relative-trigger.test.ts:988`. - **Section rules.** `schedule-trigger.test.ts:327`, `time-relative-trigger.test.ts:794` and `:862`: the 16-character phrase replaces the 6-character number and the trailing rule loses 10 characters, so each line keeps its width exactly. The `:862` heading keeps 「F2」 beside the sha; F2 is the selection finding `ecdfc9411`'s own diff names. - **「before objectstack-ai#16659」** at `time-relative-trigger.ts:365` and `:543` became 「before commit ecdfc94」: before that commit the sweep queried with `isSystem` alone, which is the unscoped selection both sentences describe. - **「the objectstack-ai#16659 defect」** at `time-relative-trigger.ts:561` and `time-relative-trigger.test.ts:1371` became 「the defect commit ecdfc94 fixed」: a commit fixes a defect, it is not one, and the widening both sentences name is the selection half that commit closed. - **`schedule-trigger.test.ts:512`.** 「the exact defect objectstack-ai#16659's own refusal was shaped to avoid」 became 「the exact defect commit ecdfc94's own refusal was shaped to avoid」: the defect is a refusal that logs and arms anyway, and that commit is where the refusal became a throw so the engine records it. - **`schedule-trigger.test.ts:588`.** 「(the objectstack-ai#16659 suite above)」 became 「(commit ecdfc94's refusal suite above)」, so the pointer still lands on the refusal suite at `:337`. ## The 4 sites left - **Test strings, 3 sites on 3 lines**, all `describe` titles, left as stages 1 to 11 left theirs: `schedule-trigger.test.ts:337` and `:462`, `time-relative-trigger.test.ts:805` (all `objectstack-ai#16659`). - **One comment that quotes a kept title verbatim:** `schedule-trigger.test.ts:71` points the reader at 「`ScheduleTrigger — the acting-organization refusal (objectstack-ai#16659)` below」, the exact text of the `describe` title at `:337`. The number there belongs to the quotation, so it stays with the title it quotes: rewriting it would point at a title that does not exist. It moves when the title does. - No source string, operator log string, assertion message, quoted maintainer ruling or generated file in this package carries a dead number. - Outside `src`, the package's `CHANGELOG.md` names `objectstack-ai#16659` on 6 lines and `objectstack-ai#16589` on 2 (lines 149, 153, 238, 273, 297, 300, 302, 304). It is release-owned and deliberately not edited here (see Acceptance notes). The package `README.md`, which also ships, names neither number. ## Mechanical guard: no code token moves The guard compares, base `cba417a8f` against head, over all 6 touched `.ts` files: - **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild` walk, so comments are trivia and JSDoc nodes are never visited). String and template literals are therefore read in full. - **Reading 2**, the full token stream in parser context (a `getChildren` walk, so punctuation and keywords are included; JSDoc nodes skipped). Results: - Real run: 10,192 base leaf tokens, **0 files with a token change** on either reading (exit 0). - Comment control in `schedule-trigger.ts` (「the same way `schedule` is.」 to 「the same way as `schedule`.」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `time-relative-trigger.ts` (`resolveBindingOrganization(binding)` given `as FlowTriggerBinding`): DIFFER, 1,215 to 1,216 leaf tokens and 2,760 to 2,762 full tokens (exit 1). - Positive control, one digit changed inside a kept test title (`schedule-trigger.test.ts:462`, `objectstack-ai#16659` to `objectstack-ai#16658`): DIFFER on the string literal (exit 1). Every mutation went through `scripts/ablation-replace.mjs` (wrap mode) under a shell trap that restores by absolute path, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`651170483856`, `c85aadbd168d`, `78a5dea4a463`), with `git diff HEAD` empty and a clean tree afterwards. A first version of reading 2 used TypeScript's context-free scanner and was discarded before any control ran: it opened template tokens on backticks it could not place and swallowed comment text into them, so it reported comment edits as token changes (4 files) while reading 1 read 0. The parser-context stream replaced it, and every figure above is from the replacement. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/trigger-schedule` (`.changeset/20596-trigger-schedule-provenance-anchors.md`) is included. Its body is stage 11's, word for word, with the package name changed. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. After the build: - `ecdfc9411` appears 3 times in each of `dist/index.js` and `dist/index.mjs`: the inline comments at `schedule-trigger.ts:777` and `time-relative-trigger.ts:585` and `:702`, which the bundle keeps. - It appears twice in each of `dist/index.d.ts` and `dist/index.d.mts`: the `FlowTriggerBinding.organization` docblock (`schedule-trigger.ts:32`) and the sweep-context docblock (`time-relative-trigger.ts:50`). - `555a89cbd` appears once in each JS entry (`time-relative-trigger.ts:615`). - Positive controls, one unchanged line beside each shipped rewrite, land exactly where their neighbours do: four neighbours once in each JS file and 0 in the declaration files, and two once in each declaration file and 0 in the JS files. - A never-written negative phrase appears nowhere in `dist`. - Neither dead number is left in `dist`. ## Gates (head `14314f49c`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 1 added citation across 2 files, the live `objectstack-ai#8844`, and it resolves. - **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the sibling-package prose-id baseline holds, no growth). - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `14314f49c` (after a fresh fetch) derived 59 commands. They are all 53 derived at dispatch, plus `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. - Each ran with its exit code captured before any pipe, and all 59 exit 0. - `--ran`, fed each command with its exit code, reports 59 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. - A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/trigger-schedule test`: 8 files pass and 170 tests pass. `vitest list --filesOnly` names 8 files, all the tracked test files, the 4 touched ones included. - `pnpm --filter @objectstack/trigger-schedule typecheck` exits 0, and `tsc --listFiles` holds all 14 files under `src/`, the 6 touched ones included. - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 6 touched `.ts` files, gives 6 files, 0 errors and 0 warnings (its `--format json` output). All 6 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 7 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word 「option」. In this package: `#N-word` none, `#A/#B` none, `option #N` none, at the base and at the head, which is the claim's 0 / 0 / 0. The two `pre-objectstack-ai#10220` spellings in `time-relative-trigger.test.ts` are extracted by the gate as this repository's `objectstack-ai#10220`, which the census judges live. - **`CHANGELOG.md` is left.** `packages/triggers/trigger-schedule/CHANGELOG.md` names `objectstack-ai#16659` and `objectstack-ai#16589` on 8 lines. It is release-owned (AGENTS.md, Documentation Guardrails), a deferred surface of the citation gate, and ⛔ not part of this stage. - **「The card」 phrases are left.** 8 comment lines in 5 files of this package speak of 「this card」, 「that card」 or 「the card」. They carry no number and neither instrument sees them. The one beside a rewritten line, `schedule-trigger.test.ts:329` (「the card's consequence (3)」), sits under the heading `:327` that now names `ecdfc9411`, whose own message pins those three consequences, so it keeps a referent. The rest are unchanged, as in stages 8 to 11. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#16589` → `555a89cbd` is new to the sweep; `driver-memory`'s own `src` still names `objectstack-ai#16589` on 29 lines in 4 files (26 of them comments; corrected by the seat from the dev report, which measured it), all outside this lane's stage surface. `objectstack-ai#16659` → `ecdfc9411` reuses the spec and lint stages' anchor. - **Base.** The branch is on `main` at `cba417a8f`. `main` has since moved three commits (`0d9349fea`, `7053333e1`, `f284ab26d`). Their 9 files are one changeset, ADR-0053, and sources and tests under `service-analytics` and `service-automation`. They touch nothing under `trigger-schedule`, nor `scripts/check-issue-citations.mjs`, `.changeset/config.json` or the `doc-authoring-prose-id` baseline. `service-automation` is a dev dependency of this package, but this diff moves no code token, so nothing here can interact with it. No merge was taken; the merge queue rebuilds on the merged generation. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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 free
to 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.
Part of #15021 — the measurement half only.
⛔ #15021 remains open after this PR merges, deliberately, and no closing keyword names it anywhere in this body. The card's remaining half is a runtime-authorization fork that the 2026-08-28 negative-boundary ruling reserves to the maintainer, so the card has to survive to reach the decision inbox.
⛔ No behaviour change.
packages/rest/src/rest-server.tsis not edited — that file is held by open PR #16648 (card #16337), and a measurement should not need to rewrite the door it reads. The whole diff is one new test file.Why this exists
#15021 declares itself NOT MEASURED in its author's own words — "I did not drive this" — and is a reading of where the
AuthzStoreUnavailableErrorthrow sits relative to thepathExemptcomputation inenforceAuth. Triage routed it as work rather than escalating it, and scoped the work to exactly one thing:This is that drive.
Verdict —⚠️ IT REPRODUCES
Under the #13906 decision-2-B window (
isAuthGateActive()answeredtrueand the gate's session re-read then failed), every allow-listed path receives the same 503 as a protected one.enforceAuthis never reached, sopathExemptis never computed for that request, and the refusal is byte-identical on an allow-listed and a protected path.⛔ Nothing is chosen here. The card's options 2 and 3 are a runtime-authorization judgement; this PR states the reading and stops.
What was driven
All legs run the real composition — the public entry to
computeExecCtx(resolvePackageRouteExecutionContext), then the real consumer (enforceAuth) on the same request. Constructor seams only; nothing private is replaced.SERVICE_UNAVAILABLE/object: auth_gate, re-read counter proves the fault was reachedauthGate,enforceAuthdoes not blockcodeisAuthGateAllowlistednorreq.pathCONTROL B is what makes the verdict readable: on this exact fixture, on these exact paths, the allow-list does keep a gated user through. Remove only the fault and the door opens; put the fault back and it does not. So the 503 is the window, not an allow-list that never applied.
⭐ Reachability qualifier — please read this before ruling
The card's impact sentence is "a gated user cannot remediate during a session-backend fault." The seam-level finding above is certain. The impact claim needs one more fact, and §3 measures it:
GET /api/v1/discovery,GET /api/v1/environments/:environmentId/discovery).GET /api/v1/discoveryanswers 200 while the gate re-read is failing: its handler resolves no execution context, so the window cannot reach it./api/v1/:object/:idwould capture/api/v1/auth/sign-in. Measured against the shipped:param/*semantics, over five canonical remediation paths and all three configurations: zero mounted patterns capture any of them. That is now a pinned assertion, not a recorded number.computeExecCtx—plugin-authmountsrawApp.all('/api/v1/auth/*')(defaultbasePath/api/v1/auth) andplugin-hono-serverregisters/auth/me/*ahead of it.⇒ On today's tree the defect is real at the seam and not reachable through any mounted remediation route on this door. That is a materially different thing to rule on than the card's impact sentence implies, in the direction that makes option 1 easier — and it is fragile in the direction that makes it harder. §5 is the tripwire: if it ever goes red, a mounted route now answers a remediation path, the qualifier is gone, and §2 says every such answer is a 503 for the duration of the fault. ⛔ It must not be relaxed to let a new mount pass.
Grading inputs for triage — ⛔ not a re-grade
Triage named the two conditions that would move
priority:p3. Both are measured (§4), and both are reported as readings:WeakMapon the request object), so the refusal does not carry forward.The fork — for the decision inbox
Stated as the card states them, ⛔ with no recommendation and no ranking:
isAuthGateAllowlisted(req.path)before raising. Cost:computeExecCtxstarts branching on the request path for an authorization decision, and the memoized per-request context becomes path-sensitive.resolveExecCtxcaches on(req, environmentId)with no path in the key.enforceAuth— carry "the gate could not be read" as a distinct context value and let the consumer that already knows aboutpathExemptdecide. WidensExecutionContext.Options 2 and 3 both change
packages/rest/src/rest-server.ts, which #16648 holds. Whoever takes the ruling should sequence against that PR.Verification
All of it ran on
d084b089da, this branch's final commit.pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/auth-gate-allowlist-fault-window.measurement.test.ts— 14 tests, all green.pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/rest-auth-gate.test.ts src/execctx-authz-input-seam-reachability.test.ts src/rest-exec-ctx-principal-kind.test.ts src/package-door-execctx-fault-reachability.test.ts— the sibling pins on this window: 4 files, 93 tests, all green and unmodified.pnpm --filter @objectstack/rest typecheck.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsand reconciled with--ran: 46 derived, 46 run, 0 UNRUN. 44 exit 0.pnpm check:dual-build-cjs-loadsandpnpm check:type-check-debt. Both read built output across the whole tree, and this worktree built only the@objectstack/restdependency closure. Declared to CI, which builds the whole tree and is the authority on them. The narrowing is safe to declare rather than close locally for a reason that is checkable: this diff adds one.test.ts,git grepfinds no importer of it anywhere in the repo, and tsup's entry issrc/index.ts— so nothing it changes can reach any package'sdist/.Every exit code above was captured before any pipe.
skip-changeset: this diff publishes nothing.packages/rest'sfilesis["dist","README.md","CHANGELOG.md"]and tsup's entry issrc/index.ts, so a.test.tsundersrc/never reachesdist.验收备注
Out of scope for this card, filed nowhere, recorded here so ACCEPT can see them:
ALLOW_SUFFIXESrule inpackages/core/src/security/auth-gate.tsis a suffix test on the path, not a route test, so a concrete/api/v1/data/health(an object literally namedhealth) reads as allow-listed and the ADR-0069 gate does not fire on it. That over-broad direction is [Decision] Fail-close isAuthGateAllowlisted's "no path ⇒ exempt" default (Option 2 of #7432) #7898's subject — the card explicitly asks that the two not be merged by keyword, and they are not merged here. Noted, not filed.pathExemptinsideenforceAuthis reached by no allow-listed route pattern this server mounts (§3). Whether that makes the branch load-bearing only through the:objectsubstitution above is a question about [Decision] Fail-close isAuthGateAllowlisted's "no path ⇒ exempt" default (Option 2 of #7432) #7898's territory, not this card's. Noted, not filed.If a repair lands
§2 pins what the tree does, including the answer the card questions. Options 2 and 3 both change it. When one lands, re-aim the assertion in place and quote the superseded text beside it — the discipline
execctx-authz-input-seam-reachability.test.tsalready follows for this same window. ⛔ Do not delete the leg.🤖 Generated with Claude Code
https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
Generated by Claude Code