Repository navigation
fix(core,rest,services): fail loud when a permission-store read fails - #13475
Conversation
…#13279) `resolveAuthzContext`'s `tryFind` answered a THROWN read the same way it answered an EMPTY one — `[]` — so a permission-store outage resolved as an authenticated principal holding zero capabilities and the package door answered a 403 byte-identical to a genuine capability denial. `tryFind` now distinguishes the two facts and raises `AuthzStoreUnavailableError` (existing ADR-0112 code `SERVICE_UNAVAILABLE`, status 503) when a read is issued and throws. The `!ql` guard is untouched: an absent engine is not a failed read. Making the resolver loud is necessary but not sufficient, which was measured rather than assumed: six of the eight production transports wrap the call in a fail-closed `catch`, and with those untouched the door answered 401 — the outage had merely changed disguises. Each now re-raises the branded error and keeps its prior behaviour for every other fault. Maintainer ruling 2026-08-30, verbatim: 第一批其余同意 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
📓 Docs Drift CheckThis PR changes 9 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 45 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4d210ae2d9409e9eb324951b73b28cb8612041ca && git checkout 4d210ae2d9409e9eb324951b73b28cb8612041ca
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b2dea862cb2c7caf67bddf99ef8b5f7542e68f80 cfe54c80d08462121f7f51db504f4a5b9f70122d && git checkout -B drift-repro b2dea862cb2c7caf67bddf99ef8b5f7542e68f80 && git merge --no-ff cfe54c80d08462121f7f51db504f4a5b9f70122d
node scripts/docs-audit/affected-docs.mjs --json b2dea862cb2c7caf67bddf99ef8b5f7542e68f80
|
Declared-breaking changesets must answer the ledger question in writing. This change touches no metadata surface in either direction — no Zod schema, no spec declaration, no authorable key, no stored row, no object definition — so `objectstack migrate meta` has nothing to visit and no tombstone exists to mint. `SERVICE_UNAVAILABLE` is an existing `StandardErrorCode` member, so even the wire vocabulary is unchanged; only which declared code an outage selects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
The changeset claimed "embedders without a data plane are unaffected". Measured false: the pinned cases cover an UNWIRED engine and an EMPTY one, not a real engine whose sys_* tables were never created, where find is issued and throws `no such table`. That shape is currently treated as an outage and must not be — with no permission tables provisioned, zero capabilities is the true answer. Records the measured cost (client CRUD 503s, 400s becoming 503s, two silenced diagnostic channels) and why the boundary cannot be drawn inside core. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
|
PM review — no ACCEPT/REWORK verdict. This card is escalated to the maintainer as The ruled repair is implemented, measured and pinned. What blocks it is not defect quality: implementing the ruling faithfully falsified a premise the card was built on, and the remaining question — where the loud/quiet boundary sits, and which package owns the classifier that draws it — is a security-boundary and cross-package-structure call. That is a human floor, not a seat's. Reconciling the report against the CI record I collected — including one correction to my own readingI collected six red checks independently at the previous head
The scope question I made mandatory at dispatchWas each transport edit forced by the new signal, or opportunistic? — answered by measurement, not argument: with the resolver loud and the nets untouched, the package door answered 401. The outage changed disguise rather than disappearing, from "you hold no capability" into "you are not authenticated", which is byte-identical to a genuine anonymous caller. Each of the seven transport files carries the same three-line re-raise inside a pre-existing fail-closed catch, with no behaviour change for any other fault; Handling I want on the record⛔ No assertion was relaxed, re-baselined, allow-listed or deleted. The reds are left standing because they are the finding. The alternative — editing tests to match code on a security surface — is what I would have rejected the PR for. The changeset's own earlier overclaim, that "embedders without a data plane are unaffected", is retracted in writing by its author; recorded here so the retraction is not only in a superseded revision. Posture⛔ Draft. ⛔ Not enqueued. ⛔ Nothing merged, and nothing pushed to this branch by me at any point. #13279 moves Generated by Claude Code |
|
Addendum to In that comment I wrote that
Relayed to the implementing seat with the merge-base check first: if the census is already red on the base, this is not this PR's and the outcome is a comment, not a fix. ⛔ The PR stays draft and unenqueued regardless; the five
Generated by Claude Code |
… line shift Pure line rot from this PR's transport edits, not a population change — measured both ways before touching the page: merge base 71627f7 census EXIT=0, 109 sites / 145 anchors / 27 non-read this branch same 109 sites; rest-server.ts holds 6 read sites on BOTH sides, shifted [1266 4270 5633 5865 6210 6403] -> [1269 4281 5644 5876 6221 6414] Every re-anchored line was checked content-identical between base and head (+3 before the import insertion, +11 after the computeExecCtx expansion). `--fix` repaired package-routes.ts and sharing-plugin.ts mechanically. It REFUSED rest-server.ts, reporting "page anchors 7 distinct read line(s), census finds 6 -- the POPULATION changed". That refusal is a limitation of the --fix heuristic, not a population change: it counts the two ledger-excused non-read citations (`:1234`, `:1263`, the "never settable from inbound HTTP" pair) as read anchors. The census itself reports 6 reads on both sides. Those five anchors were therefore re-anchored by hand to lines proven byte-identical, and NON_READ_ANCHORS needed no edit — it locates rows by needle, so it re-resolved itself once the page pointed at the right lines. No row was written, deleted or reworded; only line numbers move. The gate's verdict line is now byte-identical to the merge base's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
|
On the census repair at Straightening the record, because My ⛔ rested on a premise: a What I verified myself, by content on the commit rather than from the report:
⇒ The distinction that matters: re-baselining is changing what the page asserts; this changed only where it points. A
⛔ PR remains draft and unenqueued. The fork on #13279 is untouched by any of this and still needs the maintainer. Generated by Claude Code |
|
Census repair at
The push landed at 14:40:14, so this is not a scheduling lag — no workflow was created for that sha at all. What it means concretely: the census ⛔ Deliberately not doing any of: an empty commit, a close-and-reopen, or a speculative re-run to kick CI. The situation resolves itself — #13279 is now ruled (option A, The fact is also handed forward in the R4 claim ( Generated by Claude Code |
…uinely unreachable store Implements the maintainer's 2026-08-30 option-A ruling on top of the existing loud-failure implementation. "The read failed" is two facts. A read also throws when the table was never PROVISIONED -- a real engine, wired and reachable, whose sys_* tables were never created. There "zero capabilities" is the true answer, not a fabrication; only an UNREACHABLE store leaves it unknown. Treating them alike turned four CI suites red. - Relocate isMissingTableError (and its sibling isSchemaAlreadyExistsError, which shares its matcher and cannot be separated from it) from @objectstack/metadata to @objectstack/types, the package @objectstack/core already depends on. @objectstack/metadata/errors still exports isMissingTableError by re-export, so no consumer of that published subpath changes. - tryFind raises AuthzStoreUnavailableError only for a read failure that is NOT positively identified as an unprovisioned table. - Record the signed-off false-positive risk beside the predicate, as the ruling requires, and pin both directions by name in authz-store-unavailable.test.ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
Resolves one conflict, in content/docs/permissions/system-context.mdx. All four conflict hunks were PURE LINE-NUMBER ROT -- proven mechanically by comparing both sides with digits stripped: prose identical, no census row written, deleted or reworded on either side. Resolved to main's side and the anchors re-derived from the merged tree by check-system-context-census, since rest-server.ts moved on BOTH sides and therefore neither side's numbers were correct after the merge. No generated artifact was touched: the branch never edited an os-regen path, os-regen-pending is empty, and every generated path equals main's side byte for byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
… merge `rest-server.ts` moved on BOTH sides of the merge, so neither side's anchors were correct afterwards. `--fix` repaired `package-routes.ts:92 -> :97` and then refused `rest-server.ts` with "page anchors 7 distinct read line(s), census finds 6 -- the POPULATION changed". That refusal is FALSE, and the same run says so: its own `[ledger-row-unused]` line reports NON_READ_ANCHORS excusing `rest-server.ts:1238` with no anchor pointing there. The page carries EIGHT anchors into that file -- six elevation reads plus the two `NON_READ_ANCHORS`-excused inbound seams -- and `--fix` counts the excused pair as read anchors. Filed as #13490. Re-anchored by hand, every target proven BY CONTENT rather than by arithmetic: :1235 -> :1238 holds the exact ledger needle '"authenticated". `isSystem` flags are never set on inbound HTTP' :1264 -> :1267 holds the exact ledger needle '`isSystem` is never set on inbound HTTP, so it cannot bypass.' :1267 -> :1270 row 17's READ anchor. The arithmetic could not see this: :1267 was simultaneously the second ledger seam's new home and row 17's stale anchor. :4271 -> :4282, :5634 -> :5645, :5866 -> :5877, :6211 -> :6222, :6404 -> :6415 row 50's five read anchors, each the census's own read site No row was written, deleted or reworded -- only where they point. Population proven unchanged against origin/main in a compare worktree: both verdict lines are byte-identical, "109 elevation read sites in 20 packages across 45 files, all anchored; 145 anchors resolve, 27 declared non-read". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
|
Closing my own caveat: the census repair is now CONFIRMED by CI, and the CI silence is explained. I said twice on this PR that the census green was a local reading only ( The silence had a cause, and it is measured rather than assertedAt 16:09Z this PR was The conflict is now resolved — Results at head
|
| check | verdict |
|---|---|
Lint & Repo Gates (carries check-system-context-census) |
✅ success |
Test Core 2/6, 3/6, 4/6, 5/6, 6/6 |
✅ success |
Test Core 1/6 |
in progress |
| totals | 30 success · 1 skipped · 1 in progress · 0 red |
⇒ The census repair at 603c9cbb6b is green on the gate itself, not just locally. The hand re-anchoring — which overrode an explicit ⛔ of mine after measuring my premise false — was correct end to end, and #13490 stands as the gate defect that made --fix refuse falsely.
⇒ The four previously-red Test Core shards have recovered, which is what ruling A predicted.
Fences, re-checked on the current 23-file diff
No content/docs/releases/, packages/spec/, docs/adr/, .claude/ or skills/. ⭐ Worth naming: the docs-drift run flagged content/docs/releases/v17.mdx as carrying isMissingTableError — the very symbol this PR relocates — and the page was correctly left alone. That is a release-owned page; a code PR editing it is exactly what the guardrail exists to stop, and the tempting "fix" was declined.
⛔ Still draft, still unenqueued. This is 条款② and goes through the review chain; a PM ACCEPT is not the landing authority here.
Generated by Claude Code
|
Completing the count I deliberately left open: it is six of six. Head In
⇒ Ruling A's prediction is confirmed in full. All four previously-red signatures recovered — ⇒ And the census repair at Where this PR now stands⛔ It does not get flipped ready by this seat. The ruling files it 条款②档 with the PR going through the review chain, so a green CI plus a PM reading is not the landing authority here — contract review at Holding as draft. The implementing seat has not filed its closing report yet, so I am recording the measurement rather than issuing a verdict; a head that moves again re-opens both the CI reading and the review. Generated by Claude Code |
|
PM review — implementation ACCEPTED. Landing now waits on 条款② contract review, which I have dispatched at State: head What I verified myself
⭐ The check that a clean merge makes easiest to skip
Zone 2 — assumption A broke, in three ways worth recordingI assumed a mechanical single-function move. It was not:
What held: the signature moved byte-identical, the published The signed-off risk is enforced, not merely writtenThe note sits in The four reds, per the ruling's conditionAll four recovered, in CI and locally: Routed#13516 (three stale comment pointers in Generated by Claude Code |
条款② contract review — PASS WITH NOTESRun at Limb 1 — public surface: correct and minimalThe widening is forced, not opportunistic, and the reviewer measured it rather than accepting the framing:
Limb 2 — contract behaviour: as ruled, pinned in both directions
⭐ The reviewer ran both ablation directions, including the inverse one the implementing seat did not:
⇒ The two directions are genuinely independent tripwires; neither is gated on the other. That is a stronger statement than either party had before. Grants-cache joint-correctness — independently verified.
|
…rce the census claim Contract review (条款②) on PR #13475 found four `this.resolveExecCtx(environmentId, req)` sites still passing `.catch(() => undefined)` — the CONTINUATION-layout ones the census file's own docblock warns about in as many words ("a single-line grep counts 16 and misses those four"). The conversion had reached the 16 single-line sites only. serveMetaItemLayered, GET meta/:type, GET meta/:type/:name and GET meta/:type/:name/published each swallowed the loud failure, so during a permission-store outage they could serve an org-unscoped, env-wide 200 instead of the declared 503. Converted, same shape as the other 16. Measured after: 20 resolveExecCtx sites guarded (16 inline + 4 continuation), 0 remaining `() => undefined` after a resolver call, plus computeExecCtx's own net = 21 guarded sites. ⛔ The sharper half was a FALSE COMPLETENESS CLAIM this PR shipped in the census file: "every site now passes `rethrowAuthzStoreUnavailable`". It was false when written, in the very file that documents the trap, and no pin failed — the sibling ledger's per-transport check is PRESENCE-based, so one converted site satisfies it for the whole file and a PARTIAL conversion is invisible to it. So the sentence is not merely corrected; it is replaced by measurement: - §7 re-derives the catch ARGUMENT at every site from source, in both layouts, and fails on any `() => undefined` survivor or any local re-spelling of the shared guard. A CONTROL asserts it finds both layouts, so a regex that stopped matching cannot read as a clean pass. - §8 drives the doors: with the resolver rejecting, no route touching a continuation site answers 200, and each keeps the declared 503 or propagates. Its healthy leg is the anti-vacuity control — it proves those sites are reached AND that 200 is their healthy answer, which is exactly what the defect fabricated. - The docblock records the miss, and states the ledger's reach so nobody reads its green as covering this.⚠️ §8's OWN reach is recorded too, and it was measured by ablation rather than assumed: reverting one of the four sites turns §7 red and leaves §8 GREEN, because the `${metaPath}/:type` handler (4332-4794) resolves the context three times, so under a total outage a later guarded site still refuses. §8 pins what a DOOR answers; §7 pins what each SITE spells. The case neither covers — a PARTIAL outage where only the first read fails, which is where the org-unscoped 200 actually appears — is stated as not measured rather than implied away. Site lines are DERIVED, never transcribed: hardcoded numbers in this file are what went stale and hid the four sites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
条款② delta review — PASS, now unconditional on
|
| ablation | result |
|---|---|
revert one continuation site to .catch(() => undefined) |
§7's two enforcement tests red, and the failure names the site: expected [ '2746 (continuation)' ] to deeply equal [] |
reformat so .catch( lands alone and the argument moves a line further down — invisible to the regexes while siteTable still counts it caught |
red anyway — the both-layouts CONTROL fails (continuation count drops below 4, length mismatch with CAUGHT), and §8's reachability CONTROL fails too |
⇒ Every evasion path the reviewer could construct fails toward detection, never toward a silent pass. And §8 stayed green under ablation 1 exactly as its own docblock predicts (the :type handler resolves three times, so a total outage still refuses via a later site) — §7 is the per-site tripwire, §8 the door-level pin, and the file says so rather than letting §8's green be over-read.
⇒ The false completeness claim did not merely get corrected; it became a machine-checked assertion on the blind spot that let it ship — the ledger's presence-based check.
Nothing else moved, and the base advance is clean
git log 0b71a8a13e..cfe54c80d084 is one commit; git diff --stat is two files. No types, metadata, core, spec or docs file touched. And main's own advance 4a24b96086..b2dea862cb touched none of packages/core/src/security/, rest-server.ts, packages/types/src/ or packages/metadata/src/ (empty log, measured) ⇒ no new auto-merge joint-correctness interaction of the kind the grants cache created earlier.
Limb 1 is untouched by the delta and carries forward. Limb 2 re-verified at the new head: core pin suite 37/37 (including the transport ledger's set-equality and both direction pins), the two rest suites 63/63.
The body now reads truthfully
"18 nets" is explicitly retracted as a +-line count, with the miss it produced, and replaced by the measured 21 guarded sites (20 call sites = 16 inline + 4 continuation, plus computeExecCtx's catch). The surface statement now declares six exports on @objectstack/core's root alongside the two on @objectstack/types.
Two residuals, recorded as residuals — not conditions
An outage against the grants cache turned ON (still #11971's contract, unpinned by anyone) and §8's declared not-measured partial-outage case. Both are stated in the PR itself rather than discovered later.
Landing posture
⛔ Still draft. CI on cfe54c80d084 is 19 success · 1 skipped · 11 running · 0 red. The two cancelled entries are superseded duplicates — each has a newer successful run of the same workflow on this same head (99296607297, 99296608523), so both guards did reach a verdict. On full green I flip ready and enqueue; ⛔ I do not merge it.
Generated by Claude Code
…ges/cloud-connection/src to the commits that decided them (objectstack-ai#20735) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 9 of the `domain:cli` lane of the dead-citation sweep: `packages/cloud-connection/src`. Every comment site there that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and keeps saying in its own words what that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 8 of this card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703, PR objectstack-ai#20713, PR objectstack-ai#20723) are the precedents. The card stays open for the lane's remaining packages, so this PR says `Part of`. That is **10 sites on 10 lines in 3 files, covering 3 numbers**, rewritten to **3 distinct commits**: - the census's **5 sites**, all in `src/marketplace-install-local-plugin.ts` (3 numbers); - **5 test-file comment sites** in 2 test files (the census defers `*.test.ts`; stages 1 to 8 took test comments too). Only comments changed: **10 lines out, 10 in**, and every touched file keeps its line count (1,969 / 377 / 308), so no line citation into these files moves. **No citation number is added**: over the 10 line pairs, added-minus-removed numbers is empty, and no PR number stands on an added line. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records any of these 3 decisions (a grep for the 3 numbers there reads 0 hits, with a control number from the same tree, `objectstack-ai#7329`, reading 1), so every anchor is a commit. A **`patch` changeset** for `@objectstack/cloud-connection` rides along, because the rewritten docblocks reach `dist` (measured below). That is stage 6's case (PR objectstack-ai#20703), not stages 5 and 7's. ## Census: `packages/cloud-connection`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run under `with-fleet.sh --read` for the token. The count is its `allocated-but-absent` findings under `packages/cloud-connection/`. Both runs enumerated the whole board. | reading | tree | board | whole-repo `allocated-but-absent` | `packages/cloud-connection` sites | lines | numbers | files | |---|---|---|---|---|---|---|---| | before | base `b291fcdae9`, run 2026-09-29T22:34:12Z to 22:38:18Z | enumerated, 186 pages, frontier objectstack-ai#20731, 18,558 numbers | 1,153 | **5** | 5 | 3 | 1 | | after | `4a1f38a4e6`, run 22:44:08Z to 22:47:58Z | enumerated, 186 pages, frontier objectstack-ai#20731, 18,558 numbers | 1,148 | **0** | 0 | 0 | 0 | The whole-repo drop of 5 is exactly these sites: a site-by-site diff of the two JSON outputs has 5 findings gone, all in `packages/cloud-connection/src/marketplace-install-local-plugin.ts`, and none added. The other three tallies (`resolves` 32,994, `resolves-as-pull-request` 1,984, `cross-repo-unjudged` 995) are equal in both runs. `packages/cloud-connection/src` is byte-identical at `4a1f38a4e6` and at the head. **Supplementary scan (test files included).** The gate's exported `extractCitations` and `classifyCitation` over all 44 `.ts` files under `src/`, with the board from the gate's own `probeBoard`: 301 citations and 14 dead before (src comments 5, test comments 5, src strings 1, test strings 3), 291 and 4 after (0, 0, 1, 3). Its before list of src comment sites is identical to the census's. The 4 left are strings, the form-D stage (see Acceptance notes). ## Per-site table `git blame` at the base ties each line to the commit that wrote it, and each anchor was read in its message, changeset or diff, not only its subject. | number | sites (base line) | anchor: what it decided | |---|---|---| | `objectstack-ai#9011` | `marketplace-install-local-plugin.ts:35`, `:1001`, `:1821`; `marketplace-install-local-capability-enumeration.test.ts:50`, `:303`; `marketplace-install-local-list-posture.test.ts:4`, `:302` | `01074e551`: the install-local listing requires an authenticated principal (anonymous gets 401) and serves `installedBy` / `storageDir` only to a `manage_metadata` holder, the maintainer's 2026-08-16 "Option 3" that `:1008` still names; it also extracts the one `refuseUnauthenticated` 401 envelope that `:1821` describes. All seven lines blame to it. The PR that landed it (PR objectstack-ai#9256, which answers 200) names objectstack-ai#9011 on its first line. `list-posture.test.ts:302` now reads "The wire shape before commit 01074e5" for "The pre-(number) wire shape". | | `objectstack-ai#8919` | `marketplace-install-local-plugin.ts:98`; `marketplace-install-local-capability-enumeration.test.ts:40` | `b5378550e`: gates the `/meta` publish and rollback promotion verbs on `manage_metadata` and adds `meta-write-door-capability-enumeration.test.ts`, the enumeration pin `:40` names as its precedent. Both lines blame to `e0695b582`, the commit that gated the four mutating install-local doors for objectstack-ai#8976 (which answers 200), whose message cites this gate as the precedent. Stages 2 and 5 gave the number this anchor. The PR that landed `b5378550e` answers 404 too. | | `objectstack-ai#13279` | `marketplace-install-local-plugin.ts:1813` | `6a180e42d`: a failed permission-store read raises `AuthzStoreUnavailableError` instead of resolving as an unauthenticated or capability-less principal, and each fail-closed transport `catch` re-raises it. The line blames to it; PR objectstack-ai#13475 names objectstack-ai#13279. Stages 1, 2 and 4 gave the number this anchor. | **Anchor checks.** Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 3), is a commit, has one parent, and is an ancestor of `main` (`merge-base --is-ancestor` against `36d043be17`, exit 0 for all 3). The checkout is not shallow. The control leg `818fcafda` (2026-08-16, the parent of the oldest anchor `b5378550e` of 2026-08-16) exits 0, and the negative control, this branch's own `16a88d69b2`, exits 1. Two anchors reuse the landed stages' (`b5378550e`, `6a180e42d`), so each number carries one anchor across the tree; one is new (`01074e551`). **Numbers.** All 3 dropped numbers answer 404 by REST (probed 2026-09-29T22:40:35Z). The numbers kept near the changed lines (`objectstack-ai#8976`, `objectstack-ai#15353`) answer 200. Three slash-joined groups stand in `packages/cloud-connection/src`, whose later halves the citation grammar does not read (`objectstack-ai#6603/objectstack-ai#7020`, `objectstack-ai#4127/objectstack-ai#4251` twice); every half answers 200, so none is dead. ## Mechanical guard: no code token moves **H2 holds on the comment-stripped reading; the emitted `dist` is NOT byte-identical, because the docblocks ship.** **Token guard.** It compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded) of the 3 touched files at base `b291fcdae9` and at `4a1f38a4e6`. Controls mutate the head text in memory only. - Real run: 12,349 base tokens (8,351 / 2,246 / 1,752), 0 differing (exit 0 for each file). - Comment-insertion control: 0 differing (exit 0). - Code-insertion control: all 3 files differ (exit 1). - String control (`'Authentication required.'` to `'Authentication requireD.'` in `refuseUnauthenticated`): exactly 1 differing `StringLiteral`, at token 7,973 of `marketplace-install-local-plugin.ts` (exit 1). **Emitted `dist`.** `pnpm --filter @objectstack/cloud-connection build` at the head, then at base (the base tree of `packages/cloud-connection/src` restored in place under a trap-armed restore; an on-disk probe read `[objectstack-ai#13279]` 1 and `commit 6a180e4` 0 before that build; afterwards every touched blob equals its HEAD blob and `git diff HEAD` is empty), with the same dependency builds: - `index.cjs`, `index.js`, `index.d.ts` and `index.d.cts` differ; `index.cjs.map` and `index.js.map` are equal. - The same parser comparison over the four differing `dist` files reads 0 differing tokens (19,465 / 18,741 / 16,868 / 16,868), so the whole `dist` delta is comment text. Its code control (a code line appended after a newline) reads COUNT DIFFERS in each. - The new wording is in `dist`: "commit 01074e5" appears 2 times in `index.js` and `index.cjs` and 3 times in each declaration file, where the base build carries `objectstack-ai#9011` in the same places. - Code-mutation control (`scripts/ablation-replace.mjs`, anchor `'Authentication required.'` hit 1 to 0, planted marker 0 to 1, blob `2ef0f0ae8bac` to `77c3cef6324b`; `scripts/ablation-dist-preflight.mjs` found the marker in `dist`): `index.cjs`, `index.js` and both `.map` files differ from the head build. The blob was restored to HEAD `2ef0f0ae8bac` with `git diff HEAD` empty, `dist` was rebuilt, its six sha256 values equal the first head build, and the preflight in `--absent` mode reads the marker absent from all 6 files with a clean tree. A raw scan of the 4 changed files for control bytes finds none (a positive probe on a scratch file matched). ## Changeset **`patch` for `@objectstack/cloud-connection`** (`.changeset/cloud-connection-provenance-anchors.md`), in PR objectstack-ai#20632's form. `@objectstack/cloud-connection`'s `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the build above emits different `index.js` / `index.cjs` / `index.d.ts` / `index.d.cts` at base and head, so this diff publishes. `check-changeset-no-major`, `check-empty-changeset`, `check-adr-0087-registration` and `check-changeset-fixed` all exit 0. ## Gates (head `16a88d69b2`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its disclosure, verbatim, from each run (the closure build at `4a1f38a4e6`, whose `packages/cloud-connection` and dependency closure are byte-identical to this head; the whole-workspace build, the tests and the typecheck at this head; the three `dist` builds at `4a1f38a4e6`, whose `packages/cloud-connection/src` is byte-identical to this head): ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 59s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/cloud-connection...' build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 118s (1m58s) · declare it in the PR body · pnpm turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 11s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2 os-verify-lock: VERDICT command-exit 2 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm exec tsc --noEmit -p tsconfig.json --listFiles os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection build ``` - **Build:** `@objectstack/cloud-connection` with its closure (33 of 81 workspace projects), then the whole workspace, `turbo run build --filter='./packages/*' --filter='./packages/*/*'`, 71 of 71 tasks, after the merge. The tree was clean after both, and the package's six `dist` files after the whole build equal the first head build by sha256. - **Tests:** `vitest run`: 30 files, 397 tests passed (every `*.test.ts` under `src/`), at this head and before the merge. - **Typecheck:** `@objectstack/cloud-connection` has no `typecheck` script; it is a `DEBT` entry in `scripts/check-type-check-coverage.mjs` (13 errors: 11 TS2493, 2 config-tier). `tsc --noEmit -p tsconfig.json` exits 2 with exactly those 13 (11 TS2493, 2 TS2550), all in three test files this PR does not touch (`cloud-connection-plugin.test.ts` 4, `connection-credential-store.test.ts` 7, `marketplace-install-local-bundle.test.ts` 2). `--listFiles` compiles all three touched files and all 30 test files. `check:type-check-debt` and `check:type-check-coverage` exit 0, and the `dist` build's DTS step, this package's type gate, succeeds. - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at this head (2026-09-29T23:01:12Z to 23:01:39Z). - **Citation judging:** after merging `origin/main` (`36d043be17`), `node scripts/check-issue-citations.mjs --base origin/main` reports "no issue citations added against 36d043b (1 file(s) read)" (exit 0); pinned `--base 36d043b` reads the same. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 61 families. All 61 exit 0, and `--ran` with the exit-coded record reads "61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero). Among them: `check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`, `check:published-files`, `check:type-check-debt`, `check-adr-0087-registration`, `check-empty-changeset`. - **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0, including the four the derivation marks as keeping their roster under one of this diff's paths (`check-changeset-fixed`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`). The other three need a pull request's context; they are run against this PR once it exists and reported on the card. The 18 self-test-only rows grade their checkers' fixtures and cannot judge this diff. ## Hypotheses (measured first) - **H0 holds.** At base `b291fcdae9` the filtered census answers 5 sites on 5 lines, 3 numbers, all in `src/marketplace-install-local-plugin.ts`, as on the seat's `0be898499f`. The whole-repo count is 1,153. - **H1 holds.** After the rewrite, the filtered census answers 0 for `packages/cloud-connection`. No site was left for an open PR (the file lists of all 10 open PRs were read at 2026-09-29T22:41:18Z: only the Version Packages PR objectstack-ai#20639 touches `packages/cloud-connection`, in `CHANGELOG.md` and `package.json`) or for an unfound anchor. - **H2 holds on the token reading, not on the `dist` reading.** The parser leaf-token diff of all 3 touched files is empty with its controls firing. The emitted `dist` differs, and the difference is comment text only (token-identical `dist` with a code control). That is why the changeset ships. ## Acceptance notes - **Strings, the form-D stage.** 4 dead numbers remain in string literals in `packages/cloud-connection/src`: `objectstack-ai#9011` in the three `describe` titles of `marketplace-install-local-list-posture.test.ts` (`:206`, `:247`, `:287`, no assertion text), and `objectstack-ai#9011` in the `note` string of the `GET /api/v1/marketplace/install-local` row of `cloud-connection-route-ledger.ts` (`:215`), a runtime string already recorded in `scripts/doc-authoring-prose-id.baseline.json`. They stay on the card for its form-D stage; no string moved here. - **Outside `src/**`, a later stage of the card:** `packages/cloud-connection/vitest.config.ts:64` cites `objectstack-ai#16917` (404). The other citations in `packages/cloud-connection` outside `src/**` (`CHANGELOG.md` excluded) answer 200: `README.md:108` (`objectstack-ai#10805`, `objectstack-ai#12681`) and `vitest.config.ts` (`objectstack-ai#10374`, `objectstack-ai#11480`, `objectstack-ai#7668/objectstack-ai#7778`, `objectstack-ai#7955`, `objectstack-ai#10374/objectstack-ai#13522`). - **Card-word residue, cited nowhere.** A few docblocks still say "this card's ruling" or "That ruling has since landed" a paragraph away from a rewritten line (`marketplace-install-local-capability-enumeration.test.ts:48`, `marketplace-install-local-list-posture.test.ts:12`). They cite no number, so they were left, as the landed stages left theirs. - **The moving `origin/main`.** The branch merged `origin/main` once (`16a88d69b2`, merging `36d043be17`: `service-automation` and two changesets, nothing in `packages/cloud-connection` or its dependency closure). ## Deviations - **The first token-guard run was void.** It looped over the touched files in a zsh shell, which does not word-split an unquoted variable, so each invocation received all three paths as one argument and read nothing; it was rerun under bash before any reading was used. - **Commit trailers** are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push. The harness's attribution reminder asked for a model-named trailer and a different PR footer, and AGENTS.md overrides it. The merge commit carries git's default message. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…kages/plugins/plugin-hono-server/src to the commits that decided them (objectstack-ai#20741) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 10 of the `domain:cli` lane of the dead-citation sweep: `packages/plugins/plugin-hono-server/src`. Every comment site there that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and keeps saying in its own words what that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 9 of this card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703, PR objectstack-ai#20713, PR objectstack-ai#20723, PR objectstack-ai#20735) are the precedents. The card stays open for the lane's remaining packages, so this PR says `Part of`. That is **24 sites on 24 lines in 5 files, covering 5 numbers**, rewritten to **5 distinct commits**: - the census's **5 sites**: `src/adapter.ts` 4, `src/current-user-endpoints.ts` 1 (4 numbers); - **19 test-file comment sites** in 3 test files (the census defers `*.test.ts`; stages 1 to 9 took test comments too): `ui-plugin-auto-discovery.pin.test.ts` 16, `handler-throw-declared-envelope.test.ts` 2, `current-user-endpoints-localization.test.ts` 1. Only comments changed: **24 lines out, 24 in**, every one of them a site (no companion line), and every touched file keeps its line count (1,660 / 1,020 / 335 / 403 / 697), so no line citation into these files moves. **No citation number is added**: over the 24 line pairs, the added numbers are a subset of the removed ones (`objectstack-ai#16599` x2, `objectstack-ai#9864`, `objectstack-ai#16334`, all answering 200, stay where they stood), and no PR number stands on an added line. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records any of these 5 decisions (a grep for the 5 numbers there reads 0 hits; the control number `objectstack-ai#7329` reads 1 in the same tree), so every anchor is a commit. A **`patch` changeset** for `@objectstack/plugin-hono-server` rides along, because one rewritten comment reaches `dist` (measured below). That is stage 6's and stage 9's case (PR objectstack-ai#20703, PR objectstack-ai#20735), not stages 5 and 7's. ## Census: `packages/plugins/plugin-hono-server`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run under `with-fleet.sh --read` for the token. The count is its `allocated-but-absent` findings under `packages/plugins/plugin-hono-server/`. Both runs enumerated the whole board. | reading | tree | board | whole-repo `allocated-but-absent` | package sites | lines | numbers | files | |---|---|---|---|---|---|---|---| | before | base `f927864ea0`, run 2026-09-29T23:42:29Z to 23:46:56Z | enumerated, 186 pages, frontier objectstack-ai#20735, 18,562 numbers | 1,105 | **5** | 5 | 4 | 2 | | after | `91ce7e5e8f`, run 23:58:22Z to 2026-09-30T00:02:19Z | enumerated, 186 pages, frontier objectstack-ai#20737, 18,564 numbers | 1,100 | **0** | 0 | 0 | 0 | The whole-repo drop of 5 is exactly these sites: a site-by-site diff of the two JSON outputs has 5 findings gone (`adapter.ts:225`, `:227`, `:308`, `:349`; `current-user-endpoints.ts:448`) and none added. The other three tallies (`resolves` 33,003, `resolves-as-pull-request` 1,984, `cross-repo-unjudged` 995) are equal in both runs. `packages/plugins/plugin-hono-server` is byte-identical at `91ce7e5e8f` and at the head. **Supplementary scan (test files, strings and files outside `src/` included).** The gate's exported `extractCitations` and `classifyCitation` over all 41 tracked files of the package (`CHANGELOG.md` excluded), comment-prose and whole-file projections, with the board from the gate's own `probeBoard`: 347 citations and 32 dead before, 323 and 8 after. Under `src/`: comments 5 dead to 0, test comments 19 to 0, strings 0 and test strings 2 unchanged. Its before list of `src/` comment sites equals the census's. The 8 left are 2 test strings and 6 sites outside `src/` (see Acceptance notes). ## Per-site table `git blame` at the base ties each line to the commit that wrote it, and each anchor was read in its message, changeset or diff, not only its subject. | number | sites (base line) | anchor: what it decided | |---|---|---| | `objectstack-ai#13279` | `adapter.ts:225`, `:227`; `handler-throw-declared-envelope.test.ts:84`; `current-user-endpoints-localization.test.ts:90` | `6a180e42d`: `tryFind` in `resolveAuthzContext` raises `AuthzStoreUnavailableError` (503 `SERVICE_UNAVAILABLE`) when a permission-store read is issued and throws, instead of answering it as an empty read, and each fail-closed transport `catch` (`requireDatasourceAdmin` in `service-datasource` among them) re-raises it: an unreadable store licenses no verdict, the maintainer's 2026-08-30 ruling recorded in its message. The first three lines blame to `cefe068702` (the declared-envelope rendering, PR objectstack-ai#17412) and the fourth to `5f7fa1de0`; both wrote them citing this ruling. PR objectstack-ai#13475 (200) names objectstack-ai#13279. Stages 1, 2, 4 and 9 gave the number this anchor. | | `objectstack-ai#9934` | `adapter.ts:308`; `handler-throw-declared-envelope.test.ts:152` | `79c46da90`: the producer-side user-facing marking for hook refusals, the `userMessage` channel, a text field a producer sets at throw time and every classified envelope carries. Both lines blame to `cefe068702`. The PR that landed it (PR objectstack-ai#9992) answers 404 too. Stages 1, 2, 4 and 6 gave the number this anchor. | | `objectstack-ai#6307` | `adapter.ts:349` | `293476148`: refuse a repeated `?version=` on `GET`/`DELETE /packages/:id` rather than pick one value, through `readSingleQueryValue`, which it introduces. The line blames to `7cdbcbb306` (surface repeated query parameters as arrays, PR objectstack-ai#7396), which says it follows that direction. Stages 2 and 8 gave the number this anchor. | | `objectstack-ai#6216` | `current-user-endpoints.ts:448` | `f586f1a89`: one `ExecutionContext` assembler with two named anonymous entries, `assembleExecutionContext` the default, fail-closed one and `assembleExecutionContextOrGuest` the explicit guest one, the maintainer's 2026-08-08 Option A recorded in its docblock. The line blames to `6615a024c3` (the current-user faces adopt the shared assembler). Stages 1, 2 and 7 gave the number this anchor. | | `objectstack-ai#16721` | `ui-plugin-auto-discovery.pin.test.ts:27`, `:37`, `:42`, `:180`, `:211`, `:217`, `:360`, `:363`, `:495`, `:498`, `:594`, `:596`, `:604`, `:608`, `:631`, `:646` | `51ae73123`: `LiteKernel.use()` runs the same `assertPluginContract` as `ObjectKernel.use()` and refuses the same plugin objects with the same envelope, the maintainer's 2026-09-08 option A (the kernels converge) recorded in its changeset. 15 lines blame to it; `:180` blames to `3c48234b3`, which re-wrapped that sentence and keeps its fact. Its `lite-kernel.ts` docblock records the measurement taken before converging, which `:604` describes ("before commit 51ae731"). `:363`, `:498` and `:631` said the `hono-plugin.ts` question was "noted on" the dead number; that note is the text this commit wrote into this file, so they now say "raised with" / "recorded with" it. New anchor; no other package has re-anchored this number yet. | **Anchor checks.** Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 5), is a commit, has one parent, and is an ancestor of `main` (`merge-base --is-ancestor` against `f927864ea0`, exit 0 for all 5). The checkout is not shallow. The control leg `2672f855fa` (2026-08-09, the parent of the oldest anchor `293476148`) exits 0 against `origin/main`, and the negative control, this branch's own head, exits 1. Four anchors reuse the landed stages' (`6a180e42d`, `79c46da90`, `293476148`, `f586f1a89`), so each number carries one anchor across the tree; one is new (`51ae73123`). **Numbers.** All 5 dropped numbers answer 404 by REST (probed 2026-09-30T00:14:17Z). The numbers kept on or beside the changed lines answer 200: `objectstack-ai#16599`, `objectstack-ai#9864`, `objectstack-ai#16334`, `objectstack-ai#16363`, `objectstack-ai#16049`, `objectstack-ai#6878`, `objectstack-ai#3867`, `objectstack-ai#8086`, `objectstack-ai#16545`, `objectstack-ai#15999`, `objectstack-ai#5090`. `packages/plugins/plugin-hono-server/src` has no slash-joined `#A/#B` without spaces; the spaced pairs (`objectstack-ai#3867 / objectstack-ai#8086`, `objectstack-ai#2408 / objectstack-ai#3361`) are read by the grammar and every half answers 200. ## Mechanical guard: no code token moves **H2 holds on the parser-token reading; the emitted `dist` is NOT byte-identical, because one rewritten `//` comment sits inside a returned object literal and the bundler keeps it.** **Token guard.** It compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, `getChildren` walk, JSDoc nodes excluded) of the 5 touched files at base `f927864ea0` and at the head. Controls mutate the head text in memory only. - Real run: 15,054 base tokens (4,831 / 2,134 / 3,559 / 2,363 / 2,167), 0 differing, exit 0. - Comment-insertion control: 0 differing, exit 0. - Code-insertion control: all 5 files differ, exit 1. - String control (the first character of the first import specifier flipped in each file): exactly 1 differing `StringLiteral` per file, exit 1. All 48 changed lines (24 out, 24 in) are `//` or `*` comment lines. **Emitted `dist`.** `pnpm --filter @objectstack/plugin-hono-server build` at the head, then at base (the base blobs of the 5 touched files restored in place under a trap-armed restore; an on-disk probe read `objectstack-ai#9934` 1 and `commit 79c46da` 0 in `adapter.ts` before that build; afterwards every touched blob equals its HEAD blob, `git diff HEAD` is empty and the status is clean), with the same dependency builds: - `index.js` and `index.mjs` differ, in one line each: the `adapter.ts:308` comment, `refusal text (objectstack-ai#9934)` at base and `refusal text (commit 79c46da)` at head. `index.d.ts`, `index.d.mts` and both `.map` files are equal. No docblock of this diff reaches the declaration files. - The same parser comparison over the two differing files reads identical tokens (10,818 in `index.js`, 10,521 in `index.mjs`), so the whole `dist` delta is comment text. Its code control (a code line appended) reads COUNT/TOKENS DIFFER in each. - Code-mutation control (`scripts/ablation-replace.mjs`, wrap mode, anchor `message: 'No response from handler' }` hit 1 to 0, planted marker 0 to 1, blob `6a0c10f76282` to `d5223196afeb`; `scripts/ablation-dist-preflight.mjs` found the marker in `index.js` and `index.mjs`): `index.js`, `index.mjs` and both `.map` files differ from the head build. The blob was restored to HEAD `6a0c10f76282` with `git diff HEAD` empty, `dist` was rebuilt, its six sha256 values equal the first head build, and the preflight in `--absent` mode reads the marker absent from all 6 files with a clean tree. A raw scan of the 6 changed files for ASCII control bytes finds none, and `check:nul-bytes` exits 0. ## Changeset **`patch` for `@objectstack/plugin-hono-server`** (`.changeset/plugin-hono-server-provenance-anchors.md`), in PR objectstack-ai#20632's form. The package's `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the build above emits different `index.js` / `index.mjs` at base and head, so this diff publishes. `check-changeset-no-major`, `check-empty-changeset`, `check-adr-0087-registration` and `check-changeset-fixed` all exit 0. ## Gates (head `03e5f4c0fd`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its official wording, verbatim (printed by every run; the command line differs per run and is listed in the verdicts below): > **Declared narrowing — verification ran UNLOCKED.** `scripts/pm/os-verify-lock.sh` > could not take the shared verify lock on this host: no usable `flock`. The shared > verify lock is declared Linux-only (`flock` is util-linux, and a stock macOS does > not ship it), so the command below was run directly, without the lock — > a declared narrowing, not a silent one. No serialization guarantee held for this > run, nor for any sibling agent in this container while it ran. Its verdict line from each run (the closure build and the three `dist` builds at `feaf0c9b73`, whose `packages/plugins/plugin-hono-server` is byte-identical to this head; the first whole-workspace build, tests and typecheck at `91ce7e5e8f`; the second whole-workspace build, tests and typecheck at this head after the merge; the scratch-script paths shortened to `SCRATCH`): ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 27s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-hono-server...' build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · bash SCRATCH/base-build.sh SCRATCH os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · node scripts/ablation-replace.mjs --file packages/plugins/plugin-hono-server/src/adapter.ts --anchor "message: 'No response from handler' }" --replacement "message: 'No response from handler ABLMARK20594S10' }" -- bash SCRATCH/mut-inner.sh SCRATCH os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 12s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 17s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server typecheck os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 118s (1m58s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 118s (1m58s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 11s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 16s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server typecheck ``` - **Build:** `@objectstack/plugin-hono-server` with its closure (7 of 81 workspace projects), then the whole workspace, `turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2`, 71 of 71 tasks, before and again after the merge. The tree was clean after each, and the package's six `dist` files after each whole build equal the first head build by sha256. - **Tests:** `vitest run --maxWorkers=2`: 27 files, 324 tests passed (every `*.test.ts` under `src/`), at this head and at `91ce7e5e8f`. - **Typecheck:** `pnpm --filter @objectstack/plugin-hono-server typecheck` exits 0 at this head and at `91ce7e5e8f` (`tsc --noEmit`, `tsc --noEmit -p tsconfig.typecheck.json`, and `check:test-typecheck` OK with 0 files / 0 errors / 0 pinned signatures). `--listFiles`: `tsconfig.json` and `tsconfig.test.json` each compile 33 `src/` files including all 27 tests and all 5 touched files. - **Spec artifacts:** `origin/main` brought a `packages/spec` change, so `pnpm --filter @objectstack/spec check:generated` ran after the rebuild: "All 15 generated artifacts are up to date" (exit 0). - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at this head (2026-09-30T00:25:09Z to 00:25:36Z), and at `91ce7e5e8f`. - **Citation judging:** after merging `origin/main` (`fbec216e2d`), `node scripts/check-issue-citations.mjs --base origin/main` reports "no issue citations added against fbec216 (2 file(s) read)" (exit 0). - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 63 families, the same list at `91ce7e5e8f` and at this head. All 63 exit 0 at this head in one pass, and `--ran` with the exit-coded record reads "63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero). Among them: `check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`, `check:published-files`, `check:dts-closure`, `check:dual-build-cjs-loads`, `check:type-check-debt`, `check-adr-0087-registration`, `check-empty-changeset`. - **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0 at this head, including the four the derivation marks as keeping their roster under one of this diff's paths (`check-changeset-fixed`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`). The other three need a pull request's context; they are run against this PR once it exists and reported on the card. The 18 self-test-only rows grade their checkers' fixtures and cannot judge this diff. ## Hypotheses (measured first) - **H0 holds.** At base `f927864ea0` the filtered census answers 5 sites on 5 lines, 4 numbers, 2 files, as on the seat's `0be898499f`. The whole-repo count is 1,105. - **H1 holds.** After the rewrite, the filtered census answers 0 for `packages/plugins/plugin-hono-server`. No site was left for an open PR (the file lists of all open PRs were read at 2026-09-29T23:48:36Z, 6 PRs, and again at 2026-09-30T00:15:14Z, 9 PRs: only the Version Packages PR objectstack-ai#20639 touches the package, in `CHANGELOG.md` and `package.json`) or for an unfound anchor. - **H2 holds on the token reading, not on the `dist` reading.** The parser leaf-token diff of all 5 touched files is empty with its controls firing. The emitted `dist` differs in one comment line of `index.js` and of `index.mjs`, token-identical with a code control. That is why the changeset ships. ## Acceptance notes - **Strings, the form-D stage.** One dead number remains in a string literal in `packages/plugins/plugin-hono-server/src`: `objectstack-ai#16721` at the end of the group-F `describe` title of `ui-plugin-auto-discovery.pin.test.ts` (`:635`, a test title, no assertion text). It stays on the card for its form-D stage; no string moved here. The supplementary scan's second test-string hit, `:111` (`'.os-pin{color:#123456}'`), is a CSS hex colour in a fixture, not a citation: the whole-file projection reads it as a six-digit number, while the census blanks strings and defers test files. - **Outside `src/**`, a later stage of the card:** `objectstack.config.ts:19` (`objectstack-ai#11332`) and `:26` (`objectstack-ai#10724`), `tsconfig.test.json:3` and `:61` (`objectstack-ai#13176`), `tsconfig.typecheck.json:12` (`objectstack-ai#11332` and `objectstack-ai#10724`; `objectstack-ai#4914` in the same group answers 200). The other citations in the package outside `src/**` (`CHANGELOG.md` excluded) answer 200: `tsconfig.test.json` (`objectstack-ai#14062`, `objectstack-ai#5286`, `objectstack-ai#5449`, `objectstack-ai#12542`), `tsconfig.typecheck.json` (`objectstack-ai#13284`, `objectstack-ai#5475`, `objectstack-ai#10756`), `vitest.config.ts` (`objectstack-ai#10374`, `objectstack-ai#9457`, `objectstack-ai#7378`; `objectstack-ai#8129` resolves as a pull request). `README.md` carries none. - **An open question now lives only in this file.** `hono-plugin.ts:521` and `:523` still carry the `&& plugin.staticPath` conjunct and the `plugin.slug || plugin.name.split('/').pop()` derivation that, since `51ae73123`, neither published kernel's `use()` lets an input reach. The pin file says so and leaves the call to `hono-plugin.ts`; the tracker note it pointed at is gone, so this file's text (and commit `3c48234b3`'s message) are the record. Unreachable defensive code, not a defect: noted, not filed. - **Card-word residue, cited nowhere.** `handler-throw-declared-envelope.test.ts` still says "before this card" (`:85`) and "the card" (`:19`, `:32`) around its rewritten lines. They cite no dead number, so they were left, as the landed stages left theirs. - **The moving `origin/main`.** The branch merged `origin/main` once (`03e5f4c0fd`, merging `fbec216e2d`: the ADR-0087 migration chain moves to `@objectstack/spec/migrations`). `packages/spec` is in this package's dependency closure, so the workspace was rebuilt and the package's tests, typecheck and every gate above were rerun at the merge head; nothing in `packages/plugins/plugin-hono-server` changed. ## Deviations - **Three derived gates first read NOT MEASURED.** `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:type-check-debt` exited 3 (PREREQUISITE NOT MET: built output absent) in the first pass, before the whole-workspace build. Rerun after it, each exits 0, and all 63 exit 0 in the single pass at this head. - **The first `check:generated` run was void.** This host's global `pnpm` is a v11 front end that rejects the `-s` each sub-gate passes, so all 15 rows read "unexpected argument '-s'" (exit 1, nothing measured). Rerun with the real pnpm 10.31 binary first on `PATH`, it reads all 15 up to date (exit 0). - **Commit trailers** are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push. The harness's attribution reminder asked for a model-named trailer and a different PR footer, and AGENTS.md overrides it. The merge commit carries git's default message. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…ommits that decided them (objectstack-ai#20836) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the fifteenth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-settings/src/**` and nothing else. By the seat's claim (`5908460751`), it is the largest package left in the lane. 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 14 (the latest is PR objectstack-ai#20816, landed as `73155fedc`). That is **11 sites on 11 lines in 9 files, covering 4 numbers**: - 4 census sites (every census site this package has at the base); - 7 sites in test comments, which the census defers. One of their numbers, `objectstack-ai#11318`, stands only in test files here; it was read on its own and answers 404. Each rewritten line now cites the commit in this repository that decided what the line describes, and says in its own words what was decided: **4 distinct commit shas**. No ADR records any of the four decisions (see the per-number table), so ruling C's commit rung applies. No number was dropped. Only comments changed. Every touched source file keeps its line count (11 lines out, 11 in, over 9 files), so no line citation into these files moves. All 11 changed lines carried a dead citation. 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#10251`, once, in `settings-prebind-read-warning.test.ts:17`. It already stood on that line, and it now sits beside the sha as the convenience link ruling C allows: 「(commit 1ec36b7, PR objectstack-ai#10251)」. `1ec36b730` is that pull request's squash commit. 2 dead sites are left on purpose: a test title and a test assertion message (see the list below). One more file: a `patch` changeset for `@objectstack/service-settings`, because the rewritten prose ships (see Changeset below). ## Census: `service-settings`, 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/services/service-settings/`. 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` | service-settings sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `73155fedc`, run 2026-09-30T09:39:38Z to 09:43:17Z | enumerated, 187 pages, frontier objectstack-ai#20830 (newest objectstack-ai#20830 before and after) | 752 | **4** | 4 | 4 | 3 | | after | head `ac05607d6`, run 10:02:17Z to 10:06:00Z | enumerated, 187 pages, frontier objectstack-ai#20834 (newest objectstack-ai#20834 before and after) | 748 | **0** | 0 | 0 | 0 | The whole-repo drop is 4, exactly this diff's census sites. The `resolves` tally is 33,134 in both runs, and `resolves-as-pull-request` (1,985) and `cross-repo-unjudged` (1,018) did not move either. Neither run was truncated or discarded: both enumerations read 187 pages at the newest frontier. The seat's census counted 4 here at `6bff748b`, and the base agrees: `6bff748b` is an ancestor of the base, and no commit between them touches this package's `src`. **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 `service-settings/src` (64 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 the gate's own census-scope extraction judged it and the census did not report it. 10 numbers are covered by neither, because they stand only in test files, or as the second number of an `#A/#B` pair. Each was read on its own through the read-only tools. 1 answers 404 (`objectstack-ai#11318`, on the issue and the pull-request endpoint alike); 6 answer as issues; 3 answer as pull requests (`objectstack-ai#7554`, `objectstack-ai#10251`, `objectstack-ai#5133`). The probe's control: the known issue `objectstack-ai#11352` answers 404 on the pull-request endpoint. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `73155fedc` | 534 | **13** | 4 | 7 | 0 | 2 | | after, `ac05607d6` | 523 | **2** | 0 | 0 | 0 | 2 | Its src-comment column equals the census's 4, which is the control on the second instrument. The 519 live citations and 2 cross-repo citations are the same in both readings, and the drop of 11 citations is exactly the rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 547 occurrences before and 536 after, the same drop of 11. The 13 tokens beyond the gate's grammar are the same before and after, and none is dead (see Acceptance notes). ## 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. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#13279` | 4/4 | 4/0 | `6a180e42d` (PR objectstack-ai#13475): `resolveAuthzContext` raises `AuthzStoreUnavailableError` (`SERVICE_UNAVAILABLE`, 503) when a permission-store read throws, instead of answering an outage as a caller with zero capabilities, and each production transport's fail-closed `catch` re-raises that brand. The settings plugin's `verifiedContextFromRequest` is one of them. Its message names `objectstack-ai#13279` 4 times and its diff 54 times; `git blame` puts `settings-service-plugin.ts:307` in it, and the other three lines were written by `ac9376a74` (PR objectstack-ai#16580), a descendant, which describes that re-raise. Stage 6's anchor, reused by the storage, datasource and analytics stages | | `objectstack-ai#10159` | 3/2 | 3/0 | `1ec36b730` (PR objectstack-ai#10251): a settings write issued before the engine is bound is refused with `SETTINGS_ENGINE_NOT_BOUND` (503). Its message states that every read in any state is unchanged, which is the "left reads open" all three lines describe. The message does not name `objectstack-ai#10159`, but its own diff does, once, in its changeset ("refused loudly instead of resolving successfully while nothing reaches `sys_setting` (objectstack-ai#10159)"), and `settings-prebind-read-warning.test.ts:17` already paired the two numbers. `git blame` puts the three lines in `a24b7fa4d` (PR objectstack-ai#11044), the later read-half fix, a descendant. New to the sweep | | `objectstack-ai#17062` | 3/2 | 2/1 | `50b6f17d4` (PR objectstack-ai#17071): adds the package-local route-ledger conformance guard beside the dogfood live-mount parity gate, and updates the ledger header that had said such a guard was deliberately omitted. Its message does not name `objectstack-ai#17062`; its diff does, on 3 added lines, which are the three sites here (`git blame` puts all three in it). New to the sweep | | `objectstack-ai#11318` | 3/1 | 2/1 | `99ccbb9c8` (PR objectstack-ai#11467): the Settings, AI "Test connection" fallback keeps its mount instruction on all three real-provider branches and gains the cloud-only boundary read from `PLATFORM_CAPABILITY_PROVIDERS.ai`. Its own changeset states that "the embedder hint at the fourth site is deliberately left alone ... and pinned by a contrast test", which is the fence `:339` describes. Its message's trailer names `objectstack-ai#11318` as the issue it answers, its diff names the number 3 times, and `git blame` puts all three lines in it. New to the sweep | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 4), and all 4 are ancestors of the base (`merge-base --is-ancestor`, exit 0 for each; reverse leg, base against each anchor, exit 1 for each; control legs exit 0: stage 1's landing `422db788a`, and the repository's root commit, which lies deeper than every anchor; the history is complete, `--is-shallow-repository` false, 15,193 commits). Each of the 4 numbers answers 404 on the issues endpoint, which serves pull requests too, read one by one. No ADR, `scripts/adr-anchors/` file or other `docs/` page records the decision of any of the 4: `docs/adr` names none of the numbers, and none of their mechanisms (`AuthzStoreUnavailableError`, `SETTINGS_ENGINE_NOT_BOUND`, `engineBindPending`, the settings route ledger, the AI hint's edition boundary). ## Wordings to check - **Tag swaps in place.** 「[objectstack-ai#13279]」 became 「[commit 6a180e4]」 (`settings-service-plugin.ts:307`); 「(objectstack-ai#13279)」 became 「(commit 6a180e4)」 on 2 lines; 「objectstack-ai#13279's permission-store re-raise」 became 「commit 6a180e4's permission-store re-raise」. These are the forms the storage and datasource stages used for the same sha. - **`objectstack-ai#10159`.** 「is why objectstack-ai#10159's fix deliberately left reads open」 became 「is why commit 1ec36b7's write refusal deliberately left reads open」; 「(objectstack-ai#10159's fix left reads open on purpose)」 became 「(commit 1ec36b7 left reads open on purpose)」; 「(objectstack-ai#10159 / PR objectstack-ai#10251)」 became 「(commit 1ec36b7, PR objectstack-ai#10251)」, with the pull-request number kept as the convenience link beside its own squash commit. - **`objectstack-ai#17062`.** 「Two layers, since objectstack-ai#17062.」 became 「Two layers, since commit 50b6f17.」 The docblock goes on to describe the conformance test that commit added as the second layer. - **Two headers keep the antecedent of the prose below them**, the form stage 14 used: - `settings-route-ledger.conformance.test.ts:4`: 「Settings route-ledger conformance (objectstack-ai#17062)」 became 「Settings route-ledger conformance (the issue behind commit 50b6f17)」, because `:25` of the same docblock says 「(per the issue)」. - `manifests/ai.manifest.test.ts:277`: 「objectstack-ai#11318 —」 became 「The card behind commit 99ccbb9:」, because `:288` 「the very claim this card is about」 and `:329` 「The un-followable form this card retired」 speak of that card. - **`ai.manifest.test.ts:339`.** 「deliberately not edited — objectstack-ai#11318 fences this site out by name」 became 「... — commit 99ccbb9 fences this site out by name」. The commit's own changeset names that site (quoted in the table). ## The 2 sites left - **Test strings, 2 sites on 2 lines**, left as stages 1 to 14 left theirs: - `manifests/ai.manifest.test.ts:292`, a `describe` title (`objectstack-ai#11318`); - `settings-route-ledger.conformance.test.ts:88`, the assertion message a failing run prints (`objectstack-ai#17062`). It is a string, not a comment, and form C does not touch strings. - No operator log string, runtime refusal, quoted maintainer ruling or generated file in this package carries a dead number. - **Outside `src`, listed and left, not edited in this stage:** - the shipping `README.md` names only the live `objectstack-ai#8026`; - `vitest.config.ts` names only live numbers (`objectstack-ai#8020`, `objectstack-ai#8030`, `objectstack-ai#8063`, `objectstack-ai#8104`, `objectstack-ai#10374`); - `tsconfig.json` and `package.json` name none; - the release-owned `CHANGELOG.md` names `objectstack-ai#13279` and `objectstack-ai#10159` on 2 lines, the entries of `6a180e4` and `1ec36b7`, which are this PR's anchors. ## Mechanical guard: no code token moves The guard compares, base `73155fedc` against head, over all 9 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 at the final head `ac05607d6`: 9,999 base leaf tokens, **0 files with a token change** on either reading (exit 0). The first commit `ff7ef46f4` gave the same, and no `.ts` path changed after it. - Comment control in `settings-service.ts` (「deliberately left reads open.」 to 「deliberately kept reads open.」): 0 files changed, as expected (exit 0). - Positive control, a code token renamed in `settings-service-plugin.ts` (`isAuthzStoreUnavailableError(err)` to `isAuthzStoreUnavailableErrorX(err)`): DIFFER on the identifier (exit 1). - Positive control, one digit changed inside the kept test title `ai.manifest.test.ts:292` (`objectstack-ai#11318` to `objectstack-ai#11319`): 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 (`1248149a428d`, `a2fac9ad1f0b`, `79c2b40a48a6`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-settings` (`.changeset/20596-service-settings-provenance-anchors.md`) is included. Its body is stages 12 and 13's commit-anchor text, word for word, with the package name changed. Measured on the built package (A3), after a full workspace build in which this package was a cache miss (71 of 71 tasks, 0 cached, at `ff7ef46f4`, which holds every source-line change): `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. - The rewritten `settings-service.ts:685` docblock, on the pre-bind read reporter, is in all four entries: `dist/index.js`, `dist/index.cjs`, `dist/index.d.ts` and `dist/index.d.cts` (once each). - The other three rewrites (`settings-route-ledger.ts:17`, `settings-routes.ts:72`, `settings-service-plugin.ts:307`) are stripped by the bundle, and the other seven sit in test files. - Positive controls, the unchanged line beside each rewrite, land exactly where their neighbours do: the line before `settings-service.ts:685` once in each of the four entries, and the neighbours of the three stripped rewrites 0 everywhere. - A never-written negative phrase appears nowhere in `dist`, and none of the four old numbers is left there. The later commit adds only the changeset. ## Gates (final head `ac05607d6`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` exits 0 (self-test, 114 cases, 8 batteries). `node scripts/check-issue-citations.mjs` exits 0: 「no issue citations added against 73155fe (4 file(s) read)」. - **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 `ac05607d6` derived 63 commands, the same 63 as at dispatch. - Each ran with its exit code captured before any pipe, and all 63 exit 0; none exited 3. - `--ran`, fed each command with its exit code, reports 63 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. - The full `turbo run build` above ran first under the shared verify lock, 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, at `ac05607d6`:** - `pnpm --filter @objectstack/service-settings test`: 33 files pass and 584 tests pass, which is every tracked test file under `src/`, the 5 touched ones included. - `pnpm --filter @objectstack/service-settings typecheck` (`tsc --noEmit`) exits 0, and `tsc --listFiles` puts all 9 touched files in the program. - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 9 touched `.ts` files, gives 9 files, 0 errors and 0 warnings (its `--format json` output). All 9 are in eslint's own population (none reported ignored; `dist/index.js`, the control, reads 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 10 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked** (objectstack-ai#20636, including the `clause #N` position). At the base, `#N-word` is on 0 lines. `#A/#B` is on 11 lines (12 second numbers), and every second number is live: `objectstack-ai#6580`, `objectstack-ai#5094`, `objectstack-ai#11230`, `objectstack-ai#5480`, `objectstack-ai#5932`, `objectstack-ai#6199` and `objectstack-ai#5204` by the census's own judgement, and `objectstack-ai#5133` read on its own as a pull request. `option #N`, `clause #N` and URL-spelled links are on 0 lines. So the claim's 0 / 11 / 0 / 0 hold, and nothing dead hides behind them. The one other raw token beyond the grammar is the colour literal `'#6366f1'` in `manifests/branding.manifest.ts:32`. - **「The card」 phrases.** 38 lines in 18 files under this package's `src` speak of 「the card」 or 「this card」. They carry no number, and neither instrument sees them. The ones whose antecedent this diff would have removed are handled above; the rest are unchanged, as in stages 8 to 14. - **The census instrument did not truncate in this stage.** Both enumerations read 187 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#10159` → `1ec36b730`; `objectstack-ai#17062` → `50b6f17d4`; `objectstack-ai#11318` → `99ccbb9c8`; and the reused `objectstack-ai#13279` → `6a180e42d`. - **Base.** The branch is on `main` at `73155fedc`. `main` has since moved two commits (`4b45afaed`, `15b586dcf`). Neither touches `packages/services/service-settings`, `scripts/check-issue-citations.mjs`, `.changeset/config.json` or a path in this diff. `15b586dcf` moves `packages/spec/liveness/**`, a gate input this comment-only diff cannot interact with. 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>
Part of #13279
⛔ DRAFT. Landing is the PM's call, not this branch's.
Implements the maintainer's second ruling of 2026-08-30 (第 5 场总监席决裁批 #9, verbatim
「同意」) — option A — on top of the loud-failure implementation this PR already carried.
The 7 transport guards, the
rest-server.tsnets, the inverted #13282 pin and the censusrepair are all retained unchanged.
rest-server.tsnets". That was a +-linecount (16 catch swaps +
computeExecCtx's catch + the import), i.e. exactly thesingle-line counting the census file warns against — and it is consistent with how four
sites came to be missed. The measured site count, after the fix below, is 21 guarded
sites: 20
resolveExecCtxcall sites (16 inline, 4 continuation) pluscomputeExecCtx'sown blanket catch.
What was blocking, and what A does about it
The previous revision made
tryFindfail loud on any thrown read. That was faithful tothe first ruling and it turned four CI suites red, because "the read failed" is not one fact.
A read also throws when the table was never provisioned — a real engine, wired and
reachable, whose
sys_*tables were never created. That is a supported, deliberately testeddeployment shape (
packages/runtime'snotifications.hono.integration.test.tsnames itABSENT_AUTHZ_TABLES). There "zero capabilities" is the true answer rather than afabrication: nothing is provisioned, so nothing was withheld. Only an unreachable store —
the ruling's own word 不可达 — leaves the answer unknown, and only an unknown answer may not
be reported as a capability denial.
Ruling, verbatim:
The relocation, and its real blast radius
@objectstack/corecannot import@objectstack/metadata— metadata depends on core — sothe one classifier moved to the package both sides already depend on. This is the repo's own
stated plan:
packages/metadata/src/errors.tsrecorded it as option 2, "architecturallyattractive and explicitly not precluded by this module", and kept its export as "a single,
greppable seam to delete if the maintainer later takes option 2".
attention:
isMissingTableErrorisSchemaAlreadyExistsErrormatchesDriverErrorwith the above — the module's own docblock says the sharing is the point. Leaving it behind meant either exporting the matcher as machinery, or re-rolling it inmetadata; the second is the duplication this module exists to retiregit mv, so the predicate and its pins stay in one placepackages/types/src/driver-error-classification.ts, exported from the packageindex. No new dependency edge, in either direction:
@objectstack/typesdepends only on@objectstack/spec, and the module's own front-exclusion has readisRelationSubObjectPhrasefrom
@objectstack/typessince [finding] The Postgres"x" of relation "y"phrase is now open-coded in three packages, each repairing the same superstring hole separately —@objectstack/typesis the established home #6615 — so the move actually removes a cross-package edge.@objectstack/metadata/errors— the published subpath — is unchanged in surface and nowre-exports from the new home. No out-of-repo consumer changes, and no call site of that
subpath was edited (
metadata-protocolandobjectqlimport it and were left alone).metadata's internalutils/schema-sync-errors.tsis deleted rather than left as aforwarding stub; its two in-package readers (
errors.ts,loaders/database-loader.ts) nowimport
@objectstack/typesdirectly. That is the only consumer edit the move required.@objectstack/typesgains two public exports, not one.declared:
@objectstack/core's root surface gains SIX exports viasecurity/index.ts— the error class, the two guards(
isAuthzStoreUnavailableError,rethrowAuthzStoreUnavailable) and three constants(status, code, message). That is the necessary mechanism — every guarded transport
imports the guard from
@objectstack/core— but six is the number, not two.The signed-off risk, and the two pins that make it auditable
The ruling approved gating loudness on a driver-error predicate with its false-positive
direction stated on the record:
Both obligations are discharged, and both are checked by tests rather than asserted here:
Written beside the predicate —
packages/core/src/security/resolve-authz-context.ts,in
tryFind'scatch, immediately aboveif (isMissingTableError(err, object)) return [];.A test reads that
catchbody back and fails if the risk note is removed.Both directions pinned by name, in
packages/core/src/security/authz-store-unavailable.test.ts:[#13279 option A] ⭐ THE OUTAGE DIRECTION — a read failure that is not an unprovisioned table stays LOUD[#13279 option A] ⭐ THE UNPROVISIONED DIRECTION — a never-provisioned table resolves QUIETLYThe loud table carries the shapes a naive predicate would get wrong: a permission denial on
the table, the [finding]
isMissingTableError同样把 Postgres 写路径的「缺列」措辞判为「缺表」——其 docblock 明说 42703 必须响亮失败,而消费点会据此「从 1 开始编号」 #6347 Postgres missing-COLUMN phrase that contains a legal missing-tablephrase, and the isMissingTableError never checks WHICH table the "no such table" names — a view over a missing base table is read as "this table is not provisioned yet" #13324 phrase naming a different relation. The quiet table carries every
dialect spelling plus the production wrapper shape, where the driver's phrase is on
causerather than the outer message.Also pinned: a store that loses one table mid-resolution is still loud (the quiet branch
must not leak past the table it is about), and an unprovisioned deployment resolves
byte-identically to a reachable-but-empty one, with a healthy store as the control that
keeps that comparison from being vacuous.
⛔ No second safety net was added to hedge the accepted risk. A hedge makes loudness
conditional on two classifiers agreeing, which is strictly more ways to fall silent.
The four reds, measured
All four recover, and none by touching an assertion. ⛔ Nothing was relaxed, re-baselined,
allow-listed, skipped or deleted anywhere in this PR.
bf297e4ea7)5c7c0b66b9@objectstack/clientordinary CRUD / batch 400s answering 503client.hono.test.tspackages/runtimenotifications answering 401trigger-record-changenoise guardplugin-approvalsstatus-mirror-cascadenoise guardThe two diagnostic channels recover for the mechanism the report predicted:
sys_positionisread at
resolve-authz-context.ts:562, after the concurrent batch, so once absent tablesanswer
[]again the later read is issued and both channels emit.Four surviving swallow sites, found by contract review
⛔ The conversion had reached the 16 single-line sites and none of the four
continuation-layout ones —
serveMetaItemLayered,GET meta/:type,GET meta/:type/:name,GET meta/:type/:name/published. Each carried.catch(() => undefined)on the line after the resolver call, so during a permission-storeoutage they could swallow the loud failure and serve an org-unscoped, env-wide
200insteadof the declared 503.
That is precisely the miss this repo's own census file warns about in as many words — "a
single-line grep counts 16 and misses those four" — committed by the author who had just
written that sentence down. All four are now converted, same three-line shape as the other 16.
The sharper half was a false completeness claim this PR had shipped in
execctx-consumer-census.test.ts: "every site now passesrethrowAuthzStoreUnavailable".It was false when written, and no pin failed, because the sibling ledger's per-transport
check is presence-based — it asks whether the file contains the guard at all, so one
converted site satisfies it for the whole file and a partial conversion is invisible to it.
On a surface whose entire method is measured claims, a false completeness sentence in the
census file is worse than the four sites, because it is what a future reader trusts instead
of re-measuring.
So it is not merely corrected — it is replaced by measurement, and the docblock now records
both the miss and the ledger's reach:
fails on any
() => undefinedsurvivor or any local re-spelling of the shared guard. ACONTROL asserts it finds both layouts, so a regex that quietly stopped matching cannot read
as a clean pass.
answers
200, and each keeps the declared 503 or propagates. Its healthy leg is theanti-vacuity control — proving those sites are reached and that
200is their healthyanswer, which is exactly what the defect fabricated.
reverting one of the four sites turns §7 red and leaves §8 green, because the
${metaPath}/:typehandler (lines 4332–4794) resolves the context three times, so underthe total outage §8 drives, a later guarded site still refuses. §8 pins what a door
answers; §7 pins what each site spells. The case neither covers — a partial outage
where only the first read fails, which is where the org-unscoped
200actually appears —is stated as not measured rather than implied away.
Verification
The local runs below ran on
5c7c0b66b9.origin/mainwas then merged in and the wholefarm re-ran in CI on the final head
0b71a8a13e— see CI at the end of this section,which is the authority. Exit codes were captured by redirecting to a file before
capture, never through a pipe.
Full workspace build:
turbo build --concurrency=2— 72 successful, 72 total. Therelocation compiles across every package.
Tests — 14 packages, all exit 0:
@objectstack/core@objectstack/rest@objectstack/types@objectstack/metadata@objectstack/runtime@objectstack/client@objectstack/metadata-protocol@objectstack/objectqltrigger-record-change/plugin-approvalsAblation — the new pins can actually fail. Implementation committed first, so restore had
a real reference. Mutation: the narrowing line replaced by a marker. Confirmed on disk, not
by an editor exit code — removed-text count 0, injected-marker count 1, and the blob hash moved.
No rebuild was needed and that is a measured fact rather than an omission:
packages/core/vitest.config.tsaliases
@objectstack/typesto../types/src/index.ts, so the suite reads both the mutatedresolver and the relocated predicate from source, and no stale
distcan sit between them.Result: 7 failed / 30 passed — all six UNPROVISIONED pins and the risk-note pin fell, and
every OUTAGE pin stayed green, which is the point: the two directions are independent, and
the loud path is not gated on the narrowing. Restore ran from an
EXIT INT TERMtrap with anabsolute repo root and is proven by a HEAD-blob hash match plus an empty whole-tree
git diff HEADandgit status --porcelain.Gates — family derived against the actual change set with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(25 paths; 62 distinctgate commands). 60 exit 0, including
check:authz-resolver,check:adr-0087-registration,check:empty-changeset,check:changeset-gate-self-tests,check:test-source-alias,check:type-source-resolution,check:cross-package-test-inputs,check:undeclared-dep-imports,check:published-files,check:engine-double-contract,check:route-envelope,check:dispatcher-error-vocabulary,check:nul-bytes, and — answering the open question fromreview —
check-system-context-censusexit 0, whose verdict line is byte-identical to thebase's.
check:type-check-debt --re-measureis green after rebuilding core: "29 ledger entriesre-measured, 1547 raw tsc errors total, none above its recorded number".
The other 2 are PREREQUISITE NOT MET, not reds, and say so in their own output:
check-test-completeness(exit 3 — it grades a savedturbo run testlog, and none existslocally) and
check-half-states(exit 3 — the container token is not a GitHub credential).Lint. The repo-wide farm is not measured locally:
pnpm lintdied with anout-of-memory abort (exit 134) at the 4096 MB ceiling on a shared box. That is a resource
failure, not a finding. The declared narrowing instead, with all three pieces of evidence:
the universe is 5468 files, read from eslint's own config via
isPathIgnoredovergit ls-filesrather than guessed; the narrowed run covered 21 files — every lintablefile in the diff — counted from
--format json, with 0 errors and 0 warnings; and thenarrowing excludes nothing, because this repo's single
eslint.config.mjsnever enablestype-aware linting for any file (no
parserOptions.project, no typed rules — stated atline 328 and measured there with a positive control), so no verdict on an untouched file is
movable by this diff. CI owns the full farm.
The merge, and why CI had been silent
For three consecutive pushes this branch produced zero CI runs — measured, not assumed:
21 workflow runs existed on it, all on
85d27f4f9fandbf297e4ea7, and none on603c9cbb6bor5c7c0b66b9, while Actions was demonstrably healthy repo-wide. The cause wasa merge conflict against
main(mergeable: false,mergeable_state: dirty): aconflicted PR has no computable merge commit for
pull_requestworkflows to run against.Merging
origin/main(27 commits) fixed it, and the confirmation is the strongest available— CI fired immediately on the very next push.
One conflict, in
content/docs/permissions/system-context.mdx. All four hunks were pureline-number rot, proven mechanically by comparing both sides with digits stripped: prose
identical, no census row written, deleted or reworded on either side.
rest-server.tsmoved on both sides, so neither side's anchors were correct afterwards.
--fixrepairedpackage-routes.ts:92 -> :97and then refusedrest-server.ts— "pageanchors 7 distinct read line(s), census finds 6 — the POPULATION changed".
false, and the same run says so: its own
[ledger-row-unused]line reportsNON_READ_ANCHORSexcusingrest-server.ts:1238with no anchor pointing there. The pagecarries eight anchors into that file — six elevation reads plus the two excused inbound
seams — and
--fixcounts the excused pair as read anchors. That is #13490, reproduced.Re-anchored by hand, every target proven by content, never by arithmetic — the same
handling already reviewed and accepted on this PR. The arithmetic could not have found one of
them:
:1267was simultaneously the second ledger seam's new home and row 17's stale readanchor. Population proven unchanged against
origin/mainin a compare worktree — both verdictlines byte-identical.
CI on the final head
0b71a8a13e— 10 of 10 workflows greenSo the four previously-red suites are green in CI, not merely locally, and the census gate
is CI-confirmed for the first time on this branch. Its own verdict line from the job log,
byte-identical to my local run and to
origin/main's:not re-run the package suites locally. The shared verify lock was held ~24 minutes by
another agent's run and two 9-minute waits both timed out. CI was already running the full farm
on this exact commit, which is strictly stronger evidence than my local re-run would have been,
so I read that instead. The build state was refreshed per AGENTS.md §9
(
pnpm install --frozen-lockfile,rm -rf packages/runtime/.objectstack, full rebuild72/72 green) before any of it.
mainalso editedresolve-authz-context.tsand itauto-merged.
mainadded the #11971 cross-request grants cache toresolveUserAuthzGrants.Checked rather than assumed: the cache's
commit(...)sits after every read, so a throw fromtryFindcan never commit an envelope — an outage is not negatively cached — and the cache isOFF by default (
OS_AUTHZ_GRANTS_CACHE_TTL_MS=0). Test Core is green with both changespresent. Not measured, and stated as such: the behaviour of an outage against a cache that is
turned on is #11971's TTL/epoch contract and I added no pin for it.
Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
Scope
⛔ Untouched, as fenced:
content/docs/releases/,packages/spec/,docs/adr/,.claude/,skills/,AGENTS.md,CLAUDE.md. Out of scope and carried by their own cards, not addressedhere: #13476 and #13490 both remain open.
Generated by Claude Code