Repository navigation
runtime dispatcher's two discovery bodies (.well-known + REST-less {prefix}/discovery fallback) are the machine-read { data } class #9436 ruled on, and sit outside check-route-envelope's scan #9813
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Aug 19, 2026 os-support-ai commented
on Aug 19, 2026 CollaboratorMore actionsTriage (triage seat, session
session_019ZyCHcjXeTFPiS8RWRk4nN, 2026-08-19 round): lands inpackages/runtime/src/dispatcher-plugin.ts(envelope flip) plusscripts/check-route-envelope.mjs(scan-surface growth) and the two named doc lines →domain:cli(anchoring rule: the behavioural fix lands inruntime). Type: Bug — the sites violate the envelope contract as ruled.Ruling inheritance (sibling-of-adjudicated-family rule, not a new adjudication): the #9436 maintainer ruling (2026-08-18, option A — envelope machine-read discovery bodies, because they are the envelope's core constituency and the migration is one additive key) is inherited here with its reason intact. The mother ruling's rationale applies identically: same body shape (
{ data }, one key short), same machine-read consumer population (SDKconnect()fallback probe reads/.well-known/objectstack), reader tolerance already measured in the same sweep (additivesuccess: truebreaks no known reader), and the #9389 exemption is a closed list these sites are not on. No branch-specific semantic difference found → straight topm:queue; reopening the decision would only be warranted if implementation measures a reader that chokes on the added key — in that case stop and report the fork, do not silently pick a side.Dispatch-side notes (advisory): this changes a public response shape → clause-② content limb applies (
Clause-②: yesin the claim; contract-review dispatch tier). The gate half (fourth scan population or moving the sites behind an audited seam) rides the same PR only if small; otherwise file it as adomain:devxfollow-up rather than widening. Size/model suggestion: M.
Generated by Claude Code
huangyiirene commented
on Aug 19, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 24
Session:session_01WeN7F6jQFpcqW2BN56RdPa
Branch:claude/issue-9813-dispatcher-discovery-envelope
Worktree:objectstack-issue-9813
Domain:domain:cli
File surface:packages/runtime/src/dispatcher-plugin.ts(the two discovery bodies only —:775and:801on the merged ref),scripts/check-route-envelope.mjs(the scan-surface half, only if small — see below),content/docs/api/index.mdxandcontent/docs/protocol/kernel/http-protocol.mdx(the two documented lines),.changeset/**(stop on breach; explain in the report)
Container & model:M,mode:subagent,model:contract-review tier attempted first (CONTRACT_REVIEW_TIERinscripts/pm/dispatch-gates.mjs);--tierreturns no path-derived mandate, ⛔ not recalled — the mandate is Clause ②, judged from card content
Clause-②: yes — triage-declared; this changes a published response shape read by SDK/codegen clients
Serial constraints cleared:packages/runtime/src/dispatcher-plugin.tsis 🔓 free — the card that held it, #9823, landed (b2789adbdb, PR #9848 merged). That hold is the reason this card waited a full round; the maintainer ruled 「维持 2 个,等 #9823 落地」 rather than relax same-file serial to region level, and it has now been honoured. No other in-flight PR names this file. #9805 (packages/rest) also landedUnlock re-verified on the merged ref, ⛔ not inherited
⚠️ The card's line numbers are stale — locate by text. It says ~738 / ~765; onorigin/mainat45862a53d8the two sites are at:775and:801::775 res.json({ data: await dispatcher.getDiscoveryInfo(prefix) }); :801 res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });Both still bare. Control:
success: trueappears zero times in that file, so there is no pre-existing envelope to confuse the reading.⭐ Independently confirmed the card's second-half claim:
scripts/check-route-envelope.mjscontains no mention ofdispatcher-pluginat all — the gate genuinely cannot see these sites, whichever way the fork is ruled.Scope — the ruling is INHERITED, ⛔ not a new adjudication
Triage applied the sibling-of-adjudicated-family rule: the #9436 maintainer ruling (2026-08-18, option A — envelope machine-read discovery bodies, because they are the envelope's core constituency and the migration is one additive key) carries here with its reason intact. Same body shape, same machine-read consumer population (
/.well-known/objectstackis the SDKconnect()fallback probe), reader tolerance already measured in the same sweep, and #9389's exemption is a closed list these sites are not on.⇒ Add
success: trueto both bodies. Additive only.⛔ One reopening condition, and only one: if implementation measures a reader that chokes on the added key, STOP and report the fork — ⛔ do not silently pick a side. That is the sole circumstance in which the inherited ruling does not settle this.
The gate half — bounded, with an explicit escape
The second half (grow
check-route-envelope.mjsa fourth scan population for express-styleres.json(...)on anIHttpServer, or move these sites behind an already-audited seam) rides this PR only if it is small.⚠️ If it turns into real gate-architecture work, file it as adomain:devxfollow-up and ship the envelope flip alone — ⛔ do not widen this PR into a gate redesign. Triage said exactly this and I am not overriding it.PM mechanism assumptions — measure; falsifying them is a good run
- That both sites are reachable and worth flipping. The
{prefix}/discoveryone is registered only whencom.objectstack.rest.apiis absent (REST-less compositions) — verify that condition still holds rather than assuming, and say so if the route is dead in practice. - That the two doc lines are the complete documented set. Re-sweep with a control; ⛔ a bare zero is not an answer.
- That no changeset-visible behaviour outside
@objectstack/runtimemoves.
Gates
Re-derive with
node scripts/pm/dispatch-gates.mjsfrom the actually changed paths.⚠️ Known gap, three confirmed instances (#9721 / #9700): it names neitherpnpm lintnorcheck:slot-lookup— runpnpm lintregardless.⚠️ If you touchcheck-route-envelope.mjs, run that gate before and after and report both readings; ⛔ never weaken it — anexemptentry for these two sites is the rejected option B wearing a fix's clothes.Read verdicts from reported counts, ⛔ never a piped exit code. Report at draft-PR time — ⛔ do not wait on CI.
⚠️ Post the report as an issue comment on this card with theos-dev-reportmarker first; the terminal copy is the accelerator, ⛔ not the record (that channel dropped two reports this shift).
Generated by Claude Code
- That both sites are reachable and worth flipping. The
- added a commit that references this issue
on Aug 19, 2026 huangyiirene commented
on Aug 19, 2026 CollaboratorAuthorMore actions{ "issue": 9813, "status": "done", "branch": "claude/issue-9813-dispatcher-discovery-envelope", "pr": "https://github.com/objectstack-ai/objectstack/pull/9949", "premise_still_valid": true, "summary": "Inherited #9436 ruling implemented additively: both discovery bodies in packages/runtime/src/dispatcher-plugin.ts flipped to { success: true, data } (located by text; the claim's :775/:801 held at 45862a53d8), exact top-level key set pinned in dispatcher-plugin.routes.test.ts, and the two doc pages updated. Zone 2 results: the {prefix}/discovery registration condition is live, not dead — kernel.hasPlugin('com.objectstack.rest.api') still gates it as the REST-less fallback owner; the reopening condition did not fire — no measured reader chokes (SDK connect() body.data||body, QA http-adapter 'routes' in body, objectui success-boolean discriminators which the flip fixes rather than breaks); the two doc PAGES are the complete set but held THREE wrap-documenting spots, not two — http-protocol.mdx's REST-less callout at ~:224 also stated the { data } envelope and was missed by the card, updated with the other two. Gate half judged small and rode: check-route-envelope.mjs's scanner receiver set is now a parameter and an ENUMERATED fourth population (IHTTP_ROUTE_MODULES) audits dispatcher-plugin.ts through the unchanged auditPluginRouteModule grammar — no exempt entry, no counter weakened; the discovery walk for the express dialect is real gate-architecture work (zero-arg fetch res.json() READ shares the receiver spelling; 20 candidate files measured) and is filed as the domain:devx follow-up #9937. The new population surfaced one further off-envelope literal body (SSE-fallback res.json({ events })) — outside the inherited ruling's reach (different consumer population), filed as #9936 and pinned unenveloped: 1, ratchet #9936 via the gate's own first-registration path.", "tests": "All readings at head f78a83e0cc. check:route-envelope before: 3 populations (10 route modules: 7 conformant/2 ratcheted/1 exempt; 16 domains; 11 hono modules, 161 bodies: 8/0/3), exit 0, self-test green; after: same three unchanged plus population 4 (1 module, 10 bodies: 0 conformant/1 ratcheted/0 exempt), exit 0, self-test green including 4 new express-receiver pins. Ablation from the committed state, both legs stating their rebuild: NO dist in either chain (gate reads source text; the pin test imports ./dispatcher-plugin.js relatively through vitest's transform), so neither leg needed a rebuild — mutate leg: de-enveloping the .well-known body -> gate exit 1 reading 'unenveloped: found 2, declared 1' (exit read from an unpiped run; the piped grep read 0, which is the trap) and exactly the mutated route's pin red ('GET /.well-known/objectstack success flag: expected undefined to be true', 1 failed | 15 passed); restore leg via git checkout HEAD -- <path>: gate exit 0, 16/16 passed. pnpm --filter @objectstack/runtime test: 'Test Files 176 passed (176) / Tests 2629 passed (2629)', exit 0; typecheck (tsc --noEmit, script name echoed — not a zero-match green): exit 0. Downstream integration (consumer direction, prefix-filter equivalent by name): @objectstack/http-conformance against REBUILT runtime dist — 5 files / 86 tests passed, exit 0. Derived union via node scripts/pm/dispatch-gates.mjs with NO hand-fed paths (matched exactly this PR's 6 files): check:changeset-gate-self-tests, check:cross-package-test-inputs (pnpm + node spellings), check:doc-anchors, check:docs-audit-scope, check:docs-redirects, check:objectui-changeset, check:published-readme-links, check:role-word, check:route-envelope, check:slot-lookup, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, docs-audit/check-affected-docs, spec check:empty-state/check:liveness/check:strictness-ledger/check:variant-docs, and convention-triggered check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage, check:type-check-debt --re-measure (packages closure built first per its instruction, 70 turbo tasks; 33 ledger entries re-measured, none above recorded) — all exit 0. Mandated beyond the derivation: pnpm lint exit 0; check:nul-bytes green (6310 files). Sweep populations: readers — 38 well-known hits across 4457 non-dist .ts files in-repo; objectui at 97fba31, success-boolean discriminator sites found with control 156 fetch( sites; docs — 12 well-known hits across 393 mdx files, each hit read and classified (3 wrap-documenting spots updated; realtime-protocol.mdx :110 shows the inner document with no envelope at all, a pre-existing elision equally true post-flip, left; client-sdk.mdx :82 documents probing not shape, left).", "open_questions": [], "out_of_scope_findings": [ "filed as #9936: dispatcher-plugin's SSE-fallback res.json({ events }) is off-envelope — different consumer population from the discovery bodies, so the inherited ruling does not reach it; pinned in the new gate population as unenveloped 1, ratchet #9936", "filed as #9937 (finding, domain:devx per dispatch instruction): the express-style gate population is enumerated, not discovered — a new res.json module on an IHttpServer still joins the repo unaudited; the discovery walk needs a read/write discriminator (fetch's zero-arg res.json() READ) and first-audit verdicts for 20 measured candidate files" ] }
Generated by Claude Code
Generated by Claude Code
huangyiirene commented
on Aug 19, 2026 CollaboratorAuthorMore actionsACCEPT — PR #9949
domain:cliseat, sessionsession_01WeN7F6jQFpcqW2BN56RdPa, round 25. Reviewer of record; checked against GitHub and the diff, ⛔ not against the report's self-description. ⏳ Landing gated on CI.⭐ Report posted to this card with the
os-dev-reportmarker first, read back and verified. That channel discipline was the explicit ask after it dropped two reports earlier today, and it was honoured.Checklist
- Form — draft · base
main· 6 files. ⛔ No governed path:content/docs/**is not in the governed set (docs/adr/**·.claude/**·skills/**·AGENTS.md·CLAUDE.md), so no ACCEPT fork and this seat may land it. - Boundary HELD — the only
dispatcher-plugin.tsedits are the two discovery bodies. ⛔ Endpoint-route 401 in dispatcher-pluginmountRouteOnServerwrites an inline flat body with nocodekey — the one producer #9487's constant change does not reach #9823's 401 arm, landed hours ago in the same file, is untouched. - Clause ② — declared
yes; dispatched at the contract-review tier ⇒ the enqueue gate is satisfied by the dispatch itself, noneeds:contract-reviewowed. - Changeset —
@objectstack/runtimeminor with a reasoned ADR-0087 marker. Correct for an additive wire change.
The three findings that make this more than a two-line flip
① Zone 2 falsified in the useful direction: two doc pages, but THREE wrap-documenting spots. The card named
content/docs/api/index.mdxandhttp-protocol.mdx. The dev foundhttp-protocol.mdxdocuments the{ data }envelope in three places — including the REST-less callout at ~:226, which the card missed — and updated all three.⚠️ A sweep that stopped at the card's list would have left a doc page contradicting the shipped shape.Sweep populations stated, and the hits read rather than counted: 12
well-knownhits across 393 mdx files, each classified; two deliberately left with reasons (realtime-protocol.mdxpre-existing envelope elision,client-sdk.mdxdocuments probing not shape).② The reopening condition was tested, not assumed. I gave exactly one: if a measured reader chokes on the added key, stop and report the fork. It did not fire — SDK
connect()(body.data || body), QA http-adapter ('routes' in body), objectui'ssuccess-boolean discriminators, which the flip fixes rather than breaks. And the{prefix}/discoveryregistration condition is live, not dead:kernel.hasPlugin('com.objectstack.rest.api')still gates it, so both sites were worth flipping.③ The pin is stronger than asked for. It asserts the exact top-level key set —
Object.keys(body).sort() === ['data','success']— not merely thatsuccessis true, with the reason written down: "an extra sibling key would be the strayKeys dialect arriving back."The gate half — judged small, and it earns its place
I bounded this: ride the PR only if small, else file
domain:devx. The dev did both, correctly.What rode:
scanHonoRouteSourcegained areceiversparameter (default unchanged), and a fourth enumerated populationIHTTP_ROUTE_MODULESauditsdispatcher-plugin.tsthrough the unchangedauditPluginRouteModulegrammar — one grammar, two receiver dialects. ⛔ No exempt entry, ⛔ no counter weakened.⭐ The self-test pins the blind spot's own mechanism: the same source reads 0 bodies under the Hono receivers and 1 unenveloped under the express receivers — "which is exactly how dispatcher-plugin.ts's discovery bodies sat invisible beside a green surface 3." That is a regression test for the reason the gate missed it, not just for the miss.
What did not ride, and why the split is right: a genuine discovery walk needs a read/write discriminator, because
fetch'sResponse.json()is a zero-argument READ on the same receiver nameres— 20 non-test files carry theres.json(spelling today, most of them fetch readers. Filed as #9937 (domain:devx), and the table says ENUMERATED, not discovered in its own header so nobody mistakes it for a closed net.⚠️ On the new ratchet entry (unenveloped: 1,ratchet: #9936): this is not gate-weakening. Before this PR the file sat in no population and that residue was invisible to every check in the repo; now it is counted, tracked, ticks down only, and its anchor #9936 is open and in this lane's queue — a live pointer, not the dangling kind #9436 spent half a card cleaning up.④ The SSE-fallback body was found by the new population, not by luck —
res.json({ events }), a different consumer population (callers who asked for a stream), so the inherited ruling correctly does not reach it. Filed as #9936.Verification
Ablation from the committed state, both legs stating their rebuild posture (no dist in either chain, with the reason): de-enveloping the
.well-knownbody → gate exit 1,unenveloped: found 2, declared 1, and exactly the mutated route's pin red; restore → gate exit 0, 16/16.⚠️ ⭐ And the trap was hit and named: "exit read from an unpiped run; the piped grep read 0, which is the trap." That is the masked-exit-code failure mode catching a real reviewer, and being reported instead of silently producing a false green.Downstream integration run against rebuilt runtime dist:
@objectstack/http-conformance5 files / 86 tests.@objectstack/runtime176 files / 2629 tests. Gate union re-derived with no hand-fed paths, matching exactly this PR's 6 files;pnpm lintandcheck:nul-bytesrun beyond the derivation.Deviation
⛔ None. Scope, boundary and the file-hold on the sibling card all honoured; the gate half respected the smallness bar in both directions rather than picking whichever was easier.
⏳ Landing
Build Coresuccess;TypeScript Type Check,Lint & Repo Gates,Test Core(3 shards),Dogfood Regression Gate(3 shards),Temporal Conformancein_progress.in_progressis not a pass — PR stays draft; flip-ready then enqueue on convergence.
Generated by Claude Code
- Form — draft · base
- added a commit that references this issue
on Aug 20, 2026
Found while implementing #9436 (which stays open until its own PR lands; nothing here closes it). Measured on
mainat 4c260cd. Filed unassigned — recording, not starting.The gap
packages/runtime/src/dispatcher-plugin.tsserves two discovery bodies in the exact shape #9436 was ruled on, and neither is visible toscripts/check-route-envelope.mjs:GET /.well-known/objectstackres.json({ data: await dispatcher.getDiscoveryInfo(prefix) }), ~line 738GET {prefix}/discoverycom.objectstack.rest.apiis absent (REST-less compositions; otherwise ceded to REST)Both are
{ data }with nosuccesskey — one key short ofBaseResponseSchema, the same distance the hono adapter's discovery bodies were before #9436's ruling (2026-08-18, option A: envelope them, because machine-read discovery surfaces are the envelope's core constituency and the migration is one key).Why this is a triage call, not an inherited fix
The #9436 ruling's operative text names only
packages/adapters/hono/src/index.ts. The consumer population here is the same —/.well-known/objectstackis the SDK's own discovery fallback probe (connect()inpackages/client/src/index.tsreads it), pre-auth, machine-read — so the ruling's reasoning extends naturally, but extending a ruling is not an implementer's move. Note the boundary drawn in #9389 does not cover these either: that exemption is a closed list of SPA-read shell-bootstrap surfaces, and these are not on it.Reader tolerance is already measured (same sweep as #9436, controls reported there): the SDK unwraps
body.data || body; the QA http-adapter discriminates on'routes' in body; objectui's readers unwrap only whentypeof body.success === 'boolean' && 'data' in body. An additivesuccess: truebreaks none of them.Second half: the gate cannot see these sites either way
check-route-envelope.mjsaudits three populations: response-envelope write sites,runtime/src/domains/*(returned payload objects), and Hono-context modules listed inPLUGIN_ROUTE_MODULES(judgingc.json(...)/ctx.json(...)calls).dispatcher-plugin.tswrites through an express-styleres.json(...)on anIHttpServer, which is none of the three — so whichever way the fork is ruled (envelope, or exempt-with-reason), the verdict has no place in the gate table to be recorded until the scan surface grows a fourth population (or these two sites move behind an audited seam).If the fork is ruled toward the envelope
Two doc pages document the current
{ data }wrap for the well-known endpoint and would ride the flip:content/docs/api/index.mdx(~line 154) andcontent/docs/protocol/kernel/http-protocol.mdx(~lines 111–115).Related: #9436 (the ruling on the hono adapter's mount) · #9389 (the pre-auth exemption boundary, a closed list) · #9559 (rest-server's own envelope conversion;
dispatcher-plugin.tsis not in its scope).Generated by Claude Code