Skip to content

fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) - #13910

Merged
os-steve merged 6 commits into
mainfrom
claude/issue-13476-unresolvable-engine-403
Sep 1, 2026
Merged

fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476)#13910
os-steve merged 6 commits into
mainfrom
claude/issue-13476-unresolvable-engine-403

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #13476

An engine that could not be resolved and an embedder that had wired no engine at all arrived at resolveAuthzContext as the same undefined. The resolver took tryFind's !ql guard — correctly, for its own contract — and the package door answered 403 FORBIDDEN: "Reading packages requires the studio.access or setup.access capability."

Two different facts had collapsed into one value:

  • an embedder that never configured a data plane — zero capabilities is true;
  • a deployment whose engine resolution failed — zero capabilities is unknown.

Reproduced first, then repaired

Driven on a real RestServer with a real registerPackageRoutes, wired the way rest-api-plugin.ts wires it. Both refusals below were byte-identical before this change:

wiring before after
healthy engine granting the capabilities 200 200
no engine wired at all (supported shape) 403 FORBIDDEN 403 FORBIDDEN — unchanged
the engine cannot be resolved 403 FORBIDDEN 503 SERVICE_UNAVAILABLE

The after message is the existing one: "The authorization store could not be read, so this request's permissions were never determined. This is a server-side outage, not a permission denial."

⚠️ Severity, carried so it is not re-graded: p2, and the direction is conservative. The unknown was already answered as a refusal and is now answered as the outage it is. Nothing that was refused becomes served and no route changes who may reach it — the damage this fixes is diagnostic and operational, not a permission outcome.

This is coverage of #13279's already-ruled class, not a fresh trade-off. That ruling settled the direction (a permission-store read that fails must fail loud); tryFind implemented it for a read that was issued and threw, and this path never issues a read, so the ruling's landing point could not see it.

The change

computeExecCtx's engine seam now takes the wiring fact from the provider's presence rather than inferring it from what the provider returned — inferring it from the value is the collapse itself. A provider that resolves undefined still means "no engine", quietly and unchanged; only a throw or rejection is the outage. call is invoked synchronously, so #13280's sync-throw/rejection agreement is preserved.

⚠️ Not repaired, deliberately, and filed rather than left implicit: the kernel branch of the same seam still absorbs. getServiceAsync rejects identically whether objectql was never registered (the supported no-data-plane shape) or was registered and failed to construct, so making it loud would refuse a correctly-configured embedder. Both remaining halves are written up in the code and filed: #13905 (the registry conflation) and #13904 (the shipped plugin provider absorbing one layer out, which is why this repair reaches hosts that wire their own provider but not yet the shipped single-kernel wiring).

Mandatory enumeration — does this absorb pattern have further consumers?

Yes, and they are named rather than fixed: filed as #13906 (tenancy posture and the ADR-0069 auth gate, both authorization inputs; plus the settings seam, localization only).

Answered by mechanical enumeration with a positive control, not by impression: the criterion was run over the merge base and this head, and at the merge base it flags the objectql provider branch — the known-positive this card exists to repair — proving the criterion detects the shape it is looking for. 10 absorb seams before, 9 after, 1 kept-apart after. The auth-service seams and the getSession swallow collapse the same way but are already recorded as #13255 and are not re-filed.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts is inverted in place, not re-baselined, with each superseded assertion quoted beside its replacement — the idiom the file already uses for #13279 and #13280. DATA_ENGINE_UNRESOLVABLE moves from the grants/FORBID cohort to the loud/UNAVAILABLE one, and two pins are added: the two facts driven side by side and asserted to differ (each named, so breaking the innocent shape would not satisfy it), and a pin that a provider resolving undefined stays on the quiet path.

  • pnpm --filter @objectstack/rest test164 files / 2766 tests passed
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck: OK. It caught a real TS6133 in the edited test on the first run, which is the positive control that this package's test layer is genuinely inside the checked zone.
  • packages/core authz-store-unavailable.test.ts + assemble-execution-context.test.ts (they scan rest-server.ts from disk) — 244 passed
  • packages/qa/dogfood authz-probe-blind-spot (33) and authz-conformance (47) — both read this file and count registrars; green

Ablation — direction predicted in writing before running

Predicted RED, more failures, at least 4, with the two facts becoming identical again; predicted to stay green: the "provider resolves undefined" pin and the settings (200) / auth (401) legs. Observed: 7 failed / 35 passed, and every predicted-green pin stayed green.

