You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[finding] class closure: the runtime dispatcher's /meta list still diverges from RestServer's off the gate path (?id=, ?object=, plural /meta/docs bodies, locale) and refuses a public audience to anonymous callers #20320
Filing gate: ① a defect with a named landing site, packages/runtime/src/domains/meta.tshandleMetadataRequest: its one-segment LIST branch, and the anonymous-deny block at its entry. Filed as the family's class-closure card. Row A is class (a) and row B is class (b), each with reach: measured at a public door through dispatch(), which is the createHonoApp catch-all's delegate.
Found by the os-dev round on #20237 (PR #20319, which puts the dispatcher's list through the same per-caller gate RestServer uses). Both rows were confirmed at source by that PR's at-tier contract review (record 5859805795). Filed by the domain:cli execution seat (#6024, session_01UYBdGBzWSrAMzpW8ah3GbP). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
The family
The dispatcher's /meta read is a second implementation of RestServer's. AGENTS.md 〈Route & surface ownership〉 rule 1 says one owner per route. The PER-CALLER half of the family is closed or closing:
What remains is every other place the two answers diverge. None of them leaks gated content, because the per-caller gate prunes first. They are correctness and contract drift, enumerated below.
Row A (class a): list projections RestServer honours and the dispatcher ignores
Measured after PR #20319's fix, dispatch() vs RestServer's GET /meta/:type:
At source: the dispatcher's list branch reads only query.package and query.preview, and slimDocList compares the raw segment (type !== 'doc').
Row B (class b): a public book audience is unreachable anonymously through the dispatcher
Take a book with audience: 'public' that claims crm_intro. Anonymous GET /meta/doc and GET /meta/book then answer 401 UNAUTHENTICATED through the dispatcher, where RestServer lists [crm_intro] and [public_guide].
At source, handleMetadataRequest opens with shouldDenyAnonymous({userId, isSystem}), with no path or method (packages/core/src/security/anonymous-deny.ts about :152).
RestServer's umbrella gate grants anonymous reachability of book and doc GETs via isPublicAudienceRead (rest-server.ts about :2752). Its docblock reads: 「so audience: 'public' works on a secure-by-default deployment instead of only on one that opened its whole data plane」.
The contract is ADR-0046 §6.7's public audience.
It fails closed: it withholds, and exposes nothing.
Seam: spec:BookSchema.audience ('public') → runtime:packages/runtime/src/domains/meta.ts handleMetadataRequest (the anonymous-deny block).
The closure pin this card owes
One census over (list type × query parameter × caller) that asserts the dispatcher answers what RestServer answers, including anonymous callers against a public audience. It should be derived from RestServer's own list handler where feasible, so the next projection added there fails the census instead of drifting.
Direction (for triage, ⛔ not presumed here)
Extend the shared seam: the list projections become transport-neutral beside createMetaListReadGate, and the dispatcher's anonymous gate learns isPublicAudienceRead.
Dedupe words: dispatcher meta list query params ignored · handleMetadataRequest id object filter dropped · slimDocList plural docs content · dispatcher anonymous public book 401 · catch-all meta list projection divergence
Filing gate: ① a defect with a named landing site,
packages/runtime/src/domains/meta.tshandleMetadataRequest: its one-segment LIST branch, and the anonymous-deny block at its entry. Filed as the family's class-closure card. Row A is class (a) and row B is class (b), each withreach:measured at a public door throughdispatch(), which is thecreateHonoAppcatch-all's delegate.Found by the
os-devround on #20237 (PR #20319, which puts the dispatcher's list through the same per-caller gateRestServeruses). Both rows were confirmed at source by that PR's at-tier contract review (record5859805795). Filed by thedomain:cliexecution seat (#6024,session_01UYBdGBzWSrAMzpW8ah3GbP). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.The family
The dispatcher's
/metaread is a second implementation ofRestServer's. AGENTS.md 〈Route & surface ownership〉 rule 1 says one owner per route. The PER-CALLER half of the family is closed or closing:What remains is every other place the two answers diverge. None of them leaks gated content, because the per-caller gate prunes first. They are correctness and contract drift, enumerated below.
Row A (class a): list projections
RestServerhonours and the dispatcher ignoresMeasured after PR #20319's fix,
dispatch()vsRestServer'sGET /meta/:type:RestServerRestServerdoes itGET /meta/app?id=crm(holder)[crm]?id=blockGET /meta/view?object=nope[]?object=view switcherGET /meta/docs(plural, no?include=content)metaTypeSingularresolveDocLocaleapiserved-set facematchEndpointfaceAt source: the dispatcher's list branch reads only
query.packageandquery.preview, andslimDocListcompares the raw segment (type !== 'doc').Row B (class b): a
publicbook audience is unreachable anonymously through the dispatcherTake a book with
audience: 'public'that claimscrm_intro. AnonymousGET /meta/docandGET /meta/bookthen answer 401UNAUTHENTICATEDthrough the dispatcher, whereRestServerlists[crm_intro]and[public_guide].handleMetadataRequestopens withshouldDenyAnonymous({userId, isSystem}), with no path or method (packages/core/src/security/anonymous-deny.tsabout :152).RestServer's umbrella gate grants anonymous reachability of book and doc GETs viaisPublicAudienceRead(rest-server.tsabout :2752). Its docblock reads: 「soaudience: 'public'works on a secure-by-default deployment instead of only on one that opened its whole data plane」.publicaudience.It fails closed: it withholds, and exposes nothing.
Seam:
spec:BookSchema.audience('public') →runtime:packages/runtime/src/domains/meta.ts handleMetadataRequest(the anonymous-deny block).The closure pin this card owes
One census over (list type × query parameter × caller) that asserts the dispatcher answers what
RestServeranswers, including anonymous callers against apublicaudience. It should be derived fromRestServer's own list handler where feasible, so the next projection added there fails the census instead of drifting.Direction (for triage, ⛔ not presumed here)
createMetaListReadGate, and the dispatcher's anonymous gate learnsisPublicAudienceRead./metareads: [finding] the runtime dispatcher's /meta item reads apply NO per-caller read gate: through a catch-all host, GET /meta/doc/:name serves a permission-set-gated doc body to a non-holder, and /meta/app/:name serves requiredPermissions-gated entries #20193 rejected this for the item reads, becausecreateHonoAppcannot mount REST and ADR-0076 item 9 keeps the catch-all as the cloud fallback. Re-check that before choosing it.Dedupe
MCP issue search in this repository, run 2026-09-27:
/approvals/requests— decide whether the closed-parameter-set rule becomes ingress policy #7606 and UI view route speaks two dialects — REST's path-param form is unreachable by the SDK's query-param form (#3587 finding) #3611 (all closed) are other route-dialect cards. None is this defect.Dedupe words:
dispatcher meta list query params ignored·handleMetadataRequest id object filter dropped·slimDocList plural docs content·dispatcher anonymous public book 401·catch-all meta list projection divergenceGenerated by Claude Code