Repository navigation
adapters/hono's two bare { data } discovery bodies lose their ratchet owner when #9364 lands #9436
Description
Filed unassigned by the #9364 dev seat while banking that card's counter deltas. Nobody is on it.
Blocked-by: #9389 — the ruling there decides which way this one goes, so it should not be dispatched before that card lands.
The gap
scripts/check-route-envelope.mjs pins packages/adapters/hono/src/index.ts at two counters:
| counter | what it is | owner after #9364 |
|---|---|---|
errorCodeNotString 1 |
errorJson wrote the HTTP status into error.code |
fixed by #9364 |
unenveloped 2 |
two { data: await dispatcher.getDiscoveryInfo(prefix) } bodies, no success flag |
nobody |
Triage scoped #9364 to the error-path half only (errorJson), so the errorCodeNotString counter goes to zero there and the file's entry survives carrying unenveloped 2 alone. That entry's ratchet field names #9364 — a card the same PR closes.
The three bare-payload surfaces #9389 rules on are a deliberately closed list: /api/v1/runtime/config, current-user-endpoints.ts, and /bootstrap-status. These two bodies are not among them, so #9389 will not sweep them up either.
Why they are the same class as #9389, and why that matters
Both are the discovery payload for a mount:
app.get(prefix, async (c) => c.json({ data: await dispatcher.getDiscoveryInfo(prefix) }));
app.get(`${prefix}/discovery`, async (c) => c.json({ data: await dispatcher.getDiscoveryInfo(prefix) }));They are read before authentication, by SDKs and codegen rather than by a browser SPA — which is why they were not in #9389's SPA-driven list, and also why the answer may legitimately differ from it. { data } is one key away from the envelope (success is the whole difference), so the migration cost here is far lower than for the SPA-read surfaces #9389 weighs.
What needs deciding
The same fork #9389 states, on a different consumer population:
- A. Envelope them — add
success: true; sweep SDK/codegen/AI-client readers ofbody.datafrom this mount first. Route and surface ownership rule 4 ("machine-readable surfaces must not lie") argues for uniformity here more strongly than it does for a browser bootstrap payload. - B. Rule them exempt with a written reason, the mechanism [Decision] Pre-auth discovery/bootstrap payloads: inside BaseResponseSchema (coordinated objectui flip) or ruled exempt with reasons — today they are neither #9389 was ruled onto (maintainer, 2026-08-17, option B).
Whichever way it goes, the gate records it — but only if the entry keeps pointing at a card that is open. Today's ratchet pointer goes stale the moment #9364 merges.
Same-shape residue in the ratchet table
Three other entries name #9364 in their ratchet field and are #9389's to retarget when it lands: runtime-config-plugin.ts, current-user-endpoints.ts, auth-plugin.ts. Noting it here so the dangling-pointer sweep is one job rather than four discoveries.
Related: #9364 (the error-path half, where this was measured) · #9389 (the pre-auth bare-payload ruling) · #9267 (added the gate surface) · #3843 (the envelope guard's founding class).
Activity
fix(api): envelope the plugin-mounted Hono error paths (#9364)
os-support-ai commentedon Aug 18, 2026
Triage — first-touch grading (concentrated round, triage seat, session
session_0138jAR5jeREBpm4dhVvbWY7): graded Task,domain:cli,pm:blocked.Blocked-by: #9389
Two halves, both correctly parked behind #9389's landing: ① the dangling
ratchetpointer sweep (this file plus the three entries the card lists) is mechanical but scoped to the post-#9389 table state; ② the envelope-vs-exempt fork for the two pre-auth discovery bodies is the same fork the maintainer personally ruled on 2026-08-17 (option B, exempt-with-reason) for a deliberately closed list this surface is not on — so on unlock this card gets re-priced, not auto-inherited: the population differs (SDK/codegen readers, one-key migration cost), the card's own reasoning leans A, and whether that adjacent fork is adjudicable or needs the maintainer is the unlock-time call. Do not dispatch on the strength of the B ruling alone.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Unlock scan + re-price (triage seat, fire ~19:47Z 2026-08-18; per the 10:19Z first-touch note this card is re-priced, not auto-inherited). Upstream discharged: #9389 closed completed 2026-08-18 02:16Z, ruled option B (exempt-with-reason), landed by PR #9455. Gate face re-verified on
origin/main: theunenvelopedcounters are still pinned, including this file's 2. The mechanical half (danglingratchetpointer sweep) rides whichever fix. The live fork — envelope vs. exempt for the two hono discovery bodies — is a public wire-contract question on a different consumer population (SDK/codegen/AI clients, not SPA shells), so it does not inherit the B ruling, and option A is a breaking wire change ⇒ human floor either way.pm:blocked→needs-user-decision.Four-facet block:
success) plus a reader sweep of this mount's SDK/codegen consumers. Low stakes, low cost.Prisms split (3 leans A, 4 leans B) ⇒ maintainer's call. Recommendation: A —
{ data }is one key from the envelope, the readers are the envelope's core constituency, and ruling machine-read surfaces exempt dilutes the #9389 boundary's stated rationale (pre-auth browser bootstrap). If B is chosen, the exempt entries must state the SDK-population reason explicitly so the enumerated boundary stays principled.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Maintainer ruling recorded (2026-08-18, live chat with the triage session
session_014v3dpZ2224xgf98dfY4rw4; authorization verbatim: 「其他接受你的建议」, accepting the recommendation presented in that session, restated as the operative ruling).Ruling: Option A — envelope the two discovery bodies. Add
success: trueto both{ data: await dispatcher.getDiscoveryInfo(prefix) }responses inpackages/adapters/hono/src/index.ts. This deliberately does not inherit #9389's B-exemption: that ruling covered a closed list of SPA-read pre-auth surfaces, and its reasoning (our own shells are the only readers, migration cost high) inverts here — these bodies are read by SDKs/codegen, uniformity of machine-readable surfaces is the stronger rule, and the migration is one key. Sequence: sweep SDK/codegen/AI-client readers ofbody.dataon this mount first, then flip, per this card's own A text.Also in scope: the ratchet-pointer sweep this card enumerates — retarget the four
ratchetentries naming #9364 (this file's, plusruntime-config-plugin.ts,current-user-endpoints.ts,auth-plugin.ts→ their #9389/PR #9455 resolution) so the gate table points at live anchors; this file'sunenvelopedcounter goes 2 → 0.Blocked-by discharged: #9389 closed completed 2026-08-18 (ruling B recorded via merged PR #9455). Label:
needs-user-decision→pm:queue,domain:cli.Generated by Claude Code
huangyiirene commentedon Aug 19, 2026
Claim: PM loop round 23
Session:
session_01WeN7F6jQFpcqW2BN56RdPaBranch:
claude/issue-9436-envelope-hono-discovery-bodiesWorktree:
objectstack-issue-9436Domain:
domain:cliFile surface:
packages/adapters/hono/src/index.ts(the twogetDiscoveryInfo(prefix)response bodies only),scripts/check-route-envelope.mjs(this file'sunenvelopedcounter plus the fourratchetpointer retargets), the SDK/codegen/AI-client reader sites the sweep identifies,.changeset/**(stop on breach; explain in the report)Worktree note:
packages/adapters/*isdomain:cliground per the lane tableContainer & model:
M,mode:subagent,model:contract-review tier attempted first (theCONTRACT_REVIEW_TIERconstant inscripts/pm/dispatch-gates.mjs);node scripts/pm/dispatch-gates.mjs --tierover the surface above returns no path-derived mandate (floor sonnet · default opus · ceiling fable), ⛔ not recalledClause-②: yes
Serial constraints cleared:
packages/adapters/hono/src/index.ts— no in-flight PR names it; #9364 (the error-path half) and #9455 (the #9389 exemption) both landed.scripts/check-route-envelope.mjs— held by this card for the round; sibling #9650 is instructed to stop-and-report rather than edit it. #9774 holdspackages/cli/src/commands/init.ts, disjoint. Siblings #9487 (packages/core) and #9650 (plugin-hono-server) are disjoint by constructionWhy this is dispatchable now — two state corrections
needs-user-decisionby triage" at handover. Both halves of that were stale:pm:queue.Blocked-by: #9389is discharged — [Decision] Pre-auth discovery/bootstrap payloads: inside BaseResponseSchema (coordinated objectui flip) or ruled exempt with reasons — today they are neither #9389 closedcompleted2026-08-18 02:16Z via merged PR chore(lint): record the ruled pre-auth exemption in the route-envelope gate #9455. Verified on GitHub, ⛔ not inherited from the card body, which still carries the live-lookingBlocked-by:line. That line is stale text; treat the upstream as closed.The ruling (⛔ not re-litigable)
Option A — envelope both discovery bodies. Add
success: trueto both{ data: await dispatcher.getDiscoveryInfo(prefix) }responses inpackages/adapters/hono/src/index.ts.⭐ This deliberately does NOT inherit #9389's B-exemption, and the reasoning is the load-bearing part: #9389 ruled a closed list of SPA-read pre-auth surfaces exempt, on the grounds that our own shells are the only readers and migration cost was high. Both grounds invert here — these two bodies are read by SDKs, codegen and AI clients, which is the envelope's core constituency, and the migration is one key. ⛔ Do not "simplify" this into an exempt entry; that option was considered and rejected by name.
Ruled sequence: sweep the SDK/codegen/AI-client readers of
body.dataon this mount first, then flip. ⛔ Not the other way round.Also in scope — the ratchet-pointer sweep. Four
ratchetentries inscripts/check-route-envelope.mjsname #9364, a card that is already closed, leaving the gate table pointing at dead anchors. Retarget all four — this file's own, plusruntime-config-plugin.ts,current-user-endpoints.tsandauth-plugin.ts(those three to their #9389 / PR #9455 resolution). This file'sunenvelopedcounter goes 2 → 0.Clause ② — my call, not triage's, and stated so it can be overruled
Triage did not annotate Clause ② here. I am declaring
yes, and the reasoning belongs on the record because a wrongnois a silent gap while a wrongyescosts only one contract-review sub-round:yeson Every 401 answers{error, message}with nocodekey, while every other error family carries{error, code}#9487 (adding a key to a published error body).check-route-envelope.mjsalready declares these bodies non-conformant and pins them as violations, so addingsuccessarguably restoresdeclared = enforcedrather than widening anything — the代裁side of the mechanical boundary test.yesbecause the ruling itself mandates a consumer reader sweep before the flip, which is what a wire-contract change looks like, and because the skill's instruction on doubt is to go up a tier.⇒ The contract-review sub-round may overrule this. If it does, ⛔ that is a correction to record, not a defeat.
Premise-first — verify before writing
origin/main. The card quotes them asapp.get(prefix, ...)andapp.get(${prefix}/discovery, ...); confirm both, and confirm theunenveloped 2counter is still pinned at 2.\.method\s*\??\s*\(finds zero optional-call sites (the real text is.method?.(— a.follows the?; use(\?\.)?), and a generic type argument between identifier and paren needs(<[^>]*>)?. Some call sites are unreachable by any literal grep and are found only by reading.objectui, generated clients), report before flipping — a cross-repo reader makes this a contract-first split with aBlocked-by:, ⛔ not a wider PR.PM mechanism assumptions — measure; falsifying them is a good run
success: trueis the only key needed forBaseResponseSchemaconformance on these two bodies, and that the gate'sunenvelopedcounter is what actually drops to 0. If the gate wants more thansuccess, report the delta.ratchetretargets are pure pointer edits with no counter movement. If retargeting moves a count, that is a finding — report it rather than absorbing it.premise_still_valid: falsewith no PR is a legitimate delivery. The issue body is a clue, ⛔ not a specification.Gates
Re-derive with⚠️ Known gap, twice-measured: it names neither
node scripts/pm/dispatch-gates.mjsfrom the actually changed paths — a lead, not a spec.pnpm lintnorcheck:slot-lookup. Runpnpm lintregardless.check:route-envelopeis obviously implicated here; run it before and after and report both readings.⛔ Never weaken a gate. If the envelope gate resists, root-cause it — an
exemptentry for these two files is the rejected option B and would be gate-weakening dressed as a fix.Shared verify lock⚠️ A piped vitest run masks the exit code, and a run can pass every test and still exit 1 at teardown — read verdicts from counts.
/tmp/os-heavy-verify.lock,-w 540with loop-retry, ⛔ never a long budget.Report at draft-PR time — ⛔ do not wait on CI convergence. Terminal report: machine-checkable fields only.
Generated by Claude Code
feat(adapters): envelope the hono adapter's two discovery bodies (#9436)
huangyiirene commentedon Aug 19, 2026
{ "issue": 9436, "status": "done", "branch": "claude/issue-9436-envelope-hono-discovery-bodies", "pr": "https://github.com/objectstack-ai/objectstack/pull/9814", "premise_still_valid": true, "summary": "Ruled option A implemented in the ruled sequence: reader sweep first (with controls; no intolerant reader found in-repo or in objectui, so no Blocked-by split), then both discovery bodies in packages/adapters/hono/src/index.ts flipped to { success: true, data }, then the gate table updated: the file's entry graduates to conformant {} and the exempt-block prose records the ruling. Zone 2b delta, measured not absorbed: the card's four-#9364-ratchet-pointer premise was already discharged on main before dispatch — PR #9455 had converted the three #9389 files to exempt entries and repointed this file's ratchet at #9436, so no pointer edits remained beyond this entry's graduation and zero counters moved by pointer work (the two remaining '#9364' strings in the gate script are self-test fixture literals). Cross-repo note: objectui's shape-discriminating readers (typeof body.success === 'boolean' && 'data' in body) mis-parse today's bare { data } and unwrap the post-flip shape correctly — the flip improves them, read-only sweep, no objectui change shipped.", "tests": "All readings at head c938eea. check:route-envelope before: 11 modules / 161 bodies / 7 conformant, 1 ratcheted (this file, unenveloped 2), 3 exempt, exit 0; after: 8 conformant, 0 ratcheted, 3 exempt, exit 0, self-test green — unenveloped counter 2 -> 0 (Zone 2a confirmed: success was the only key the gate needed). pnpm --filter @objectstack/hono test: 'Test Files 2 passed (2) / Tests 73 passed (73)', exit 0; tsc --noEmit on the package exit 0. Reverse verification from the committed state: de-enveloping one body -> gate exit 1 ('unenveloped: found 1, declared 0') and exactly the 3 pins on that route red (70 passed | 3 failed); restored via checkout from the branch, gate exit 0. Rebuild statement: both legs ran the gate on source text and vitest on relatively-resolved src imports — no dist in either chain, so no rebuild was required for either leg. Derived union via node scripts/pm/dispatch-gates.mjs with no hand-fed paths (matched exactly this PR's 4 files): check:changeset-gate-self-tests, check:cross-package-test-inputs, check:objectui-changeset, check:route-envelope, check:test-source-alias, check:type-source-resolution, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, docs-audit/check-affected-docs, and convention-triggered check:query-options-erasure, check:type-check-coverage, check:type-check-debt (packages closure built first, 70 turbo tasks, 0 cached; re-measure: 33 entries, none above recorded), check:engine-double-contract, check:where-matcher — all exit 0. pnpm lint (mandated beyond the derivation): exit 0. check:nul-bytes: green. Reader-sweep controls: .well-known literal 20 subject / 20 intolerant-spelling; objectui fetch( count 156; examples 197 ts files scanned, 0 readers (2 hits were prose); cli 7 mentions all comments vs 17 fetch( sites; optional-call spelling (\\?\\.)? variant 74 vs naive 74.", "open_questions": [], "out_of_scope_findings": ["filed as #9813: packages/runtime/src/dispatcher-plugin.ts serves the same machine-read { data } discovery shape on /.well-known/objectstack (unconditional) and the REST-less {prefix}/discovery fallback — outside the #9436 ruling's named file and outside check-route-envelope's three scan populations, with the two documenting doc pages noted for whichever way that fork is ruled"] }Generated by Claude Code
Generated by Claude Code
huangyiirene commentedon Aug 19, 2026
ACCEPT — PR #9814
domain:cliseat, sessionsession_01WeN7F6jQFpcqW2BN56RdPa, round 23. Reviewer of record. Checked against GitHub and the tree, ⛔ not against the report's self-description. ⏳ Landing is gated on CI convergence — see the bottom.Checklist
main✅ · first lineFixes #9436✅, and it is the right keyword: the PR discharges the card whole (both bodies + the gate table), so closing on merge is correct rather than aPart of.get_files, all inside the declared surface:packages/adapters/hono/src/index.ts, its test,scripts/check-route-envelope.mjs, the changeset. ⛔ Nopackages/spec/**. ⛔ No governed path (docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md) — no ACCEPT fork, this seat may land it.http_requests_totalnever sees auth routes or the REST data API — the two highest-traffic inbound surfaces are outside the only HTTP counter the docs tell operators to monitor #9650 was instructed to stop-and-report rather than touchscripts/check-route-envelope.mjs; this PR is its sole writer this round, as designed.minoron@objectstack/honowith the ADR-0087 marker reasoned, not asserted. Correct: additive wire change on a published package, soskip-changesetwould have been wrong.Spot-checks I ran rather than accepted
① The
#9364dangling-pointer claim — the one that decides whetherFixesis honest. The dev reported Zone 2b falsified: the card's "fourratchetentries name #9364" premise was already discharged onmainby PR #9455, leaving only this file's entry to graduate. Verified independently on the branch: every surviving#9364string is either historical prose (5 sites) or a self-test fixture literal (auditPluginRouteModule('x.ts', { …, ratchet: '#9364' … })at 1775/1793) — ⛔ none is a live table pointer. The only liveratchet:entries left (×2) name #9559, which is open. ⇒ The gate table points at no dead anchors, so the card is genuinely fully discharged.⭐ Worth naming: the dev measured the card's own premise false and reported the delta instead of quietly doing less work. A shrunken scope reported with its measurement is the good outcome here.
② The sibling-surface question — this card family's characteristic failure mode. The diff leaves a
.well-knownroute sitting directly beneath the two fixed bodies in the same file, which is exactly where "fixed one surface, reproduced the card one surface over" would hide. Read it:app.get('/.well-known/objectstack', (c) => c.redirect(prefix))— a redirect, ⛔ no JSON body, so no envelope question in this file. And zero bare{ data }responses survive anywhere in it (control: 7c.json(sites in the same file, so the query is live rather than silently matching nothing). ⇒ #9813 correctly names a different file (packages/runtime/src/dispatcher-plugin.ts), not residue left behind here.③ Reader sweep ran first, as ruled, and every reported zero carries a control —
.well-knownliteral 20 subject / 20 intolerant; objectuifetch(156 as a live harness; examples 197 files scanned with both hits read and classified as prose, ⛔ not counted. No intolerant reader found, so no cross-repoBlocked-by:split — the conclusion I would otherwise have had to take on faith.⭐ The cross-repo finding is better than "unaffected": objectui's readers discriminate on
typeof body.success === 'boolean' && 'data' in body, which today's bare{ data }fails — so the flip does not merely preserve those readers, it fixes them. That is the ruling's own rationale arriving as a measurement.④ Reverse verification is real. De-enveloping one body from the committed state turned the gate red with the specific message (
unenveloped: found 1, declared 0) and exactly the 3 route pins red (70 passed / 3 failed) — a targeted ablation, ⛔ not a template one, and it states that nodistwas in either leg's chain so the result is not a stale-build artifact.Deviation
⛔ None from the ruling. Option A implemented in the ruled sequence (sweep → flip → gate table). The exempt-block prose now records both sides of the #9389/#9436 boundary as one rule — who reads the body decides — which is more than was asked for and is the right place for it.
Clause ② — my
yesstands, and the gate does not blockI declared⚠️ Recorded explicitly because "the gate did not fire" and "the gate was never evaluated" are indistinguishable after the fact.
Clause-②: yesat claim time on my own judgment. The enqueue gate's declaration limb therefore fires. It does not block: the card was dispatched at the contract-review tier (CONTRACT_REVIEW_TIER), not below it, so noneeds:contract-reviewlabel is owed and the gate is satisfied by the dispatch itself.⏳ Landing — ⛔ not yet
Of the six required checks, read by name from each job's own
conclusion(⛔ never an aggregate):Build Coresuccess;TypeScript Type Check,Lint & Repo Gates,Test Core(3 shards),Dogfood Regression Gate(3 shards) andTemporal Conformanceallin_progress.in_progressis not a pass. ⇒ PR stays draft; flip-ready and enqueue happen on convergence, and PR-side green is necessary but ⛔ never sufficient for the queue's full-suite profile.Out-of-scope finding #9813 filed unassigned for triage grading — ⛔ not graded by this seat.
Generated by Claude Code
feat(runtime): envelope the dispatcher's two discovery bodies (#9813)
fix(api): envelope the plugin-mounted Hono error paths — six refusal …
feat(adapters): envelope the hono adapter's two discovery bodies (obj…
feat(runtime): envelope the dispatcher's two discovery bodies (object…