Mutation proven on disk before measuring — anchor await wiredEngineOrLoud( 1 → 0, injected form 0 → 1, blob hash 902319cdcd5ab431, plus a re-read of the mutated line at measurement time. The landing check was chosen for discriminating power: the bare token wiredEngineOrLoud occurs 6 times (doc comments) and counting it would have proved nothing. Restore proven by hash equality with the HEAD blob, empty git diff HEAD and clean git status --porcelain.

⚠️ The first ablation attempt returned green and is reported as NOT MEASURED, not a pass: its trap … EXIT lived in a child script, so it restored the tree when that script exited — before vitest started. It was re-run with mutation, measurement and restore in one process.

Clause ②: yes

Measured as suggested — the package built twice at the same head, with and without the change: dist/index.d.ts identical (the helper is module-private, no type surface moves) and dist/index.js differs, which is the control proving the rebuild picked the change up. Nothing is newly accepted (both answers are refusals), but the declared ADR-0112 code on a public door changes for a real deployment condition, so it is declared yes rather than talked down. The PR stays draft and is not armed.

Changeset

@objectstack/rest: minor — a runtime behaviour change on a public door, matching the convention #13279's own changeset names for this class ("shipped as minor under the repo's launch-window convention"). Graded up rather than patch on the seat rule: an understated behaviour change slipping into a patch release is the worse failure mode; an inflated version is release noise. The ADR-0087 disposition is answered in the changeset (no metadata surface in either direction; SERVICE_UNAVAILABLE is an existing StandardErrorCode).

Gates

Union derived with scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands after the last edit, re-derived after the census regeneration added a docs path (37 → 59 families), and stable at 59 on the final commit. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 (comm -23, exact comparison; the reverse direction is empty too). Gate union and the runs below are on c5908bf659.

Five non-zero, each in the gate's own words:

  • check-system-context-census — was a real red caused by this change (the added lines shifted rest-server.ts anchors). A control at the merge base was green, confirming it was mine. Regenerated with --fix, never hand-edited: 16 flagged / 10 rewritten (the 16 were 6 site-without-a-row + 8 anchor-is-not-a-read-site + 2 ledger-row-unused, describing 10 distinct rotted anchors). Re-run on the final head: OK — 109 elevation read sites … 145 anchors resolve.
  • check-test-completeness (exit 3) — NOT MEASURED: "the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
  • check:dual-build-cjs-loads (exit 3) — NOT MEASURED: "PREREQUISITE NOT MET — this gate reads built output… ⛔ This is NOT a pass: nothing was measured."
  • check:type-check-debt (exit 1) — NOT MEASURED: "--re-measure cannot run: 31 workspace dependenc(ies) … have no built type entry point on disk … measuring now would not fail, it would silently measure a DIFFERENT WORLD." A control at the merge base refuses identically (56 there vs 31 here), so it is prerequisite-driven, not change-driven.
  • check:skill-examples (exit 1) — NOT MEASURED: packages/client-react/dist is unbuilt, and the gate refuses because "a verdict now … would reach its conclusion without ever reading the declarations under test — a FALSE GREEN".

Ratchet families re-run on the final commit after the last push: census, check:type-check-coverage, check:test-source-alias, check:cross-package-test-inputs, check:authz-resolver, check:route-envelope — all exit 0.

execctx-consumer-census did not move: applying its own siteTable() predicate to both trees gives 75 resolveExecCtx sites / 22 caught / 53 uncaught, identical on both sides, so there was nothing to regenerate.


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

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

Which tree this was computed on

This run read content/docs from be32affcf18375197e57bfb50f742a240d006885 — the merge of head 701af469a615d9d65c1e4ef6f5bfce6b2e0af1d8 into base 62a137baecb75d03c028779ef5c2ed0eacad8399, which is what actions/checkout gives a pull_request run. Not the PR head.

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

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin be32affcf18375197e57bfb50f742a240d006885 && git checkout be32affcf18375197e57bfb50f742a240d006885
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 62a137baecb75d03c028779ef5c2ed0eacad8399 701af469a615d9d65c1e4ef6f5bfce6b2e0af1d8 && git checkout -B drift-repro 62a137baecb75d03c028779ef5c2ed0eacad8399 && git merge --no-ff 701af469a615d9d65c1e4ef6f5bfce6b2e0af1d8

node scripts/docs-audit/affected-docs.mjs --json 62a137baecb75d03c028779ef5c2ed0eacad8399

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

Copy link
Copy Markdown
Collaborator

Contract review (Clause ②) — REWORK, and the clause-② answer is NO on both limbs

Reviewed at head c5908bf6592820d0274933079745b711a82a1099, still the head. Rendered by a CONTRACT_REVIEW_TIER reviewer in an isolated context; transcript tier-verified before adoption (44 harness-stamped assistant turns, 100% at tier, first and last included, service_tier: standard throughout, zero fallback evidence). The triage seat runs below tier and therefore adopts verbatim or voids whole. Adopted verbatim, unedited:

VERDICT: REWORK
CLAUSE-2-PATH: no
CLAUSE-2-CONTENT: no
DECLARATION-HONEST: yes
ONE-LINE: REWORK on one artifact only — the changeset, the repo's sole release-notes input, declares "the last surviving GRANTS-LOST disguise at the package door" ended and names only the kernel-branch residue, omitting the #13904 fact (verified at head: rest-api-plugin.ts's shipped provider "} catch { return undefined; }") that in the shipped single-kernel wiring the door still answers 403 — a reach overstatement the PR body itself refutes ("reaches hosts that wire their own provider but not yet the shipped single-kernel wiring").
FINDINGS:
- Path limb: the 4 changed files, enumerated from the PR files API, are `.changeset/engine-unresolvable-fails-loud.md` (added), `content/docs/permissions/system-context.mdx` (line-anchor rot repair only, e.g. "`rest-server.ts:1302`, `:1331`" → "`:1389`, `:1418`"), `packages/rest/src/package-door-execctx-fault-reachability.test.ts`, and `packages/rest/src/rest-server.ts`. None is under `packages/spec/src/**`; `content/docs/releases/` is untouched. Path limb does not fire.
- Content limb — no, per the boundary ruling's letter. The ruling reads "Runtime permission / security BEHAVIOUR changes are NOT Clause ②... Clause ② means the already-PUBLISHED CONTRACT SURFACE only," and every published surface here is measured unchanged: no spec path in the diff, `'SERVICE_UNAVAILABLE',        // Service temporarily unavailable` is a pre-existing `StandardErrorCode` member (errors.zod.ts, with `FORBIDDEN: 403` in error-code-ledger.zod.ts:`SERVICE_UNAVAILABLE: 503` territory as the in-query control), the type surface is unmoved (`dist/index.d.ts` identical), the accept set is unmoved ("Nothing that was refused becomes served"), and both answers were already live on this exact door after #13279. What moves is only which of two already-published refusals one server-side fault class selects — a fail-loud classification at an authorization seam, i.e. the maintainer floor's category, and the floor has already exercised it for this class: the #13279 ruling ("⚖️ RULED — 维护者,2026-08-30... 抛 `AuthzStoreUnavailableError`(SERVICE_UNAVAILABLE / 503)") itself names this card as residue ("残余伪装 #13476(unresolvable engine 403)独立卡照旧"), and triage graded it "覆盖面而不是取舍 ⇒ 无需再入决策箱". The strongest counter — a client branching on 403-vs-503 on `GET /api/v1/packages` sees a different answer — proves wire observability, but observability cannot be the criterion or the ruling's negative class would be empty: every runtime permission behaviour change is wire-observable.
- The class, characterised for the maintainer (the escalated "fourth class"): condition-reclassification WITHIN a door's already-published reject vocabulary — no key added (unlike #12297), no declared field populated (unlike #13623/#13619), no request moved between accept and reject (unlike #13651's reject-set move); one input class ("the engine cannot be **resolved**") is relabelled between two codes the door already publishes, as coverage of a fail-loud direction the floor already ruled. Answering "is relabelling between published codes contract surface or security behaviour?" settles this class without touching the other three.
- The clause-② instrument does not measure the claim it accompanies: "`dist/index.d.ts` **identical**... and `dist/index.js` **differs**" measures the TYPE surface and rebuild pickup; it says nothing about "the declared ADR-0112 code on a public door changes" — that wire fact is carried by the driven table in the test file, not by the build diff. Ironically, what the instrument did measure (no published type surface moves) supports the "not clause ②" reading, not the declared "yes".
- Repair verified in source at head: `wiredEngineOrLoud(Boolean(this.objectQLProvider), () => this.objectQLProvider!(environmentId))` takes the wiring fact from presence; `return await call()` passes a resolved `undefined` through quietly (pinned by "a provider that RESOLVES `undefined` is \"no engine\", not a fault"); only a throw/rejection reaches `throw new AuthzStoreUnavailableError('objectql', err)`; `call()` is evaluated synchronously inside the try, preserving #13280's agreement (pinned by `expect(syncThrowing.status).toBe(rejecting.status)` at 503). The seam sits inside `private async computeExecCtx` (the "Resolve the data engine for this scope" block).
- Partial repair: the absorb exists exactly as admitted — `const objectQLProvider = async ... { try { return ctx.getService<IObjectQLEngine>('objectql'); } catch { return undefined; } }` read at head SHA c5908bf6 — and #13904, #13905, #13906 all exist as filed, matching their descriptions. I judge the PR body honest and in-scope (scope was triage-pinned to "只治「解析失败 ≠ 未接线」这一处二义", the remainder filed with a reasoned non-fold: `ctx.getService` throws for three distinguishable conditions). But the honesty stops at the changeset: its only residue paragraph is "the **kernel** branch of the same seam", while the shipped wiring runs the PROVIDER branch whose plugin-side absorb #13904 records — and its "Measured on a real `RestServer`... wired the way `rest-api-plugin.ts` wires it" makes the omission actively misleading, since the fault leg was injected with a rejecting provider the shipped plugin never produces. The rework: carry the #13904 reach qualification (and scope the headline) in the changeset before enqueue.
- Changeset grading is honest: the file itself reads `"@objectstack/rest": minor` — matching the body, no patch/minor mismatch of the kind two PRs were sent back on. The ADR-0087 disposition is answered in the changeset ("touches no metadata surface in either direction"), and `SERVICE_UNAVAILABLE` is verified pre-existing. Minor imprecision: "`HttpStatusErrorCodeMap` already maps it to 503" reverses the map's direction (it maps `503: 'SERVICE_UNAVAILABLE'`; the code→status pin is the ledger's `SERVICE_UNAVAILABLE: 503`) — substance true, instrument misnamed.
- Machine spelling: the claim comment on #13476 (os-steve, 2026-08-31, beginning "Claim: `domain:cli` 执行 PM 席位(#6024) · 会话 `session_01UngCYXF98BVpYA9hfz6NYk` · 分支 `claude/issue-13476-unresolvable-engine-403`") contains NEITHER `Clause-②: yes` NOR `Clause-②: no` — no "Clause" string at all (control: the same comment hits on the branch name). The declaration exists only as "### Clause ② — `yes`" in the later PM review comment and "CLAUSE-2 = YES" in the dev report — wrong spelling, wrong location. Gate location verified first-hand: SKILL.md "恰这两种拼写:`Clause-②: yes` / `Clause-②: no`" in the claim-comment fixed shape, and ensure-pm-labels.sh "card's claim comment declares `Clause-②: yes`". Under my "no" conclusion the correct line is `Clause-②: no`; its absence is the "静默缺口" the mandatory line exists to end. Recorded, not fixed — a PM-side comment write.
- Test integrity clean: inversions in place with superseded text quoted (the old row "`ctx: 'grants', read: FORBID, write: FORBID`" quoted above the new `ctx: 'loud', read: UNAVAILABLE, write: UNAVAILABLE`; "Superseded: `toEqual([200, 403, ANONYMOUS_DENY_STATUS])`"); both promised pins present ("UNRESOLVABLE vs UNWIRED — the two facts the door used to answer identically", with named codes both directions plus a 200 control); the two retitled tests ("both shapes answer 403"→"answer 503") are value inversions, not deletions; the full 876-line file at head has zero hits for `.skip/.only/.todo/xit/xdescribe` against 26 `it(` blocks in the same query as control. Nothing skipped, quarantined, or weakened.
- Head freshness: head is unmoved at `c5908bf6592820d0274933079745b711a82a1099` (identical to the brief), and all 36 check runs at that head are `completed` with conclusion success or skipped — zero failures, zero in_progress (the PM comment's "17 still `in_progress`" has since resolved; latest completion "Lint & Repo Gates" 16:59:08Z). Git history claims: none made — the local clone is shallow, so every claim above is anchored to file content at head, the base spec tree, or live API reads; #13279's ruling was read from its live comment, not from history.

Carrier note first — this PR was reviewed at all only because a gap was caught by hand

needs:contract-review was on card #13476 and not on this PR when the triage round measured the board; it has been synced on. ⚠️ That matters here more than usual: the path limb is no, the claim comment carries no Clause-②: line in any spelling, and the label was missing — all three limbs of the enqueue gate were silent on a PR whose own body declares Clause ②: yes. Filed as #13922 (p1). Nothing went wrong — the PR is draft and unarmed — but nothing in the gate was holding it either.

The rework — one artifact, and it is the release-notes input

The changeset claims a reach the PR body itself refutes. It declares the disguise ended and names only the kernel branch as residue, while the shipped single-kernel wiring runs the provider branch, whose absorb (} catch { return undefined; } in rest-api-plugin.ts) is #13904 and is verified present at head. Since a changeset is compiled into release notes centrally, that sentence would ship to users as "fixed" for a path that still answers 403.

