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
{{ message }}
Repository navigation
meta doors: the bundle-member selector member is dropped silently by the runtime dispatcher GET, by PUT, and by the by-name doors outside the ruling (no closed query sets); honour it or refuse it loudly #23072
Filing gate ①, class (b): a declared selector that some doors ignore silently. reach: read, not measured on a live host. PR #23070 adds the bundle-member selector member to five RestServer doors, and to the four protocol requests behind them. Other transports and doors either ignore it or have no closed query set, so a caller's ?member= is dropped with no signal. AGENTS.md route rule 5 names exactly this as "fails undetectably".
Filed by domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T16:54Z, at PR #23070's ACCEPT (#22877 stage 2, ruling 6116165299 letter A). It comes from the dev's report 6127796890 and the contract records 6128093700 and 6128724141. ⛔ Not a claim. Lane and grade are triage's.
Where ?member= is dropped today, one family
The runtime dispatcher (packages/runtime/src/domains/meta.ts): its /meta item GET declares no closed query set and never forwards member. A host served by the dispatcher alone answers GET …?member=zh-CN with the canonical member. (DELETE, publish, history and rollback answer 405 there.)
PUT /meta/:type/:name (packages/rest/src/rest-server.ts) declares no closed set. A PUT …?member=zh-CN with an en-US body writes en-US, and the selector is dropped. Under the ruling, PUT stays body-addressed, so the honest answer is a located 400 for member (and for any other unrecognised name). It must not read the selector.
The by-name doors outside the ruling's list: /diff, /layers, /audit, /published and /references have no closed sets and stay canonical-only. Either each honours member, or each refuses it loudly.
Wanted
Each door above declares its closed query set, measured from its real read points (route rule 5: "measure the set from the handler's ACTUAL read points").
A door that does not take the selector refuses member with a located 400.
The runtime dispatcher reaches the same answer as RestServer.
Pins: one per door, ?member= either honoured or refused, never dropped.
Dedupe: this is new with PR #23070. No open card names member on these doors.
✅ ACCEPT: PR #23133 at d813364338 (the RestServer half, Part of #23072). The dispatcher half goes to the maintainer, and the PR queues after one CONTRACT_REVIEW_TIER PASS and after #23148 clears
domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T04:25Z · holder of claim 6136144688. Report: os-dev-report6137337775 (needs_decision). The dev's final message corrects three prose sentences of that comment; every measurable field is identical. Thread-read: 6137337775.
Checklist, read against GitHub:
PR shape: draft, base main. Line 1 is Part of #23072, with no closing keyword, because the dispatcher half is open. Line 2 is Clause-②: no. Assignee os-steve.
Scope: 5 files, +351 / −3. check-governed-merges --pr 23133: not governed. git merge-tree against today's origin/main is clean.
The doors, read in the diff: six named sets beside META_ITEM_*_PARAMS, each measured from the handler's read points, and one refuseUnknownQueryParams line per door:
PUT: force, package, mode;
/references: none;
/layers: package;
/audit: limit;
/diff: from, fromVersion, to, toVersion;
/published: none.
?member= and any unread name answer a located 400 VALIDATION_ERROR. The protocol is not called, and nothing is saved. PUT stays body-addressed. None of the five starts honouring member, as ruled.
The caller census: the SDK (saveItem, getReferences, getPublished, getAudit, diffItem), objectui at the console pin 1fb1ced36243 (read-only shallow fetch), and 18 dogfood files all send only names the doors read. One caller was inert: meta-item-save-capability-gate.test.ts sent ?organizationId= to PUT. It is split. The body half is unchanged, and the query half now pins the 400 with saveMetaItem never called, which is stronger than the old 200.
One edit outside the claim, accepted (the seat amends the surface): packages/rest/src/query-allowlist.ts's unknownQueryParamMessage says "This endpoint takes no query parameters." for an empty set. Otherwise the first empty sets would ship the sentence Supported parameters: .. Every non-empty sentence is byte-identical, and its three pins pass unchanged.
The finding for the spec lane (which by-name doors could honour member): /references never can (a reference names an item, not a member); /layers and /diff can, with threading; /audit needs a column; /published partly. Recorded here, not built.
Tests and gates:
@objectstack/rest: typecheck exits 0. The whole local project at 52b332b6d5: 275 of 276 files passed, the one red being the inert caller, since fixed. At d813364338: the three touched test files (72 tests) and the repo project (213 passed) are green.
The dispatcher half is put to the maintainer in this session's chat. Honouring ?member= on the dispatcher's item GET, and refusing what META_ITEM_READ_PARAMS refuses, from one source, needs a new export from @objectstack/rest. The dev's three options are A, B (recommended) and C. The answer is recorded on this card verbatim. The dispatcher's /layers, /published and PUT branches have no closed set either (out-of-scope finding 1), and they fold into that half, not into a card of their own.
Maintainer ruling on #23072's dispatcher half: B. One portable read decision in meta-item-read-gate.ts, called by both transports
Recorded by domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T04:59Z · holder of claim 6136144688. This records the maintainer's answer, given in this session's chat, to the dev's open question (os-dev-report6137337775, open_questions[0]; ACCEPT 6138208712).
The maintainer's choice, verbatim: 「B 共用判定函数 (推荐)」. The option text the maintainer chose, as the seat put it in chat:
The dispatcher half is one portable decision, for example metaItemReadQueryRefusal(query), which returns { code, status, message } or nothing, plus the member reading. It lives in packages/rest/src/meta-item-read-gate.ts beside metaTypeReadRefusal, and is exported once from @objectstack/rest's entry.
RestServer's item GET is re-pointed at it in spelling only. Its answers stay byte-identical, pinned by the existing member-selector file.
The runtime dispatcher's item GET calls it. It honours ?member=, refuses what RestServer refuses, and writes the same error.code and message in its own transport's body.
The dev's measured costs are in scope: member reaches the three getMetaItem calls; the two bare catches let the protocol's located 400 out; the name-keyed MetadataService fallback never answers a named member; and the ?layers= + member refusal holds.
The dispatcher's /layers, /published and PUT branches (out-of-scope finding 1) fold into the same slice, under the same one-source rule.
Clause-②: yes (1-2 new exports widen @objectstack/rest's public surface), so @objectstack/rest is minor, and one review at CONTRACT_REVIEW_TIER is owed before the queue.
Seat review of PR #23133 at d813364338: the contract review FAILs on ② only, and patch round 1 is asked. Changeset text only, no source change
domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T05:01Z · holder of claim 6136144688. ⛔ The ACCEPT 6138208712 stands for everything but this.
The record:6138381862 on PR #23133, CONTRACT_REVIEW_TIER, FAIL. The served model was checked against the reviewer's transcript.
① (each closed set against its read points, where each refusal sits, the caller census, the split census test, the empty-set sentence, the five ruled doors unchanged, the changeset's door-by-door text) and ③ (every flag) hold.
② fails: the changeset grades a breaking accept-set narrowing patch, with a bare Clause-②: no and no ADR-0087 disposition. So the gates classify it non-breaking, and the CHANGELOG would reach consumers with no breaking carrier.
Exactly one ADR-0087 disposition marker in the HTML-comment form, adr-0087: not-required (no-migration-prescription), stating its facts as fix(metadata-protocol)!: the save door refuses a protection block instead of storing a lock that binds nothing #23115's does. No spec key, authorable spelling, export or stored shape moves. What narrows is six runtime doors' query accept sets, and the remedy is in the request a caller sends. Each other category is closed on facts.
The per-door remedies already in the changeset stay as written.
Also through the relay, in one issue_patch: PR #23133's body line 2 becomes Clause-②: no (narrowing), and its title gains the ! (fix(rest)!: …).
Then pnpm check:adr-0087-registration --base origin/main, check:changeset-no-major, check:empty-changeset and the derived gates, and a delta review at CONTRACT_REVIEW_TIER on the new head judges ② only, re-recording ① and ③ as written.
os-dev-report
{
"issue": 23072,
"patch_round": 1,
"status": "done",
"branch": "claude/issue-23072-meta-doors-closed-sets",
"pr": "#23133",
"head": "d64fc8e605e08f7748dd5e858ca80770ef9ec50b",
"session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (subagent of the domain:cli seat 2 PM; the parent's id)",
"premise_still_valid": true,
"summary": "Patch round 1 answers the contract review 6138381862 (FAIL on item 2 only). One commit, 49994b3, edits .changeset/23072-meta-doors-closed-sets.md only. It grades @objectstack/rest minor, adds the summary line fix(rest)!: ..., changes the declaration to Clause-②: no (narrowing), adds exactly one ADR-0087 disposition marker, not-required (no-migration-prescription), with its facts checked against the diff, and adds the launch-window BREAKING line. The per-door remedies are unchanged. origin/main (dc93781, 15 commits, none under packages/rest) is merged in by merge commit d64fc8e, the new head. One relay issue_patch set PR #23133's title to fix(rest)!: ... and its body line 2 to Clause-②: no (narrowing), and added a Patch round 1 section that states the round and the new head; the read-back is byte-identical. No source file moved.",
"claim_check": "The newest Claim: on #23072 is 6136144688 (2026-10-13T01:33:55Z, the only claim among the 8 comments read). It names branch claude/issue-23072-meta-doors-closed-sets, which matches. No claim was posted, and the card assignee was not written.",
"changeset_before": {
"at": "d813364338",
"frontmatter": "'@objectstack/rest': patch",
"summary_line": "none (the body opened with the paragraph Six by-name /meta doors now refuse ...)",
"clause_line": "Clause-②: no",
"adr_0087_marker": "none",
"breaking_line": "none"
},
"changeset_after": {
"at": "d64fc8e605 (content from 49994b3)",
"frontmatter": "'@objectstack/rest': minor",
"summary_line": "fix(rest)!: PUT and the five by-name /meta sub-doors declare closed query sets, so ?member= is refused with a located 400 VALIDATION_ERROR, never dropped",
"clause_line": "Clause-②: no (narrowing)",
"adr_0087_marker": "HTML-comment form (the opening and closing comment brackets are spelled out here for the sanitizer): adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, package export or stored shape is added, removed, renamed or re-shaped. The six closed sets are module constants that @objectstack/rest's entry does not re-export, and no stored row or package source changes, so there is nothing for objectstack migrate meta to rewrite. What narrows is six runtime doors' query accept sets (PUT /meta/:type/:name, /references, /layers, /audit, /diff, /published): a query parameter those doors dropped now answers a located 400 VALIDATION_ERROR, and the remedy is in the request a caller sends to that door, never a rewrite of a package source or a stored row. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and no exported declaration is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired).",
"breaking_line": "BREAKING (an accept-set narrowing), shipped as minor under the launch-window convention for breaking changes.",
"marker_facts_checked": [
"No path under packages/spec, no .object.ts, and no conversion or migration path is in the PR diff (git diff --name-only against the merge base; grep exit 1, no match).",
"packages/rest/src/index.ts re-exports none of the six META_ITEM__PARAMS constants and nothing from query-allowlist.ts (grep exit 1). The build entry is src/index.ts alone (tsup.config.ts), the exports map holds only ., and index.ts is not in the diff (0 lines).",
"The only removed source line in the PR is inside unknownQueryParamMessage (query-allowlist.ts), which the entry does not export. Its signature is unchanged.",
"The diff names no ADR-0087 id and touches no conversion registry (grep for adr-0087, conversion, retired, migrat: exit 1, no match).",
"@objectstack/rest is not private (package.json private undefined, version 17.7.0, files dist, README.md, CHANGELOG.md)."
]
},
"gates": {
"head": "d64fc8e605",
"named": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition. Signals BREAKING+bang+clause-②-narrowing, disposition not-required (no-migration-prescription).",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ This diff introduces no major bump. The level axis reads NOT APPLICABLE locally: no pull_request payload.",
"node scripts/check-changeset-no-major.mjs --base origin/main --event (the patched title and body) :: exit 0 :: ✓ LEVEL AXIS: this PR declares clause-② no (narrowing), and no package whose published source ... it moves is graded patch.",
"control: the same event against the old head (--head d813364, changeset still patch) :: exit 1 :: Check Changeset (level axis): clause-② declares no (narrowing) while no moved package is graded minor or above. Second control: the old body at d64fc8e :: exit 0, direction arm none declared. So the declaration and the grade move together, and at d64fc8e they do.",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added). ✓ No changeset from the merge base modified or deleted by this diff.",
"pnpm check:changeset-gate-self-tests :: exit 0 :: 170 + 548 + 401 assertions (empty-changeset, adr-0087-registration, changeset-no-major self-tests)."
],
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at d64fc8e: 65 commands over 5 paths against merge base dc93781. All 65 exit 0. dispatch-gates --ran: ✓ 65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED (a DERIVED zero — all 65 recorded an exit code and none of them is 3).",
"derived_verdicts": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 548 assertions over real temp git repos (real scan()/assertInputs() path)",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@17.7.0 — the one exception, evidenced: pre mode (tag next) and a pending major for @objectstack/spec",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
"node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every $TURBO_ROOT$ input of a build Build Core runs) and all 11 sc",
"node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 11187 tracked file(s),",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
"node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 ",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS check-comment-mask-adoption --self-test (0 failure(s))",
"node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8937 files, 0 disagree, 0 unparseable, 142.5s (comparator self-test: 26 cases pass).",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
"node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
"node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 115 .object.ts files under packages/ + apps/* + examples/** (walk is repo-wide; 0 outside), 120 object declarations, 248 declared index ent",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS check-keyed-text-bounds --self-test (0 failure(s))",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 87 platform-namespace objects, 52 in the machinery's reach, 35 outside it, every exclusion explained by a declaration o",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ check-platform-object-tenancy-census self-test: all checks pass (87 objects, 35 outside the machinery)",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 76 Plugin implementation(s) across 8351 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits ",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every roster name",
"node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 74 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 17 cases + level-vocabulary read + population declaration + real-tree selection floor.",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 32 of 282 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s).",
"node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 132 elevation read sites in 21 packages across 60 files, living in 112 symbol(s); the page cites 125 symbol(s) against 125 required, ove",
"node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 82 workspace packages under packages/ + apps/** + examples/, 3063 non-test src files, 2563 @objectstack/* specifiers (0 assembled, not",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS check-undeclared-dep-imports --self-test (0 failure(s))",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: ✓ affected-docs self-test: 605 cases pass.",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ self-test passed",
"node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ release-pending-publish self-test: 92 cases across 22 batteries pass.",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3068 source file(s) all carry their unit in the key name (or in a sibling unit, or under a declare",
"pnpm check:authz-resolver :: exit 0 :: ✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.",
"pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
"pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split 'test:repo' task); 13 walked root(s) judged, 3 on",
"pnpm check:dispatcher-error-vocabulary :: exit 0 :: check-dispatcher-error-vocabulary: OK — 56 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (#8846).",
"pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 94674 string(s) read in 1320 parsed source(s) (seen floor 40000 st",
"pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states '#6664 census: 2 ruled consumers'. This gate polic",
"pnpm check:dts-closure :: exit 0 (post-build re-run; first pass exit 3 PREREQUISITE NOT MET) :: check-dts-closure: 74 built package(s) swept - 174/174 declared declaration file(s) present across 73 package(s); 1 built package(s) declare no declaration entry point an",
"pnpm check:dual-build-cjs-loads :: exit 0 (post-build re-run; first pass exit 3 PREREQUISITE NOT MET) :: ✓ check:dual-build-cjs-loads — 108 published require entry point(s) across 67 package(s) load; 718 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree",
"pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: 907 (file, verb) row(s) held by the RETAINED ledger — a pin that leaves names itself.",
"pnpm check:error-status-conformance :: exit 0 :: ✓ every derivable runtime status is documented, and every documented status is reachable.",
"pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (11187 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
"pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strategie",
"pnpm check:lean-entry-closure :: exit 0 (post-build re-run; first pass exit 3 PREREQUISITE NOT MET) :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
"pnpm check:logger-receiver-detach :: exit 0 :: OK every log channel keeps its receiver: 3364 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
"pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 11178 text file(s) -- 11178 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
"pnpm check:objectql-double-limit :: exit 0 :: OK ObjectQL double limit conformance holds: 480 double(s) graded, 289 apply the caller's bound or refuse it loudly.",
"pnpm check:objectui-changeset :: exit 0 :: ✓ objectui-range --self-test: all checks passed",
"pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3386 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
"pnpm check:page-declaration-shape :: exit 0 :: check-page-declaration-shape: OK — 36 page entries across 3389 sources under packages/, examples/, apps/ all reach the kernel through a discoverable declaration (:", "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, the verdict truth ta", "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 71 publishable package(s) of 82 workspace member(s) declare a fileswhitelist that covers every entry point plus CHANGELOG.md and admits no te", "pnpm check:query-options-erasure :: exit 0 :: ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ is c", "pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8931 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.", "pnpm check:route-envelope :: exit 0 :: ✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conforma", "pnpm check:slot-lookup :: exit 0 :: ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered bypnpm li",
"pnpm check:sourcemap-no-sources-content :: exit 0 (post-build re-run; first pass exit 0 while the build was mid-write) :: check-sourcemap-no-sources-content: 70 built package(s) swept - 554 map(s), none embed source text.",
"pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 74 packages with tests scanned; 60 registered as still resolving a workspace dep through dist/; 53 published subpath(s) resolved through ev",
"pnpm check:tier-file-adoption :: exit 0 :: OK: 82 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
"pnpm check:type-check-coverage :: exit 0 :: check-type-check-coverage: OK — 80/82 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https://github.com/objectstack-ai/objec",
"pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 13.2s, 26 raw tsc error(s) total, none above its recorded number.",
"pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -",
"pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 501 matcher(s) discovered, 501 answer the combinator battery correctly or refuse it loudly (322 refuse)."
],
"build": "The first pass had check:dts-closure, check:dual-build-cjs-loads and check:lean-entry-closure at exit 3 (PREREQUISITE NOT MET: the fresh worktree had no dist/). Then OS_VERIFY_LOCK_SLOT=issue-23072-pr1-build os-verify-lock.sh -c pnpm exec turbo run build --filter=!@objectstack/docs --concurrency=2 gave Tasks: 74 successful, 74 total (50 cached), os-verify-lock: VERDICT command-exit 0, held 201s, waited 25s. After it, all three exit 0. check:sourcemap-no-sources-content had run while the build was still writing, so it was re-run on the finished build: exit 0.",
"stale_main": "origin/main moved to e540e7f during the run: 5 commits, none under packages/rest, none in the changeset or dispatch-gates scripts, and ADR-0087's edit (#23161) changes no disposition category (0 matching lines). The same derivation over this PR's 5 paths, on a throwaway detached checkout of e540e7f (since removed), gives the identical 65 commands (diff exit 0). The branch was not re-merged; CI runs on the merge ref, and the queue re-merges.",
"rest_tests": "Not run. The merge brought no change under packages/rest (0 paths), and this round moves no source file. That is the dispatch's condition for skipping them.",
"ci": "Neither waited for nor read, as the dispatch says: CI on d64fc8e is the PM's to read."
},
"files_changed": [
".changeset/23072-meta-doors-closed-sets.md (+8 / -2 in 49994b3; the PR delta against origin/main is still 5 files, +357 / -3)"
],
"commits": [
"49994b3335 chore(changeset): the six /meta doors' closed sets are a breaking accept-set narrowing",
"d64fc8e605 Merge origin/main into claude/issue-23072-meta-doors-closed-sets (merge commit; 15 incoming commits, 0 under packages/rest; packages/spec byte-identical to origin/main's)"
],
"pushes": "Two fast-forward git pushes: d813364..49994b3 and 49994b3..d64fc8e. No force, no rebase, no amend. The pre-push check:commit-card-trailers passed both times.",
"pr_body": "Read before the write (15268 bytes), then changed only what this round changes. Line 2 became Clause-②: no (narrowing). A section, Patch round 1: the changeset declares the narrowing (new head d64fc8e), went in before ## The defect, per door. Two acceptance notes this round made false were rewritten: the not-merged-with-origin/main note, and the note that graded patch under Clause-②: no. The title gained the !. The body keeps its own session-URL footer. Read back over REST after the relay: title equal, body equal, 19107 bytes. Draft, the assignee os-steve and the labels are unchanged.",
"deviations": [
"The changeset had no separate summary line: its first line was the paragraph Six by-name /meta doors now refuse .... The gate reads the body's first line as the summary, so the fix(rest)!: line is a NEW first line above that paragraph. The paragraph is unchanged.",
"The marker says package export, not bare export. rest-server.ts does gain six module-level export const declarations; none reaches the published entry, and the marker states exactly that.",
"The PR body change goes beyond line 2 and the round statement: two acceptance notes that this round made false were corrected (the merge, and the changeset grade). Everything else is byte-identical.",
"The full build (74 tasks) was run although the dispatch named none. Three derived gates read built output and exited 3 without it, so the build was the only way to read them.",
"AGENTS.md section 10 asks for spec build and check:generated when packages/spec moved on either side. Main's side moved it, but this branch touches no packages/spec path, and the merged tree's packages/spec is byte-identical to origin/main (git diff origin/main HEAD -- packages/spec: 0 lines). So no jointly-merged generated artifact exists. @objectstack/spec was built inside the full build, and spec check:generated was not run separately: declared, not hidden.",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer. AGENTS.md requires the model-free pair, and pre-push refuses a model identifier, so both commits carry Claude-Session plus Co-authored-by: Claude (noreply address)."
],
"cleanup": "Worktree /home/user/objectstack-issue-23072: node_modules removed, then git worktree remove without --force (exit 0), after confirming local HEAD equals origin's branch tip d64fc8e and a clean status. The throwaway e540e7f worktree was removed the same way. Both background processes this run started (the gate runner and the locked build) had exited.",
"mcp_calls": "0",
"api_writes": "2 relay strokes as objectstack-fleet[bot]: (1) issue_patch, PATCH /repos//issues/23133 (title and body), run 38352033419, conclusion success, 19107 bytes sent and 19107 stored; (2) this os-dev-report comment, POST /repos//issues/23072/comments. Plus 2 git pushes, which are not REST. REST reads (GET), not writes: the four named comments, the card's comment list, and PR #23133 three times.",
"open_questions": [],
"out_of_scope_findings": []
}
Landed: PR #23133 → c4b84ad702 (the RestServer half, Part of #23072). The card stays open for the dispatcher half, under ruling B
domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T06:10Z · holder of claim 6136144688, released by this landing.
Landed: through the merge queue at 2026-10-13T06:10:30Z as c4b84ad702, a squash with one parent. PUT /meta/:type/:name and the five by-name doors /references, /layers, /audit, /diff and /published declare closed query sets, each measured from its read points. ?member=, and any name a door does not read, answer a located 400 VALIDATION_ERROR instead of being dropped. PUT stays body-addressed.
Review chain:
ACCEPT 6138208712;
the CONTRACT_REVIEW_TIER record FAIL on d813364338 (6138381862, ② only: the changeset grade);
the patch-round ask 6138616966, and the delta record PASS on d64fc8e605 (6139157658). The served models were checked against each reviewer's transcript.
The changeset is @objectstack/restminor, fix(rest)!:, Clause-②: no (narrowing), with an ADR-0087 not-required (no-migration-prescription) disposition and the launch-window BREAKING line. Every check on the head was success or a roster skip.
The caller census held: the SDK, objectui at the console pin, and 18 dogfood files send only names the doors read. One inert in-repo caller (?organizationId= on PUT) was split. Its query half now pins the 400, with nothing written.
The dispatcher half is next, under the maintainer's ruling B (6138588561). One portable read decision in packages/rest/src/meta-item-read-gate.ts, exported once, is called by both transports. RestServer's item GET is re-pointed at it in spelling only. The dispatcher's item GET honours ?member= and refuses what RestServer refuses. The dispatcher's /layers, /published and PUT fold in. Clause-②: yes (@objectstack/restminor), so a contract review is owed. This seat claims it as the card's next slice, and it closes the card.
The finding for the spec lane (which by-name doors could honour member): /layers and /diff can, with threading; /audit needs a column; /published partly; /references never. Recorded here, not built.
Claim: PM loop round 11 (#23072 slice 2: the dispatcher half, under the maintainer's ruling B) · 2026-10-13T06:13Z
Session: session_01VoSxBQujKLZKPwK2u5ehQ6
Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23072-s2-dispatcher-read-decision
Worktree: objectstack-issue-23072-s2
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
File surface (read on origin/mainc4b84ad702; stop on a breach and explain it in the report):
packages/rest/src/meta-item-read-gate.ts: ONE portable read decision for the /meta item GET (for example metaItemReadQueryRefusal(query), returning { code, status, message } or nothing), plus the member reading, beside metaTypeReadRefusal. The ruling is 6138588561.
packages/rest/src/index.ts: exported once, through the entry that already re-exports meta-item-read-gate.ts's family (1-2 names).
packages/rest/src/rest-server.ts: the item GET is re-pointed at the decision in spelling only. Its answers stay byte-identical, pinned by the existing member-selector file.
packages/runtime/src/domains/meta.ts: the dispatcher's item GET calls the decision. It honours ?member= and refuses what RestServer refuses, with the same error.code and message in its own body.
It covers the dev's measured costs (6137337775): member reaches the three getMetaItem calls; the two bare catches let the protocol's located 400 out; the name-keyed MetadataService fallback never answers a named member; and the ?layers= + member refusal holds.
The dispatcher's /layers, /published and PUT branches (out-of-scope finding 1) close their query sets from the same one source.
Tests in packages/rest/src/ and packages/runtime/src/. meta-list-projection-parity's ITEM_PROBES gains member.
⛔ Not touched: packages/spec, packages/metadata-protocol, packages/client, and any other register*Endpoints section of rest-server.ts.
Container & model: M, judged (two transports, one decision, about 350-550 lines by the dev's estimate), mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). Ceiling CONTRACT_REVIEW_TIER, reason: the new export widens @objectstack/rest's public surface (Clause-②: yes, as the maintainer's ruling B names it), and the dispatcher's closed sets narrow accept sets. One review at that tier is owed before the queue. check-governed-merges --test over the claimed paths: not governed.
Clause-②: yes
Responsibility: packages/runtime domains/meta.ts (this lane), the dispatcher's /meta item GET drops ?member= and every unknown name, so a dispatcher-only composition (@objectstack/hono createHonoApp) answers a declared selector with the canonical member | GetMetaItemRequest declares member, and PR #23133 (c4b84ad702) closed RestServer's six sibling doors; ruling B (6138588561) names one portable decision both transports call | read from source by #23072's dev (6137337775), not measured on a dispatcher-only host
Thread-read: 6139451070
Serial constraints cleared:
Of the open PRs (file lists read in this act), none touches rest-server.ts, meta-item-read-gate.ts, packages/rest/src/index.ts or packages/runtime/src/domains/meta.ts.
os-dev-report
{
"issue": 23072,
"status": "done",
"branch": "claude/issue-23072-s2-dispatcher-read-decision",
"pr": "#23211",
"head": "6fe8e7462997a86173eedf17e0625062bdebfd76",
"session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (subagent of the domain:cli seat 2 PM; the parent id, as the dispatch names it)",
"premise_still_valid": true,
"summary": "Ruling B is built. packages/rest/src/meta-item-read-gate.ts gains metaItemQueryRefusal(door, query) and metaMemberSelector(query), exported once from @objectstack/rest (two value exports). RestServer's item GET is re-pointed at it, and its answers are byte-identical (a 480-record BASE/HEAD differential; the member-selector file is unchanged and green). The runtime dispatcher asks the decision on its item GET, PUT, /layers and /published. Its item read honours ?member= through all three getMetaItem calls; with a member named, the two bare catches let the protocol's refusal out, and the registry and MetadataService fallbacks never answer the member. Draft PR #23211 (Fixes #23072, Clause-②: yes (narrowing)) closes the card.",
"decision": {
"signature": "metaItemQueryRefusal(door: 'item' | 'layers' | 'published' | 'save', query: unknown): { status: 400; code: 'VALIDATION_ERROR'; message: string; rule: 'unknown-parameter' | 'repeated-parameter' | 'layered-member' } | undefined",
"rules_in_order": [
"1 recognition: a name outside the door's closed set, with the sentence refuseUnknownQueryParams answers (unknownQueryParamRefusalMessage)",
"2 arity: a single-valued name supplied twice, with repeatedQueryParamMessage; a one-element array is unwrapped IN PLACE, as refuseRepeatedQueryParams does",
"3 item only: a member beside a non-empty ?layers=, with RestServer's sentence byte for byte"
],
"member_reading": "metaMemberSelector(query): { member } | {}, moved unchanged from rest-server.ts",
"entry_exports_added": [
"metaItemQueryRefusal",
"metaMemberSelector"
],
"entry_types_added": "none. MetaItemQueryDoor and MetaItemQueryRefusal are module exports; dist/index.d.ts and index.d.cts declare them locally beside the two exported functions (read after the build).",
"moved_into_meta_item_read_gate_ts": "META_ITEM_MEMBER_PARAM, META_ITEM_READ_PARAMS, META_ITEM_SAVE_PARAMS, META_ITEM_LAYERS_PARAMS, META_ITEM_PUBLISHED_PARAMS, metaMemberSelector. Values unchanged; rest-server.ts imports them for its doors and re-exports the five constants, so both existing door test files import them unchanged. They had to move: rest-server.ts imports the gate module, so the gate module cannot import it back.",
"new_not_exported": "the per-door table; the dispatcher save set, derived as META_ITEM_SAVE_PARAMS less force (not hand-kept); the layered-member sentence",
"query_allowlist_ts": "unknownQueryParamRefusalMessage(query, allowed) is the recognition sentence as a pure function; refuseUnknownQueryParams now writes it. Every route keeps its answer."
},
"restserver_byte_identity": "The re-point: one metaItemQueryRefusal('item', req.query) at the handler top. Rules 1 and 2 are written there as the nested { error: { code, message } } their gates wrote. Rule 3 is thrown at its old position (after resolveProtocol and the mask posture) to handleRouteError, flat. The differential: a scratch harness (never committed) drove both item GET routes (/api/v1/meta/:type/:name and the environment-scoped one) with 30 queries under 8 configurations (cache on/off, layered read present/absent, protocol member refusal on/off, system/plain caller). It recorded status, body, headers, thrown error, the query after the call and every protocol call, on the base tree de24373 and on af356c3. Result: 480 records (256 status 400, 224 status 200). Both dumps are 348398 bytes, cmp identical, sha256 e27c1b55dc34648f12861a8489e8d75516b384bc6c8b4b6e54de0b92f8177ca9. rest-server.ts is unchanged between af356c3 and the final head except through the merge, which touched no packages/rest file. The pinned controls rest-server-meta-member-selector.test.ts and rest-server-meta-door-closed-sets.test.ts are unchanged and green at 6fe8e74.",
"dispatcher": [
"item GET/HEAD (handleMetadataRequest two-segment, after the 405 verb check) — read points: state, preview (inline), package (metaItemPackageBinding), layers (wantsMetaItemLayers), locale (metaRequestLocale behind createMetaItemAnswer), member (metaMemberSelector, new). Before: no set, member never read or forwarded, every unknown name dropped. After: META_ITEM_READ_PARAMS plus the arity list [layers, state, preview, package, member], both RestServer's. Rules 1 and 2 at the read entry; rule 3 after the mask posture, as on RestServer. The member reaches readPendingDraft, the object branch's readFromProtocol and the generic getMetaItem. With a member named: the object branch is protocol-first and skips the registry; both bare catches rethrow to the outer errorFromThrown (the protocol's located 400); MetadataService.getItem is skipped, so an absent member is 404. With no member named, every fall-through is unchanged (control pinned).",
"PUT /meta/:type/:name — read points: package (metaItemPackageBinding), mode (metaSaveRequestOptions); force is not read (settled ruling #11095; its 409 meta-dispatch remedy says the door accepts no force). Before: no set; force and member dropped (?member=zh-CN beside an en-US body wrote en-US). After: [package, mode] = META_ITEM_SAVE_PARAMS less force, both single-valued, answered after the capability and walled-catalog gates and before any query read; packageId is now read after the decision.",
"/layers — read points: package (metaItemPackageBinding). Before: no set. After: META_ITEM_LAYERS_PARAMS ([package]), package single-valued (RestServer's serveMetaItemLayered declares the same), first statement.",
"/published — read points: none. Before: no set. After: META_ITEM_PUBLISHED_PARAMS (empty: "This endpoint takes no query parameters."), first statement.",
"Envelope: deps.error(message, status, { code }), the nested { success: false, error: { code: VALIDATION_ERROR, message, httpStatus } }. On the item read, /layers and /published the code and message equal RestServer's for the same request (pinned per door). PUT's list differs by force, as the doors do."
],
"census": [
"@objectstack/client: meta.getItem sends package (accepted); getPublished sends nothing (accepted); saveItem sends package and mode (accepted) and force only when its caller passes force: true (refused on the dispatcher PUT). No /layers caller. In-repo saveItem callers passing force are only client.test.ts URL-builder tests over a mocked fetch; cli meta register passes none.",
"objectui at the console pin 1fb1ced (blobless read-only fetch): MetadataClient.readItemResponse sends state, preview, package (accepted); layered sends package (accepted); save sends force only from ResourceEditPage and PermissionMatrixEditor force-save, after a 409 DESTRUCTIVE_CHANGE and a confirm. On a dispatcher-only host that retry already failed with the same 409 (the remedy says the door accepts no force); it now answers the located 400 naming force. A changed failure, not a newly red flow. The direct fetches with a query hit /drafts and list reads, not these four doors.",
"In-repo test callers of the dispatcher: none sends a name outside the new sets, so none needed fixing. @objectstack/runtime local 365 of 365 and repo 3 of 3. @objectstack/http-conformance hono-meta-item-read-gate, hono-meta-list-read-gate and hono-meta-save-preconditions (real createHonoApp plus dispatcher; PUT ?mode=draft, GET ?state=draft): 3 files, 17 tests green. @objectstack/hono: 6 files, 139 tests green.",
"Dogfood (the showcase serves these doors through RestServer, so it exercises the re-pointed item GET): email-template-two-locales-survive-boot, flow-shipped-name-layered-read, flow-unshipped-name-layered-code, multi-package-artifact, showcase-object-extension-meta-read, showcase-object-extension-scalar-divergence, meta-published-and-state-routes: 7 files, 47 tests green at 6fe8e74."
],
"changeset": ".changeset/23072-dispatcher-meta-query-decision.md: @objectstack/rest minor (two new exports) and @objectstack/runtime minor (four dispatcher accept sets narrow; BREAKING, shipped minor under the launch-window convention). Summary line fix(runtime)!:, Clause-②: yes (narrowing), exactly one ADR-0087 disposition not-required (no-migration-prescription) stating its facts (in the comment-marker form the gate reads), and the launch-window BREAKING line, as PR #23133's changeset. The Clause-② spelling: check-changeset-no-major --event (the PR title and body) exits 0 for yes, yes (widening), yes (narrowing), no and no (narrowing) (both packages are minor); no (widening) is malformed (level axis NOT MEASURED). yes (narrowing) is the truthful one and is on PR line 2 and in the changeset. check-adr-0087-registration reads it [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription), exit 0. check-empty-changeset exit 0.",
"tests": [
"All at 6fe8e74 through scripts/pm/os-verify-lock.sh, VERDICT command-exit 0 each.",
"TYPECHECK @objectstack/rest: tsc --noEmit + check:test-typecheck OK (0 file(s) / 0 error(s)). @objectstack/runtime: check:test-typecheck OK (27 file(s) / 190 error(s) / 68 pinned, ledger unchanged). --listFiles: rest tsconfig.test.json compiles the new test (1/1); runtime's compiles both touched test files (2/2). The first runtime typecheck after the tests were written was red (TS2493 on an untyped vi.fn in the new test); fixed in 6fe8e74 and re-run green.",
"REST vitest --project local (whole): Test Files 277 passed (277), Tests 5802 passed | 326 skipped (6128). --project repo: Test Files 5 passed (5), Tests 213 passed | 2 skipped (215).",
"RUNTIME vitest --project local (whole): Test Files 365 passed (365), Tests 5201 passed | 19 skipped (5220). --project repo (with meta-list-projection-parity): Test Files 3 passed (3), Tests 808 passed (808).",
"CENSUS: http-conformance 3 files 17 passed; hono 6 files 139 passed; dogfood 7 files 47 passed.",
"NEW PINS: packages/rest/src/meta-item-query-decision.test.ts, 19 tests (sets, sentences, arity, in-place unwrap, rule order, metaMemberSelector, RestServer writing exactly the decision: nested for 1 and 2, flat for 3). packages/runtime/src/domains/meta-item-query-decision.test.ts, 23 tests (the four dispatcher doors, refusal and preservation each; item, /layers and /published refusals compared with RestServer's code and message for the same request; value pins against a real ObjectStackProtocolImplementation({})). meta-list-projection-parity ITEM_PROBES gains ?member=zh-CN and ?layers=true&member=zh-CN (the double refuses a member on a non-bundle type, as the protocol's first step does), plus a reference-moves pin: 682 to 739 tests, both transports agreeing on 26 cells x 4 callers and the 2 undetermined object cells.",
"ABLATION on committed 6fe8e74 (and earlier af356c3; meta.ts is blob 2fbc1e6b65f1 on both), inside the lock, via scripts/ablation-replace.mjs wrap mode plus a bash EXIT/INT/TERM trap restoring git checkout HEAD -- on the absolute path, proven by blob hash and an empty git diff HEAD. The tests import meta.ts from src (../http-dispatcher.js), so no dist preflight applies. Leg 1, decision dropped on all four doors: anchor metaItemQueryRefusal( hit 4 times, replaced by a function answering undefined (marker ABLATION_23072_DECISION_DROPPED); blob 2fbc1e6b65f1 to a21ea5f22452; Tests 38 failed | 724 passed (762): 10 dispatcher refusal and unwrap pins, 28 parity layers+member cells; restored blob == HEAD, diff empty, marker 0. Leg 2, member forward dropped from the generic getMetaItem: blob to df1a6d6c06e8; Tests 31 failed | 731 passed (762): 7 honour, value and absent-member pins, 24 parity generic-type member cells (object cells stay green, their forward intact); restored likewise. Expected red, observed red.",
"LINT, proven narrowing (whole-repo pnpm lint is CI's): (1) population from eslint.config.mjs files ['/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] less NEVER_LINTED; ESLint isPathIgnored false for all 8 touched .ts files. (2) eslint --no-inline-config --format json: 8 files, errorCount 0, warningCount 0, fatal 0, exit 0, at 6fe8e74. (3) invariance: calculateConfigForFile answers parserOptions.project null and projectService null for each, so no type-aware rule; the diff cannot move an untouched file's verdict."
],
"gates_head": "union at HEAD 6fe8e74 (the final head)",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 548 assertions over real temp git repos (real scan()/assertInputs() path)",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@17.7.0 — the one exception, evidenced: pre mode (tag next) and a pending major for @ob",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions ",
"node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every $TURBO_ROOT$ input of a build Build Core runs) and all 11 script(s)",
"node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 11207 tracked file(s), ",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
"node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/ + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 sp",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS check-comment-mask-adoption --self-test (0 failure(s))",
"node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8947 files, 0 disagree, 0 unparseable, 154.4s (comparator self-test: 26 cases pass).",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
"node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
"node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 115 .object.ts files under packages/ + apps/* + examples/** (walk is repo-wide; 0 outside), 120 object declarations, 248 declared index entries, ",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS check-keyed-text-bounds --self-test (0 failure(s))",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 87 platform-namespace objects, 52 in the machinery's reach, 35 outside it, every exclusion explained by a declar",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ the family inherits the literal packages/objectql/src/tenancy/system-write-organization.ts",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 76 Plugin implementation(s) across 8361 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits be",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every r",
"node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 74 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 17 cases + level-vocabulary read + population declaration + real-tree selection floor.",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 32 of 283 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: ✓ the real tree yields a NON-EMPTY observer population that is a strict subset of its test files",
"node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 132 elevation read sites in 21 packages across 60 files, living in 112 symbol(s); the page cites 125 symbol(s) against 125 required, over ",
"node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 82 workspace packages under packages/ + apps/** + examples/**, 3063 non-test src files, 2565 @objectstack/* specifiers (0 assembled, not ",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS check-undeclared-dep-imports --self-test (0 failure(s))",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: ✓ affected-docs self-test: 605 cases pass.",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ C10 the rehearsal doc names this script",
"node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ nothing unconsumed -> one plain line, no annotation, still naming the version commit it read",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3068 source file(s) all carry their unit in the key name (or in a sibling unit, or u",
"pnpm check:authz-resolver :: exit 0 :: ✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.",
"pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #7005 diff ",
"pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3 on ACCEPTED",
"pnpm check:dispatcher-error-vocabulary :: exit 0 :: check-dispatcher-error-vocabulary: OK — 56 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (#8846).",
"pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 94717 string(s) read in 1319 parsed source(s) (seen floor 40000 strings / 600 sources), ",
"pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consumers". This gate polices the census, ",
"pnpm check:dts-closure :: exit 0 :: check-dts-closure: 64 built package(s) swept - 163/163 declared declaration file(s) present across 64 package(s); 0 built package(s) declare no declaration entry point and owe none.",
"pnpm check:dual-build-cjs-loads :: exit 0 :: ✓ check:dual-build-cjs-loads — 108 published require entry point(s) across 67 package(s) load; 718 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree; 106 require c",
"pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: 910 (file, verb) row(s) held by the RETAINED ledger — a pin that leaves names itself.",
"pnpm check:error-status-conformance :: exit 0 :: ✓ every derivable runtime status is documented, and every documented status is reachable.",
"pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (11207 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
"pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strategies agree, diff scope ",
"pnpm check:lean-entry-closure :: exit 0 :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
"pnpm check:logger-receiver-detach :: exit 0 :: OK every log channel keeps its receiver: 3364 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
"pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 11198 text file(s) -- 11198 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
"pnpm check:objectql-double-limit :: exit 0 :: OK ObjectQL double limit conformance holds: 481 double(s) graded, 290 apply the caller's bound or refuse it loudly.",
"pnpm check:objectui-changeset :: exit 0 :: ✓ --help does NOT leak mid-file implementation comments (#11952)",
"pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3386 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
"pnpm check:page-declaration-shape :: exit 0 :: check-page-declaration-shape: OK — 36 page entries across 3389 sources under packages/, examples/, apps/** all reach the kernel through a discoverable declaration (: Page or de", "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ …and --help exits 0", "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 71 publishable package(s) of 82 workspace member(s) declare a fileswhitelist that covers every entry point plus CHANGELOG.md and admits no test, test-harness con", "pnpm check:query-options-erasure :: exit 0 :: ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ is covered bypnp",
"pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8941 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.",
"pnpm check:route-envelope :: exit 0 :: ✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conformant, 2 ratcheted, 0 ex",
"pnpm check:slot-lookup :: exit 0 :: ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered by pnpm lint.",
"pnpm check:sourcemap-no-sources-content :: exit 0 :: check-sourcemap-no-sources-content: 61 built package(s) swept - 538 map(s), none embed source text.",
"pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 74 packages with tests scanned; 60 registered as still resolving a workspace dep through dist/; 53 published subpath(s) resolved through every alias table.",
"pnpm check:tier-file-adoption :: exit 0 :: OK: 82 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
"pnpm check:type-check-coverage :: exit 0 :: check-type-check-coverage: OK — 80/82 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https://github.com//issues/43",
"pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 143.3s, 26 raw tsc error(s) total, none above its recorded number.",
"pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -- every one an ar",
"pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 502 matcher(s) discovered, 502 answer the combinator battery correctly or refuse it loudly (323 refuse).",
"pnpm check:route-ledger-census :: exit 0 :: ✓ check:route-ledger-census — all 1 census sentence(s) match their arrays.",
"node scripts/check-changeset-no-major.mjs --base origin/main --event (PR title + body) :: exit 0 :: ✓ LEVEL AXIS: this PR declares clause-② yes (narrowing), and no package whose published source … it moves is graded patch."
],
"gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list at 6fe8e74 :: exit 0 :: ✓ dispatch-gates --ran: 65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED (a DERIVED zero — all 65 recorded an exit code and none of them is 3). pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: 8 packages without dist); after building them (58/58 cached) it exits 0 and that exit is the one recorded. Beyond the list: pnpm check:route-ledger-census exit 0; check:route-envelope (in the list) exit 0.",
"gates_derivation": "Final: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths, at 6fe8e74: change set from the merge base d4aaeaf, 9 paths, +1073/-110; 65 commands, the identical 65 the PM derived. An earlier derivation at af356c3 warned STALE TREE (origin/main 5 commits ahead); answered by merging origin/main (689e8f6) and refreshing the build state before the union ran.",
"line_budget": "+1073 / -110 = 1183 changed lines in 9 files (dispatch-gates count), under the 3,000-line threshold. Source +583 (meta-item-read-gate.ts +247, of which about 100 are moved declarations and their docblocks; meta.ts +117/-11; rest-server.ts +51/-90; query-allowlist.ts +27/-8; index.ts +9), the changeset +23, tests +599/-1. The ruling estimated 350-550 lines; the source half is in it, the tests are the rest.",
"deviations": [
"File surface: none outside the claim. query-allowlist.ts was edited under its conditional (the recognition sentence is now shared from there as unknownQueryParamRefusalMessage). rest-server.ts changed in 7 hunks: one import block, the moved declarations (with their re-export) and the item GET's two hunks. No other handler line changed: the four other member doors' metaMemberSelector calls and the PUT, /layers and /published gate lines are byte-unchanged and resolve those names through the import. The 480-record differential covers the item GET; slice 1's closed-sets file and the member-selector file, both unchanged and green, pin the others.",
"Exports: two values on the entry, as ruled. No type was added to the entry; the two types the signature uses are declared locally in the emitted .d.ts.",
"Dispatcher ordering: rules 1 and 2 answer at each door's entry. On /layers that is before its 501 for a protocol with no layered read; RestServer's /layers (untouched) answers arity inside serveMetaItemLayered, after its 501. So only a request both malformed (a repeated package) and sent to a host with no layered read answers 400 on the dispatcher and 501 on RestServer. Through createHonoApp no repeated name reaches the dispatcher (out_of_scope_findings[0]).",
"Merged origin/main (d4aaeaf, 5 commits, none touching a file in this diff) as 689e8f6, and refreshed the build state (install, closure build, packages/runtime/.objectstack removed). Every test, the ablation re-run, lint and the gate union ran on the final head 6fe8e74.",
"NOT MEASURED and re-run: one runtime local run was terminated by its background budget mid-run (the wrapper's 10-minute cap, which included the lock wait); one acquisition ended exit 99 queue-timeout and resumed with the same OS_VERIFY_LOCK_SLOT. The recorded results are the completed re-runs.",
"Commits carry the model-free trailer pair (Claude-Session + Co-authored-by: Claude) per AGENTS.md, not the model-named Co-Authored-By the harness reminder suggested. The PR body ends with the session-URL footer form, not the harness two-line block (the role file: the harness reminder yields).",
"Zero label writes: the dispatch named no label, and skip-changeset is not owed (the PR carries a publishing changeset)."
],
"files_changed": [
".changeset/23072-dispatcher-meta-query-decision.md",
"packages/rest/src/index.ts",
"packages/rest/src/meta-item-query-decision.test.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/rest/src/query-allowlist.ts",
"packages/rest/src/rest-server.ts",
"packages/runtime/src/domains/meta-item-query-decision.test.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts",
"packages/runtime/src/domains/meta.ts"
],
"mcp_calls": "0 — no MCP GitHub tool called; reads went through gh api (REST GET) and git.",
"api_writes": "2 repository_dispatch POSTs from this session (POST /repos/objectstack-ai/objectstack/dispatches), one per relay stroke, executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft PR #23211; read back: 17290 bytes sent = 17290 stored, identical) + POST /repos//issues/23211/assignees [os-steve] (read back: os-steve), run 38372715165; (2) POST /repos//issues/23072/comments (this os-dev-report, scripts/pm/post-stamped.mjs). 3 REST writes in all. No label write, no PATCH, no claim, no card-assignee write. Not REST: 6 git pushes of the branch (the empty landing marker, then 7675d9a, e6d9b3e, af356c3, 689e8f6, 6fe8e74).",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: measured at the public door's adapter: createHonoApp (packages/adapters/hono/src/index.ts:789, the catch-all builds queryParams with url.searchParams.forEach, last value wins). A scratch request GET /api/meta/email_template/acme.welcome?member=zh-CN&member=ja-JP handed dispatch() {"member":"ja-JP"} (status 200, mocked dispatcher, @objectstack/hono at 6fe8e74). So no repeated name ever reaches a dispatcher domain on a createHonoApp host: the multiplicity rule (#6877; the #6878 cli-lane ruling that the handler must SEE the ambiguity to reject it, which plugin-hono-server's readQuery honours) is unreachable there. The first value is dropped silently, and the dispatcher now honours the last member where RestServer answers 400 "supplied 2 times". Seam: spec: none → runtime: @objectstack/hono createHonoApp catch-all query flattening → every dispatcher domain that declares an arity rule (/meta here, /packages' repeatedQueryParamMessage). Family: query multiplicity, not #23072. Dedupe words: createHonoApp repeated query parameter last value wins; hono catch-all searchParams forEach flattens; dispatcher multiplicity unreachable"
]
}
✅ ACCEPT: PR #23211 at 6fe8e74629 (#23072 slice 2, the dispatcher half under ruling B; Fixes #23072). It queues after one CONTRACT_REVIEW_TIER PASS on this head and a green head
PR shape: draft, base main, line 1 Fixes #23072, line 2 Clause-②: yes (narrowing), assignee os-steve, title fix(runtime)!:.
The arm is the truthful one: two new value exports on @objectstack/rest (yes) and four dispatcher accept sets narrowed (BREAKING).
check-changeset-no-major --event and check-adr-0087-registration both exit 0 on it.
Scope: 9 files, +1073 / −110, all inside the claim. query-allowlist.ts falls under the claim's conditional ("only if the sentence must be shared from there"): the recognition sentence is now one pure function both transports write. check-governed-merges --pr 23211: not governed. git merge-tree against today's origin/main (150da61aa5) is clean.
Ruling B, read in the diff:
One decision:metaItemQueryRefusal(door, query) in meta-item-read-gate.ts, and the member's one reading metaMemberSelector(query). Both are exported once from @objectstack/rest, and no type joins the entry.
The sets moved: they now live beside the decision. rest-server.ts imports them and re-exports the five constants, because the gate module cannot import rest-server.ts back.
RestServer's item GET, re-pointed in spelling only: rules 1 and 2 at the handler top, and rule 3 at its old position after the mask posture.
The dispatcher (domains/meta.ts):
the item read asks the decision first and holds rule 3 until after the mask posture, as RestServer does;
it hands memberSelector to all three getMetaItem calls;
with a member named, the object branch goes protocol-first, both bare catches rethrow, and the registry and MetadataService fallbacks are skipped, so an absent member is 404 and a protocol refusal is itself;
RestServer byte identity: a 480-record BASE/HEAD differential over both item GET routes (30 queries × 8 configurations) gives two dumps that are cmp-identical. Slice 1's closed-sets file and the member-selector file are unchanged and green.
Pins and ablations: 19 new rest tests and 23 new runtime tests, and the parity file's ITEM_PROBES gains the member cells (682 → 739, both transports agreeing). Two ablations, each red and each restored to the HEAD blob:
dropping the decision on all four doors reds 38;
dropping the generic read's member forward reds 31.
The census held:
@objectstack/client sends only accepted names. It sends force only when its caller passes force: true, and no in-repo caller does outside URL-builder tests.
objectui at the console pin sends force only on the post-409 force-save retry. On a dispatcher-only host that retry already failed with the same 409, and it now answers the located 400 naming force: a changed failure, not a newly red flow.
http-conformance (17), hono (139) and seven dogfood files (47) are green.
Tests and gates at 6fe8e74629:
rest local 277 files / 5802 passed, and repo 5 / 213;
runtime local 365 / 5201, and repo 3 / 808;
both typechecks exit 0, and the ledgers are held;
65 gates derived, 65 run, all exit 0, reconciled with --ran;
lint is narrowed with the three-part proof.
CI on 6fe8e74629 at this stamp: latest run per name, 25 success and 6 skipped, every skip in the roster. The six Test Core shards and Type Check · consumer gates are in progress. Not green yet.
Writes:mcp_calls 0; api_writes 2 relay strokes (pr_create with its assignee, and the report).
Deviation recorded, no action: a repeated package sent to a host with no layered read answers 400 on the dispatcher's /layers and 501 on RestServer's, because the arity rule sits before the 501 there and after it here. Through createHonoApp, no repeated name reaches the dispatcher (the finding below).
From the report, to be filed by the seat after dedupe: the class-b finding that createHonoApp's catch-all flattens a repeated query name to its last value, so the dispatcher's multiplicity rule (#6877 / #6878) is unreachable on a Hono host.
Landed: PR #23211 → 8024e1243a (Fixes #23072, the dispatcher half under ruling B). The card closes completed
domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T09:55Z · holder of claim 6139487972, released by this landing.
Landed: through the merge queue at 2026-10-13T09:54:51Z as 8024e1243a, a squash with one parent.
The decision: the by-name /meta doors on both transports ask ONE query decision, metaItemQueryRefusal(door, query) in @objectstack/rest's meta-item-read-gate.ts, with the member's one reading metaMemberSelector(query). Both are exported once.
The dispatcher's item read honours ?member= through all three of its protocol reads, and a named member is the protocol's alone to answer.
Closed sets: the dispatcher's item read, PUT, /layers and /published refuse any name they do not read with RestServer's code and sentence, where they used to drop it.
RestServer's item GET is re-pointed in spelling only, and its answers are byte-identical (a 480-record differential).
every check on the head was success or a roster skip.
The changeset is @objectstack/restminor and @objectstack/runtimeminor, fix(runtime)!:, Clause-②: yes (narrowing), with one ADR-0087 not-required (no-migration-prescription) marker and the launch-window BREAKING line.
What changes for a caller:
On a dispatcher host, GET /meta/:type/:name?member=zh-CN answers that member, or the protocol's located refusal, instead of the canonical item.
A misspelled or unread query name on the four doors is a located 400 VALIDATION_ERROR instead of being ignored.
Carried, not filed (the record's two residuals, neither a defect at this head):
RestServer's PUT and serveMetaItemLayered still spell slice 1's arity lists beside the decision's once table. Each is a one-line re-point, best made by the next change that touches those doors.
MetaItemQueryDoor and MetaItemQueryRefusal are not on the entry's export type block, where the sibling refusal types are. Adding them is a type-surface-only widening, done only if a consumer needs the names.
The spec-lane finding recorded at slice 1's landing stands: which by-name doors could honour member (/layers and /diff with threading, /audit with a column, /published partly, /references never).
Holds released:meta-item-read-gate.ts, query-allowlist.ts, packages/rest/src/index.ts, rest-server.ts's item GET, packages/runtime/src/domains/meta.ts, and the three test files.
Activity
objectstack-fleet commentedon Oct 13, 2026
✅ ACCEPT: PR #23133 at
d813364338(theRestServerhalf,Part of #23072). The dispatcher half goes to the maintainer, and the PR queues after oneCONTRACT_REVIEW_TIERPASS and after #23148 clearsdomain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-13T04:25Z · holder of claim6136144688. Report:os-dev-report6137337775(needs_decision). The dev's final message corrects three prose sentences of that comment; every measurable field is identical. Thread-read: 6137337775.Checklist, read against GitHub:
PR shape: draft, base
main. Line 1 isPart of #23072, with no closing keyword, because the dispatcher half is open. Line 2 isClause-②: no. Assigneeos-steve.Scope: 5 files, +351 / −3.
check-governed-merges --pr 23133: not governed.git merge-treeagainst today'sorigin/mainis clean.The doors, read in the diff: six named sets beside
META_ITEM_*_PARAMS, each measured from the handler's read points, and onerefuseUnknownQueryParamsline per door:PUT:force,package,mode;/references: none;/layers:package;/audit:limit;/diff:from,fromVersion,to,toVersion;/published: none.?member=and any unread name answer a located 400VALIDATION_ERROR. The protocol is not called, and nothing is saved.PUTstays body-addressed. None of the five starts honouringmember, as ruled.The caller census: the SDK (
saveItem,getReferences,getPublished,getAudit,diffItem), objectui at the console pin1fb1ced36243(read-only shallow fetch), and 18 dogfood files all send only names the doors read. One caller was inert:meta-item-save-capability-gate.test.tssent?organizationId=toPUT. It is split. The body half is unchanged, and the query half now pins the 400 withsaveMetaItemnever called, which is stronger than the old 200.One edit outside the claim, accepted (the seat amends the surface):
packages/rest/src/query-allowlist.ts'sunknownQueryParamMessagesays "This endpoint takes no query parameters." for an empty set. Otherwise the first empty sets would ship the sentenceSupported parameters: .. Every non-empty sentence is byte-identical, and its three pins pass unchanged.The finding for the spec lane (which by-name doors could honour
member):/referencesnever can (a reference names an item, not a member);/layersand/diffcan, with threading;/auditneeds a column;/publishedpartly. Recorded here, not built.Tests and gates:
@objectstack/rest: typecheck exits 0. The whole local project at52b332b6d5: 275 of 276 files passed, the one red being the inert caller, since fixed. Atd813364338: the three touched test files (72 tests) and therepoproject (213 passed) are green.restre-run at the final head is NOT MEASURED, after three lock timeouts behind finding(verify): bootStack composes no MetadataPlugin, so a stack's declaredapisanswer 404 ENDPOINT_NOT_FOUND there whileos serveof the same stack answers them — its "what serve composes" rule (#22301, ruling A) has a gap #23088's run. Between the two heads only the census test file changed. CI ran the full set and is green.@objectstack/runtime: 363 files, 5174 passed; therepoproject 751 passed.PUT's refusal reds exactly its 3 pins, and the restore was proven.check:route-ledger-censusandcheck:route-envelopeexit 0.CI on
d813364338at this stamp: latest run per name, 34 success and 6 skipped.Contract review owed: the PR narrows six published accept sets (
Clause-②: no).needs:contract-reviewgoes on PR fix(rest)!: PUT and the five by-name /meta sub-doors declare closed query sets, so ?member= is refused, never dropped (#23072, RestServer half) #23133 in this act.The dispatcher half is put to the maintainer in this session's chat. Honouring
?member=on the dispatcher's item GET, and refusing whatMETA_ITEM_READ_PARAMSrefuses, from one source, needs a new export from@objectstack/rest. The dev's three options are A, B (recommended) and C. The answer is recorded on this card verbatim. The dispatcher's/layers,/publishedandPUTbranches have no closed set either (out-of-scope finding 1), and they fold into that half, not into a card of their own.Sequence: hourly full run: red on main (CI) #23148 is this lane's full-battery red on
main. Under the two-speed doctrine (skills(ci): two-speed CI — fast set on PR and merge-queue runs, full battery hourly on main and before a release, main-red stops the lane, four required contexts plus the guard, timing gates report-only, test budgets not deletions #22998), this PR arms the queue only after it clears.Writes:
mcp_calls0;api_writes2 relay strokes (pr_createwith its assignee, and the report).Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Maintainer ruling on #23072's dispatcher half: B. One portable read decision in
meta-item-read-gate.ts, called by both transportsRecorded by
domain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-13T04:59Z · holder of claim6136144688. This records the maintainer's answer, given in this session's chat, to the dev's open question (os-dev-report6137337775,open_questions[0]; ACCEPT6138208712).The maintainer's choice, verbatim: 「B 共用判定函数 (推荐)」. The option text the maintainer chose, as the seat put it in chat:
What this rules:
metaItemReadQueryRefusal(query), which returns{ code, status, message }or nothing, plus thememberreading. It lives inpackages/rest/src/meta-item-read-gate.tsbesidemetaTypeReadRefusal, and is exported once from@objectstack/rest's entry.RestServer's item GET is re-pointed at it in spelling only. Its answers stay byte-identical, pinned by the existing member-selector file.?member=, refuses whatRestServerrefuses, and writes the sameerror.codeand message in its own transport's body.memberreaches the threegetMetaItemcalls; the two barecatches let the protocol's located 400 out; the name-keyedMetadataServicefallback never answers a named member; and the?layers=+memberrefusal holds./layers,/publishedandPUTbranches (out-of-scope finding 1) fold into the same slice, under the same one-source rule.Clause-②: yes(1-2 new exports widen@objectstack/rest's public surface), so@objectstack/restisminor, and one review atCONTRACT_REVIEW_TIERis owed before the queue.RestServerhalf,Part of #23072) lands first, after its changeset patch round. The dispatcher half is this card's next slice, claimed after that landing, and it closes the card.rest-server.ts's serial queue holds [maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050's next slice behind it.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Seat review of PR #23133 at
d813364338: the contract review FAILs on ② only, and patch round 1 is asked. Changeset text only, no source changedomain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-13T05:01Z · holder of claim6136144688. ⛔ The ACCEPT6138208712stands for everything but this.The record:
6138381862on PR #23133,CONTRACT_REVIEW_TIER, FAIL. The served model was checked against the reviewer's transcript.patch, with a bareClause-②: noand no ADR-0087 disposition. So the gates classify it non-breaking, and the CHANGELOG would reach consumers with no breaking carrier.datecell year below 1000 unpadded (0500-01-01→500-01-01), so after PR #20524 a valid ISO date cell is refused per row asinvalid_date, and before it a non-day was stored #20534 / /import: atimecell with milliseconds (10:00:00.250), exactly as/exportwrites it, is refused per row as invalid_date, so the export does not re-import #20722 (minor,Clause-②: no (narrowing), a disposition marker), and fix(metadata-protocol)!: the save door refuses aprotectionblock instead of storing a lock that binds nothing #23115 / PR fix(metadata-protocol)!: the save door refuses aprotectionblock instead of storing a lock that binds nothing #23115, which narrowed one runtime write door the same way.What the patch does, one commit,
.changeset/23072-meta-doors-closed-sets.mdonly:'@objectstack/rest': minor(waspatch).protectionblock instead of storing a lock that binds nothing #23115's does:fix(rest)!: ….Clause-②: no (narrowing)(wasClause-②: no).adr-0087: not-required (no-migration-prescription), stating its facts as fix(metadata-protocol)!: the save door refuses aprotectionblock instead of storing a lock that binds nothing #23115's does. No spec key, authorable spelling, export or stored shape moves. What narrows is six runtime doors' query accept sets, and the remedy is in the request a caller sends. Each other category is closed on facts.**BREAKING** (an accept-set narrowing), shipped as \minor` under the launch-window convention for breaking changes.`, as fix(metadata-protocol)!: the save door refuses aprotectionblock instead of storing a lock that binds nothing #23115's.The per-door remedies already in the changeset stay as written.
Also through the relay, in one
issue_patch: PR #23133's body line 2 becomesClause-②: no (narrowing), and its title gains the!(fix(rest)!: …).Then
pnpm check:adr-0087-registration --base origin/main,check:changeset-no-major,check:empty-changesetand the derived gates, and a delta review atCONTRACT_REVIEW_TIERon the new head judges ② only, re-recording ① and ③ as written.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23072,
"patch_round": 1,
"status": "done",
"branch": "claude/issue-23072-meta-doors-closed-sets",
"pr": "#23133",
"head": "d64fc8e605e08f7748dd5e858ca80770ef9ec50b",
"session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (subagent of the domain:cli seat 2 PM; the parent's id)",
"premise_still_valid": true,
"summary": "Patch round 1 answers the contract review 6138381862 (FAIL on item 2 only). One commit, 49994b3, edits .changeset/23072-meta-doors-closed-sets.md only. It grades @objectstack/rest minor, adds the summary line fix(rest)!: ..., changes the declaration to Clause-②: no (narrowing), adds exactly one ADR-0087 disposition marker, not-required (no-migration-prescription), with its facts checked against the diff, and adds the launch-window BREAKING line. The per-door remedies are unchanged. origin/main (dc93781, 15 commits, none under packages/rest) is merged in by merge commit d64fc8e, the new head. One relay issue_patch set PR #23133's title to fix(rest)!: ... and its body line 2 to Clause-②: no (narrowing), and added a Patch round 1 section that states the round and the new head; the read-back is byte-identical. No source file moved.",
"claim_check": "The newest Claim: on #23072 is 6136144688 (2026-10-13T01:33:55Z, the only claim among the 8 comments read). It names branch claude/issue-23072-meta-doors-closed-sets, which matches. No claim was posted, and the card assignee was not written.",
"changeset_before": {
"at": "d813364338",
"frontmatter": "'@objectstack/rest': patch",
"summary_line": "none (the body opened with the paragraph Six by-name /meta doors now refuse ...)",
"clause_line": "Clause-②: no",
"adr_0087_marker": "none",
"breaking_line": "none"
},
"changeset_after": {
"at": "d64fc8e605 (content from 49994b3)",
"frontmatter": "'@objectstack/rest': minor",
"summary_line": "fix(rest)!:
PUTand the five by-name/metasub-doors declare closed query sets, so?member=is refused with a located400 VALIDATION_ERROR, never dropped","clause_line": "Clause-②: no (narrowing)",
"adr_0087_marker": "HTML-comment form (the opening and closing comment brackets are spelled out here for the sanitizer): adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, package export or stored shape is added, removed, renamed or re-shaped. The six closed sets are module constants that
@objectstack/rest's entry does not re-export, and no stored row or package source changes, so there is nothing forobjectstack migrate metato rewrite. What narrows is six runtime doors' query accept sets (PUT /meta/:type/:name,/references,/layers,/audit,/diff,/published): a query parameter those doors dropped now answers a located400 VALIDATION_ERROR, and the remedy is in the request a caller sends to that door, never a rewrite of a package source or a stored row. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and no exported declaration is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired).","breaking_line": "BREAKING (an accept-set narrowing), shipped as
minorunder the launch-window convention for breaking changes.","marker_facts_checked": [
"No path under packages/spec, no .object.ts, and no conversion or migration path is in the PR diff (git diff --name-only against the merge base; grep exit 1, no match).",
"packages/rest/src/index.ts re-exports none of the six META_ITEM__PARAMS constants and nothing from query-allowlist.ts (grep exit 1). The build entry is src/index.ts alone (tsup.config.ts), the exports map holds only ., and index.ts is not in the diff (0 lines).",
"The only removed source line in the PR is inside unknownQueryParamMessage (query-allowlist.ts), which the entry does not export. Its signature is unchanged.",
"The diff names no ADR-0087 id and touches no conversion registry (grep for adr-0087, conversion, retired, migrat: exit 1, no match).",
"@objectstack/rest is not private (package.json private undefined, version 17.7.0, files dist, README.md, CHANGELOG.md)."
]
},
"gates": {
"head": "d64fc8e605",
"named": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition. Signals BREAKING+bang+clause-②-narrowing, disposition not-required (no-migration-prescription).",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ This diff introduces no major bump. The level axis reads NOT APPLICABLE locally: no pull_request payload.",
"node scripts/check-changeset-no-major.mjs --base origin/main --event (the patched title and body) :: exit 0 :: ✓ LEVEL AXIS: this PR declares clause-② no (narrowing), and no package whose published source ... it moves is graded patch.",
"control: the same event against the old head (--head d813364, changeset still patch) :: exit 1 :: Check Changeset (level axis): clause-② declares no (narrowing) while no moved package is graded minor or above. Second control: the old body at d64fc8e :: exit 0, direction arm none declared. So the declaration and the grade move together, and at d64fc8e they do.",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added). ✓ No changeset from the merge base modified or deleted by this diff.",
"pnpm check:changeset-gate-self-tests :: exit 0 :: 170 + 548 + 401 assertions (empty-changeset, adr-0087-registration, changeset-no-major self-tests)."
],
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at d64fc8e: 65 commands over 5 paths against merge base dc93781. All 65 exit 0. dispatch-gates --ran: ✓ 65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED (a DERIVED zero — all 65 recorded an exit code and none of them is 3).",
"derived_verdicts": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 548 assertions over real temp git repos (real scan()/assertInputs() path)",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@17.7.0 — the one exception, evidenced: pre mode (tag
next) and a pendingmajorfor @objectstack/spec","node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
"node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every
$TURBO_ROOT$input of a build Build Core runs) and all 11 sc","node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 11187 tracked file(s),",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
"node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 ",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS check-comment-mask-adoption --self-test (0 failure(s))",
"node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8937 files, 0 disagree, 0 unparseable, 142.5s (comparator self-test: 26 cases pass).",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
"node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
"node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 115 .object.ts files under packages/ + apps/* + examples/** (walk is repo-wide; 0 outside), 120 object declarations, 248 declared index ent",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS check-keyed-text-bounds --self-test (0 failure(s))",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 87 platform-namespace objects, 52 in the machinery's reach, 35 outside it, every exclusion explained by a declaration o",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ check-platform-object-tenancy-census self-test: all checks pass (87 objects, 35 outside the machinery)",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 76 Plugin implementation(s) across 8351 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits ",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every roster name",
"node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 74 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 17 cases + level-vocabulary read + population declaration + real-tree selection floor.",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 32 of 282 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s).",
"node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 132 elevation read sites in 21 packages across 60 files, living in 112 symbol(s); the page cites 125 symbol(s) against 125 required, ove",
"node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 82 workspace packages under packages/ + apps/** + examples/, 3063 non-test src files, 2563 @objectstack/* specifiers (0 assembled, not",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS check-undeclared-dep-imports --self-test (0 failure(s))",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: ✓ affected-docs self-test: 605 cases pass.",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ self-test passed",
"node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ release-pending-publish self-test: 92 cases across 22 batteries pass.",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3068 source file(s) all carry their unit in the key name (or in a sibling
unit, or under a declare","pnpm check:authz-resolver :: exit 0 :: ✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.",
"pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
"pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split 'test:repo' task); 13 walked root(s) judged, 3 on",
"pnpm check:dispatcher-error-vocabulary :: exit 0 :: check-dispatcher-error-vocabulary: OK — 56 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (#8846).",
"pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 94674 string(s) read in 1320 parsed source(s) (seen floor 40000 st",
"pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states '#6664 census: 2 ruled consumers'. This gate polic",
"pnpm check:dts-closure :: exit 0 (post-build re-run; first pass exit 3 PREREQUISITE NOT MET) :: check-dts-closure: 74 built package(s) swept - 174/174 declared declaration file(s) present across 73 package(s); 1 built package(s) declare no declaration entry point an",
"pnpm check:dual-build-cjs-loads :: exit 0 (post-build re-run; first pass exit 3 PREREQUISITE NOT MET) :: ✓ check:dual-build-cjs-loads — 108 published require entry point(s) across 67 package(s) load; 718 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree",
"pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: 907 (file, verb) row(s) held by the RETAINED ledger — a pin that leaves names itself.",
"pnpm check:error-status-conformance :: exit 0 :: ✓ every derivable runtime status is documented, and every documented status is reachable.",
"pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (11187 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
"pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strategie",
"pnpm check:lean-entry-closure :: exit 0 (post-build re-run; first pass exit 3 PREREQUISITE NOT MET) :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
"pnpm check:logger-receiver-detach :: exit 0 :: OK every log channel keeps its receiver: 3364 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
"pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 11178 text file(s) -- 11178 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
"pnpm check:objectql-double-limit :: exit 0 :: OK ObjectQL double
limitconformance holds: 480 double(s) graded, 289 apply the caller's bound or refuse it loudly.","pnpm check:objectui-changeset :: exit 0 :: ✓ objectui-range --self-test: all checks passed",
"pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3386 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
"pnpm check:page-declaration-shape :: exit 0 :: check-page-declaration-shape: OK — 36 page entries across 3389 sources under packages/, examples/, apps/ all reach the kernel through a discoverable declaration (
:", "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, the verdict truth ta", "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 71 publishable package(s) of 82 workspace member(s) declare afileswhitelist that covers every entry point plus CHANGELOG.md and admits no te", "pnpm check:query-options-erasure :: exit 0 :: ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ is c", "pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8931 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.", "pnpm check:route-envelope :: exit 0 :: ✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conforma", "pnpm check:slot-lookup :: exit 0 :: ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered bypnpm li","pnpm check:sourcemap-no-sources-content :: exit 0 (post-build re-run; first pass exit 0 while the build was mid-write) :: check-sourcemap-no-sources-content: 70 built package(s) swept - 554 map(s), none embed source text.",
"pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 74 packages with tests scanned; 60 registered as still resolving a workspace dep through
dist/; 53 published subpath(s) resolved through ev","pnpm check:tier-file-adoption :: exit 0 :: OK: 82 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
"pnpm check:type-check-coverage :: exit 0 :: check-type-check-coverage: OK — 80/82 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https://github.com/objectstack-ai/objec",
"pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 13.2s, 26 raw tsc error(s) total, none above its recorded number.",
"pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -",
"pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 501 matcher(s) discovered, 501 answer the combinator battery correctly or refuse it loudly (322 refuse)."
],
"build": "The first pass had check:dts-closure, check:dual-build-cjs-loads and check:lean-entry-closure at exit 3 (PREREQUISITE NOT MET: the fresh worktree had no dist/). Then OS_VERIFY_LOCK_SLOT=issue-23072-pr1-build os-verify-lock.sh -c pnpm exec turbo run build --filter=!@objectstack/docs --concurrency=2 gave Tasks: 74 successful, 74 total (50 cached), os-verify-lock: VERDICT command-exit 0, held 201s, waited 25s. After it, all three exit 0. check:sourcemap-no-sources-content had run while the build was still writing, so it was re-run on the finished build: exit 0.",
"stale_main": "origin/main moved to e540e7f during the run: 5 commits, none under packages/rest, none in the changeset or dispatch-gates scripts, and ADR-0087's edit (#23161) changes no disposition category (0 matching lines). The same derivation over this PR's 5 paths, on a throwaway detached checkout of e540e7f (since removed), gives the identical 65 commands (diff exit 0). The branch was not re-merged; CI runs on the merge ref, and the queue re-merges.",
"rest_tests": "Not run. The merge brought no change under packages/rest (0 paths), and this round moves no source file. That is the dispatch's condition for skipping them.",
"ci": "Neither waited for nor read, as the dispatch says: CI on d64fc8e is the PM's to read."
},
"files_changed": [
".changeset/23072-meta-doors-closed-sets.md (+8 / -2 in 49994b3; the PR delta against origin/main is still 5 files, +357 / -3)"
],
"commits": [
"49994b3335 chore(changeset): the six /meta doors' closed sets are a breaking accept-set narrowing",
"d64fc8e605 Merge origin/main into claude/issue-23072-meta-doors-closed-sets (merge commit; 15 incoming commits, 0 under packages/rest; packages/spec byte-identical to origin/main's)"
],
"pushes": "Two fast-forward git pushes: d813364..49994b3 and 49994b3..d64fc8e. No force, no rebase, no amend. The pre-push check:commit-card-trailers passed both times.",
"pr_body": "Read before the write (15268 bytes), then changed only what this round changes. Line 2 became Clause-②: no (narrowing). A section, Patch round 1: the changeset declares the narrowing (new head d64fc8e), went in before ## The defect, per door. Two acceptance notes this round made false were rewritten: the not-merged-with-origin/main note, and the note that graded patch under Clause-②: no. The title gained the !. The body keeps its own session-URL footer. Read back over REST after the relay: title equal, body equal, 19107 bytes. Draft, the assignee os-steve and the labels are unchanged.",
"deviations": [
"The changeset had no separate summary line: its first line was the paragraph Six by-name /meta doors now refuse .... The gate reads the body's first line as the summary, so the fix(rest)!: line is a NEW first line above that paragraph. The paragraph is unchanged.",
"The marker says package export, not bare export. rest-server.ts does gain six module-level export const declarations; none reaches the published entry, and the marker states exactly that.",
"The PR body change goes beyond line 2 and the round statement: two acceptance notes that this round made false were corrected (the merge, and the changeset grade). Everything else is byte-identical.",
"The full build (74 tasks) was run although the dispatch named none. Three derived gates read built output and exited 3 without it, so the build was the only way to read them.",
"AGENTS.md section 10 asks for spec build and check:generated when packages/spec moved on either side. Main's side moved it, but this branch touches no packages/spec path, and the merged tree's packages/spec is byte-identical to origin/main (git diff origin/main HEAD -- packages/spec: 0 lines). So no jointly-merged generated artifact exists. @objectstack/spec was built inside the full build, and spec check:generated was not run separately: declared, not hidden.",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer. AGENTS.md requires the model-free pair, and pre-push refuses a model identifier, so both commits carry Claude-Session plus Co-authored-by: Claude (noreply address)."
],
"cleanup": "Worktree /home/user/objectstack-issue-23072: node_modules removed, then git worktree remove without --force (exit 0), after confirming local HEAD equals origin's branch tip d64fc8e and a clean status. The throwaway e540e7f worktree was removed the same way. Both background processes this run started (the gate runner and the locked build) had exited.",
"mcp_calls": "0",
"api_writes": "2 relay strokes as objectstack-fleet[bot]: (1) issue_patch, PATCH /repos//issues/23133 (title and body), run 38352033419, conclusion success, 19107 bytes sent and 19107 stored; (2) this os-dev-report comment, POST /repos//issues/23072/comments. Plus 2 git pushes, which are not REST. REST reads (GET), not writes: the four named comments, the card's comment list, and PR #23133 three times.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23133 →
c4b84ad702(theRestServerhalf,Part of #23072). The card stays open for the dispatcher half, under ruling Bdomain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-13T06:10Z · holder of claim6136144688, released by this landing.Landed: through the merge queue at 2026-10-13T06:10:30Z as
c4b84ad702, a squash with one parent.PUT /meta/:type/:nameand the five by-name doors/references,/layers,/audit,/diffand/publisheddeclare closed query sets, each measured from its read points.?member=, and any name a door does not read, answer a located400 VALIDATION_ERRORinstead of being dropped.PUTstays body-addressed.Review chain:
6138208712;CONTRACT_REVIEW_TIERrecord FAIL ond813364338(6138381862, ② only: the changeset grade);6138616966, and the delta record PASS ond64fc8e605(6139157658). The served models were checked against each reviewer's transcript.The changeset is
@objectstack/restminor,fix(rest)!:,Clause-②: no (narrowing), with an ADR-0087not-required (no-migration-prescription)disposition and the launch-window BREAKING line. Every check on the head was success or a roster skip.The caller census held: the SDK, objectui at the console pin, and 18 dogfood files send only names the doors read. One inert in-repo caller (
?organizationId=onPUT) was split. Its query half now pins the 400, with nothing written.The dispatcher half is next, under the maintainer's ruling B (
6138588561). One portable read decision inpackages/rest/src/meta-item-read-gate.ts, exported once, is called by both transports.RestServer's item GET is re-pointed at it in spelling only. The dispatcher's item GET honours?member=and refuses whatRestServerrefuses. The dispatcher's/layers,/publishedandPUTfold in.Clause-②: yes(@objectstack/restminor), so a contract review is owed. This seat claims it as the card's next slice, and it closes the card.The finding for the spec lane (which by-name doors could honour
member):/layersand/diffcan, with threading;/auditneeds a column;/publishedpartly;/referencesnever. Recorded here, not built.Serial queue on
rest-server.ts: this slice's hold is released. The dispatcher half (it re-pointsRestServer's item GET) and [maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050's next slice go one at a time.Holds released:
packages/rest/src/rest-server.ts(registerMetadataEndpointsInner's six doors),query-allowlist.ts, and the two rest test files.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 11 (#23072 slice 2: the dispatcher half, under the maintainer's ruling B) · 2026-10-13T06:13Z
Session:
session_01VoSxBQujKLZKPwK2u5ehQ6Account:
os-steve(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23072-s2-dispatcher-read-decisionWorktree:
objectstack-issue-23072-s2Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on
origin/mainc4b84ad702; stop on a breach and explain it in the report):packages/rest/src/meta-item-read-gate.ts: ONE portable read decision for the/metaitem GET (for examplemetaItemReadQueryRefusal(query), returning{ code, status, message }or nothing), plus thememberreading, besidemetaTypeReadRefusal. The ruling is6138588561.packages/rest/src/index.ts: exported once, through the entry that already re-exportsmeta-item-read-gate.ts's family (1-2 names).packages/rest/src/rest-server.ts: the item GET is re-pointed at the decision in spelling only. Its answers stay byte-identical, pinned by the existing member-selector file.packages/runtime/src/domains/meta.ts: the dispatcher's item GET calls the decision. It honours?member=and refuses whatRestServerrefuses, with the sameerror.codeand message in its own body.6137337775):memberreaches the threegetMetaItemcalls; the two barecatches let the protocol's located 400 out; the name-keyedMetadataServicefallback never answers a named member; and the?layers=+memberrefusal holds./layers,/publishedandPUTbranches (out-of-scope finding 1) close their query sets from the same one source.packages/rest/src/andpackages/runtime/src/.meta-list-projection-parity'sITEM_PROBESgainsmember..changeset/23072-*.md:@objectstack/restminor(new export) and@objectstack/runtime(the dispatcher's narrowing, graded and marked as PR fix(rest)!: PUT and the five by-name /meta sub-doors declare closed query sets, so ?member= is refused, never dropped (#23072, RestServer half) #23133's was), with theClause-②line the gate takes.packages/spec,packages/metadata-protocol,packages/client, and any otherregister*Endpointssection ofrest-server.ts.Container & model:
M, judged (two transports, one decision, about 350-550 lines by the dev's estimate),mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). CeilingCONTRACT_REVIEW_TIER, reason: the new export widens@objectstack/rest's public surface (Clause-②: yes, as the maintainer's ruling B names it), and the dispatcher's closed sets narrow accept sets. One review at that tier is owed before the queue.check-governed-merges --testover the claimed paths: not governed.Clause-②: yes
Responsibility:
packages/runtime domains/meta.ts (this lane), the dispatcher's /meta item GET drops ?member= and every unknown name, so a dispatcher-only composition (@objectstack/hono createHonoApp) answers a declared selector with the canonical member | GetMetaItemRequest declares member, and PR #23133 (c4b84ad702) closed RestServer's six sibling doors; ruling B (6138588561) names one portable decision both transports call | read from source by #23072's dev (6137337775), not measured on a dispatcher-only hostThread-read: 6139451070
Serial constraints cleared:
c4b84ad702, sorest-server.tscarries no other claim of this seat. [maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050's next slice waits behind this slice (one at a time onrest-server.ts).rest-server.ts,meta-item-read-gate.ts,packages/rest/src/index.tsorpackages/runtime/src/domains/meta.ts.packages/verify,packages/cli) and PR fix(runtime): server-composed clone and re-flag bodies drop a stored protection block before the save door #23188 (packages/runtime/src/flow-clone.ts,domains/packages.ts).Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23072,
"status": "done",
"branch": "claude/issue-23072-s2-dispatcher-read-decision",
"pr": "#23211",
"head": "6fe8e7462997a86173eedf17e0625062bdebfd76",
"session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (subagent of the domain:cli seat 2 PM; the parent id, as the dispatch names it)",
"premise_still_valid": true,
"summary": "Ruling B is built. packages/rest/src/meta-item-read-gate.ts gains metaItemQueryRefusal(door, query) and metaMemberSelector(query), exported once from @objectstack/rest (two value exports). RestServer's item GET is re-pointed at it, and its answers are byte-identical (a 480-record BASE/HEAD differential; the member-selector file is unchanged and green). The runtime dispatcher asks the decision on its item GET, PUT, /layers and /published. Its item read honours ?member= through all three getMetaItem calls; with a member named, the two bare catches let the protocol's refusal out, and the registry and MetadataService fallbacks never answer the member. Draft PR #23211 (Fixes #23072, Clause-②: yes (narrowing)) closes the card.",
"decision": {
"signature": "metaItemQueryRefusal(door: 'item' | 'layers' | 'published' | 'save', query: unknown): { status: 400; code: 'VALIDATION_ERROR'; message: string; rule: 'unknown-parameter' | 'repeated-parameter' | 'layered-member' } | undefined",
"rules_in_order": [
"1 recognition: a name outside the door's closed set, with the sentence refuseUnknownQueryParams answers (unknownQueryParamRefusalMessage)",
"2 arity: a single-valued name supplied twice, with repeatedQueryParamMessage; a one-element array is unwrapped IN PLACE, as refuseRepeatedQueryParams does",
"3 item only: a member beside a non-empty ?layers=, with RestServer's sentence byte for byte"
],
"member_reading": "metaMemberSelector(query): { member } | {}, moved unchanged from rest-server.ts",
"entry_exports_added": [
"metaItemQueryRefusal",
"metaMemberSelector"
],
"entry_types_added": "none. MetaItemQueryDoor and MetaItemQueryRefusal are module exports; dist/index.d.ts and index.d.cts declare them locally beside the two exported functions (read after the build).",
"moved_into_meta_item_read_gate_ts": "META_ITEM_MEMBER_PARAM, META_ITEM_READ_PARAMS, META_ITEM_SAVE_PARAMS, META_ITEM_LAYERS_PARAMS, META_ITEM_PUBLISHED_PARAMS, metaMemberSelector. Values unchanged; rest-server.ts imports them for its doors and re-exports the five constants, so both existing door test files import them unchanged. They had to move: rest-server.ts imports the gate module, so the gate module cannot import it back.",
"new_not_exported": "the per-door table; the dispatcher save set, derived as META_ITEM_SAVE_PARAMS less force (not hand-kept); the layered-member sentence",
"query_allowlist_ts": "unknownQueryParamRefusalMessage(query, allowed) is the recognition sentence as a pure function; refuseUnknownQueryParams now writes it. Every route keeps its answer."
},
"restserver_byte_identity": "The re-point: one metaItemQueryRefusal('item', req.query) at the handler top. Rules 1 and 2 are written there as the nested { error: { code, message } } their gates wrote. Rule 3 is thrown at its old position (after resolveProtocol and the mask posture) to handleRouteError, flat. The differential: a scratch harness (never committed) drove both item GET routes (/api/v1/meta/:type/:name and the environment-scoped one) with 30 queries under 8 configurations (cache on/off, layered read present/absent, protocol member refusal on/off, system/plain caller). It recorded status, body, headers, thrown error, the query after the call and every protocol call, on the base tree de24373 and on af356c3. Result: 480 records (256 status 400, 224 status 200). Both dumps are 348398 bytes, cmp identical, sha256 e27c1b55dc34648f12861a8489e8d75516b384bc6c8b4b6e54de0b92f8177ca9. rest-server.ts is unchanged between af356c3 and the final head except through the merge, which touched no packages/rest file. The pinned controls rest-server-meta-member-selector.test.ts and rest-server-meta-door-closed-sets.test.ts are unchanged and green at 6fe8e74.",
"dispatcher": [
"item GET/HEAD (handleMetadataRequest two-segment, after the 405 verb check) — read points: state, preview (inline), package (metaItemPackageBinding), layers (wantsMetaItemLayers), locale (metaRequestLocale behind createMetaItemAnswer), member (metaMemberSelector, new). Before: no set, member never read or forwarded, every unknown name dropped. After: META_ITEM_READ_PARAMS plus the arity list [layers, state, preview, package, member], both RestServer's. Rules 1 and 2 at the read entry; rule 3 after the mask posture, as on RestServer. The member reaches readPendingDraft, the object branch's readFromProtocol and the generic getMetaItem. With a member named: the object branch is protocol-first and skips the registry; both bare catches rethrow to the outer errorFromThrown (the protocol's located 400); MetadataService.getItem is skipped, so an absent member is 404. With no member named, every fall-through is unchanged (control pinned).",
"PUT /meta/:type/:name — read points: package (metaItemPackageBinding), mode (metaSaveRequestOptions); force is not read (settled ruling #11095; its 409 meta-dispatch remedy says the door accepts no force). Before: no set; force and member dropped (?member=zh-CN beside an en-US body wrote en-US). After: [package, mode] = META_ITEM_SAVE_PARAMS less force, both single-valued, answered after the capability and walled-catalog gates and before any query read; packageId is now read after the decision.",
"/layers — read points: package (metaItemPackageBinding). Before: no set. After: META_ITEM_LAYERS_PARAMS ([package]), package single-valued (RestServer's serveMetaItemLayered declares the same), first statement.",
"/published — read points: none. Before: no set. After: META_ITEM_PUBLISHED_PARAMS (empty: "This endpoint takes no query parameters."), first statement.",
"Envelope: deps.error(message, status, { code }), the nested { success: false, error: { code: VALIDATION_ERROR, message, httpStatus } }. On the item read, /layers and /published the code and message equal RestServer's for the same request (pinned per door). PUT's list differs by force, as the doors do."
],
"census": [
"@objectstack/client: meta.getItem sends package (accepted); getPublished sends nothing (accepted); saveItem sends package and mode (accepted) and force only when its caller passes force: true (refused on the dispatcher PUT). No /layers caller. In-repo saveItem callers passing force are only client.test.ts URL-builder tests over a mocked fetch; cli meta register passes none.",
"objectui at the console pin 1fb1ced (blobless read-only fetch): MetadataClient.readItemResponse sends state, preview, package (accepted); layered sends package (accepted); save sends force only from ResourceEditPage and PermissionMatrixEditor force-save, after a 409 DESTRUCTIVE_CHANGE and a confirm. On a dispatcher-only host that retry already failed with the same 409 (the remedy says the door accepts no force); it now answers the located 400 naming force. A changed failure, not a newly red flow. The direct fetches with a query hit /drafts and list reads, not these four doors.",
"In-repo test callers of the dispatcher: none sends a name outside the new sets, so none needed fixing. @objectstack/runtime local 365 of 365 and repo 3 of 3. @objectstack/http-conformance hono-meta-item-read-gate, hono-meta-list-read-gate and hono-meta-save-preconditions (real createHonoApp plus dispatcher; PUT ?mode=draft, GET ?state=draft): 3 files, 17 tests green. @objectstack/hono: 6 files, 139 tests green.",
"Dogfood (the showcase serves these doors through RestServer, so it exercises the re-pointed item GET): email-template-two-locales-survive-boot, flow-shipped-name-layered-read, flow-unshipped-name-layered-code, multi-package-artifact, showcase-object-extension-meta-read, showcase-object-extension-scalar-divergence, meta-published-and-state-routes: 7 files, 47 tests green at 6fe8e74."
],
"changeset": ".changeset/23072-dispatcher-meta-query-decision.md: @objectstack/rest minor (two new exports) and @objectstack/runtime minor (four dispatcher accept sets narrow; BREAKING, shipped minor under the launch-window convention). Summary line fix(runtime)!:, Clause-②: yes (narrowing), exactly one ADR-0087 disposition not-required (no-migration-prescription) stating its facts (in the comment-marker form the gate reads), and the launch-window BREAKING line, as PR #23133's changeset. The Clause-② spelling: check-changeset-no-major --event (the PR title and body) exits 0 for yes, yes (widening), yes (narrowing), no and no (narrowing) (both packages are minor); no (widening) is malformed (level axis NOT MEASURED). yes (narrowing) is the truthful one and is on PR line 2 and in the changeset. check-adr-0087-registration reads it [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription), exit 0. check-empty-changeset exit 0.",
"tests": [
"All at 6fe8e74 through scripts/pm/os-verify-lock.sh, VERDICT command-exit 0 each.",
"TYPECHECK @objectstack/rest: tsc --noEmit + check:test-typecheck OK (0 file(s) / 0 error(s)). @objectstack/runtime: check:test-typecheck OK (27 file(s) / 190 error(s) / 68 pinned, ledger unchanged). --listFiles: rest tsconfig.test.json compiles the new test (1/1); runtime's compiles both touched test files (2/2). The first runtime typecheck after the tests were written was red (TS2493 on an untyped vi.fn in the new test); fixed in 6fe8e74 and re-run green.",
"REST vitest --project local (whole): Test Files 277 passed (277), Tests 5802 passed | 326 skipped (6128). --project repo: Test Files 5 passed (5), Tests 213 passed | 2 skipped (215).",
"RUNTIME vitest --project local (whole): Test Files 365 passed (365), Tests 5201 passed | 19 skipped (5220). --project repo (with meta-list-projection-parity): Test Files 3 passed (3), Tests 808 passed (808).",
"CENSUS: http-conformance 3 files 17 passed; hono 6 files 139 passed; dogfood 7 files 47 passed.",
"NEW PINS: packages/rest/src/meta-item-query-decision.test.ts, 19 tests (sets, sentences, arity, in-place unwrap, rule order, metaMemberSelector, RestServer writing exactly the decision: nested for 1 and 2, flat for 3). packages/runtime/src/domains/meta-item-query-decision.test.ts, 23 tests (the four dispatcher doors, refusal and preservation each; item, /layers and /published refusals compared with RestServer's code and message for the same request; value pins against a real ObjectStackProtocolImplementation({})). meta-list-projection-parity ITEM_PROBES gains ?member=zh-CN and ?layers=true&member=zh-CN (the double refuses a member on a non-bundle type, as the protocol's first step does), plus a reference-moves pin: 682 to 739 tests, both transports agreeing on 26 cells x 4 callers and the 2 undetermined object cells.",
"ABLATION on committed 6fe8e74 (and earlier af356c3; meta.ts is blob 2fbc1e6b65f1 on both), inside the lock, via scripts/ablation-replace.mjs wrap mode plus a bash EXIT/INT/TERM trap restoring git checkout HEAD -- on the absolute path, proven by blob hash and an empty git diff HEAD. The tests import meta.ts from src (../http-dispatcher.js), so no dist preflight applies. Leg 1, decision dropped on all four doors: anchor metaItemQueryRefusal( hit 4 times, replaced by a function answering undefined (marker ABLATION_23072_DECISION_DROPPED); blob 2fbc1e6b65f1 to a21ea5f22452; Tests 38 failed | 724 passed (762): 10 dispatcher refusal and unwrap pins, 28 parity layers+member cells; restored blob == HEAD, diff empty, marker 0. Leg 2, member forward dropped from the generic getMetaItem: blob to df1a6d6c06e8; Tests 31 failed | 731 passed (762): 7 honour, value and absent-member pins, 24 parity generic-type member cells (object cells stay green, their forward intact); restored likewise. Expected red, observed red.",
"LINT, proven narrowing (whole-repo pnpm lint is CI's): (1) population from eslint.config.mjs files ['/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] less NEVER_LINTED; ESLint isPathIgnored false for all 8 touched .ts files. (2) eslint --no-inline-config --format json: 8 files, errorCount 0, warningCount 0, fatal 0, exit 0, at 6fe8e74. (3) invariance: calculateConfigForFile answers parserOptions.project null and projectService null for each, so no type-aware rule; the diff cannot move an untouched file's verdict."
],
"gates_head": "union at HEAD 6fe8e74 (the final head)",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 548 assertions over real temp git repos (real scan()/assertInputs() path)",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@17.7.0 — the one exception, evidenced: pre mode (tag
next) and a pendingmajorfor @ob","node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions ",
"node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every
$TURBO_ROOT$input of a build Build Core runs) and all 11 script(s)","node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 11207 tracked file(s), ",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
"node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/ + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 sp",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS check-comment-mask-adoption --self-test (0 failure(s))",
"node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8947 files, 0 disagree, 0 unparseable, 154.4s (comparator self-test: 26 cases pass).",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
"node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
"node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 115 .object.ts files under packages/ + apps/* + examples/** (walk is repo-wide; 0 outside), 120 object declarations, 248 declared index entries, ",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS check-keyed-text-bounds --self-test (0 failure(s))",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 87 platform-namespace objects, 52 in the machinery's reach, 35 outside it, every exclusion explained by a declar",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ the family inherits the literal packages/objectql/src/tenancy/system-write-organization.ts",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 76 Plugin implementation(s) across 8361 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits be",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every r",
"node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 74 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 17 cases + level-vocabulary read + population declaration + real-tree selection floor.",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 32 of 283 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: ✓ the real tree yields a NON-EMPTY observer population that is a strict subset of its test files",
"node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 132 elevation read sites in 21 packages across 60 files, living in 112 symbol(s); the page cites 125 symbol(s) against 125 required, over ",
"node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 82 workspace packages under packages/ + apps/** + examples/**, 3063 non-test src files, 2565 @objectstack/* specifiers (0 assembled, not ",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS check-undeclared-dep-imports --self-test (0 failure(s))",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: ✓ affected-docs self-test: 605 cases pass.",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ C10 the rehearsal doc names this script",
"node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ nothing unconsumed -> one plain line, no annotation, still naming the version commit it read",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3068 source file(s) all carry their unit in the key name (or in a sibling
unit, or u","pnpm check:authz-resolver :: exit 0 :: ✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.",
"pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #7005 diff ",
"pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3 on ACCEPTED",
"pnpm check:dispatcher-error-vocabulary :: exit 0 :: check-dispatcher-error-vocabulary: OK — 56 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (#8846).",
"pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 94717 string(s) read in 1319 parsed source(s) (seen floor 40000 strings / 600 sources), ",
"pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consumers". This gate polices the census, ",
"pnpm check:dts-closure :: exit 0 :: check-dts-closure: 64 built package(s) swept - 163/163 declared declaration file(s) present across 64 package(s); 0 built package(s) declare no declaration entry point and owe none.",
"pnpm check:dual-build-cjs-loads :: exit 0 :: ✓ check:dual-build-cjs-loads — 108 published require entry point(s) across 67 package(s) load; 718 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree; 106 require c",
"pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: 910 (file, verb) row(s) held by the RETAINED ledger — a pin that leaves names itself.",
"pnpm check:error-status-conformance :: exit 0 :: ✓ every derivable runtime status is documented, and every documented status is reachable.",
"pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (11207 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
"pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strategies agree, diff scope ",
"pnpm check:lean-entry-closure :: exit 0 :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
"pnpm check:logger-receiver-detach :: exit 0 :: OK every log channel keeps its receiver: 3364 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
"pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 11198 text file(s) -- 11198 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
"pnpm check:objectql-double-limit :: exit 0 :: OK ObjectQL double
limitconformance holds: 481 double(s) graded, 290 apply the caller's bound or refuse it loudly.","pnpm check:objectui-changeset :: exit 0 :: ✓ --help does NOT leak mid-file implementation comments (#11952)",
"pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3386 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
"pnpm check:page-declaration-shape :: exit 0 :: check-page-declaration-shape: OK — 36 page entries across 3389 sources under packages/, examples/, apps/** all reach the kernel through a discoverable declaration (
: Pageorde", "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ …and --help exits 0", "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 71 publishable package(s) of 82 workspace member(s) declare afileswhitelist that covers every entry point plus CHANGELOG.md and admits no test, test-harness con", "pnpm check:query-options-erasure :: exit 0 :: ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ is covered bypnp","pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8941 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.",
"pnpm check:route-envelope :: exit 0 :: ✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conformant, 2 ratcheted, 0 ex",
"pnpm check:slot-lookup :: exit 0 :: ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered by
pnpm lint.","pnpm check:sourcemap-no-sources-content :: exit 0 :: check-sourcemap-no-sources-content: 61 built package(s) swept - 538 map(s), none embed source text.",
"pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 74 packages with tests scanned; 60 registered as still resolving a workspace dep through
dist/; 53 published subpath(s) resolved through every alias table.","pnpm check:tier-file-adoption :: exit 0 :: OK: 82 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
"pnpm check:type-check-coverage :: exit 0 :: check-type-check-coverage: OK — 80/82 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https://github.com//issues/43",
"pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 143.3s, 26 raw tsc error(s) total, none above its recorded number.",
"pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -- every one an ar",
"pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 502 matcher(s) discovered, 502 answer the combinator battery correctly or refuse it loudly (323 refuse).",
"pnpm check:route-ledger-census :: exit 0 :: ✓ check:route-ledger-census — all 1 census sentence(s) match their arrays.",
"node scripts/check-changeset-no-major.mjs --base origin/main --event (PR title + body) :: exit 0 :: ✓ LEVEL AXIS: this PR declares clause-②
yes (narrowing), and no package whose published source … it moves is gradedpatch."],
"gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list at 6fe8e74 :: exit 0 :: ✓ dispatch-gates --ran: 65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED (a DERIVED zero — all 65 recorded an exit code and none of them is 3). pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: 8 packages without dist); after building them (58/58 cached) it exits 0 and that exit is the one recorded. Beyond the list: pnpm check:route-ledger-census exit 0; check:route-envelope (in the list) exit 0.",
"gates_derivation": "Final: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths, at 6fe8e74: change set from the merge base d4aaeaf, 9 paths, +1073/-110; 65 commands, the identical 65 the PM derived. An earlier derivation at af356c3 warned STALE TREE (origin/main 5 commits ahead); answered by merging origin/main (689e8f6) and refreshing the build state before the union ran.",
"line_budget": "+1073 / -110 = 1183 changed lines in 9 files (dispatch-gates count), under the 3,000-line threshold. Source +583 (meta-item-read-gate.ts +247, of which about 100 are moved declarations and their docblocks; meta.ts +117/-11; rest-server.ts +51/-90; query-allowlist.ts +27/-8; index.ts +9), the changeset +23, tests +599/-1. The ruling estimated 350-550 lines; the source half is in it, the tests are the rest.",
"deviations": [
"File surface: none outside the claim. query-allowlist.ts was edited under its conditional (the recognition sentence is now shared from there as unknownQueryParamRefusalMessage). rest-server.ts changed in 7 hunks: one import block, the moved declarations (with their re-export) and the item GET's two hunks. No other handler line changed: the four other member doors' metaMemberSelector calls and the PUT, /layers and /published gate lines are byte-unchanged and resolve those names through the import. The 480-record differential covers the item GET; slice 1's closed-sets file and the member-selector file, both unchanged and green, pin the others.",
"Exports: two values on the entry, as ruled. No type was added to the entry; the two types the signature uses are declared locally in the emitted .d.ts.",
"Dispatcher ordering: rules 1 and 2 answer at each door's entry. On /layers that is before its 501 for a protocol with no layered read; RestServer's /layers (untouched) answers arity inside serveMetaItemLayered, after its 501. So only a request both malformed (a repeated package) and sent to a host with no layered read answers 400 on the dispatcher and 501 on RestServer. Through createHonoApp no repeated name reaches the dispatcher (out_of_scope_findings[0]).",
"Merged origin/main (d4aaeaf, 5 commits, none touching a file in this diff) as 689e8f6, and refreshed the build state (install, closure build, packages/runtime/.objectstack removed). Every test, the ablation re-run, lint and the gate union ran on the final head 6fe8e74.",
"NOT MEASURED and re-run: one runtime local run was terminated by its background budget mid-run (the wrapper's 10-minute cap, which included the lock wait); one acquisition ended exit 99 queue-timeout and resumed with the same OS_VERIFY_LOCK_SLOT. The recorded results are the completed re-runs.",
"Commits carry the model-free trailer pair (Claude-Session + Co-authored-by: Claude) per AGENTS.md, not the model-named Co-Authored-By the harness reminder suggested. The PR body ends with the session-URL footer form, not the harness two-line block (the role file: the harness reminder yields).",
"Zero label writes: the dispatch named no label, and skip-changeset is not owed (the PR carries a publishing changeset)."
],
"files_changed": [
".changeset/23072-dispatcher-meta-query-decision.md",
"packages/rest/src/index.ts",
"packages/rest/src/meta-item-query-decision.test.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/rest/src/query-allowlist.ts",
"packages/rest/src/rest-server.ts",
"packages/runtime/src/domains/meta-item-query-decision.test.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts",
"packages/runtime/src/domains/meta.ts"
],
"mcp_calls": "0 — no MCP GitHub tool called; reads went through gh api (REST GET) and git.",
"api_writes": "2 repository_dispatch POSTs from this session (POST /repos/objectstack-ai/objectstack/dispatches), one per relay stroke, executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft PR #23211; read back: 17290 bytes sent = 17290 stored, identical) + POST /repos//issues/23211/assignees [os-steve] (read back: os-steve), run 38372715165; (2) POST /repos//issues/23072/comments (this os-dev-report, scripts/pm/post-stamped.mjs). 3 REST writes in all. No label write, no PATCH, no claim, no card-assignee write. Not REST: 6 git pushes of the branch (the empty landing marker, then 7675d9a, e6d9b3e, af356c3, 689e8f6, 6fe8e74).",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: measured at the public door's adapter: createHonoApp (packages/adapters/hono/src/index.ts:789, the catch-all builds queryParams with url.searchParams.forEach, last value wins). A scratch request GET /api/meta/email_template/acme.welcome?member=zh-CN&member=ja-JP handed dispatch() {"member":"ja-JP"} (status 200, mocked dispatcher, @objectstack/hono at 6fe8e74). So no repeated name ever reaches a dispatcher domain on a createHonoApp host: the multiplicity rule (#6877; the #6878 cli-lane ruling that the handler must SEE the ambiguity to reject it, which plugin-hono-server's readQuery honours) is unreachable there. The first value is dropped silently, and the dispatcher now honours the last member where RestServer answers 400 "supplied 2 times". Seam: spec: none → runtime: @objectstack/hono createHonoApp catch-all query flattening → every dispatcher domain that declares an arity rule (/meta here, /packages' repeatedQueryParamMessage). Family: query multiplicity, not #23072. Dedupe words: createHonoApp repeated query parameter last value wins; hono catch-all searchParams forEach flattens; dispatcher multiplicity unreachable"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
✅ ACCEPT: PR #23211 at
6fe8e74629(#23072 slice 2, the dispatcher half under ruling B;Fixes #23072). It queues after oneCONTRACT_REVIEW_TIERPASS on this head and a green headdomain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-13T09:03Z · holder of claim6139487972. Report:os-dev-report6142050405. Thread-read: 6142050405.Checklist, read against GitHub:
main, line 1Fixes #23072, line 2Clause-②: yes (narrowing), assigneeos-steve, titlefix(runtime)!:.@objectstack/rest(yes) and four dispatcher accept sets narrowed (BREAKING).check-changeset-no-major --eventandcheck-adr-0087-registrationboth exit 0 on it.query-allowlist.tsfalls under the claim's conditional ("only if the sentence must be shared from there"): the recognition sentence is now one pure function both transports write.check-governed-merges --pr 23211: not governed.git merge-treeagainst today'sorigin/main(150da61aa5) is clean.metaItemQueryRefusal(door, query)inmeta-item-read-gate.ts, and the member's one readingmetaMemberSelector(query). Both are exported once from@objectstack/rest, and no type joins the entry.rest-server.tsimports them and re-exports the five constants, because the gate module cannot importrest-server.tsback.RestServer's item GET, re-pointed in spelling only: rules 1 and 2 at the handler top, and rule 3 at its old position after the mask posture.domains/meta.ts):RestServerdoes;memberSelectorto all threegetMetaItemcalls;objectbranch goes protocol-first, both barecatches rethrow, and the registry andMetadataServicefallbacks are skipped, so an absent member is 404 and a protocol refusal is itself;PUT,/layersand/publishedrefuse first.PUT's set isMETA_ITEM_SAVE_PARAMSlessforce(ruling Two more faces of the DESTRUCTIVE_CHANGE 409 prescribe?force=trueon routes that never thread it — the compound-namePUT /meta/:type/:a/:band the runtime dispatcherPUT /meta#11095), derived, not hand-kept, andpackageIdis read after the decision.RestServerbyte identity: a 480-record BASE/HEAD differential over both item GET routes (30 queries × 8 configurations) gives two dumps that arecmp-identical. Slice 1's closed-sets file and the member-selector file are unchanged and green.ITEM_PROBESgains the member cells (682 → 739, both transports agreeing). Two ablations, each red and each restored to the HEAD blob:@objectstack/clientsends only accepted names. It sendsforceonly when its caller passesforce: true, and no in-repo caller does outside URL-builder tests.forceonly on the post-409 force-save retry. On a dispatcher-only host that retry already failed with the same 409, and it now answers the located 400 namingforce: a changed failure, not a newly red flow.6fe8e74629:--ran;6fe8e74629at this stamp: latest run per name, 25 success and 6 skipped, every skip in the roster. The sixTest Coreshards andType Check · consumer gatesare in progress. Not green yet.@objectstack/rest's surface plus four narrowed accept sets.needs:contract-reviewgoes on PR fix(runtime)!: the dispatcher's by-name /meta doors ask RestServer's query decision, so ?member= is honoured or refused, never dropped (#23072, dispatcher half) #23211 in this act, and the seat runs the isolated review on this head.mcp_calls0;api_writes2 relay strokes (pr_createwith its assignee, and the report).packagesent to a host with no layered read answers 400 on the dispatcher's/layersand 501 onRestServer's, because the arity rule sits before the 501 there and after it here. ThroughcreateHonoApp, no repeated name reaches the dispatcher (the finding below).From the report, to be filed by the seat after dedupe: the class-b finding that
createHonoApp's catch-all flattens a repeated query name to its last value, so the dispatcher's multiplicity rule (#6877 / #6878) is unreachable on a Hono host.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23211 →
8024e1243a(Fixes #23072, the dispatcher half under ruling B). The card closescompleteddomain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-13T09:55Z · holder of claim6139487972, released by this landing.Landed: through the merge queue at 2026-10-13T09:54:51Z as
8024e1243a, a squash with one parent./metadoors on both transports ask ONE query decision,metaItemQueryRefusal(door, query)in@objectstack/rest'smeta-item-read-gate.ts, with the member's one readingmetaMemberSelector(query). Both are exported once.?member=through all three of its protocol reads, and a named member is the protocol's alone to answer.PUT,/layersand/publishedrefuse any name they do not read withRestServer's code and sentence, where they used to drop it.RestServer's item GET is re-pointed in spelling only, and its answers are byte-identical (a 480-record differential).Review chain:
6142115078;CONTRACT_REVIEW_TIERrecord PASS on6fe8e74629(6142424713on PR fix(runtime)!: the dispatcher's by-name /meta doors ask RestServer's query decision, so ?member= is honoured or refused, never dropped (#23072, dispatcher half) #23211), with the served model checked against the reviewer's transcript;The changeset is
@objectstack/restminorand@objectstack/runtimeminor,fix(runtime)!:,Clause-②: yes (narrowing), with one ADR-0087not-required (no-migration-prescription)marker and the launch-window BREAKING line.What changes for a caller:
GET /meta/:type/:name?member=zh-CNanswers that member, or the protocol's located refusal, instead of the canonical item.400 VALIDATION_ERRORinstead of being ignored.PUT …?force=…on the dispatcher is refused withforcenamed. That door was already ruled never to take one (Two more faces of the DESTRUCTIVE_CHANGE 409 prescribe?force=trueon routes that never thread it — the compound-namePUT /meta/:type/:a/:band the runtime dispatcherPUT /meta#11095), and objectui's post-409 force-save retry there goes from one failure to another.Filed from the report: hono adapter:
createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 (finding, awaiting triage).createHonoApp's catch-all flattens a repeated query name to its last value, so the dispatcher's arity rule is unreachable on a@objectstack/honohost. The record required that card before this one closed.Carried, not filed (the record's two residuals, neither a defect at this head):
RestServer'sPUTandserveMetaItemLayeredstill spell slice 1's arity lists beside the decision'soncetable. Each is a one-line re-point, best made by the next change that touches those doors.MetaItemQueryDoorandMetaItemQueryRefusalare not on the entry'sexport typeblock, where the sibling refusal types are. Adding them is atype-surface-onlywidening, done only if a consumer needs the names.The spec-lane finding recorded at slice 1's landing stands: which by-name doors could honour
member(/layersand/diffwith threading,/auditwith a column,/publishedpartly,/referencesnever).Serial queue on
rest-server.ts: this hold is released, and [maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050's next slice may claim it.Holds released:
meta-item-read-gate.ts,query-allowlist.ts,packages/rest/src/index.ts,rest-server.ts's item GET,packages/runtime/src/domains/meta.ts, and the three test files.Generated by Claude Code