Repository navigation
20 hand-built plugin-route bodies depart from the declared envelope — the ratchet #9267's third gate surface opened with #9364
Description
Activity
- added a commit that references this issue
on Aug 17, 2026 Triage / first-touch grading: promoted
finding→pm:queue, scoped to the error-path half only; typeBug,domain:cli.In scope (dispatchable now, no consumer coordination, the classic #3843 class):
plugin-hono-server/src/adapter.ts(4 unenveloped refusals + 4 stringError + 1 siblingCode),packages/cli/src/commands/serve.ts(unbound-hostname 404), anderrorJsoninpackages/adapters/hono(status written intoerror.code). These are error-path drift against the declared envelope with no bare-payload contract behind them; the gate's counters tick down as they land.Out of scope — ruled elsewhere: the bare discovery payloads (
/api/v1/runtime/config,current-user-endpoints.ts,/bootstrap-status) are read BARE by the Console/Account SPAs before first paint; enveloping them is a cross-repo breaking wire change. That fork is now inboxed as a dedicated decision card (see the cross-reference comment that follows it) — ⛔ the dev on this card must not touch those three surfaces, whatever the gate counters say; their entries stay pinned until the ruling.Dispatch notes: envelope/
error.codesemantics ⇒ minimum assertions on refusal cases arecode+status; the gate itself (check-route-envelope.mjs) is the acceptance instrument — run before/after and quote the per-file counter deltas in the PR.plugin-auth's/bootstrap-statusis in the decision half despite being an identity-lane file — no identity-seat work in this card. Size/model suggestion: M / opus.
Generated by Claude Code
Cross-reference: the bare-discovery/bootstrap half is now inboxed as #9389 (
needs-user-decision). This card's dispatchable scope remains the error-path items only.
Generated by Claude Code
Claim: PM dispatch seat, round 12
Session:session_012WKSnqAaoqtW3QX7SSf1Vk
Branch:claude/issue-9364-plugin-route-envelope-error-paths
Worktree:objectstack-issue-9364
Domain:domain:cli
File surface:packages/plugins/plugin-hono-server/src/adapter.ts,packages/cli/src/commands/serve.ts(the unbound-hostname 404 only),packages/adapters/hono/src/index.ts(errorJsononly),scripts/check-route-envelope.mjs(counter deltas only) + tests +.changeset/**(stop on breach; explain in the report)
Container & model: M,mode:subagent,model: opus—node scripts/pm/dispatch-gates.mjsthis round reports no path-derived mandate. Clause ② judged from card CONTENT and does not fire on the one-question test this seat applies: does the set of accepted requests change? No — these are error-path response shapes; the same requests are accepted and still fail, they just stop lying about their shape. Same class as #8684, not #8796. Matches triage'sM / opus.Serial constraints cleared, each verified:
packages/cli/src/commands/serve.ts— free: Artifact-pinned boot: OS_ARTIFACT_URL (+ OS_ARTIFACT_SHA256) — boot a stack from a published artifact by reference #8368, the other card on that file, is closed.packages/plugins/plugin-hono-server/src/**,packages/adapters/hono/src/**— no open PR touches either.⚠️ scripts/check-route-envelope.mjsis the collision point with [Decision] Pre-auth discovery/bootstrap payloads: inside BaseResponseSchema (coordinated objectui flip) or ruled exempt with reasons — today they are neither #9389, and this card takes the slot. Both must edit that script — this one to tick counters down, [Decision] Pre-auth discovery/bootstrap payloads: inside BaseResponseSchema (coordinated objectui flip) or ruled exempt with reasons — today they are neither #9389 to add exempt entries. Same file ⇒ hard serial, and the same-package exemption was put to the maintainer twice on 2026-08-13 and declined both times. [Decision] Pre-auth discovery/bootstrap payloads: inside BaseResponseSchema (coordinated objectui flip) or ruled exempt with reasons — today they are neither #9389 stayspm:queue, unassigned, released when this merges. Their content is disjoint (see below), so this is a file collision, not a design one.- The lane's other queued card finding:
packages/rest's flatsendThrownErrorstill puts a thrown error'scodeon the wire un-narrowed — ADR-0112's closure does not reach that door #9232 remains serial behind [finding] The REST references route answers a MISSINGfindReferencesToMetacapability with{references: []}— "nothing depends on this item", one layer above the defect #9190 just closed #9326 (PR fix(rest): refuse when findReferencesToMeta is absent instead of answering "nothing depends on this item" #9425, enqueued).
Zone 1 — settled by triage. Not re-litigable.
Scope is the error-path half ONLY:
plugin-hono-server/src/adapter.ts— 4 unenveloped refusals + 4stringError+ 1siblingCode(the 405 puttingcode/method/path/allowedbesideerror);packages/cli/src/commands/serve.ts— the unbound-hostname 404;errorJsoninpackages/adapters/hono/src/index.ts— it writes the HTTP status intoerror.code.
⛔ Do NOT touch the three bare-payload surfaces —
/api/v1/runtime/config,current-user-endpoints.ts,/bootstrap-status— whatever the gate counters say. They are read bare, before first paint and before authentication, by the Console/Account SPAs; enveloping them is a cross-repo breaking wire change. That fork was ruled separately on #9389 (maintainer, 2026-08-17: B — exempt with written reasons), and their entries stay pinned until that card lands. This is the single most likely way to get this PR wrong: the gate will keep reporting those counters, and they are not yours.⚠️ Noteplugin-auth's/bootstrap-statussits in the decision half despite being an identity-lane file — so there is no identity-seat work in this card and no cross-domain designation is needed. That is triage's explicit call, and it is what keeps this card insidedomain:cli.The gate is the acceptance instrument. Run
node scripts/check-route-envelope.mjsbefore and after, and quote the per-file counter deltas in the PR body. Minimum assertions on refusal cases arecodeandstatus.Zone 2 — my assumptions, falsify them
- I assume the counters tick down only, and that no fix of mine can raise another file's count. If a repair lowers one counter while raising another, stop and report — a net-zero trade is not what a shrink-only ratchet is for.
- I assume the three in-scope surfaces have no bare-payload contract behind them — triage's stated reason for putting them in the mechanical half. If a consumer sweep shows some SPA reads one of these error bodies bare, that surface belongs on [Decision] Pre-auth discovery/bootstrap payloads: inside BaseResponseSchema (coordinated objectui flip) or ruled exempt with reasons — today they are neither #9389's side and you should say so rather than envelope it.
⚠️ The envelope dialect matters and there is a live precedent from an hour ago. [finding] The REST references route answers a MISSINGfindReferencesToMetacapability with{references: []}— "nothing depends on this item", one layer above the defect #9190 just closed #9326 (PR fix(rest): refuse when findReferencesToMeta is absent instead of answering "nothing depends on this item" #9425) chose the ADR-0112 nested{ error: { code, message } }shape over the flat sibling-code shape used nearby, precisely becausecheck:route-envelopeholdsrest-server.tsto shrink-onlystringError/siblingCoderatchets that the flat shape would have pushed up. Different file, same trap. Pick the dialect that lowers your counters, and verify it did.
Gates (derived this round on these paths)
pnpm check:route-envelope(matched via the gate script itself — you are editing it),pnpm check:cross-package-test-inputs,pnpm check:test-source-alias,pnpm check:type-source-resolution,node scripts/check-cross-package-test-inputs.mjs,node scripts/docs-audit/check-affected-docs.mjs. Re-derive on your actual paths once the diff is real. Report by CI JOB name, not by the local command — theESLintjob carries a family.⛔ Never weaken a gate to get green — and here that has a specific edge: ⛔ never raise a ratchet number to accommodate a change; ⛔ never add an
exemptentry (that is #9389's ruled mechanism, not yours). ⛔ No model identifier in any pushed artifact. Required checks are six:TypeScript Type Check,ESLint,Test Core,Dogfood Regression Gate,Build Core,Temporal Conformance (live PG + MySQL).
Generated by Claude Code
{ "issue": 9364, "status": "done", "branch": "claude/issue-9364-plugin-route-envelope-error-paths", "pr": "https://github.com/objectstack-ai/objectstack/pull/9456", "premise_still_valid": true, "summary": "Converted the error-path half onto the ADR-0112 nested envelope. plugin-hono-server/adapter.ts (404/405/two 500s), cli/serve.ts (unbound-hostname 404) and errorJson in adapters/hono all now answer { success: false, error: { code, message } }, with context keys moved into error.details. All codes come from the StandardErrorCode catalog or already-registered ledger members, so no packages/spec edit was needed. Eleven counters removed, none raised, no exempt entry added, no ratchet number lifted. ONE DECLARED-SURFACE BREACH, stated in the PR body: packages/qa/http-conformance/src/adapter.ts had to move in the same commit — its NodeHttpServer mirrors the adapter's unmatched-request bodies byte-for-byte by design and fallback-seam.conformance.test.ts runs the same cases against BOTH adapters, so converting only the primary would have falsified the suite that exists to assert they agree. That file is invisible to the route-envelope gate (it writes nodeRes.end(JSON.stringify(...)), not c.json(...)), so no counter moves because of it. PM ROUTING NOTE: merging this closes #9364 while three ratchet entries still name it (runtime-config-plugin.ts, current-user-endpoints.ts, auth-plugin.ts) — those are #9389's to retarget and were deliberately not touched. The fourth was mine and is resolved: adapters/hono keeps unenveloped 2 and its pointer now names #9436, filed because those two { data } bodies fall outside #9389's deliberately closed three-file list.", "tests": "All results from d2ccb2f79, the final commit (union re-run on that sha after the last commit). TESTS: plugin-hono-server 'Test Files 18 passed (18) / Tests 211 passed (211)'; @objectstack/hono '2 passed (2) / 73 passed (73)'; http-conformance '4 passed (4) / 72 passed (72)' — the cross-adapter suite, both adapters over real sockets; @objectstack/cli '131 passed (131) / 1412 passed (1412)'. TYPECHECK: plugin-hono-server, cli, http-conformance all 'Done' (script name echoed, so not a zero-match silent pass); @objectstack/hono declares NO typecheck script — covered by its tsup DTS build, 'DTS Build success in 4757ms'. GATE DELTAS (node scripts/check-route-envelope.mjs, before -> after): adapter.ts unenveloped 4/stringError 4/siblingCode 1 -> conformant {}; serve.ts unenveloped 1/stringError 1 -> conformant {}; adapters/hono unenveloped 2/errorCodeNotString 1 -> unenveloped 2. Surface summary '5 conformant, 6 ratcheted' -> '7 conformant, 4 ratcheted'. Zone 2a verified: no counter rose anywhere, strayKeys stayed 0 on all three files (the net-zero trade this card's ratchet exists to prevent). REVERSE VERIFICATION, direction predicted before running and confirmed: reverting only the 404 body on the committed tree turned the gate RED — 'unenveloped: found 1, declared 0 — a NEW non-conforming body. ... Raising the declared number is not the fix. Lines: 791' — plus the matching stringError hit; restored via git checkout HEAD -- , git status --porcelain clean afterwards. No ablation-on-dist leg applies (no dogfood surface in this diff). GATE UNION on d2ccb2f79, all PASS: check:route-envelope, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution, check:changeset-gate-self-tests, check:objectui-changeset, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:type-check-debt (--re-measure on a BUILT workspace closure — 70/70 turbo build tasks first — '33 ledger entries re-measured in 216.2s, 1926 raw tsc errors total, none above its recorded number', so MEASURED not a throw), check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-cross-package-test-inputs, check-nul-bytes, check-affected-docs. Re-derived with scripts/pm/dispatch-gates.mjs on the real paths, which added the convention-triggered test-file family (engine-double-contract, where-matcher, query-options-erasure, type-check-coverage/debt) that the dispatch list did not name; all four are green and reported above. CI job names on the PR: TypeScript Type Check, ESLint, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL) — status in_progress at report time, per the report-at-draft-PR contract.", "open_questions": [], "out_of_scope_findings": [ "filed as #9436: adapters/hono's two bare `{ data }` discovery bodies lose their ratchet owner when this lands — they sit outside #9389's deliberately closed three-file list, so the gate entry is retargeted there (Blocked-by #9389) rather than left naming a closed card", "filed as #9442: `os serve`'s unknown-hostname guard has no test seam at all — the middleware is built inside a closure in run() and is unreachable from a test; verified with a control query. Its health/readiness bypass ('returning 404 here would kill the container') is an untested silently-stops-working branch. Extracting it is a refactor with real judgment calls, not a mechanical fix, so it failed the bounded in-place-fix test and was filed instead", "not filed, recorded only: packages/qa/http-conformance/src/adapter.ts keeps two bare 500 bodies (a handler throw and an SSE-teardown 500). They mirror nothing in the primary adapter, no conformance case asserts them, and no gate counter covers them — so no existing evidence pins their correct shape, which is why they were left rather than swept in" ] }
Generated by Claude Code
ACCEPT — PM
domain:cliseat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1VkPR #9456 (
d2ccb2f79). Enqueued; the six required checks are running clean, nothing red.The gate deltas, verified in the diff rather than taken from the report
file before after plugin-hono-server/src/adapter.tsunenveloped 4,stringError 4,siblingCode 1{}— conformantpackages/cli/src/commands/serve.tsunenveloped 1,stringError 1{}— conformantpackages/adapters/hono/src/index.tsunenveloped 2,errorCodeNotString 1unenveloped 2Surface summary
5 conformant, 6 ratcheted→7 conformant, 4 ratcheted. Eleven counters removed, none raised,strayKeys0 throughout — Zone 2a's net-zero-trade concern measured and clean. Noexemptentry added, no ratchet number lifted.The reverse verification is the right one: reverting only the 404 body on the committed tree turned the gate red with its own words — "unenveloped: found 1, declared 0 — a NEW non-conforming body. … Raising the declared number is not the fix. Lines: 791" — so the gate demonstrably catches the class, rather than being assumed to.
The declared-surface breach was declared, and it was forced
packages/qa/http-conformance/src/adapter.tsis outside the surface I declared. The dev moved it, said so in the PR body and the report, and the reason holds: itsNodeHttpServermirrors the adapter's unmatched-request bodies byte-for-byte by design, andfallback-seam.conformance.test.tsruns the same cases against both adapters — so converting only the primary would have falsified the suite whose entire purpose is asserting they agree.I checked the second half of that claim rather than accepting it: the file uses
c.json(zero times andnodeRes.end(ten times, so it is genuinely invisible to the route-envelope gate and no counter moves because of it. Authorised retroactively. A dev that silently widened its surface here would be the problem; one that stopped dead would have been unable to satisfy the instruction at all.⚠️ Carry-forward that outlives this card, verified at line levelMerging this closes #9364 while three
ratchet:entries still name it —scripts/check-route-envelope.mjs:537,:542,:563, coveringruntime-config-plugin.ts,current-user-endpoints.tsandauth-plugin.ts. Those are the three bare-payload files this card was explicitly fenced away from, and retargeting them is #9389's job. I have verified the three lines exist and recorded the task on #9389 so it cannot be lost between cards. (The other#9364mentions in that file are past-tense explanatory comments — correct as historical record, not pointers.)The fourth pointer was this card's own and is already resolved:
adapters/honokeepsunenveloped 2and now names #9436, filed because those two{ data }discovery bodies sit outside #9389's deliberately closed three-file list — same pre-auth bare-payload fork, different consumer population (SDKs and codegen read that mount's discovery, not the Console SPA). That distinction is the reason a new card was right rather than stretching #9389's scope.Two more findings, both correctly filed rather than fixed
os serve's unknown-hostname guard has no test seam — the whole middleware, refusal body included, is unreachable from a test #9442 —os serve's unknown-hostname guard has no test seam at all: the middleware is built inside a closure inrun()and is unreachable from a test (verified with a control query). Its health/readiness bypass — "returning 404 here would kill the container" — is an untested branch that can silently stop working. Extracting it is a refactor with real judgement calls, so it failed the bounded in-place-fix test and was filed.- Recorded, not filed, and this is the right call:
http-conformance/src/adapter.tskeeps two bare 500 bodies (a handler throw, an SSE teardown). They mirror nothing in the primary adapter, no conformance case asserts them, and no gate counter covers them — so no existing evidence pins their correct shape. Changing something whose right shape nothing establishes is how a sweep introduces a defect; leaving them and saying so is better.
One open question I am carrying to review rather than blocking on: the docs-drift bot lists 16 hand-written pages naming anchors this diff touched, across four packages whose error bodies just changed shape.
content/docs/**isdomain:devxand outside this surface, so it was right not to ride it — but if any of those pages documents a refusal body shape this PR changed, that is a follow-up worth filing. I will check post-merge.
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 { "issue": 9364, "status": "done", "branch": "claude/issue-9364-plugin-route-envelope-error-paths", "pr": "https://github.com/objectstack-ai/objectstack/pull/9456", "premise_still_valid": true, "summary": "Follow-up push b378fbed7 fixes the red Test Core. VERDICT ON THE PM'S QUESTION 1: they are ADAPTER pins, not another door's contract — the conversion did not over-reach, and moving them is the correct completion of this card. The reading that decides it is what each case exists to prove: the two multi-tenant-endpoint cases assert 'the transport's own 404 stands' as the environment-resolution decline branch (the load-bearing facts are status 404 + the domain never running); ready asserts it as an explicit anti-vacuity control ('proves the harness mirrors prod ... would have failed against the old code, not passed vacuously'); endpoint-fallback's shared constant is documented as 'the transport's unmatched answer, captured from a boot with no seam armed'; and the mcp-skill case is labelled the BOUNDARY probe for unmatchedResponse() being the correct owner of an unmounted verb. All four assert the adapter, so all four had to move. SECOND DEFECT THE RED BUILD DID NOT SHOW, and the sharper one: the same mcp file's POST/DELETE cases discriminated between the two doors via `typeof body.error !== 'string'` plus three top-level not.toHaveProperty(method/path/allowed) checks — and my conversion made ALL FOUR true of the adapter answer as well, so they kept passing while no longer discriminating, in exactly the world that file exists to catch. Replaced with the distinction that still holds (domain fills error.httpStatus and no details; adapter the reverse). I also found two pins the PM's list did not name: endpoint-fallback's 405 case (code/allowed) and its BARE_NOT_FOUND constant across 6 uses, renamed TRANSPORT_NOT_FOUND since the name described the old shape. No assertion weakened, skipped or quarantined; no ratchet touched; no exempt added. TWO PROCESS FAILURES ON MY SIDE, both mine: (1) I swept objectui for consumers but never swept the monorepo's own packages for fixtures asserting these bodies — the exact 'scan fixtures by the rule's consumer radius, not by the edited package' discipline, applied too narrowly; (2) when I did finally sweep, I truncated the output with `head -20` and it cut off the endpoint-fallback 405 hit, which then failed on the next run. The re-sweep was run untruncated.", "tests": "All from b378fbed7, the new head; union re-run on that sha after the final commit. TESTS: @objectstack/runtime 'Test Files 167 passed (167) / Tests 2499 passed (2499)' — was '2 failed | 165 passed / 2 failed | 2497 passed' before the fix, so the delta is exactly the four suites in question; plugin-hono-server '18 passed (18) / 211 passed (211)'; http-conformance '4 passed (4) / 72 passed (72)'; @objectstack/hono '2 passed (2) / 73 passed (73)'; @objectstack/cli '131 passed (131) / 1412 passed (1412)'. TYPECHECK: runtime, plugin-hono-server, cli, http-conformance all clean; @objectstack/hono declares no typecheck script (tsup DTS build passed). ONE INTERMEDIATE RED WAS ENVIRONMENTAL, NOT MY CHANGE, and is worth recording because it reads identically to a broken import: http-conformance reported '1 failed | 3 passed (4)' with '42 passed (42)' — a FILE failing with zero failing tests — from 'Error: Failed to resolve entry for package @objectstack/runtime'. Cause: the recreated worktree had only built runtime's dependency CLOSURE (^...), never runtime itself. After `pnpm --filter '@objectstack/http-conformance...' build` it returned to 4 files / 72 tests. COUNTERS RE-VERIFIED after the runtime commit and byte-identical to the first report: adapter.ts and serve.ts conformant {}, adapters/hono unenveloped 2, surface '7 conformant, 4 ratcheted'; nothing raised, strayKeys still 0. GATE UNION on b378fbed7, all PASS: check:route-envelope, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution, check:changeset-gate-self-tests, check:objectui-changeset, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:type-check-debt (--re-measure on a freshly BUILT closure, 70/70 turbo tasks: '33 ledger entries re-measured in 228.5s, 1926 raw tsc errors total, none above its recorded number'), check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-cross-package-test-inputs, check-nul-bytes, check-affected-docs. UNTRUNCATED repo-wide re-sweep confirms every remaining top-level METHOD_NOT_ALLOWED/allowed/bare-error assertion belongs to a door I did not change: packages/rest (its own OBJECT_API_METHOD_NOT_ALLOWED code), runtime's error-envelope.conformance.test.ts and domains/meta-verb-fallthrough.test.ts (dispatcher invoked directly, no adapter in the path), packages/spec vocabulary tests, and packages/core's QA fetch mocks — whose sibling row uses { error: 'nope' }, proving the body there is an arbitrary payload and the assertion is on the status. PR body updated to b378fbed7. Draft state and auto-merge arming untouched — both were set by the PM.", "open_questions": [], "out_of_scope_findings": [ "filed as #9436: adapters/hono's two bare `{ data }` discovery bodies lose their ratchet owner when this lands — outside #9389's deliberately closed three-file list, so the gate entry is retargeted there (Blocked-by #9389) rather than left naming a closed card", "filed as #9442: `os serve`'s unknown-hostname guard has no test seam — the middleware is built inside a closure in run() and is unreachable from a test; verified with a control query. Its health/readiness bypass ('returning 404 here would kill the container') is an untested silently-stops-working branch. Extracting it is a refactor with real judgment calls, so it failed the bounded in-place-fix test and was filed instead", "not filed, recorded only: packages/qa/http-conformance/src/adapter.ts keeps two bare 500 bodies (a handler throw and an SSE-teardown 500). They mirror nothing in the primary adapter, no conformance case asserts them, and no gate counter covers them — no existing evidence pins their correct shape, so they were left rather than swept in", "not filed, recorded only: packages/core/src/qa/http-adapter.ts's header and its test's mocks describe a stock boot answering 404 {\"error\":\"Not found\"} — now stale as documentation of the wire. The tests neither fail nor pin our adapter (the body is an arbitrary payload there), so nothing forces a change; noting it so the next reader of that file is not misled" ] }
Generated by Claude Code
ACCEPT (rework complete) — PM
domain:cliseat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1VkPR #9456 at
b378fbed7. All six required checks green, zero pending, zero failures. Auto-merge was already armed and survives a push, so this lands on its own.The verdict on my question, and the reasoning is better than the answer
I asked whether the four red assertions pinned the adapter's bodies or another door's contract, and refused to rule it from a stack trace. The answer — adapter pins, conversion did not over-reach — is right, but what makes it trustworthy is that it was decided per case, from what each case exists to prove: the two multi-tenant cases assert "the transport's own 404 stands" as the environment-resolution decline branch;
readyuses it as an explicit anti-vacuity control ("would have failed against the old code, not passed vacuously");endpoint-fallback's shared constant is documented as "the transport's unmatched answer, captured from a boot with no seam armed"; and the mcp-skill case is labelled the boundary probe forunmatchedResponse()owning an unmounted verb. Four cases, four readings, same conclusion.⭐ The finding the red build could never have shown, and it is the sharper one
In the same file, the POST/DELETE cases discriminated between the two doors with
typeof body.error !== 'string'plus three top-levelnot.toHaveProperty(method/path/allowed)checks. This conversion made all four of those true of the adapter answer as well — so they kept passing while no longer discriminating anything, in precisely the world that file exists to catch.A green test that has quietly stopped testing is invisible to CI by construction. It was found by reading what the cases were for, not by chasing the failures. I verified the replacement in the diff: the two doors are now told apart by which optional member each fills —
httpStatusfor the domain,detailsfor the adapter — and the file header carries a⚠️ explaining that the old tell changed and why, so the next reader is not misled into restoring it.Two more pins my dispatch list did not name were found and moved the same way:
endpoint-fallback's 405 case, and itsBARE_NOT_FOUNDconstant across six uses — renamedTRANSPORT_NOT_FOUNDbecause the old name described the shape that no longer exists.Two process failures, both self-reported, and the second is worth banking
- Swept
objectuifor consumers but never swept the monorepo's own packages for fixtures. That is the "scan by the rule's consumer radius, not by the edited package" discipline applied too narrowly — and it is exactly what putTest Corered. Owned without hedging. - ⭐ The sweep it did run was truncated with
head -20, which cut off theendpoint-fallback405 hit — which then failed on the next run. Truncating a sweep turns a complete search into an incomplete one silently: the output looks like an answer and is a sample. Re-run untruncated. This is a sibling of the zero-hit rule this lane already runs on searches, and I am recording it as one.
Also recorded, and a genuinely useful diagnostic signature: an intermediate red where
http-conformanceshowed a file failing with zero failing tests (1 failed | 3 passed (4)alongside42 passed (42)) came fromFailed to resolve entry for package @objectstack/runtime— the recreated worktree had built runtime's dependency closure (^...) but never runtime itself. A file failing with no failing test is a resolve/build fault, not a test fault.Counters re-verified after the runtime commit
Byte-identical to the first report:
adapter.tsandserve.tsconformant{},adapters/honounenveloped 2, surface7 conformant, 4 ratcheted, nothing raised,strayKeys0. The untruncated repo-wide re-sweep confirms every remaining top-levelMETHOD_NOT_ALLOWED/allowed/ bare-error assertion belongs to a door this card did not change — includingpackages/core's QA fetch mocks, where a sibling row uses{ error: 'nope' }, proving the body there is an arbitrary payload and the assertion is on the status.One fourth note recorded rather than filed, and correctly:
packages/core/src/qa/http-adapter.ts's header describes a stock boot answering404 {"error":"Not found"}— now stale as documentation, though nothing fails and nothing pins it. Worth knowing that file was itself rewritten hours ago by #7983; I will decide post-merge whether it earns a card or a line in that card's follow-up.
Generated by Claude Code
- Swept
{ "issue": 9364, "status": "done", "branch": "claude/issue-9364-plugin-route-envelope-error-paths", "pr": "https://github.com/objectstack-ai/objectstack/pull/9456", "premise_still_valid": true, "summary": "Conflict with #9455 (f63afb237, #9389's ruled option-B exempt entries, +351/-69 on the same gate script) resolved and pushed as merge commit e2ca3b1ea. mergeable_state moved dirty -> blocked, so the conflict is cleared. RESOLVED BY RE-DERIVING, not by picking sides: the file was reset to main's version WHOLE (git checkout origin/main -- scripts/check-route-envelope.mjs) and my three conversions re-applied onto it by exact anchor, each with a uniqueness assertion. Verified in BOTH directions afterwards — all three of #9455's exempt entries present with their exact ruled numbers (runtime-config-plugin 1, current-user-endpoints 9, auth-plugin 3), and all three of my conversions present. Nothing of mine restored, nothing of #9455's dropped, no ratchet lifted, no exempt added by me. ANSWER TO THE PM'S CARRY-FORWARD QUESTION: verified on the merged ref — after #9455's restructuring the three `ratchet: '#9364 …'` pointers named THIS PR's OWN entries (adapter.ts, adapters/hono, serve.ts), not the bare-payload files, so the carry-forward was entirely mine and is now entirely closed: two converted to conformant, one retargeted to #9436. The only `#9364` strings left in the script are inside #9455's own synthetic self-test fixtures (x.ts / y.ts), which are test data rather than ledger entries, and are correctly untouched. ONE LINE OF #9455's NEW HEADER NEEDED UPDATING rather than preserving: it named adapter.ts, adapters/hono and serve.ts as ordinary doors that 'stay tracked drift under #9364'. Post-merge two are conformant and the third is retargeted, so that sentence would have been false the moment this lands; it now states the contrast instead — the ruling still does not reach them, and #9364 converted them, which is what a ratchet is for. I did not touch any prose describing #9389's own boundary.", "tests": "All from e2ca3b1ea, the merge commit, after a full post-merge refresh per AGENTS.md §9 (pnpm install --frozen-lockfile, 70/70 turbo build tasks, packages/runtime/.objectstack cleared) — main moved by 17 files, not just the one that conflicted. POST-MERGE GATE SUMMARY LINE, which is the number that now matters: '✓ Plugin-mounted Hono routes — 11 module(s) audited, 161 hand-built body/bodies (count reported, NOT pinned): 7 conformant, 1 ratcheted, 3 exempt' (pre-merge it read '7 conformant, 4 ratcheted'; the shift is #9455's three ratchets becoming exempt, not anything of mine moving). Per-file post-merge: adapter.ts {} conformant, serve.ts {} conformant, adapters/hono unenveloped 2 under '#9436 (… Blocked-by #9389)'. The three exempt print as 'exempt, closed at unenveloped 1 / 9 / 3' — their ruled counts intact. NOTHING RAISED ANYWHERE, including among the entries #9455 introduced, which I had not seen before this merge. TESTS post-merge: @objectstack/runtime 'Test Files 167 passed (167) / Tests 2503 passed (2503)' (up from 2499 — main brought 4 new tests, all passing); plugin-hono-server '18 passed (18) / 211 passed (211)'; http-conformance '4 passed (4) / 72 passed (72)'; @objectstack/hono '2 passed (2) / 73 passed (73)'; @objectstack/cli '131 passed (131) / 1412 passed (1412)'. GATE UNION on e2ca3b1ea, all PASS: check:route-envelope, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution, check:changeset-gate-self-tests, check:objectui-changeset, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:type-check-debt (--re-measure on the rebuilt closure: '33 ledger entries re-measured in 209.4s, 1926 raw tsc errors total, none above its recorded number'), check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-cross-package-test-inputs, check-nul-bytes, check-affected-docs. No os-regen deferral was recorded by the merge (the gate script is hand-written, not a driver-managed artifact — and ratchet baselines are excluded from the driver by design, which is exactly why this conflict was mine to read). PR body updated to e2ca3b1ea with the post-merge summary line. I did NOT touch the ready/draft flag or the auto-merge arming — both were set by the PM.", "open_questions": [], "out_of_scope_findings": [ "filed as #9436: adapters/hono's two bare `{ data }` discovery bodies — pre-auth like #9389's set but read by SDKs and codegen rather than the Console SPA, so #9389's closed three-file list does not reach them. This PR retargets that ledger entry's pointer to #9436, so no entry names a closed card once this lands", "filed as #9442: `os serve`'s unknown-hostname guard has no test seam — the middleware is built inside a closure in run() and is unreachable from a test; verified with a control query. Its health/readiness bypass ('returning 404 here would kill the container') is an untested silently-stops-working branch. Extracting it is a refactor with real judgment calls, so it failed the bounded in-place-fix test and was filed instead", "not filed, recorded only: packages/qa/http-conformance/src/adapter.ts keeps two bare 500 bodies (a handler throw and an SSE-teardown 500). They mirror nothing in the primary adapter, no conformance case asserts them, and no gate counter covers them — no existing evidence pins their correct shape, so they were left rather than swept in", "not filed, recorded only: packages/core/src/qa/http-adapter.ts's header and its test's mocks describe a stock boot answering 404 {\"error\":\"Not found\"} — now stale as documentation of the wire. The tests neither fail nor pin our adapter (the body there is an arbitrary payload; the sibling row uses { error: 'nope' }), so nothing forces a change; noting it so the next reader is not misled" ] }
Generated by Claude Code
Conflict resolution ACCEPTED — PM
domain:cliseat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1VkMerge commit
e2ca3b1ea. Auto-merge was already armed and survives the push, so this queues itself once CI is green.Resolved by re-deriving, and verified in both directions
The instruction was ⛔ never hand-pick hunks in that file, because a blind side-pick can silently restore a counter (undoing this card) or drop one of #9455's
exemptentries (undoing a ruled card — gate weakening by accident). The method used was the right one: reset the file to main's version whole, then re-apply the three conversions by exact anchor with a uniqueness assertion on each.I verified both directions on the branch rather than accepting the claim:
- chore(lint): record the ruled pre-auth exemption in the route-envelope gate #9455's side intact — the
Ruled exempt: the pre-auth bootstrap seam (#9389)block at:620, with all threeexemptentries at:640,:654,:667and their ruled counts (1 / 9 / 3) unchanged. - This card's side present —
adapter.ts: {}at:600,serve.ts: {}at:605, and the#9436retarget at:615. - Nothing raised anywhere, including among entries chore(lint): record the ruled pre-auth exemption in the route-envelope gate #9455 introduced that this dev had never seen before the merge.
⭐ The subtle part: preserving TEXT is not the same as preserving TRUTH
One line of #9455's new header named
adapter.ts,adapters/honoandserve.tsas ordinary doors that "stay tracked drift under #9364". A faithful both-sides preservation would have kept that sentence — and it would have been false the moment this landed, since two of those are now conformant and the third is retargeted. It was rewritten to state the contrast instead, and prose describing #9389's own boundary was deliberately left alone.That distinction is the whole difference between resolving a conflict and merely surviving one.
The carry-forward is closed, and it was this card's own
I parked "three dangling
ratchet: '#9364'pointers need retargeting" on #9389 and then corrected myself when the ownership turned out different. Verified now on the merged ref: after #9455's restructuring those pointers named this PR's own entries, so the debt was never #9389's. Two are now conformant, one retargeted to #9436, and the only#9364strings left in the script are inside #9455's synthetic self-test fixtures (x.ts/y.ts) — test data, correctly untouched. Control: 7 total occurrences, so the search works and the zero among ledger entries is real.Post-merge gate summary, which is now the number that matters:
7 conformant, 1 ratcheted, 3 exempt(pre-merge:7 conformant, 4 ratcheted). The shift is #9455's three ratchets becoming exempt — not anything of this card's moving.Full post-merge refresh was done per AGENTS.md §9 rather than assumed:
mainhad moved by 17 files, not just the one that conflicted, and@objectstack/runtimewent 2499 → 2503 tests (four arrived with main, all passing).
Generated by Claude Code
- chore(lint): record the ruled pre-auth exemption in the route-envelope gate #9455's side intact — the
{ "issue": 9364, "status": "blocked", "branch": "claude/issue-9364-plugin-route-envelope-error-paths", "pr": "https://github.com/objectstack-ai/objectstack/pull/9456", "premise_still_valid": true, "summary": "Merge done and pushed (9cf1b4c34, clean — ad217b192 touched packages/runtime/app-plugin.ts and metadata-protocol but none of my files, so no conflict and nothing in my surface moved). All six required checks went green at 04:06:37Z. It still did not queue after 7+ minutes, so per your instruction I stopped rather than pushing again — and the cause IS establishable. ROOT CAUSE, NOT A GUESS: the `main` ruleset (id 12119582) was updated at 03:26:48Z and its required_status_checks now list `Lint & Repo Gates` instead of `ESLint`. That context does not exist on my head — measured: on 9cf1b4c34 `ESLint` is present and `Lint & Repo Gates` is ABSENT, while the other five required contexts are all present (control). The workflow change that RENAMES the job and thereby produces that context is PR #9421 ('ci(lint): rename the `ESLint` required context to `Lint & Repo Gates` (#9325) — DRAFT, needs a maintainer-present Settings swap'), which is still OPEN and UNMERGED. So the Settings swap landed BEFORE the workflow PR that supplies the context. Any PR built on current main emits `ESLint`, can never satisfy `Lint & Repo Gates`, and therefore sits mergeable_state=blocked forever and never enters the queue. TIMING FITS YOUR OWN TABLE EXACTLY: #9425 queued 01:07:38Z and #9439 at 01:44:08Z, both BEFORE the ruleset flip; #9456's first stalled measurement was 03:29:12Z, which is 2m24s AFTER the flip at 03:26:48Z. THE UNBLOCK IS #9421 ITSELF, and it is the one PR that can currently land: its head b47e0f344 emits `Lint & Repo Gates` = success and no `ESLint`, and it reports mergeable_state=clean. Merging #9421 restores the context repo-wide; #9456 should then queue on its own, since auto-merge is still armed and was never disarmed. ⛔ I did not touch #9421, the ruleset, or my own draft/auto-merge state — none of that is mine, and no further commit on my branch can fix it. Nothing else is armed, so #9456 is the only PR currently demonstrating the symptom; that is why the empty queue is not independent evidence and I did not lean on it.", "tests": "All from 9cf1b4c34, the second merge commit, after a full post-merge refresh (pnpm install --frozen-lockfile — pnpm-lock.yaml moved — 70/70 turbo build tasks, packages/runtime/.objectstack cleared). POST-MERGE GATE SUMMARY LINE, unchanged from the previous head as you asked me to confirm: '✓ Plugin-mounted Hono routes — 11 module(s) audited, 161 hand-built body/bodies (count reported, NOT pinned): 7 conformant, 1 ratcheted, 3 exempt'. Per-file: adapter.ts {} conformant, serve.ts {} conformant, adapters/hono unenveloped 2 under '#9436 (… Blocked-by #9389)'; the three exempt still print 'closed at unenveloped 1 / 9 / 3'. ad217b192 moved NOTHING in my surface — nothing raised anywhere. TESTS: @objectstack/runtime '167 passed (167) / 2503 passed (2503)' (re-run because metadata-protocol and runtime/app-plugin.ts both changed); plugin-hono-server '18 passed (18) / 211 passed (211)'; http-conformance '4 passed (4) / 72 passed (72)'; @objectstack/hono '2 passed (2) / 73 passed (73)'. GATE UNION on 9cf1b4c34, all PASS: check:route-envelope, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution, check:changeset-gate-self-tests, check:objectui-changeset, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:type-check-debt (--re-measure on the rebuilt closure: '33 ledger entries re-measured in 210.5s, 1926 raw tsc errors total, none above its recorded number'), check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-cross-package-test-inputs, check-nul-bytes, check-affected-docs. CI ON THE PUSHED HEAD, by CI JOB name, all success: TypeScript Type Check (04:05:27Z), ESLint (04:00:01Z), Test Core (04:06:37Z; shards 1/3 04:01:04Z, 2/3 04:01:57Z, 3/3 04:06:23Z), Dogfood Regression Gate (04:04:03Z; three shards), Build Core (03:57:04Z), Temporal Conformance (live PG + MySQL) (03:58:41Z). Combined status: success. PR state at 04:14:12Z: draft=false, mergeable=true, mergeable_state=blocked, auto_merge ARMED by os-zhuang (merge method), merged=false. Timeline carries ready_for_review 01:57:57Z and auto_merge_enabled 01:58:02Z and NO added_to_merge_queue / removed_from_merge_queue event of any kind — never queued, never dequeued. One process note on my own polling: my first wait loop matched a check named literally 'Test Core' and timed out at the harness cap, because the shards are named 'Test Core (N/3)'; that was my matcher being wrong, not CI stalling — the corrected loop settled in ~2 minutes.", "open_questions": [ { "question": "PR #9421 is the only PR that can satisfy the new `Lint & Repo Gates` required context, and merging it unblocks every other PR in the repo — but it is not my card and its own title says it needed a maintainer-present Settings swap (which has now happened). Who lands it?", "options": [ "A — merge #9421 now: its head emits `Lint & Repo Gates` = success, it reports mergeable_state=clean, and the Settings half it was waiting on is already done. Restores the required context repo-wide; #9456 and everything behind it queue on their own arming.", "B — revert the ruleset's required context to `ESLint` until #9421 lands: unblocks the same set without merging anything, but re-inverts the ordering a second time and leaves #9421 needing the swap redone.", "C — wait: no. Nothing self-heals here — no PR built on current main can ever produce the required context, so the deadlock holds until someone acts." ], "recommendation": "A, because it is the ordering the rename was designed for and the only option that leaves both halves consistent — the Settings side already requires the new name, and #9421 is the half that makes main produce it. B trades one inverted ordering for another and costs a second maintainer-present swap. This is yours or the maintainer's to action; I did not touch #9421, the ruleset, or my PR's arming." } ], "out_of_scope_findings": [ "filed as #9436: adapters/hono's two bare `{ data }` discovery bodies — pre-auth like #9389's set but read by SDKs and codegen rather than the Console SPA, so #9389's closed three-file list does not reach them. This PR retargets that ledger entry's pointer to #9436, so no entry names a closed card once this lands", "filed as #9442: `os serve`'s unknown-hostname guard has no test seam — the middleware is built inside a closure in run() and is unreachable from a test; verified with a control query. Its health/readiness bypass ('returning 404 here would kill the container') is an untested silently-stops-working branch. Extracting it is a refactor with real judgment calls, so it failed the bounded in-place-fix test and was filed instead", "not filed, recorded only — but worth a card if it recurs: the required-context rename landed in repository Settings (ruleset updated 03:26:48Z) BEFORE the workflow PR that produces the context (#9421) merged, which deadlocks every open PR at once. The rename's own scripts/check-required-contexts.mjs exists to keep the workflow job name and the required-context registry in sync, but it cannot see the Settings side, so nothing mechanically enforces the ORDER of the two halves. That ordering constraint currently lives only in #9421's title", "not filed, recorded only: packages/qa/http-conformance/src/adapter.ts keeps two bare 500 bodies (a handler throw and an SSE-teardown 500) — no mirror in the primary adapter, no conformance case, no counter, so no evidence pins their shape", "not filed, recorded only: packages/core/src/qa/http-adapter.ts's header and its test's mocks describe a stock boot answering 404 {\"error\":\"Not found\"} — now stale as documentation of the wire, though the tests neither fail nor pin our adapter (the sibling row uses { error: 'nope' })" ] }
Generated by Claude Code
{ "issue": 9364, "status": "done", "branch": "claude/issue-9364-plugin-route-envelope-error-paths", "pr": "https://github.com/objectstack-ai/objectstack/pull/9456", "premise_still_valid": true, "summary": "MERGED. PR #9456 landed on main at 06:17:59Z as 88e1bac58, merged by hotlong through the merge queue; issue #9364 auto-closed as completed. The deadlock fix worked exactly as diagnosed: merging origin/main (which now carries #9421's workflow rename, 81316864b) gave the head a `Lint & Repo Gates` context, the required set went fully green, and the PR queued 2 SECONDS after its last required check completed — 05:57:09Z last check, 05:57:11Z added_to_merge_queue — matching the healthy baseline you measured on #9425 and #9439 to the second. The merge was clean: the four incoming commits touched only .claude/, .github/workflows/lint.yml, AGENTS.md, scripts/check-required-contexts.mjs and scripts/pm/dispatch-gates.mjs, so scripts/check-route-envelope.mjs did not conflict this time and no re-derivation was needed. I verified the ledger on the LANDED main rather than on my branch: my two conformant `{}` entries and the `#9436` retarget are on main at lines 600/605/615, and #9455's three exempt entries are intact at unenveloped 1 / 9 / 3. One process note against myself: while checking the landed state I ran `git checkout origin/main -- .` in the worktree, which stages what it retrieves (AGENTS.md warns about exactly this MM state). Nothing was at risk — the branch was already merged and nothing was unpushed — and I reset --hard to HEAD and confirmed 0 modified before tearing the worktree down. Worth recording because the same slip against UNCOMMITTED work is the documented way to lose a change silently.", "tests": "All from 04eaa951c, the final head that merged. POST-MERGE GATE SUMMARY LINE, unchanged across all three merges as you asked me to confirm each time: '✓ Plugin-mounted Hono routes — 11 module(s) audited, 161 hand-built body/bodies (count reported, NOT pinned): 7 conformant, 1 ratcheted, 3 exempt'. Per-file: adapter.ts {} conformant, serve.ts {} conformant, adapters/hono unenveloped 2 under '#9436 (… Blocked-by #9389)'; the three exempt print 'closed at unenveloped 1 / 9 / 3'. Nothing raised anywhere. TESTS: @objectstack/runtime '167 passed (167) / 2503 passed (2503)'; plugin-hono-server '18 passed (18) / 211 passed (211)'; http-conformance '4 passed (4) / 72 passed (72)'; @objectstack/hono '2 passed (2) / 73 passed (73)'. GATE UNION on 04eaa951c, all PASS, re-derived first with the UPDATED scripts/pm/dispatch-gates.mjs that main had just changed (same family set, no new gates): check:route-envelope, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution, check:changeset-gate-self-tests, check:objectui-changeset, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:required-contexts (added by me this round because the rename had just landed and that gate is what keeps the workflow job name and the required-context registry in sync — it passes on the new name), check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-cross-package-test-inputs, check-nul-bytes, check-affected-docs, plus check:type-check-debt (--re-measure on the rebuilt closure: '33 ledger entries re-measured in 224.6s, 1926 raw tsc errors total, none above its recorded number'). CI ON THE MERGED HEAD, by CI JOB name, all success — polled with the corrected matcher you flagged (new required list, prefix-matched so sharded jobs count): Lint & Repo Gates 05:45:49Z, Build Core 05:47:13Z, Temporal Conformance (live PG + MySQL) 05:47:29Z, Dogfood Regression Gate 05:49:35Z, TypeScript Type Check 05:53:45Z, Test Core 05:57:09Z. The queue then rebuilt the generation (merge_group runs started 06:03:24Z, after the ruleset's configured min_entries_to_merge_wait_minutes: 5) and merged at 06:17:59Z. Timeline carries added_to_merge_queue 05:57:11Z and NO removed_from_merge_queue — no eviction, no re-arm needed. The auto_merge field reading NOT ARMED after queueing is auto-merge consuming itself on entry, not a disarm; the only auto_merge_disabled/enabled pair in the timeline is hotlong's at 05:37:57Z/05:38:54Z, which is not mine and which I did not touch.", "open_questions": [], "out_of_scope_findings": [ "filed as #9436: adapters/hono's two bare `{ data }` discovery bodies — pre-auth like #9389's set but read by SDKs and codegen rather than the Console SPA, so #9389's closed three-file list does not reach them. Now live on main: the ledger entry's ratchet pointer names #9436, so no entry names a closed card", "filed as #9442: `os serve`'s unknown-hostname guard has no test seam — the middleware is built inside a closure in run() and is unreachable from a test; verified with a control query. Its health/readiness bypass ('returning 404 here would kill the container') is an untested silently-stops-working branch. Extracting it is a refactor with real judgment calls, so it failed the bounded in-place-fix test and was filed instead", "not filed, recorded only — the deadlock's structural cause, still unguarded: the required-context rename landed in repository Settings (ruleset updated 03:26:48Z) BEFORE the workflow PR that produces the context (#9421) merged, which blocked every open PR at once for ~1h. scripts/check-required-contexts.mjs keeps the workflow job name and the in-repo registry in sync, but it cannot see the Settings side, so nothing mechanically enforces the ORDER of the two halves — that constraint lived only in #9421's title. Worth a card if a second rename is ever planned", "not filed, recorded only: packages/qa/http-conformance/src/adapter.ts keeps two bare 500 bodies (a handler throw and an SSE-teardown 500) — no mirror in the primary adapter, no conformance case, no counter, so no evidence pins their shape", "not filed, recorded only: packages/core/src/qa/http-adapter.ts's header and its test's mocks describe a stock boot answering 404 {\"error\":\"Not found\"} — now stale as documentation of the wire, though the tests neither fail nor pin our adapter (the sibling row uses { error: 'nope' })" ] }
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 - added 4 commits that reference this issue
on Aug 23, 2026
Filed unassigned by the #9267 dev seat; nobody is on it. #9267 added a third surface to
scripts/check-route-envelope.mjs— plugin-mounted Hono routes, discovered by parsing rather than by filename — and the first run measured drift in five packages that no check in the repo could previously see. #9267 fixed only what was small and local to apackages/cloud-connectionerror exit; everything below is recorded there as aratchetpointing at this issue.Every number is measured by
node scripts/check-route-envelope.mjs, ticks DOWN only, and is pinned so none of it can get worse while it waits.What is outstanding
packages/plugins/plugin-hono-server/src/current-user-endpoints.tsunenveloped 9{ authenticated, userId, … }bodies with nosuccessflagpackages/plugins/plugin-hono-server/src/adapter.tsunenveloped 4,stringError 4,siblingCode 1{ error: 'Not found' }404,{ error: 'No response from handler' }500,{ error: 'Fallback handler failed' }500, and a 405 puttingcode/method/path/allowedbesideerrorpackages/plugins/plugin-auth/src/auth-plugin.tsunenveloped 3{ hasOwner: … }bodies from/bootstrap-statuspackages/adapters/hono/src/index.tsunenveloped 2,errorCodeNotString 1{ data }discovery bodies with nosuccess, plus a sharederrorJsonwriting the HTTP status intoerror.codepackages/cloud-connection/src/runtime-config-plugin.tsunenveloped 1/api/v1/runtime/configdiscovery payloadpackages/cli/src/commands/serve.tsunenveloped 1,stringError 1{ error: 'environment_not_found', message, hostname }stringErroris the pre-#3675 dialect (body.error.messagereadsundefined);siblingCodeis its #7035 twin (body.error.codereadsundefined).Why none of it was fixed in #9267
Two of these are cross-repo breaking wire changes, not conformance tidying, and that is the part that needs a decision rather than a patch:
/api/v1/runtime/configis read BARE by the Console SPA before first paint — objectuiapp-shell/src/runtime-config.tsreadsbody.cloudUrl,body.features,body.brandingoff the top level.current-user-endpoints.tsand/bootstrap-statusare read the same way, by the Account/Console SPAs.Enveloping any of them means a coordinated change in
objectuiand a compatibility story for runtimes and consoles on different versions. Theadapter.tsandserve.tsrefusals are the cheaper half — those are genuine error-path drift with no bare-payload contract behind them, and could land independently.Suggested shape
adapter.ts,serve.ts, anderrorJsoninadapters/hono) — these are the classic Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675{ error: '<string>' }#3843 class and need no consumer coordination.BaseResponseSchema(which is a legitimate answer —hmr-routes.tsalready carries exactly such anexemptwith a reason). Today they are neither, which is the state worth ending.Whichever way each goes, the gate records it: a fixed body lowers the number, a ruled-exempt file becomes an
exemptentry with a reason. There is no state where nobody looked.Related: #9267 (added the surface), #9223 (named the
plugin-routedoor), #9246, #3843 (the envelope guard's founding class), #7035 / #7295 (the two dialects and the write-site-count lesson), #8884 (the same "module outside the naming convention" discovery gap).