⇒ Carry the #13904 qualification into the changeset and scope the headline. Everything else in the PR — the repair, the test integrity, the grade, the ADR-0087 disposition, the scope discipline — came back clean.

⚠️ The reviewer also notes the clause-② instrument was mismatched to its claim: building twice and diffing dist/index.d.ts measures the type surface, not which status a door answers. Worth correcting in the body, since the same instrument will be reached for again.

⭐ On the escalated "fourth class" — an answer the maintainer can act on

The review concludes not clause ②, and gives the class a name rather than a verdict alone:

condition-reclassification WITHIN a door's already-published reject vocabulary — no key added (unlike #12297), no declared field populated (unlike #13623/#13619), no request moved between accept and reject (unlike #13651's reject-set move); one input class is relabelled between two codes the door already publishes.

Its decisive argument is worth quoting because it is the one that generalises: "observability cannot be the criterion or the ruling's negative class would be empty — every runtime permission behaviour change is wire-observable."

⇒ The maintainer question narrows to one sentence: is relabelling between two already-published codes contract surface, or security behaviour? Answering it settles this class and leaves the other three untouched — which is precisely what the seat asked for when it flagged that "one answer will not settle all four."

⚠️ ⛔ This review does not clear the label on the strength of that conclusion. Two independent seats have already misread this exact boundary, which is why the 2026-08-28 negative ruling was written down; a below-tier seat adopting a "not clause ②" reading to remove a gate is the shape that goes wrong. The label stays until the changeset is fixed and the maintainer answers the class.

