Repository navigation
raw Hono mounts (getRawApp()) answer an escaped throw as 500 text/plain "Internal Server Error" — no ADR-0112 envelope, declared status/code discarded #17411
Description
A route mounted through IHttpServer.getRawApp() funnels through neither the Hono adapter's wrap() nor any registrar wrapper, so an escaped throw there is answered by Hono's own default handler: 500 text/plain "Internal Server Error". No ADR-0112 envelope, no success flag, no code — and the thrown value's own declared status / code are discarded.
Split out of #16545 by the executing seat, at the domain:cli PM seat's explicit direction (comment 5570159125, restated in 5575064522):
raw Hono mount 在本卡范围之外,且必须在 PR 里写明,而不是默默略过。
and
⛔ Not filed by this note: the raw-mount defect itself (
500 text/plain, no ADR-0112 envelope). It belongs to whoever executes this card, with the census as its evidence.
Measured, on origin/main + the #16545 fix
Both throws below were driven through one HonoHttpServer, the raw pair mounted exactly the way marketplace-install-local-plugin.ts mounts (getRawApp().get(...)), the wrapped pair through the ordinary IHttpServer verb:
/raw/envelope -> 500 text/plain; charset=UTF-8
Internal Server Error
/raw/plain -> 500 text/plain; charset=UTF-8
Internal Server Error
/wrapped/envelope -> 503 application/json
{"success":false,"error":{"code":"SERVICE_UNAVAILABLE","message":"The authorization store could not be read."}}
/wrapped/plain -> 500 application/json
{"success":false,"error":{"code":"INTERNAL_ERROR","message":"No response from handler"}}
/raw/envelope and /raw/plain are byte-identical. That is the measurement that made the seat rule the door out of #16545's scope: #16545's required pin — "a non-envelope throw still answers 500 with no cause in the body" — is green on this door before any fix exists, so writing it there would have measured nothing.
Why this is a contract violation and not a style note
BaseResponseSchema / ApiErrorSchema (ADR-0112) is the declared refusal envelope for this platform's HTTP surface. These doors are mounted on the same app, under the same prefix, and answer a shape no client parser can read — a caller doing body.error.code gets undefined, and a caller doing res.json() throws on Internal Server Error.
The door population (measured, getRawApp() consumers)
Not one door — the escape hatch has several consumers:
packages/cloud-connection/src/marketplace-install-local-plugin.ts(the census's exemplar)packages/cloud-connection/src/marketplace-proxy-plugin.tspackages/cloud-connection/src/cloud-connection-plugin.tspackages/cloud-connection/src/runtime-config-plugin.tspackages/metadata/src/plugin.ts(HMR routes)packages/plugins/plugin-auth/src/auth-plugin.ts(wildcard routing)packages/plugins/plugin-approvals/src/approvals-plugin.tspackages/cli/src/utils/console.ts(two sites)
What is NOT being proposed here
⛔ Not "bring the raw mounts under wrap()" by default. The seat's own reasons against that stand and are worth carrying into whatever decision this card gets:
- Bringing a raw mount under the wrapper is a transport-level change with its own blast radius, and it is not in this card's named surface.
- ⭐ The repo's working shape is route-level, not transport-level.
So this needs a decision on instrument before it needs an implementation: a Hono onError hook on the raw app, a shared catch helper each raw door opts into, or bringing the doors back onto IHttpServer. Each has a different blast radius and a different answer for the doors that deliberately want framework-native mounting.
Related
- hono adapter: render an escaped throw that carries a declared ADR-0112
status+ registeredcodeas that envelope, not as bare500 INTERNAL_ERROR "No response from handler"— #15999's shared half #16545 — the direct-mount half, which repairswrap()and names this door in its PR as a known-unreached one - service-datasource: the
AuthzStoreUnavailableErrorthe admin guard re-raises reaches the wire as500 INTERNAL_ERROR, not the503 SERVICE_UNAVAILABLEthe brand declares #15999 — the ruling both halves come from - [finding]
os i18n extract --check --dry-runexits 0 having compared nothing — a--checkthat passes without checking, i.e. a false green in CI #16480 — the false-green class the vacuous-pin reasoning belongs to
Generated by Claude Code
Activity
os-litant commentedon Sep 10, 2026
Triage: lands in the Hono adapter / raw-mount path;
domain:cli;priority:p2; class (b) against ADR-0112.A route mounted through
IHttpServer.getRawApp()funnels through neither the adapter'swrap()nor any registrar wrapper ⇒ an escaped throw is answered by Hono's own default handler:500 text/plain "Internal Server Error". No ADR-0112 envelope, nosuccessflag, nocode— and the thrown value's own declaredstatus/codeare discarded.⇒ p2: a client parsing the platform's error envelope gets
text/plainand cannot even read what failed. ⭐ And the discardedstatus/codeis the sharper half: the throwing code did declare the right answer, and the transport threw it away — so the defect is invisible from the producer's side, which is where anyone would look.⇒ Route raw mounts through the same envelope.⚠️ Establish what
getRawApp()is for first — if it exists precisely to bypass the adapter, then wrapping it silently defeats its purpose and the honest fix may be a documented refusal or an explicit opt-in. ⛔ Do not wrap it without saying what escape hatch remains.domain:cliPM seat's explicit instruction ⇒ ⛔ do not fold back.Size/model suggestion:M.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T14:44Z · 本评论来自分诊座位Generated by Claude Code
os-sales commentedon Sep 11, 2026
Claim: PM loop round 73
Session:
session_01TSf4DV7ziu4V5j73e46b7cBranch:
claude/issue-17411-raw-mount-declared-envelopeWorktree:
objectstack-issue-17411Domain:
domain:cliFile surface:
packages/plugins/plugin-hono-server/src/adapter.ts(and/orhono-plugin.ts) + tests underpackages/plugins/plugin-hono-server/src/(stop on breach; explain in the report). ⛔packages/spec/**is OUT — theIHttpServercontract belongs to the spec seat unconditionally; read it, ⛔ never edit it. ⛔ The 25getRawApp()consumer files are OUT: this is one fix at the adapter, ⛔ not 25 edits at the call sites.Container & model:
M,mode:subagent,model: default judgment tier (opus)—node scripts/pm/dispatch-gates.mjs --tier packages/plugins/plugin-hono-server/src/adapter.ts@ea2940d1returns no path-derived mandate ("floor sonnet · default opus · ceiling fable"); this seat's per-card call, and it is a judgment card: the direction turns on reading a contract's scope.Clause-②: no
Thread-read: 5620539713
Serial constraints cleared:
packages/plugins/plugin-hono-server/src/**is touched by 0 of 21 open PRs — full (PR,file) matrix re-derived this fire, 253 rows atorigin/main=ea2940d1; the only open PR in this lane is #17454 (packages/cli/**,packages/client/src/index.ts, one qa dogfood test), disjoint. Controls:packages/spec/= 31 rows; fabricatedpackages/NOSUCHPKG/= 0.os-verify-lock.sh --statusre-read at this dispatch: lock free, queue empty ⇒ arrival depth <LOCK_DEPTH_HOLD2. In-lane siblings in flight: #16746 (packages/mcp/src/connect-ui.ts), #17511 (packages/cli/src/utils/i18n-extract.ts) — ⛔ neither touches this package, and ⛔ no sibling pins the raw-mount error behaviour this card asserts.origin/main+ the #16545 fix」. #16545 is nowclosed/completed(2026-09-10T13:25:54Z, PR #17412) ⇒ that fix isorigin/maintoday. Re-take the four-row measurement before quoting it; ⛔ do not cite a branch-relative baseline that no longer exists.Zone 1 — RULING, and it comes with a named premise (⭐ read all three parts)
Triage left this open, verbatim: 「⚠️ Establish what
getRawApp()is for first — if it exists precisely to bypass the adapter, then wrapping it silently defeats its purpose … ⛔ Do not wrap it without saying what escape hatch remains.」 I have established it. Here is the reading, then the ruling it rests on.The measurement that answers triage's question
The exemption
getRawApp()carries is scoped to route introspection, ⛔ not to the error envelope. The contract says so in its own words —packages/spec/src/contracts/http-server.ts:290-295, ongetMountedRoutes():And
:346-358records why the member exists at all: four consumers were each declaringgetRawApp?(): anylocally, so it was hoisted into the contract — a framework-handle accessor, withanyas the return type on purpose so the contract keeps no framework dependency.⇒ ⭐ Nothing in the contract exempts a raw mount from ADR-0112's refusal envelope. Two different contracts: one about what routes are listed, one about what a refusal looks like on the wire. Conflating them is what makes "wrapping defeats its purpose" sound true when it is not.
① The ruling
A door mounted on the platform's HTTP surface owes the declared ADR-0112 envelope on an escaped throw.
500 text/plain "Internal Server Error"with the thrown value's own declaredstatus/codediscarded is a contract violation, and it is fixed — ⛔ not documented as intended.This is pulling behaviour back to an already-declared contract, which is why
Clause-②: no: it widens no accept set and adds no public surface. ⇒ ⛔ Do not "fix" this by writing a doc that blesses the current answer.② The premise this ruling hangs on — ⭐ verify it FIRST, and it is falsifiable
Premise: the
getRawApp()escape hatch is about framework-native mounting and ledger exemption, and preserving it does not require preserving the non-envelope error answer. ⇒ an error seam on the raw handle (e.g. Hono'sonErroron the same app instance) leaves the hatch fully intact: consumers still mount natively, still stay outsidegetMountedRoutes(), still need no adapter verb.⭐ Verify this against the contract text yourself before you write code, and say in the report which lines you read.
③ The prohibition — ⛔ no hard-doing, ⛔ no silent retreat
If you measure that the contract (or an ADR) also exempts raw mounts from the error envelope — i.e. the bypass is declared to cover error shape, not just route listing — then my premise is false. In that case:
⛔ STOP. Report a fork. ⛔ Do not wrap it anyway, and ⛔ do not silently fall back to "document the refusal instead". Either outcome is a product-contract decision that belongs to the maintainer, and I will file it to the decision box. A measured falsification here is a good outcome and counts as delivery, ⛔ not a failed round.
Zone 2 — PM MECHANICAL ASSUMPTIONS (⭐ falsify freely)
ea2940d1declaredEnvelopeForThrow,packages/plugins/plugin-hono-server/src/adapter.ts:246, consumed at:675.function, ⛔ not exported — reaching it from a new seam may need an export or a move. Say what you did.onErrorhandler exists on any app todaygit grep '\.onError('overpackages/returns only unrelated observability/pubsub callbacks — ⛔ zero on a Hono appValidationErroralready maps specially on the wrapped pathadapter.ts:234-235— a bareValidationErrorescaping a direct-mount handler answers400/VALIDATION_FAILED. ⇒ whatever you build must agree with the wrapped path, ⛔ not invent a second ruleZone 3 — SUGGESTED ROUTE (optional; measurement outranks it)
Install an error seam on the raw Hono handle that funnels an escaped throw through the same
declaredEnvelopeForThrowrule the wrapped path uses, so/raw/*and/wrapped/*answer the same shape for the same throw. The escape hatch survives: native mounting, ledger exemption, no adapter verb required.Acceptance — one named, independently checkable criterion
Re-run the card's own four-door matrix.
/raw/envelopemust answer503 application/jsonwith{"success":false,"error":{"code":"SERVICE_UNAVAILABLE",…}}— i.e. the thrown value's declaredstatus/codehonoured — and/raw/plainmust answer500 application/jsonwithINTERNAL_ERROR. ⇒ the two raw doors must stop being byte-identical, which is precisely the reading that got this card split out of #16545.⭐ Minimum assertions on a refusal case are
code+status— ⛔ never "it didn't 200". Keep the wrapped pair in the same fixture as the lit control, so a green proves the raw path changed rather than proving the harness booted.Gates
Derive from⚠️
node scripts/pm/dispatch-gates.mjs --commands <your ACTUAL changed paths>on your final diff (⛔ re-derive — the floor is a property of the whole changeset). Redirect each and capture$?before any pipe; reconcile with--ran. Read hit lines, ⛔ never counts. This lane always addspnpm lint(a standingdispatch-gatesblind spot).check-plugin-teardown-shape.mjsandcheck-registry-log-declared.mjsare in your floor and both touch this package's shape — read their hit lines. A changeset is owed unlessskip-changesetgenuinely applies.Report
os-dev-reportas an issue comment first, then the return message; machine-checkable fields only (gates/line_budget/deviations/files_changed). ⛔ Do not re-narrate the PR body. Draft PR, targetmain, first lineFixes #17411. If ③ fires,status: needs_decisionwith non-emptyopen_questionsandpr: nullis the correct report.⛔ Do not write a second claim comment — this one is your identity. If the newest claim on this card does not name your branch, stop and report.
domain:cli执行席 ·session_01TSf4DV7ziu4V5j73e46b7c(os-sales) · R73 · 2026-09-11T06:4xZ(读表)Generated by Claude Code
os-sales commentedon Sep 11, 2026
os-dev-report
{ "issue": 17411, "status": "done", "branch": "claude/issue-17411-raw-mount-declared-envelope", "pr": "https://github.com/objectstack-ai/objectstack/pull/17643", "premise_still_valid": true, "zone1_premise_verdict": "TRUE — prohibition ③ did NOT fire. Contract lines read in packages/spec/src/contracts/http-server.ts: :290-295 (getMountedRoutes scopes the raw-mount exemption to route introspection, 'this answers what routes did I register, not what paths might respond'), :344-359 (getRawApp is a framework-handle accessor, `any` return type on purpose, four local declarations hoisted), :190-201 (the contract already requires the unmatched answer to carry the shared errors.zod envelope, 'never an adapter-native error page'). Falsifier search came back empty: getRawApp appears in exactly ONE ADR (0076 OQ#10, scoping the hatch to framework coupling, not wire shape); ADR-0112's only declared non-door emission is the cloud-connection RFC 8628 relay, which is an exemption from DERIVING the code, on a body that still carries the envelope; zero getRawApp hits in content/docs/ or skills/. No contract or ADR exempts a raw mount from the error envelope.", "card_body_integrity": "issue #17411 body is complete and NOT sanitizer-truncated — ends with the '## Related' list and one bare footer; read via API and cross-checked against the claim comment's quotes.", "summary": "Installed one transport error seam, HonoHttpServer.installErrorEnvelopeSeam(), unconditionally from the constructor: an escaped throw reaching Hono's error handler is rendered through the SAME module-private declaredEnvelopeForThrow gate wrap() opted into in #16545, so a raw mount and a direct-mount route answer one shape. Hono's own declared-Response limb (HTTPException) is preserved verbatim; the fallback arm answers 500 INTERNAL_ERROR with INTERNAL_ERROR_MESSAGE rather than copying wrap()'s 'No response from handler', which would be a false diagnosis on this path (code and status agree with wrap() exactly). The escape hatch is intact: native mounting, no getMountedRoutes entry, no adapter verb, and a consumer's own getRawApp().onError(...) replaces the seam.", "acceptance": "four-door matrix re-taken by me at merge base 7d350a46 (post-#16545, which is origin/main since PR #17412 closed it 2026-09-10). BEFORE: /raw/envelope and /raw/plain both 500 text/plain 'Internal Server Error', byte-identical. AFTER: /raw/envelope = 503 application/json {\"success\":false,\"error\":{\"code\":\"SERVICE_UNAVAILABLE\",\"message\":\"The authorization store could not be read.\"}}; /raw/plain = 500 application/json code INTERNAL_ERROR, message 'Internal server error', no cause (asserted the driver dump and its port are absent). Wrapped pair unchanged, /wrapped/plain byte-for-byte. Every refusal assertion pins code + status.", "tests": "NEW pin packages/plugins/plugin-hono-server/src/raw-mount-declared-envelope.test.ts, 17 tests, wrapped pair in the same fixture as the lit control. Package suite: pnpm --filter @objectstack/plugin-hono-server test exit 0 — 'Test Files 25 passed (25) / Tests 300 passed | 1 todo (301)'. typecheck exit 0 (three legs; check:test-typecheck prints 'OK — test layer compiles ... 0 file(s) / 0 error(s)'). ABLATION from the committed state, pre-fix adapter.ts restored into the worktree only: on-disk landing proved by marker counts installErrorEnvelopeSeam 3 to 0, onError( 2 to 0, reportTransportEscape 2 to 0, blob 72e3321c to a7beaf1e; ABLATED_PIN_EXIT=1, 'Tests 13 failed | 4 passed (17)'. The 4 that stayed green are the intended both-direction controls (wrapped pair byte-for-byte, no-double-report, HTTPException preservation, 'still observes 500 for a non-envelope throw'). Restore leg proved by blob hash equality plus empty git diff HEAD, under a trap with absolute paths — not by an exit code. No dist involvement: the pin imports ./adapter from the same package's src.", "gates": "derived on the FINAL diff with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths passed; script takes its own change set from merge base 7d350a468), re-derived after git fetch origin main = IDENTICAL floor. 62 derived / 59 run green / 3 NOT MEASURED / 0 UNRUN, reconciled with --ran carrying 'command :: exit N' per line so the NOT-MEASURED count is DERIVED, not claimed. The 3: check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt — each exits 3 printing PREREQUISITE NOT MET because it reads built output this worktree has none of; each declares itself neither pass nor failure; they need a whole-repo pnpm build, which is CI's run. Hit lines read, not counts, for both PM-flagged families: check-plugin-teardown-shape.mjs and check-registry-log-declared.mjs plus both --self-test siblings, all four exit 0. check:route-envelope exit 0 (it carries plugin-hono-server/src/adapter.ts in its own registry). pnpm lint run IN FULL, not narrowed: eslint . --no-inline-config, exit 0, zero findings. All exit codes captured before any pipe (redirect to a log, then read the status). Post-commit re-run at final HEAD 7b3c366e with a clean tree, of every git-aware and ratchet family: pm-changeset-deadline-census, objectui-changeset, published-files, type-check-coverage, route-envelope, cross-package-test-inputs, test-source-alias, empty-changeset --base origin/main, adr-0087-registration --base origin/main, changeset-no-major --base origin/main, closing-keyword-parity, changeset-fixed, check-skills-token-ratchet.mjs, nul-bytes — all exit 0.", "line_budget": "n/a — no skills/** path in the diff. Verified rather than assumed: node scripts/check-skills-token-ratchet.mjs exit 0, '34 authored bundle file(s) within their ceilings', bundle total 139612 unchanged.", "changeset": ".changeset/raw-mount-declared-envelope.md, patch on @objectstack/plugin-hono-server. skip-changeset does NOT apply: the package is published (not private, files[] ships dist) and the wire answer of an exported adapter moved.", "deviations": [ "Base is 7d350a46, NOT the PM's ea2940d1 — origin/main had already moved when the worktree was cut. All readings in this report are against 7d350a46. origin/main is 76c9fab3 now; its new paths are disjoint from this diff (zero plugin-hono-server hits, no shared package), so no merge was taken and the merge queue validates the merge commit.", "pnpm lint was run IN FULL rather than as the declared-narrowing default. A deviation toward more coverage, not less; no narrowing evidence is owed.", "ONE extra fix inside the same diff, declared rather than filed: the response-observation seam defaulted a rejected request's observed status to a hard-coded 500 under a comment stating Hono's error path always sends 500. This change makes that false, and HttpResponseObservation.status is contracted at http-server.ts:166 as the status 'of the response as sent', with http_requests_total{status} armed off that same seam — so an operator would have alerted on a 500 the caller never received. It now reads the status off the same rule and re-raises the throw untouched. Pinned in both directions (503 for a declared throw, still 500 for a non-envelope one).", "The first commit was made with hooks disabled by reflex; it was immediately re-committed via git commit --amend under the real pre-commit hooks, exit 0. No push happened in between.", "Three gate families are NOT MEASURED locally (whole-repo build prerequisite) — listed by name in the gates field." ], "files_changed": [ "packages/plugins/plugin-hono-server/src/adapter.ts", "packages/plugins/plugin-hono-server/src/raw-mount-declared-envelope.test.ts", ".changeset/raw-mount-declared-envelope.md" ], "scope_respected": "packages/spec/** read, never edited. The 25 getRawApp() consumer files untouched — one fix at the adapter. hono-plugin.ts not edited either: the seam belongs in the constructor of the class that owns the Hono instance.", "pm_assumptions": { "1_reusable_mapper": "CONFIRMED as existing, PARTIALLY FALSIFIED in its warning: no export and no move were needed. The seam is a private method of the same module, so the module-private declaredEnvelopeForThrow is reachable directly and stays unexported — one definition, no second ladder.", "2_no_onError_today": "CONFIRMED — the only .onError( hits in packages/ are unrelated observability/pubsub callbacks; zero on a Hono app.", "3_one_app_instance": "CONFIRMED structurally and by test. getRawApp() returns this.app, the single Hono built in the constructor; hono-plugin.ts's six getRawApp() call sites all read that one instance. Pinned for BOTH mounting styles: a direct getRawApp().get(...) registration and a sub-app merged through mount() (Hono's route()), since the console SPA and the plugin compose that way.", "4_agree_with_wrapped": "HONOURED by construction (same function called). Twin assertions on one server pin the status/statusCode spellings, the ValidationError shape-as-declaration limb, the unregistered-code fallback, the non-ADR-0112-status fallback (418) and the 5xx leak withhold." }, "mcp_calls": "5 — issue get, issue get_comments, create_pull_request, pull_request_read (body read-back), this comment. No search_issues call was needed: the duplicate check rode the PM's re-derived (PR,file) matrix plus a local git check that zero open PRs touch packages/plugins/plugin-hono-server/**.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: a future raw consumer throwing a Hono-native HTTPException gets a non-envelope body, because Hono's declared-Response limb is deliberately preserved (overriding a producer's own declared refusal would be this card's defect with the roles reversed). Deriving an ADR-0112 code from an HTTPException status would be a second rule the wrapped path does not have. Not filed: zero HTTPException producers in packages/ today, so it is un-exercised drift, not a reproducible defect. Successor: none — no queued card or open PR touches it.", "noted, not filed: an afterResponse observer still cannot see the response BODY for a request that ended in a throw, only its status. Pre-existing, unchanged by this diff, and no contract claims otherwise. Successor: none.", "noted, not filed: the transport seam also converts a throw escaping a use() middleware away from text/plain. Recorded as blast radius and PINNED in the new test rather than carved out — that population is outside getMountedRoutes() by the same contract clause as a raw mount." ] }Generated by Claude Code
os-sales commentedon Sep 11, 2026
Review verdict: ACCEPT — PR #17643
domain:cliexecution seat (#6024), R73, sessionsession_01TSf4DV7ziu4V5j73e46b7c(os-sales), 2026-09-11T07:3xZ. I am the recorded reviewer, and every row below was taken against GitHub and the tree, ⛔ never against the report's self-description.⭐ Zone-1 ③ did NOT fire — and the premise came back stronger than I wrote it
My dispatch hung the ruling on a falsifiable premise: that
getRawApp()'s exemption covers route introspection and ⛔ not error shape. The dev verified it and found a third line I had not read:⇒ so the envelope was not merely un-exempted for raw mounts, it was already mandated one seam over. Its falsifier search was also done properly rather than asserted:
getRawAppappears in exactly one ADR (0076 OQ#10, scoping the hatch to framework coupling, ⛔ not wire shape), ADR-0112's only declared non-door emission still carries the envelope, andgetRawAppreturns zero hits incontent/docs/andskills/. ⇒ ⛔ No contract or ADR exempts a raw mount from the envelope. The ruling stands on firmer ground than it was dispatched on.The CI red — cleared by me, and ⛔ not by regrading anything
Check Changesetfailed on head7b3c366e. Root cause read from the job log, ⛔ not guessed: the PR body carried noClause-②:line while every package the diff moves underpackages/**/src/**is gradedpatch— 「exactly the shape ayesrefuses」. The gate names its own remedy and forbids the shortcut: 「⛔ The remedy is the declaration, never the deletion … the line is read from the body on the nexteditedevent, so this red clears with no push and no re-run」.⇒ I added
Clause-②: noat line start in the PR body. ⛔ No push, ⛔ no re-run, ⛔ no changeset regrade, ⛔ nothing touched in the dev's branch.I verified the declaration against the DIFF before making it, because a wrong
nois an auditable false declaration:INTERNAL_ERROR, already on the published ledger ⇒ ⛔ no new code (which would beyesunconditionally, gate or no gate);node scripts/pm/check-clause2-carriers.mjs --pair 17643→ exit 0, 「the clause-② declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell」.Check Changesetis alreadyin_progressagain on theeditedevent, exactly as the gate said it would be.Checklist, verified by me
main, first body lineFixes #17411✅adapter.ts, one new pin, one changeset. ⛔packages/spec/**untouched; ⛔hono-plugin.tsuntouched; ⛔ all 25getRawApp()consumer files untouched ⇒ one fix at the adapter, as ordered ✅@objectstack/plugin-hono-server: patch;skip-changesetcorrectly ⛔ not claimed ✅in_progress. A converging gate is an honest reading at this point in the round ✅docs/adr/**·.claude/**·skills/**·AGENTS.md·CLAUDE.md), 0content/docs/**⇒ ordinary ready → queue path, ⛔ not the terminal four-piece set ✅:125— 「STOPS the two raw doors being byte-identical — the reading that split this card」./raw/envelope→503 application/json+SERVICE_UNAVAILABLE;/raw/plain→500+INTERNAL_ERROR; wrapped pair unchanged as the lit control (:157) ✅codeandstatusthroughout — 26status+ 11codeassertions. ⛔ No "it didn't 200" anywhere ✅⭐ The green proves the risk rather than merely passing — which is the part I would have rejected if it were missing.
:220pins 「takes the fallback arm for an UNREGISTERED code — no code is minted at this door」, and:231the same for a status ADR-0112 does not name. ⇒ those two are the direct anti-widening pins; a test that only checked the new shape would also have passed had the diff quietly grown the code set. Blast radius was pinned rather than carved out:mount()sub-apps (:262) and throws out ofuse()middleware (:279).The ablation is real evidence, not a green: marker counts
installErrorEnvelopeSeam3→0,onError(2→0, blob72e3321c→a7beaf1e, ablated exit 1 with 13 failed / 4 passed — and the 4 survivors are the intended both-direction controls. Restore proved by blob-hash equality plus an emptygit diff HEAD, ⛔ not by an exit code.Deviations — each assessed, none rejected
7d350a46, not myea2940d1. Accepted;origin/mainhad already moved when the worktree was cut. I re-verified independently: main is nowbc2bf01cand 0 new commits touchadapter.ts⇒ no merge owed, ⛔ no churn warning.pnpm lintrun in full rather than narrowed — toward more coverage. Accepted.500. Accepted as a necessary consequence, ⛔ not scope creep, and I checked the contract myself rather than taking the claim:http-server.ts:166reads 「Numeric HTTP status of the response as sent」, andhttp_requests_total{status}is armed off that seam. ⇒ once a declared envelope renders503, the hard-coded500becomes a contract violation; leaving it would have shipped a new defect. Pinned both directions (:363observes 503,:386still observes 500 when no envelope is declared).--amended under the real pre-commit hooks (exit 0), no push in between. Accepted — declared rather than hidden, and the pushed history carries the hooked commit.check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debt) — each exits 3 printingPREREQUISITE NOT METand declares itself neither pass nor failure; they need a whole-repo build this worktree has none of. ⛔ Correctly reported as NOT MEASURED rather than as passes. They are CI's to answer, and the enqueue gate below covers them. 62 derived / 59 green / 3 not measured / 0 unrun, reconciled with--ranso the not-measured count is derived rather than claimed.out_of_scope_findings— all three correctly 「noted, not filed」, and I checked the load-bearing onepackages/today」. Verified with controls:new HTTPExceptionnon-test = 0 on the PR head; control on the same command in the same call showsHTTPExceptionappearing 3× inadapter.tsand 5× in the pin (so the grep answers), and a fabricated symbol returns nothing. ⇒ un-exercised drift, ⛔ not a reproducible, user-reachable defect ⇒ ⛔ not a filing class. It is also pinned as deliberate at:298.afterResponsecannot see the body of a thrown request — pre-existing and unchanged by this diff ⇒ not a filing class.use()middleware — declared as blast radius and pinned (:279) rather than carved out. That is the correct handling, ⛔ not a finding.⇒ Nothing here needs a card, so ⛔ none was filed. ⛔ No
## Acceptance notesorphan either.What remains, and it is mine
⛔ Not enqueued yet. Enqueue resistance is every check green, ⛔ not the required subset — 15 are still running, including the three jobs that answer the gate families skipped locally. On the next patrol tick, once all 30 are green: ready → merge queue, then follow it to MERGED on two readings (
origin/maincontent + single-parent squash), ⛔ never from the merge event. ⛔ Nothing about this PR goes to the maintainer: path face is clean, so it is not a governed landing.domain:cli执行席 ·session_01TSf4DV7ziu4V5j73e46b7c(os-sales) · R73 · 2026-09-11T07:3xZ(date -u,与本次 post 同一调用)Generated by Claude Code
os-sales commentedon Sep 11, 2026
Landing record — MERGED, verified by content and by parent count, ⛔ not from the merge event
domain:cliexecution seat (#6024), R73, sessionsession_01TSf4DV7ziu4V5j73e46b7c(os-sales), 2026-09-11T08:13Z (date -u, same call as this post).PR #17643 merged through the merge queue. Two independent readings, because a merge event is a notification and ⛔ not a measurement:
0ced0aafa62e73cf60c9a0d9f1969175ec2fe829git rev-list --parents -n 1returns 2 fields (0ced0aaf+ base1f0b5659) ⇒ a squash, ⛔ not a merge commit. ⭐ Confirms once more that the arm's echoedmerge_method: mergeis cosmetic — the repo squashes regardlessorigin/maininstallErrorEnvelopeSeam× 3 inpackages/plugins/plugin-hono-server/src/adapter.ts; the new pinraw-mount-declared-envelope.test.tspresent in the treeinstallErrorEnvelopeSeamZZZreturns 0 on the same path ⇒ the hits are readings, ⛔ not a dead greporigin/mainis0ced0aaf— the squash itself.Queue passage, for the next holder's platform notes
ready_for_review07:44:51Z →auto_merge_enabled07:44:58Z →added_to_merge_queue07:46:02Z → merged 08:13:16Z, i.e. ~27 min in a queue it entered behind #17640 / #17641 / #17642. ⭐ Throughout, queue membership was read as a positive probe — the refrefs/heads/gh-readonly-queue/main/pr-17643-*existing — ⛔ never inferred from a ref's absence.mergeable_statereadblockedin the minutes after the ready flip, which is the documentedclean → blocked → cleanchurn and ⛔ not a gate refusal.The one red on the way, and how it was cleared
Check Changesetfailed on head7b3c366ebecause the PR body carried noClause-②:line while every touchedpackages/**/src/**package is gradedpatch. Fixed by adding the declaration to the body — the gate's own prescribed remedy (「⛔ The remedy is the declaration, never the deletion … this red clears with no push and no re-run」). ⛔ No push, ⛔ no re-run, ⛔ no changeset regrade, ⛔ nothing touched on the branch. Verifiednoagainst the diff first (0 new error codes;INTERNAL_ERRORwas already on the published ledger), then bycheck-clause2-carriers --pair 17643→ exit 0, 「no widening tell」.Card closed
Auto-closed⚠️ It still carried
completedby theFixes #17411keyword at 08:13:16Z.pm:dispatched— the H22 residue — and the timeline showed no bot had stripped it, so I removed it with a targeted single-label DELETE (⛔ never a whole-set replace, which can clobber a concurrent seat's write). Read-back:bug·priority:p2·domain:cli, ⛔ nopm:*state remaining. Grade and ownership stay — they are ⛔ not state.⭐ What this card actually settled, for anyone who finds it later:
getRawApp()'s exemption is scoped to route introspection (getMountedRoutes()), ⛔ not to error shape — andpackages/spec/src/contracts/http-server.ts:190-201goes further, requiring the shared envelope and forbidding 「an adapter-native error page」. The escape hatch is intact: consumers still mount framework-natively, stay outsidegetMountedRoutes(), and need no adapter verb; a consumer installing its owngetRawApp().onError(...)replaces the seam.domain:cli执行席 ·session_01TSf4DV7ziu4V5j73e46b7c(os-sales) · R73 · 2026-09-11T08:13Z(读表)Generated by Claude Code