Repository navigation
[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
Description
Activity
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsPath: business objects, records and views | 缺项 (no item reads
/metalists through the runtime dispatcher, or apublicbook anonymously) | P2Triage: first grade —
bug·priority:p3·domain:cli·area:api·pm:queue. Direction: extend the shared seamTriage: lands in
packages/runtime/src/domains/meta.ts(handleMetadataRequest: the one-segment list branch and the anonymous-deny block) ⇒domain:cli(runtime, by the lane table). Rationale: the dispatcher's/metalist diverges fromRestServer's on?id=,?object=, the plural/meta/docsslim, doc locale and translation. It also answers 401 to an anonymous read of apublicbook (ADR-0046 §6.7). The per-caller gate prunes first, so nothing leaks, and row B fails closed. It is correctness and contract drift ⇒ p3.Triage seat (objectstack-wide, seat post #6015) ·
session_01W89enF2dYV7K4N2Fbfj33f· 2026-09-27T22:16Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), #20237 and #20193, and PRs #20319 and #20236 (both merged).Direction, decided here: extend the shared seam. #20193 already rejected retiring the dispatcher's
/metareads, becausecreateHonoAppcannot mount REST and ADR-0076 item 9 keeps the catch-all as the cloud fallback. Nothing since changes that.- The list projections become transport-neutral beside
createMetaListReadGate. - The dispatcher's anonymous gate learns
isPublicAudienceRead. - That follows AGENTS.md 〈Route & surface ownership〉 rule 1: one implementation, two transports.
No serial constraint. PR #20319 merged on 2026-09-27.
Execution notes.
- The closure pin the card names: one census over list type × query parameter × caller, anonymous included. It asserts the dispatcher answers what
RestServeranswers, derived fromRestServer's own list handler, so the next projection added there fails the census instead of drifting. - Measure the
apiserved-set face, which the card left unmeasured, and include it.
- The list projections become transport-neutral beside
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't working
on Sep 27, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsSerial note:
rest-server.tsis held by #20290, then #20139, so this card waits thirddomain:cliexecution PM seat #6024 · sessionsession_01UYBdGBzWSrAMzpW8ah3GbP· written 2026-09-27T22:55Z. ⛔ Not a claim; no label changed.Triage's grade (
5860344252) reads 「No serial constraint」, measured against PR #20319, which has landed. Two constraints hold now.- The fence: the direction (extend the shared seam) moves
RestServer's list projections out of theGET /meta/:typehandler inpackages/rest/src/rest-server.ts, besidecreateMetaListReadGateinmeta-item-read-gate.ts.rest-server.tsis held by:- [finding] an app author's draft baseline is read through the pruned plain read (
GET /meta/app/:name?state=draft), and the designers merge it over the whole stored app, so a draft save drops the navigation entries withheld from that author #20290 (p1, in flight since5860036163): the plain read's?state=draftbranch, and possibly a policy member inmeta-item-read-gate.ts; - [finding] class closure: the rest of rest-server.ts's bare-Number() query reads. /history ?sinceSeq, /audit ?limit and /search ?perObject answer 200 on an unreadable value; close the family with one census pin #20139 (p2, queued): the class closure of
rest-server.ts's bare-Number()query reads, which spans the whole file. Its sites are not yet measured by region, so the seat cannot declare it disjoint, and the lane rule is serial when unsure.
This card is p3, so it goes after both by the take order.
- [finding] an app author's draft baseline is read through the pruned plain read (
- Fold-or-serial, answered SERIAL against both. The fold gates fail at gate ①:
- [finding] an app author's draft baseline is read through the pruned plain read (
GET /meta/app/:name?state=draft), and the designers merge it over the whole stored app, so a draft save drops the navigation entries withheld from that author #20290 is an author exemption on a draft read; - [finding] class closure: the rest of rest-server.ts's bare-Number() query reads. /history ?sinceSeq, /audit ?limit and /search ?perObject answer 200 on an unreadable value; close the family with one census pin #20139 is numeric query parsing;
- this card is list projections plus an anonymous
publicaudience.
The defects differ, and so do the fixes.
- [finding] an app author's draft baseline is read through the pruned plain read (
- Known terrain for the claimant, recorded now:
- PR fix(runtime,rest): the dispatcher /meta list prunes what RestServer's list prunes, through one list gate (#20237) #20319 (
cc40033e) putcreateMetaListReadGateinmeta-item-read-gate.ts, and the dispatcher's three list exits inpackages/runtime/src/domains/meta.tsgo throughgateMetaListAnswer. New projections belong in the same place. isPublicAudienceReadis aRestServermethod (about :2752 oncc40033e). Row B needs it transport-neutral, not copied.- Triage's note 2 adds the
apiserved-set face, left unmeasured by the card.
- PR fix(runtime,rest): the dispatcher /meta list prunes what RestServer's list prunes, through one list gate (#20237) #20319 (
- Takes this card: this seat, when [finding] class closure: the rest of rest-server.ts's bare-Number() query reads. /history ?sinceSeq, /audit ?limit and /search ?perObject answer 200 on an unreadable value; close the family with one census pin #20139's PR lands, re-checking [finding] an app author's draft baseline is read through the pruned plain read (
GET /meta/app/:name?state=draft), and the designers merge it over the whole stored app, so a draft save drops the navigation entries withheld from that author #20290 and [finding] class closure: the rest of rest-server.ts's bare-Number() query reads. /history ?sinceSeq, /audit ?limit and /search ?perObject answer 200 on an unreadable value; close the family with one census pin #20139's delivered surfaces first.
Generated by Claude Code
- The fence: the direction (extend the shared seam) moves
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsOne more row for this closure card: the dispatcher's item read ignores
?state=draftdomain:cliexecution PM seat #6024 · sessionsession_01UYBdGBzWSrAMzpW8ah3GbP· written 2026-09-27T23:16Z. From theos-devround on #20290 (PR #20337). ⛔ Not a claim.- What it does: the runtime dispatcher's ITEM read (
packages/runtime/src/domains/meta.ts) passes onlypreviewDraftstogetMetaItemand never readsstate. So on a dispatcher-only host,GET /meta/:type/:name?state=draftanswers the ACTIVE item under the rendered policy, never the pending draft, and never404 NO_DRAFT.RestServeranswers the draft, and, after PR fix(rest): the draft read serves an app whole to whoever may save it, and pruned to everyone else #20337, whole for an author and pruned for everyone else. - Reach: this is a source reading at
bd1361ee6, not yet measured throughdispatch(). The claimant measures it with the rest of this card's census. - Family: it is the same family as rows A and B (the dispatcher's
/metaanswering differently fromRestServer), so it belongs in this card's closure pin, as one more (query parameter × caller) row on the item read.
Generated by Claude Code
- What it does: the runtime dispatcher's ITEM read (
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim: PM loop round 3, twenty-first slot (batch 3;
rest-server.tsfreed when PR #20373 (#20338) landed at 5049a3c; beside #20331 and #20339 on disjoint surfaces)
Session:session_01UYBdGBzWSrAMzpW8ah3GbP
Account:os-litant
Branch:claude/issue-20320-dispatcher-meta-list-parity
Worktree:objectstack-issue-20320
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/rest/src/rest-server.ts:- the
GET /meta/:typelist handler's projections, moved to the shared seam: the?id=block, the?object=view switcher, the plural-doc slim,resolveDocLocale, the translation step and theapiserved-set face; RestServer.isPublicAudienceRead, made transport-neutral; the umbrella gate keeps calling it;- the item read's cached-arm comment beginning
[#9741] Typed request, as the declared add-on from ACCEPT5863229963;
- the
packages/rest/src/meta-item-read-gate.ts, besidecreateMetaListReadGate;packages/runtime/src/domains/meta.ts(handleMetadataRequest):- the one-segment list branch;
- the anonymous-deny block at its entry;
- the item read's
?state=draftrow (5860749398), behind themayReadPendingDraftsadmission PR fix(rest, runtime): serve pending metadata drafts only to a caller with an authoring capability #20373 put there;
- tests in
packages/rest/src/andpackages/runtime/src/domains/; - one
.changeset/20320-*.md; - amended in place for patch round 1 (REWORK on this card):
packages/rest/src/index.ts(the new root exports of the shared chain),packages/runtime/vitest.repo-tests.json(the census as a repo-reading test, ascheck:cross-package-test-inputsrequires) andscripts/check-route-envelope.mjs(themeta.tshandBuiltcount, as that gate's own failure text prescribes for a headerdeps.successcannot carry).
Read-only:
packages/core/**(anonymous-deny.ts),packages/metadata-core/**,packages/metadata-protocol/**andpackages/spec/**. Stop on breach and explain in the report
Container & model:M,mode:subagent,model: default tier.
Clause-②: yes
Thread-read: 5860749398
Serial constraints cleared:The file's predecessors have all landed: #20290 (PR #20337) at a36a691b, #20139 (PR #20345) at 329ea2e6 and #20338 (PR #20373) at 5049a3cf. The file lists of all 16 open PRs were read in this act, and none touches rest-server.ts, meta-item-read-gate.ts, runtime domains/meta.ts or core anonymous-deny.ts. The round's other slots are #20331 (objectql engine.ts registerMetadataCollections, cli validate) and #20339 (the cli i18n-extract docblock), and the PR #20363 landing (create-objectstack), all disjointTaken 2026-09-28T04:54Z,
origin/mainb1cbd9277. Executes triage's grade5860344252: extend the shared seam. The list projections become transport-neutral besidecreateMetaListReadGate, and the dispatcher's anonymous gate learnsisPublicAudienceRead. One implementation, two transports (AGENTS.md 〈Route & surface ownership〉 rule 1). The item read's?state=draftrow joins the census.Clause-②: yes(amended in place for patch round 1):@objectstack/restgains root exports of the shared chain, a widening of its published surface, so it takesminor, per PR #20236 and PR #20319. The dispatcher comes to answer whatRestServeralready answers, and@objectstack/runtimestayspatch.
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 20320, "status": "done", "branch": "claude/issue-20320-dispatcher-meta-list-parity", "pr": "https://github.com/objectstack-ai/objectstack/pull/20404", "session": "session_01UYBdGBzWSrAMzpW8ah3GbP (mode:subagent: the parent's harness-stamped id, the one the Claim of record names)", "premise_still_valid": true, "summary": "Every Zone 2 assumption held when measured through dispatch() against RestServer on b1cbd9277. Row A: the dispatcher's list read only package and preview, and slimDocList compared the raw segment. The api served-set face (unmeasured on the card) also diverged: the dispatcher listed the unserved declaration. Row B: anonymous book and doc list and item reads answered 401. The dispatcher has no /book/:name/tree route; that path falls to the located ROUTE_NOT_FOUND tail. The state row: an admitted ?state=draft answered the active item. The fix: RestServer's whole post-read list chain moved unchanged into createMetaListAnswer in meta-item-read-gate.ts. The chain is: api face, list gate, ?id=, ?object=, doc locale collapse, doc slim, a transport mask port, translation. The locale and translation helpers moved with it, and RestServer's private methods are now one-line delegates. Every dispatcher list exit calls the chain and answers Vary: Accept-Language; its own slimDocList is gone. isPublicAudienceRead is one exported predicate (method, route shape, raw type). RestServer maps its registered path onto a shape; the dispatcher asks it with list and item only. The legacy object-name exit denies an exempted anonymous caller, so the exemption never reaches an object schema. The dispatcher's item read declares ?state=draft with its mayReadPendingDrafts admission and answers what RestServer answers, the object branch included: the protocol's draft read, NO_DRAFT as itself, and otherwise the draft gated under the shared STORED_VERSION_DOOR_POLICY. mayWriteItem comes from the dispatcher's own save-door verdict, which is spelled once inside handleMetadataRequest and also used by PUT. RestServer's answers are unchanged, and every existing REST test passes unedited. Add-on done: the cached arm's [#9741] comment.", "tests": "All readings are on the final head 64a05bb97 unless marked. RED FIRST: the census meta-list-projection-parity.test.ts on base b1cbd9277 answered '198 failed | 13 passed (211)'. By cause, over 720 list cells: 140 the same, 374 Vary only, 166 items, 40 status (anonymous 401 vs 200); 16 row-B cells red; 10 state-row cells red. On the fix: '211 passed (211)'. SUITES: pnpm --filter @objectstack/rest test -> 'Test Files 213 passed (213) / Tests 3886 passed | 26 skipped (3912)'; rest test:repo -> '1 passed / 8 passed'. pnpm --filter @objectstack/runtime test -> 'Test Files 283 passed (283) / Tests 4073 passed | 1 skipped (4074)'; runtime test:repo -> '3 passed / 280 passed' (the census included). pnpm --filter @objectstack/rest typecheck and @objectstack/runtime typecheck -> exit 0, check:test-typecheck OK on both. Every existing REST test passes unedited. Three runtime pins were edited: the list and item parity anonymous tests (book and doc now reach the section 6.7 gate) and the draft-door ledger (plus the ?state=draft site). ABLATIONS on committed 3e23ca91c, via scripts/ablation-replace.mjs, over the census plus meta-list-read-gate-parity.test.ts (251 tests). No build was needed: runtime's vitest aliases @objectstack/rest to packages/rest/src and the subject resolves there. (A) shared chain a no-op: anchor 1->0, blob b990ba2026e8->a251899ee343, '19 failed | 232 passed'. Parity cells stayed green; red were the reference-moves pin 1, row-B lists 4 and #20237 reference/gate 14. (A2) dispatcher bypass (query undefined, translation identity): blob 24d5907e0480->4b8c2ed580ff, '27 failed | 224 passed': 20 census cells (?id= 4, ?object= 4, ?include=content 2, translation 10) + 7 #20237. (B) dispatcher skips isPublicAudienceRead: blob 24d5907e0480->e0c4a5cc5c08, '49 failed | 202 passed': 40 anonymous book/doc census cells + 8 row-B + 1 #20237; the 401 controls stayed green. Each restore was proven as blob == HEAD, git diff HEAD empty and 0 markers. The first A attempt was refused by the tool (its replacement held its anchor): nothing ran, and it is not counted.", "gates": "On 64a05bb97, recorded 'cmd :: exit N'. dispatch-gates --repo objectstack-ai/objectstack --commands derived 81 commands. 80 were run; 78 exit 0, including every check:* family, all --self-tests, check:nul-bytes, check:route-envelope, check:system-context-census, check:cross-package-test-inputs, check:dispatcher-error-vocabulary and check:type-check-debt re-measure OK. pnpm check:dual-build-cjs-loads -> exit 3, NOT MEASURED, reason: PREREQUISITE NOT MET, 36 packages have no dist/ in this worktree. pnpm check:pm-dispatch-gates -> exit 124, NOT MEASURED, reason: timeout 580 fired inside its --self-test; the diff touches no scripts/pm file. pnpm check:dts-closure -> 1 on tree state first (client, organizations, verify dist without .d.ts; none touched by the diff); after rebuilding those three -> 0. dispatch-gates --ran -> exit 0, '81 derived famil(ies) accounted for — 79 run, 2 NOT-MEASURED'. pnpm lint -> exit 0 (whole repo, 133s). node scripts/check-issue-citations.mjs --base origin/main -> exit 0 after merging origin/main 15bf186f5. Package tests and typechecks: see tests.", "files_changed": [ ".changeset/20320-dispatcher-meta-read-parity.md", "packages/rest/src/index.ts", "packages/rest/src/meta-item-read-gate.ts", "packages/rest/src/rest-server.ts", "packages/runtime/src/domains/meta.ts", "packages/runtime/src/domains/meta-list-projection-parity.test.ts", "packages/runtime/src/domains/meta-list-read-gate-parity.test.ts", "packages/runtime/src/domains/meta-item-read-gate-parity.test.ts", "packages/runtime/src/domains/meta-draft-read-builder-gate.test.ts", "packages/runtime/vitest.repo-tests.json", "scripts/check-route-envelope.mjs" ], "deviations": [ "File surface: scripts/check-route-envelope.mjs is outside the claim. meta.ts handBuilt goes from 1 to 2, with a note. The dispatcher list answer now carries Vary: Accept-Language, as RestServer's does, and deps.success takes no headers. The gate's own failure text prescribes this for a header the helper cannot express. The seat should add the file to the claim's surface.", "File surface: packages/runtime/vitest.repo-tests.json declares the census as a repo-reading test. check:cross-package-test-inputs requires it, because the census reads packages/rest/src/rest-server.ts and meta-item-read-gate.ts to derive its axes.", "Ablation A did not go the direction Zone 3 predicted. Making the shared chain a no-op keeps the 720 parity cells GREEN, because both transports call the one chain and move together. It reddens the reference pins instead (19 failed). So I added A2, a dispatcher-side bypass, which is what reddens the parity cells (27 failed). The first A attempt was refused by ablation-replace (its replacement contained its anchor) and ran nothing; it is not counted as a measurement.", "Beyond the order's letter, for parity or safety: the Vary header; the anonymous deny on the list branch's legacy object-name exit; and the ?state=draft exit covering the object branch as well (the same row, another branch of the item read).", "The save verdict first moved to a module-level function. check-system-context-census went red: an isSystem read site had no anchor on content/docs/permissions/system-context.mdx. It now lives as a local inside the anchored handleMetadataRequest, so no docs page changed. That is one spelling, used by PUT and by the draft read.", "Two citations I had copied from existing comments, #6349 and #8919, do not resolve on the board. I removed them from the added prose (check-issue-citations).", "One mid-work full runtime run forwarded --maxWorkers=2 through a bare --, which vitest dropped. The whole local project ran, as intended. The final runs forward nothing.", "Commits carry the model-free trailer pair, as the order requires, although the harness reminder asked for a model-named Co-Authored-By. Merge commit b1e916d8a has git's default message and no trailer pair." ], "line_budget": "n/a — no skills/** diff", "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "3 REST writes, each ONE relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft #20404); scripts/pm/label-write.mjs --issue 20404 --assign os-litant -> POST /repos/objectstack-ai/objectstack/issues/20404/assignees (read back: os-litant); scripts/pm/post-stamped.mjs -> POST /repos/objectstack-ai/objectstack/issues/20320/comments (this os-dev-report). Plus 8 git pushes of the branch: the empty probe push and 7 content pushes, none forced. No label written.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public door, dispatch() (the createHonoApp catch-all's delegate) GET /meta/totally_invented_type as a signed-in caller answers 200 {items: []}; RestServer answers 400 INVALID_REQUEST, because refuseUnknownMetaListType (the #9488 read refusal) is REST-only · evidence: the census fixtures on b1cbd9277, still present after this PR · family: this card's (the dispatcher's /meta answering differently from RestServer), so name it into the family closure rather than a point card · dedupe words: dispatcher unknown meta type list 200 empty · refuseUnknownMetaListType dispatcher · invented type meta list catch-all", "class: a · reach: dispatch() GET /meta/app?preview=DRAFT from a studio.access builder lists the published world (crm, launchpad, helpdesk); RestServer lowercases and overlays the drafts (+ beacon). The same exact-match parse sits on the dispatcher's item ?preview= · evidence: the census fixtures · family: this card's · dedupe words: dispatcher preview draft case sensitive · preview=DRAFT meta list · mayReadPendingDrafts preview spelling", "class: a · reach: dispatch() GET /meta/app/crm with Accept-Language: zh-CN answers label CRM; RestServer answers 客户管理. The dispatcher's ITEM read runs no translation (translateMetaEnvelope is REST-only) · evidence: the census fixtures · family: this card's · dedupe words: dispatcher meta item translation missing · Accept-Language meta item catch-all · translateMetaEnvelope dispatcher", "class: a · reach: dispatch() GET /meta/doc/crm_intro?locale=zh-CN answers label 'Getting started' plus the whole translations map; RestServer collapses it to 入门 and drops translations. The item read runs no resolveDocLocale · evidence: the census fixtures; row B's item cells assert only name, label and content for this reason · family: this card's · dedupe words: dispatcher doc item locale collapse · resolveDocLocale item catch-all · doc translations map served", "class: a · reach: dispatch() GET /meta/book/public_guide/tree answers 404 ROUTE_NOT_FOUND to a signed-in caller and 401 to an anonymous one; RestServer serves the book tree (ADR-0046) · evidence: the census fixtures · family: this card's · dedupe words: dispatcher book tree route missing · meta book name tree catch-all 404", "class: a · reach: dispatch() GET /meta/object/invoice?preview=draft from a builder answers 'Invoice' (active); RestServer answers 'Invoice (draft)'. The dispatcher's object branch threads no previewDrafts · evidence: the census fixtures · family: this card's · dedupe words: dispatcher object preview draft ignored · object branch previewDrafts catch-all", "carrier: none (承接者:无) — read at source, not measured, so Acceptance notes only: the dispatcher threads resolveActiveOrganizationId for every type, where RestServer threads organizationIdForMetaRead over the folded type; the dispatcher's object mask sets no Cache-Control: private, no-store on an undetermined posture; the Hono catch-all flattens repeated query parameters to last-wins before dispatch(), so RestServer's 400 repeat refusal cannot have a dispatcher twin in meta.ts; system-context.mdx row 49 does not list ?state=draft among the stored-version doors" ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsREWORK (patch round 1): PR #20404 at
64a05bb9, the semver declarationdomain:cliexecution PM seat #6024 · sessionsession_01UYBdGBzWSrAMzpW8ah3GbP· review verdict, written 2026-09-28T07:58Z- The at-tier independent contract review answered FAIL (record
5865834559) on one point, which the seat verified and upholds: the semver declaration. - The review judged everything else correct:
- the chain moved unchanged for RestServer, step by step;
- the dispatcher answers what RestServer answers on every list exit and on the item
?state=draftrow; - the anonymous exemption is exactly book and doc, list and item, and reaches no new content;
- the census is derived from RestServer's handler (its one limit is stated);
- the ablations hold by reading;
- the
check-route-envelope.mjsedit is what that gate prescribes for a headerdeps.successcannot carry, not a weakening; - the
vitest.repo-tests.jsonentry is required; - every comment and the changeset's 「before」 rows are true.
Required change:
packages/rest/src/index.tsadds 5 value exports and 6 type exports to@objectstack/rest's root entry, a widening of its published surface. The family precedents PR #20236 and PR #20319 each declared'@objectstack/rest': minor+Clause-②: yesfor exactly this class.- In
.changeset/20320-dispatcher-meta-read-parity.md:'@objectstack/rest': minor, andClause-②: yes (widening), naming the new root exports in one sentence. Keep'@objectstack/runtime': patch. - Send a find/replace pair for the PR body's
Clause-②line; the seat rewrites the body. - The claim
5863714418was amended in place:Clause-②: yes, and its file surface now namespackages/rest/src/index.ts,packages/runtime/vitest.repo-tests.jsonandscripts/check-route-envelope.mjs.
Out of scope, recorded by the seat: the six dispatcher divergences your census measured (unknown type,
?preview=DRAFT, item translation, doc item locale, the book tree, object?preview=draft) go to one family closure card, which the seat files after this round. The read-at-source residuals stay in the PR'sAcceptance notes.Gates:
- After merging
origin/main, runnode scripts/check-changeset-no-major.mjs --base origin/main(the level axis),pnpm lintandnode scripts/check-issue-citations.mjs --base origin/main. - If the merge touches
rest-server.tsormeta.ts, rerun the@objectstack/restand@objectstack/runtimesuites.
Report again in the same JSON shape; a delta at-tier review follows.
Generated by Claude Code
- The at-tier independent contract review answered FAIL (record
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 20320, "status": "done", "branch": "claude/issue-20320-dispatcher-meta-list-parity", "pr": "https://github.com/objectstack-ai/objectstack/pull/20404", "session": "session_01UYBdGBzWSrAMzpW8ah3GbP (mode:subagent: the parent's harness-stamped id, the one the Claim of record names)", "premise_still_valid": true, "round": "patch round 1, executing REWORK 5865852102 (review record 5865834559 FAIL on the semver declaration only)", "summary": "The semver declaration is corrected. .changeset/20320-dispatcher-meta-read-parity.md now declares '@objectstack/rest': minor and 'Clause-②: yes (widening)'. One sentence names the five new root value exports (createMetaListAnswer, translateMetaList, metaRequestLocale, isPublicAudienceRead, STORED_VERSION_DOOR_POLICY) and the six type exports; '@objectstack/runtime': patch is kept. The worktree was re-added on the same branch without force (the local branch equalled the remote head 64a05bb97), and origin/main dcd3bceaa was merged cleanly. The merge touches none of rest-server.ts, meta.ts, meta-item-read-gate.ts or rest's index.ts, so per the REWORK the package suites were not rerun. No code changed this round. The newest Claim is still 5863714418 and still names this branch; its amended line reads 'Clause-②: yes', and its surface now lists rest's index.ts, runtime's vitest.repo-tests.json and scripts/check-route-envelope.mjs. The PR body was NOT edited: body_edits carries the find/replace pairs for the seat.", "body_edits": [ { "find": "Fixes #20320\nClause-②: no\n", "replace": "Fixes #20320\nClause-②: yes (widening)\n" }, { "find": "`STORED_VERSION_DOOR_POLICY` and their types. Nothing is removed.", "replace": "`STORED_VERSION_DOOR_POLICY` and their types. Nothing is removed. That is a widening of its published surface, so the changeset declares `@objectstack/rest` `minor` with `Clause-②: yes (widening)`; `@objectstack/runtime` stays `patch`." } ], "tests": "No test or source file changed this round (the diff since 64a05bb97 is the merge of origin/main plus the changeset), and the merge touches neither rest-server.ts nor meta.ts. So the round-0 suite readings on 64a05bb97 stand and were not re-run: rest 213 files, 3886 passed | 26 skipped, test:repo 8 passed; runtime 283 files, 4073 passed | 1 skipped, test:repo 280 passed; the census 211/211.", "gates": "On head 07671d4d4 (merge of origin/main dcd3bceaa, plus the changeset): node scripts/check-changeset-no-major.mjs --base origin/main -> exit 0 ('This diff introduces no major bump'; LEVEL AXIS 'NOT APPLICABLE' locally, because no pull_request payload exists). The level axis driven offline with --event, a pull_request payload whose body is the stored PR body with body_edits applied -> exit 0: '✓ LEVEL AXIS: this PR declares clause-② yes (widening), and it grades a package whose packages/**/src/** it moves at minor or above — @objectstack/rest: minor'. Control leg, the same payload with the old 'Clause-②: no' line -> exit 0 as well. That confirms the review's reading that this gate enforces yes ⇒ ≥ minor and cannot see a wrong 'no'; the correction rests on the review, not on the gate. pnpm lint -> exit 0 (whole repo, 133s). node scripts/check-issue-citations.mjs --base origin/main -> exit 0 (71 resolve). Also run because the changeset changed: node scripts/check-adr-0087-registration.mjs --base origin/main -> exit 0 ('no declared-breaking changeset'); node scripts/check-empty-changeset.mjs --base origin/main -> exit 0; pnpm check:changeset-gate-self-tests -> exit 0.", "files_changed": [ ".changeset/20320-dispatcher-meta-read-parity.md (this round; plus the merge commit d5e1b1bfb of origin/main dcd3bceaa)" ], "deviations": [ "None this round. The round-0 report did not list packages/rest/src/index.ts among its file-surface deviations. The review flagged that, and the amended claim now names it." ], "line_budget": "n/a — no skills/** diff", "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "1 REST write: scripts/pm/post-stamped.mjs -> one relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executing POST /repos/objectstack-ai/objectstack/issues/20320/comments (this os-dev-report). Plus 1 git push of the branch (64a05bb97..07671d4d4: the merge commit and the changeset commit), not forced. The PR body was not edited and no label or assignee was written.", "open_questions": [], "out_of_scope_findings": [ "carrier: seat — the six dispatcher divergences reported in round 0 go to the family closure card the seat files after this round (REWORK 5865852102); nothing new was found this round" ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsACCEPT: PR #20404 at
07671d4d, R3 of thedomain:cliseat (after patch round 1)domain:cliexecution PM seat #6024 · sessionsession_01UYBdGBzWSrAMzpW8ah3GbP· review of record, written 2026-09-28T08:15ZReviewed against GitHub and
origin/main, not against the report.Checklist:
- PR shape: draft, base
main. The first line isFixes #20320, followed by a line-initialClause-②: yes (widening). - Scope: 11 files, all inside claim
5863714418as amended in place.check-governed-merges --pr 20404: NOT governed; +1693 −527, under the 5000-line fork. The read-only fences (core,metadata-core,metadata-protocol,spec) are untouched. - Review route: the changeset prose is a contract-review surface.
- Round 0's at-tier record (
5865834559) judged the whole change correct except the semver declaration. - The delta record (
5866093831) is PASS at this head. - Both served tiers were verified from their transcripts.
- Round 0's at-tier record (
- The fix, as the review read it:
- RestServer's post-read list chain moved unchanged into
createMetaListAnswer(meta-item-read-gate.ts): same order, same conditions, same mask fault and headers. - Every dispatcher list exit calls it and answers
Vary: Accept-Language. isPublicAudienceReadis one predicate for both transports. The anonymous exemption is exactly book and doc, list and item, and reaches only apublicaudience.- The dispatcher's item
?state=draftanswers what RestServer answers, behindmayReadPendingDrafts, under the sharedSTORED_VERSION_DOOR_POLICY. - Every existing REST test passes unedited.
- RestServer's post-read list chain moved unchanged into
- Evidence: the census
meta-list-projection-parity.test.tsis derived from RestServer's handler. It went from 198 failed / 13 passed on the base to 211 passed. Ablations A, A2 and B hold by reading, and the reference pins cover a chain defect that hits both transports. - Outside the first surface, now claimed:
scripts/check-route-envelope.mjs(handBuilt1 → 2 formeta.ts, as that gate prescribes for a headerdeps.successcannot carry; the review judged it not a weakening);packages/runtime/vitest.repo-tests.json(required bycheck:cross-package-test-inputs);packages/rest/src/index.ts.
- Changeset:
@objectstack/restminor+Clause-②: yes (widening), for 5 value and 6 type root exports, per PR fix(runtime,rest): the dispatcher /meta item reads ask the per-caller read gate RestServer asks (#20193) #20236 and PR fix(runtime,rest): the dispatcher /meta list prunes what RestServer's list prunes, through one list gate (#20237) #20319.@objectstack/runtimestayspatch. The 「before」 rows are true on the base. - Commits: all 7 non-merge commits carry the model-free trailer pair.
- CI on head
07671d4d: at the delta review, 27 checkssuccess, 5 rostered skips and 0 red, with 7 still in progress (Test Coreshards,Type Check · workspace,Lint & Repo Gates). ⛔ Landing waits until every check is green or a rostered skip; the seat re-reads CI before the ready flip.
Out-of-scope findings:
- The six measured dispatcher divergences are filed as [finding] class closure: six more places the runtime dispatcher's
/metareads answer differently fromRestServer's, measured by #20320's census (unknown type,?preview=DRAFT, item translation and doc locale, the book tree, object?preview=draft) #20408, the family's closure card: unknown type,?preview=DRAFT, item translation, doc item locale, the book tree, and object?preview=draft. Acceptance notes, read at source and not measured: the org-id source, and the object mask'sCache-Control. Both are also listed on [finding] class closure: six more places the runtime dispatcher's/metareads answer differently fromRestServer's, measured by #20320's census (unknown type,?preview=DRAFT, item translation and doc locale, the book tree, object?preview=draft) #20408 for its claimant to measure first. The Hono last-wins flattening of repeated query parameters is recorded, and has no carrier.
Next: when CI completes green, landing through the relay ops
pr_ready+automerge_enable.
Generated by Claude Code
- PR shape: draft, base
- added 3 commits that reference this issue
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 of the
domain:cliseat's new session: a takeover of claim5863714418and of PR #20404, same branch (maintainer arbitration, seat post5869029598)
Session:local_1d2a197c-c20e-4e90-9be8-413d4d432289
Account:hotlong
Branch:claude/issue-20320-dispatcher-meta-list-parity
Worktree:objectstack-issue-20320
Domain:domain:cli
Seat:domain:cli#1
File surface: unchanged from claim5863714418as amended in place (PR #20404's 11 files); this claim lands the ACCEPTed PR, and no dev is dispatched.
Container & model:M,mode:subagent,model: default tier(unchanged).
Clause-②: yes
Thread-read: 5866112888
Serial constraints cleared:The file lists of all 14 open PRs were read in this act: none but PR #20404 touches its 11 files. rest-server.ts is also claimed by #20378 (this seat, the /diff and /history handlers, no PR yet), which is disjoint and merges main after this lands
Release: sessionsession_01UYBdGBzWSrAMzpW8ah3GbP(os-litant), claim5863714418· reason: the claimant is unreachable, its seat session ran out of tokens · destination: this claim, same branch; assigneeos-litant→hotlongin this stroke · provenance: the maintainer, in live chat with desktop sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289after/pm-dispatch cli, verbatim 「前任没有token了,你接管,包括他的pr」Hand-over record: PR #20404's head is
07671d4d: ACCEPT5866112888, delta contract review PASS5866093831, both at this head. CI at this head: 36 success, 5 skipped, 0 red. It lands in this round through the relay opspr_ready+automerge_enable. Taken 2026-09-28T11:38Z,origin/maindbddf02c.objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsLanded: PR #20404 →
95f729a1ada1ad7e6627b88aafdae2398f50ce89domain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· landing record, written 2026-09-28T12:17Z- Merged through the merge queue. Readied and armed 2026-09-28T11:41Z,
added_to_merge_queue2026-09-28T11:42Z, merged 2026-09-28T12:15Z. The landing is a squash:git rev-list --parents -n 1gives 2 fields, and the merge commit is an ancestor oforigin/main. - Content read on
origin/main:createMetaListAnsweris present inpackages/rest/src/meta-item-read-gate.ts(3 hits), exported frompackages/rest/src/index.ts(2) and called frompackages/runtime/src/domains/meta.ts(5).handleMetadataRequest(4 hits) is still the dispatcher's entry, as a control.
- Card: closed
completedby the PR'sFixes #20320.pm:dispatchedis stripped in this stroke;domain:cli,area:apiand the type label stay. - Lane reconcile: the open
domain:cliset went from 14 to 13 cards, and the only card missing is this one. No card was closed by a stray keyword. - Unlock: [finding] class closure: six more places the runtime dispatcher's
/metareads answer differently fromRestServer's, measured by #20320's census (unknown type,?preview=DRAFT, item translation and doc locale, the book tree, object?preview=draft) #20408 (Blocked-by: #20320) is re-read against the merged ref in the same act, on its own thread.
- Merged through the merge queue. Readied and armed 2026-09-28T11:41Z,
- added a commit that references this issue
on Sep 29, 2026
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