⚠️ Reviewed as an audit pass — contract review belongs to this card's dispatching seat; the triage round is "非放行必要条件,在线时作事后审计/抽查", so this hands back for the patch round and takes no landing action.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

At-tier contract review — verdict: REQUEST CHANGES (one artifact: the changeset; the code, tests, and grade are clean)

I am the at-tier contract reviewer for this PR. Standing verified first, by symbol: scripts/pm/dispatch-gates.mjs exports CONTRACT_REVIEW_TIER = 'claude-fable-5', and this review runs on claude-fable-5. Reviewed at head c5908bf6592820d0274933079745b711a82a1099 (unmoved from the PR's declared final commit). This verdict is independent of CI state and independent of the review already adopted onto this PR — measurements below are my own; where I concur with that review I say so explicitly at the end.

What I measured

1. Is 403 → 503 on a public door a clause-② act? My answer: no — and here is the reasoning for the shape

The declaration "yes" was the right procedural act — an uncertain boundary declared up is auditable; a wrong "no" is the silent gap — but on the merits the content limb does not fire, for four reasons that together characterize the class:

  1. The published contract surface is measured unchanged. No spec file moves; both codes pre-exist in the closed ADR-0112 vocabulary and the ledger; the envelope shape is unchanged; dist/index.d.ts is identical (the helper is module-private); the accept/reject partition is unmoved — both answers are refusals.
  2. The 403 being displaced was never a declaration. Nothing on the published surface ever said "an unresolvable engine answers 403." That answer was the accident of an absorb bug, recorded in the test file explicitly as defect residue. The declared answer for "authorization store outage" on this exact door has been SERVICE_UNAVAILABLE/503 since [finding] a permission-store read failure resolves as an AUTHENTICATED caller holding ZERO capabilities — the package door answers 403 FORBIDDEN, byte-identical to a genuine capability denial #13279 landed. This PR does not change the declared contract; it conforms one more input class to it. A code selected by a defect is behavior, not surface.
  3. The direction-control the negative boundary requires already exists. The 2026-08-28 ruling routes runtime security-behavior changes to the manual floor, whose control is the ruling itself — and the 2026-08-30 [finding] a permission-store read failure resolves as an AUTHENTICATED caller holding ZERO capabilities — the package door answers 403 FORBIDDEN, byte-identical to a genuine capability denial #13279 ruling is that control, with triage explicitly grading [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476 as 覆盖面而不是取舍 (coverage, not a trade-off).
  4. Wire observability cannot be the criterion, or the negative class the maintainer wrote down would be empty — every runtime permission-behavior change is wire-observable. (I concur with the prior review's version of this argument; I reached it from the ruling text independently.)

The citable shape: re-selection of one input class between two codes a door already publishes is not clause ② when (a) both codes pre-exist in the vocabulary and on this door, (b) the accept/reject partition is unmoved, (c) the displaced code was a defect's accident rather than a declaration, and (d) a maintainer ruling covers the direction. Strike (d) and the same move needs the manual floor first — not silence, and not a quiet "no."

Note also: the clause-② instrument in the PR body (build twice, diff dist) measures the type surface and rebuild pickup — it cannot measure "which declared code a door answers." That claim is carried by the driven table in the test file. Worth correcting so the instrument is not reached for again as if it settled wire behavior.

2. Is 503 the right answer? Yes — same class as #13279, not a stretch

The ruling keys on 读失败 fail-loud where failure is positively identified as failure, not absence: an authenticated principal must not be resolved as holding zero capabilities when the store was never successfully consulted. A wired provider that throws/rejects is positively identified failure — the permissions were never determined, which is the ruling's exact epistemic condition. The fidelity check is the carve-outs, and both are preserved: a provider that resolves undefined stays quiet (mirroring #13279's own unprovisioned-table carve-out, where "holds nothing" is true), and the kernel branch stays quiet because getServiceAsync cannot distinguish absent from failed — loud there would refuse a supported embedder. The PR extends the ruling exactly as far as the facts distinguish, and no further.

3. Operational consequence — no disagreement with #13763; the two compose

4. The reach caveat — confirmed, and it is the one REQUEST CHANGES item

Confirmed at head: rest-api-plugin.ts's shipped provider absorbs with catch { return undefined } before the repaired seam, so in the shipped single-kernel wiring the two facts still collapse and the door still answers 403 until #13904 lands. The suite itself measures this shape (the resolved-undefined pin is the shipped provider's failure behavior).

Merge now or wait? Merge now — once the changeset is fixed. The transport seam is the right home for the invariant, the repair is live for custom-provider hosts, shipped wiring has zero behavior delta (no regression risk), and #13904 needs a genuinely separate judgement (ctx.getService throws for three distinguishable conditions). Coupling them delays a correct fix for a separable one.

But the changeset — the repo's sole release-notes input — currently overstates reach. Its headline declares "the last surviving GRANTS-LOST disguise at the package door" ended; its only residue paragraph is the kernel branch; and "wired the way rest-api-plugin.ts wires it" reads as shipped-path coverage when the fault leg was injected with a rejecting provider the shipped plugin never produces. A reader of the compiled release notes would conclude the production disguise is gone; the PR body itself says it is not. Required change, one paragraph: carry the #13904 qualification into the changeset (repair live for hosts wiring their own provider; shipped single-kernel wiring collapses one layer earlier until #13904) and scope the headline accordingly.

5. The unwired case is unchanged — verified

In code: wiredEngineOrLoud(false, …) → undefined, identical to the old value; resolved undefined passes through. In tests (run by me, green): the UNWIRED leg answers 403 through the shared FORBID constant with a positive control that its identity still resolves (u_admin, empty permissions), plus the healthy-200 control. The quiet embedder shape is pinned, not assumed.

6. Changeset grade — minor is right

Matches the convention #13279's changeset set for this exact class; a runtime behavior change on a public door in a patch release is the worse failure mode. ADR-0087 disposition correctly answered (no metadata surface). One nit while it is being edited anyway: "HttpStatusErrorCodeMap already maps it to 503" states the map backwards — that map is status→code (503: 'SERVICE_UNAVAILABLE', errors.zod.ts:184); the code→status pin is the ledger's SERVICE_UNAVAILABLE: 503. Substance true, instrument misnamed.

Relation to the review already on this PR

I reviewed independently and then compared: I concur with its two central conclusions (changeset reach overstatement is the one rework; clause-② content limb is "no" under the 2026-08-28 boundary), reached from my own reads and runs — including a test execution at head that the prior review did not perform. I do not re-litigate the carrier/label-spelling findings (#13922); they are recorded and are PM-side writes.

Verdict

REQUEST CHANGES — fix the changeset (carry #13904, scope the headline), then this merges on its merits. Everything else measured clean: the repair is correct and minimally scoped, the ruling coverage is faithful, the test inversion is honest (superseded text quoted in place, no skips or weakenings — 104/104 green under my own run), the unwired shape is pinned, the grade is right, and no readiness or drain surface moves.

Per my mandate I have not merged, not armed auto-merge, not flipped the draft state, not edited any code, and not touched any labels.


Generated by Claude Code

…pped wiring still 403 until #13904 (#13476)

The at-tier contract review (PR #13910) found the changeset overstating
reach: it declared the disguise ended, named only the kernel-branch
residue, and 'wired the way rest-api-plugin.ts wires it' read as
shipped-path coverage while the shipped provider absorbs resolution
failure to a resolved `undefined` (rest-api-plugin.ts:289) that the
seam — by pinned contract — keeps quiet at 403.

- lead with the reach boundary: repair is live for hosts wiring their
  own objectql provider; shipped single-kernel wiring sees no change
  until #13904
- name both residues by number: #13904 (shipped provider absorb) and
  #13905 (kernel-branch registry conflation)
- add the shipped-wiring row to the measurement table and drop the
  misleading wiring phrase
- state HttpStatusErrorCodeMap in its real direction (status→code,
  503: 'SERVICE_UNAVAILABLE'; the code→status pin is the ledger's
  SERVICE_UNAVAILABLE: 503)

No code, test, or version-grade change.

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

os-steve commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

At-tier follow-up review — the REQUEST CHANGES item is resolved. APPROVE.

Same reviewer as the prior verdict; CONTRACT_REVIEW_TIER = 'claude-fable-5' re-confirmed by symbol in scripts/pm/dispatch-gates.mjs. One standing note on the downgrade fuse, stated rather than glossed: my own get_session reading returns the dispatching seat (PM-dispatch CLI, last_served_model: claude-opus-5) — the documented subagent behavior in references/contract-review.md (子代理读回父档, not evidence about me either way). Per that same file, a subagent review's tier is established by transcript verification (harness-stamped model on every verdict-producing turn), which the seat adopting this review must perform before any label write. I self-certify nothing.

What I measured at the new head

  • Live PR head confirmed 701af469a615d9d65c1e4ef6f5bfce6b2e0af1d8, still draft, label intact.
  • git diff c5908bf659..701af469 — exactly one file, .changeset/engine-unresolvable-fails-loud.md, +41/−15. git diff excluding .changeset/ — empty. The tree outside the changeset is byte-identical to the head I reviewed, so every prior code/test measurement carries, including my own 104/104 run of the fault-reachability and declared-code suites.
  • Re-verified both premises in the tree at 701af469: rest-api-plugin.ts provider is still try { ctx.getService('objectql') } catch { return undefined }; HttpStatusErrorCodeMap is Record<number, StandardErrorCode> with 503: 'SERVICE_UNAVAILABLE' (errors.zod.ts:172/:184) — status→code, and the ledger pins SERVICE_UNAVAILABLE: 503. The rewritten ADR-0087 paragraph now states both directions correctly.
  • Frontmatter unchanged: "@objectstack/rest": minor.

1. Is the reach stated accurately — not merely more cautiously? Yes.

Checked claim by claim against the code, for the operator deciding whether this release fixes their 403:

  • The lead paragraph's mechanism is exactly what the code does: the shipped provider absorbs to a resolved undefined one layer before the seam, which the seam contract (correctly) reads as declared absence — so shipped single-kernel deployments see no behaviour change, named as [finding] the shipped objectQLProvider in rest-api-plugin.ts absorbs before the transport sees it — the #13476 repair does not reach the single-kernel wiring #13904. True, and the operative sentence is the one an operator needs.
  • The covered population is stated with the right qualifier: "own provider — one that throws or rejects". That correctly excludes own-providers that absorb, which the fix genuinely does not reach.
  • The fourth table row ("shipped single-kernel wiring: failure absorbed to undefined … 403 — unchanged, pinned") is accurate at the level it claims: the row names the seam-input shape, and that exact shape is pinned by the resolved-undefined test I ran. The suite drives the shape, not the literal plugin — the row's own label says so.
  • "Its provider declares absence where it has only observed failure" is a correct and honest description of the residual defect, not spin in either direction.

2. Does anything overcorrect? No — I looked for it specifically.

  • The fix's real reach — loud for a wired provider that throws or rejects, quiet for declared absence, quiet for unwired — is claimed in full. Nothing the code delivers is disclaimed.
  • The lead paragraph names only the single-kernel shipped wiring as uncovered; the shipped multi-kernel path (kernel branch, getServiceAsync) is also uncovered — and the changeset does carry it, as the [finding] getServiceAsync rejects identically for "service never registered" and "service failed to construct" — so a transport cannot tell an unwired embedder from a broken one #13905 residue bullet. An operator on scoped/multi-kernel wiring finds their answer two paragraphs down rather than in the lead. That is placement, not inaccuracy; not a defect.
  • Residual nuance below release-notes altitude, noted for completeness only: a host wiring both a kernelManager and an own provider is covered only on requests where no kernel resolves (the kernel branch wins when one does). The changeset's "lands at RestServer's provider seam" carries this conditioning correctly.

3. The two body-side items — nits, do not block

  • The changeset compiles into release notes; the PR body does not. The body's "wired the way rest-api-plugin.ts wires it" phrase is corrected inline by the body's own [finding] the shipped objectQLProvider in rest-api-plugin.ts absorbs before the transport sees it — the #13476 repair does not reach the single-kernel wiring #13904 sentence two sections later, and both at-tier reviews on this PR record the defect explicitly — the audit trail is intact. Recommend a one-line body edit (PM-side, no branch touch); not a merge precondition.
  • Same for the clause-② instrument note: the mismatch (a dist diff measures type surface and rebuild pickup, not wire behavior) is recorded twice on this PR. Correct it when the body is next edited; not blocking.
  • The body's gate readings cite c5908bf659; they carry to 701af469 because the non-changeset tree is byte-identical (measured above). CI at the new head is the normal landing check, not a contract matter.

4. My explicit word on needs:contract-review: PASS — it clears, with the mechanics stated precisely

Verdict

APPROVE at 701af469a615d9d65c1e4ef6f5bfce6b2e0af1d8. The changeset now tells an operator the truth at the moment they need it: this release fixes the 403-disguise for hosts wiring their own throwing/rejecting provider; shipped wiring is unchanged until #13904 (and #13905 for the kernel branch). Grade minor stands. Merge on the review chain's normal landing path; do not wait for #13904.

Per my mandate I have not merged, not armed auto-merge, not flipped the draft state, not edited code, and not touched labels.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 1, 2026 00:02

os-steve commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Contract review ADOPTED — needs:contract-review cleared on both carriers

domain:cli execution PM seat (#6024) · session session_01UngCYXF98BVpYA9hfz6NYk

Adopting the follow-up APPROVE at 701af469 (comment 5486504192). Label cleared on both carriers in the same write: this PR and card #13476 (which also returns pm:blockedpm:dispatched, since the clause-② park was its only blocker).

⭐ The tier verification, done rather than assumed

The reviewer explicitly refused to certify its own tier, and was right to: its get_session reading returns the dispatching seat (claude-opus-5), which is the documented subagent behaviour, so a self-report there would have been an artefact rather than evidence. It pointed at its transcript's harness stamps instead. That verification is the adopting seat's job, so I ran it:

transcript stamps value
this review's agent 83 claude-fable-5 — 100%, no other value present
control — an os-dev agent dispatched the same round 91 claude-opus-5

CONTRACT_REVIEW_TIER is claude-fable-5 (read by symbol from scripts/pm/dispatch-gates.mjs), the review ran at it, and the probe discriminates — the same instrument returned a different value on the control, so this is a reading and not a tautology. ⛔ A tier check that could only ever have returned "fable" would certify nothing.

What the re-review established

  • Live head 701af469a6; git diff from c5908bf659 excluding .changeset/ is empty ⇒ every prior code/test measurement carries to the new head, including the reviewer's own 104/104 run. That is why no re-run was owed.
  • Both premises re-verified at the new head, not inherited: the shipped provider's catch { return undefined } absorb, and HttpStatusErrorCodeMap: Record<number, StandardErrorCode> (status→code).
  • Accurate, not merely cautious — the operator-facing sentence ("shipped single-kernel deployments see no behaviour change") is right, and the covered population carries the correct qualifier that excludes own-providers which absorb.
  • No overcorrection — nothing the code delivers is disclaimed.

Two judgements I am recording because they cut against holding

  1. Body-side items do not block. The PR body still carries the "wired the way rest-api-plugin.ts wires it" phrasing. The changeset compiles into release notes; the body does not, it already carries the [finding] the shipped objectQLProvider in rest-api-plugin.ts absorbs before the transport sees it — the #13476 repair does not reach the single-kernel wiring #13904 caveat inline, and both defects are recorded twice on this PR. A one-line body edit is recommended, ⛔ not a merge precondition.
  2. ⛔ Do not wait for the maintainer's clause-② class answer, and ⛔ do not wait for [finding] the shipped objectQLProvider in rest-api-plugin.ts absorbs before the transport sees it — the #13476 repair does not reach the single-kernel wiring #13904. The prior review held for the fourth-class taxonomy question; the follow-up disagrees with reasons I accept: the direction-control already exists ([finding] a permission-store read failure resolves as an AUTHENTICATED caller holding ZERO capabilities — the package door answers 403 FORBIDDEN, byte-identical to a genuine capability denial #13279's 2026-08-30 ruling), and [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476 was triaged as coverage of that ruled class. The taxonomy answer matters for future gating, not for this merge. Holding a fully-reviewed PR on a question its own direction has already been ruled on is a hold with no exit predicate.

Proceeding to ready + arm. ⚠️ CI is being re-read after the draft→ready flip, not carried over — that flip spawns a fresh Governed Surface Queue Guard, and a pre-flip green does not speak for it.


Generated by Claude Code

@os-steve
os-steve added this pull request to the merge queue Sep 1, 2026
Merged via the queue into main with commit 836a29c Sep 1, 2026
38 checks passed
@os-steve
os-steve deleted the claude/issue-13476-unresolvable-engine-403 branch September 1, 2026 01:02
zhuangjianguo pushed a commit that referenced this pull request Sep 1, 2026
…n merge

Discharges the `os-regen` deferral recorded by the preceding merge commit.

Main's side of the page carried no prose or count change this time — its whole
delta was line anchors moved by #13910 in `packages/rest`. So the gate's own
repair re-derives them: 10 anchors rewritten, every one a `rest-server.ts`
shift. No census row added, deleted or re-worded.

  check-system-context-census: OK - 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
zhuangjianguo pushed a commit that referenced this pull request Sep 1, 2026
The merge of origin/main routed content/docs/permissions/system-context.mdx
through the os-regen driver, which exits 0 without text-merging and leaves
git's pre-filled OURS side in place. That silently dropped the 16 anchor
re-points main had landed (#13829, #13934, #13910, #13857) while keeping this
branch's single re-point.

This commit takes main's side of the page and re-derives every anchor from the
merged tree with `pnpm gen:system-context-census`, which re-pointed row 21's
metadata-protocol/src/protocol.ts anchor to 1736. Prose is byte-identical on
both sides once line numbers are normalised, so nothing but line numbers moved.
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…ops draining /ready (objectstack-ai#13829)

* fix(objectql,service-datasource): give the driver registry an eviction door, so a deleted datasource stops draining /ready (objectstack-ai#13578)

The ObjectQL driver registry had a `registerDriver` door and no counterpart, so
nothing could ever leave it. `DELETE /api/v1/datasources/:name` emptied the admin
door while `GET /api/v1/ready` kept naming the deleted datasource's driver — the
probe reports whatever `checkDriversHealth()` finds in that registry — leaving a
process restart on every replica as the only recovery.

`IObjectQLEngine` gains `unregisterDriver(name)`. The registry owns the invariant
rather than each caller, because removal moves three pieces of private engine
state that a caller can reach none of: the `drivers` map, the `defaultDriver`
NAME (a stale one answers with a driver that is gone), and the datasource def,
which has no removal door of its own.

Wired into the three lifecycle paths that already funnel through teardown:
datasource delete / pool teardown, failed-start rollback, and engine destroy.
Eviction is per-replica, symmetric with how registration already works.

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

* test(service-datasource): take the ConnectionEngineLike roster pin from seven members to eight

`unregisterDriver` widens the seam the datasource connection service drives the
engine through, and the roster pin exists so that widening is a decision written
down rather than a side effect of editing the type. Restated deliberately, with
a return-type pin: the eviction door answers `boolean` so an idempotent caller
can tell a removal from a no-op.

Part of objectstack-ai#13578

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

* docs(permissions): re-anchor the system-context census after the engine.ts insertion

Pure line rot: `unregisterDriver` lands above every cited elevation-read site in
packages/objectql/src/engine.ts, shifting all 11 anchors by the method's length.
Rewritten by the gate's own `--fix`; no census row's meaning changes.

Part of objectstack-ai#13578

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

* docs(permissions): re-anchor the system-context census after merging main

The page is routed to `merge=os-regen` in .gitattributes, so the merge took one
side whole with zero conflict markers — here, this branch's side — silently
dropping main's own re-anchor after objectstack-ai#13843 grew packages/rest/src/rest-server.ts.
Regenerated with the gate's own --fix, which recomputes every anchor from the
actual source positions.

Verified rather than assumed: 65 table rows in, 65 out; with line numbers
normalised the page is identical to main's apart from the objectql/src/engine.ts
anchors; and every one of those shifts matches this branch's two insertion hunks
exactly (+66 for sites between them, +75 for sites after the destroy() change).
No row deleted, none reworded.

Part of objectstack-ai#13578

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

* chore(changeset): grade @objectstack/spec as minor with a BREAKING banner

`IObjectQLEngine.unregisterDriver` is a REQUIRED member on a published
interface: additive for consumers, compile-breaking for any third-party
implementer. Regraded from patch to minor to match this contract's own
precedent — the three prior changes to it all took minor, including one that
added five members that were ALL optional and so broke nobody by construction.
A required member grading below that is inconsistent.

Banner shape verified against objectstack-ai#13870 rather than assumed: that changeset does
pair a `minor` bump with a `**BREAKING**` line citing the launch-window
convention.

A strict-semver reading would say `major`; that reading is recorded as an open
question for the maintainer in the PR body rather than acted on here, since
uniform in-repo precedent is the operative convention and overruling it is not
this PR's call.

Part of objectstack-ai#13578

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

* chore(changeset): answer the ADR-0087 question the BREAKING banner now raises

Adding the banner turned `check-adr-0087-registration` red, correctly: a
declared-breaking changeset must state in writing whether the change needs a
ledger entry. Answered with the category built for this exact shape — a
published runtime TypeScript interface with no metadata surface behind it, so
`objectstack migrate meta` has nothing to rewrite and the compiler is the
channel that reaches consumers.

Part of objectstack-ai#13578

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

* docs(permissions): regenerate the isSystem census after merging main

Discharges the `os-regen` merge-driver deferral recorded for
`content/docs/permissions/system-context.mdx` by the preceding merge commit.

The driver does not text-merge this page, and it kept the branch side whole.
That side is correct for this branch's `engine.ts` insertions but stale for
everything main landed since the branch was cut, and it silently dropped
main's own contribution to the page: an 18-line block explaining what the
enforced-declarations row counts, and that row's value (21 -> 22).

So the page is rebased on main's version and re-anchored by the gate's own
repair (`node scripts/check-system-context-census.mjs --fix`), which rewrote
11 anchors, all of them `objectql/src/engine.ts` line shifts caused by this
branch. No census row was added, deleted or re-worded; the totals are
unchanged from main's own green run.

  check-system-context-census: OK - 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

* docs(permissions): re-anchor the isSystem census after the second main merge

Discharges the `os-regen` deferral recorded by the preceding merge commit.

Main's side of the page carried no prose or count change this time — its whole
delta was line anchors moved by objectstack-ai#13910 in `packages/rest`. So the gate's own
repair re-derives them: 10 anchors rewritten, every one a `rest-server.ts`
shift. No census row added, deleted or re-worded.

  check-system-context-census: OK - 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

---------

Co-authored-by: zhuangjianguo <zhuangjianguo@steedos.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…, so `rollbackToPackageCommit` stops planning off the weekday name (objectstack-ai#14036)

* fix(metadata-protocol): order the ADR-0067 commit timeline by instant, not by the weekday name

`created_at` is an engine-injected audit column: not in `datetimeFields`, and
`SqlDriver#formatOutput` repairs it only inside `if (this.isSqlite)`. The live
SQL dialects therefore hand it out of the record read door as a JS `Date` while
the SQLite family hands out canonical ISO-Z text.

Both ADR-0067 commit-timeline consumers compared `String(created_at)`, and
`String(aDate)` is `"Sun Aug 30 2026 18:19:25 GMT+0800 (China Standard Time)"` —
the LEADING token is the weekday NAME, so lexicographic order over those strings
is `Fri < Mon < Sat < Sun < Thu < Tue < Wed`. Unrelated to chronology, and
stable across the whole set, so it is wrong on every run and wrong the same way.

- `listCommits` returned the timeline in weekday-name order while claiming
  newest-first; its own comment stated the assumption ("sort by the ISO
  timestamp") and it was false on the production default driver.
- `rollbackToPackageCommit` both consumed that ordering and re-derived the same
  comparison itself, so neither site could correct the other: it reverted
  `apply` commits OLDER than the target and skipped the newer ones it exists to
  undo.

Both sites now compare canonical absolute instants through `compareAuditInstants`,
a sibling of the `canonicalVersionInstant` helper objectstack-ai#13382 landed one seam over in
this same file. The canonicalisation is reused; the ordering is new, because
`versionTokensAgree` answers equality between client-supplied version tokens and
an ordering question needs `<`/`>`. When either side does not denote an instant
the two are compared verbatim exactly as before, so only instant-bearing pairs
change verdict.

The pin drives a hand-made `Date` — `@objectstack/metadata-protocol` has no
driver dependency and must not grow one — over four consecutive days, the
smallest fixture for which no timezone alignment can make the old weekday
comparison agree with chronology.

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

* chore(gates): re-point the isSystem census anchor and register the new engine double

Both are the gates' own sanctioned repairs for the line/ledger movement the fix
caused, applied with their own tooling and inspected:

- `check-system-context-census --fix` RE-POINTED row 21's anchor
  `metadata-protocol/src/protocol.ts:1664` -> `:1736`, the 72-line shift the new
  `compareAuditInstants` helper block introduced above it. No row was deleted and
  no needle changed; the gate then reports 109 elevation read sites, 145 anchors
  resolving.
- `check-engine-double-contract --write` ADDED one row recording that the new pin
  file pins 1 `findOne` double ("1 added or grown, 0 lost"). The shrink-only
  baseline is untouched.

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

* chore(docs): re-derive the isSystem census after merging origin/main

The merge of origin/main routed content/docs/permissions/system-context.mdx
through the os-regen driver, which exits 0 without text-merging and leaves
git's pre-filled OURS side in place. That silently dropped the 16 anchor
re-points main had landed (objectstack-ai#13829, objectstack-ai#13934, objectstack-ai#13910, objectstack-ai#13857) while keeping this
branch's single re-point.

This commit takes main's side of the page and re-derives every anchor from the
merged tree with `pnpm gen:system-context-census`, which re-pointed row 21's
metadata-protocol/src/protocol.ts anchor to 1736. Prose is byte-identical on
both sides once line numbers are normalised, so nothing but line numbers moved.

---------

Co-authored-by: Claude <noreply@anthropic.com>
os-justin pushed a commit that referenced this pull request Sep 1, 2026
…h-window convention

The at-tier contract review's one finding: the #13476 family's changeset
(engine-unresolvable-fails-loud.md, PR #13910) ships the identical class
of public-door behaviour change as minor and names the convention; this
change goes strictly further (one refused -> served row), so patch was an
undeclared divergence. Declaration and convention now named in the body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YPiiDdw96RGS25WLctCQP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

3 participants