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.
Triage: first grade, bug · priority:p2 · domain:cli · area:i18n, into pm:queue. Each door declares its closed query set: member is honoured or refused, never dropped
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T17:54Z. ⛔ Not a claim, ⛔ not a dispatch.
Why p2: AGENTS.md route rule 5 names a silently dropped query parameter as "fails undetectably". The worst position is PUT, where a caller's ?member= is ignored and the body's member is written.
Direction, the card's own:
The runtime dispatcher's /meta item GET, PUT /meta/:type/:name, and the five by-name doors outside the ruling's list each declare a closed query set, measured from their real read points.
PUT stays body-addressed under ruling 6116165299, so it refuses member with a located 400. ⛔ It does not start reading it.
Serial note from domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-12T18:20Z. ⛔ Not a claim, ⛔ no label change.
This card stays in pm:queue behind three things. This seat claims it when all of them are clear:
Serial note update from domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-12T22:59Z. ⛔ Not a claim, ⛔ no label change. It updates 6130072486.
A measured fact for this card's PUT closed set (hourly full run: red on main (CI) #23084's dev, 6134161286): PUT {metaPath}/:type/:name READS package (metaItemPackageBinding(req.query?.package)), and also force and mode (via metaSaveRequestOptions). Its repeat guard names ['force','package','mode']. So the closed set measured from its read points includes those three, and refuses member with a located 400.
Claim: PM loop round 9 · 2026-10-13T01:33Z
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-meta-doors-closed-sets
Worktree: objectstack-issue-23072
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
File surface (read on origin/mainbcebd8409d; stop on a breach and explain it in the report):
packages/rest/src/rest-server.ts, in registerMetadataEndpointsInner only:
PUT {metaPath}/:type/:name (about :6942) declares its closed query set, measured from its read points (package, force and mode, measured on hourly full run: red on main (CI) #23084, 6134161286), through the file's own refuseUnknownQueryParams. member and any other unrecognised name answer a located 400. ⛔ PUT stays body-addressed (ruling 6116165299), and does not start reading member.
The five by-name doors outside the ruling's list: /references (about :6100), /layers (about :6207), /audit (about :7480), /diff (about :7947) and /published (about :8299). Each declares its closed set from its real read points, and refuses member with a located 400.
packages/runtime/src/domains/meta.ts: the dispatcher's /meta item GET (about :1496-:1660) forwards member to getMetaItem as RestServer's item GET does (the request declares it), and refuses what RestServer's META_ITEM_READ_PARAMS refuses. One source for the set, ⛔ never a second hand-kept list. The other dispatcher branches the card names (DELETE, publish, history, rollback answer 405 there) are unchanged.
Tests: one pin per door, ?member= either honoured or refused, never dropped, in packages/rest/src/ and packages/runtime/src/. Plus any existing test or dogfood caller that sends a name outside a new closed set, measured (below).
One .changeset/23072-*.md (@objectstack/rest and @objectstack/runtimepatch), naming each door's new refusal with its remedy, carrying Clause-②: no.
⛔ Not touched: packages/spec, packages/metadata-protocol, packages/client, any /meta door already in the ruling's list, and any other register*Endpoints section of rest-server.ts ([maintainer] refactor(rest): split packages/rest/src/rest-server.ts (13,701 lines, 70 commits in 30 days) along its register*Endpoints seams, one move-only slice per PR, /meta after #22877 stage 2 #23050's later slices).
Container & model: M (seven doors over two transports, plus a caller census), mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). Ceiling CONTRACT_REVIEW_TIER, reason: the card NARROWS published accept sets (a name these doors dropped now answers 400), so it is Clause-②: no, stays in its lane, and owes one contract-review-tier review before the queue. The dispatcher honouring member implements a selector the spec already declares, so it widens nothing. check-governed-merges --test over the claimed paths: not governed.
Clause-②: no
Responsibility: packages/rest rest-server.ts and packages/runtime domains/meta.ts (this lane): PUT /meta/:type/:name, five by-name /meta doors and the dispatcher's item GET declare no closed query set, so a caller's ?member= is dropped with no signal | PR #23070 (038d4c9588) gave the five doors in ruling 6116165299's list closed sets and the member reader, and refuseUnknownQueryParams is the file's own refusal | a PUT ?member=zh-CN with an en-US body writes en-US, and a dispatcher-only host answers GET ?member=zh-CN with the canonical member, read from source by #22877's dev (6127796890), not measured on a live host
Thread-read: 6134242418
Serial constraints cleared:
os-dev-report
{
"issue": 23072,
"status": "needs_decision",
"branch": "claude/issue-23072-meta-doors-closed-sets",
"pr": "#23133",
"session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (subagent of the domain:cli seat 2 PM; the parent's id, as the dispatch names it)",
"premise_still_valid": true,
"summary": "The RestServer half is delivered as a draft PR (Part of #23072, Clause-② no). PUT /meta/:type/:name and the five by-name sub-doors outside ruling 6116165299's list (/references, /layers, /audit, /diff, /published) each open with refuseUnknownQueryParams over a set measured from their read points (save: force, package, mode; references: empty; layers: package; audit: limit; diff: from, fromVersion, to, toVersion; published: empty). ?member=, and any name a door does not read, is a located 400 VALIDATION_ERROR in the nested envelope, the protocol never called and nothing saved. The dispatcher half is NOT built: honouring member and refusing what META_ITEM_READ_PARAMS refuses needs META_ITEM_READ_PARAMS and unknownQueryParamMessage (and the member reading) from @objectstack/rest, and none is on its published entry, so reaching them is a new public export, which the dispatch said to stop and report. That decision is open_questions[0]. One census caller was inert (a rest pin sending ?organizationId= to PUT), and it is split and fixed. One edit is outside the claimed surface (query-allowlist.ts: an empty set's message), see deviations.",
"head_measured": "d813364338 (base bcebd84; the rest and runtime src measured is f0f7780, byte-identical in packages/rest/src/rest-server.ts and query-allowlist.ts to d813364)",
"doors": [
"PUT {metaPath}/:type/:name — read points: force (req.query?.force, handler), package (metaItemPackageBinding(req.query?.package), handler), mode (metaSaveRequestOptions -> http.query?.mode, meta-save-request.ts:118); resolveExecCtx / walledCatalogRefusal / resolveProtocol / resolveMetaWriteActor read no query key. Before: no closed set (the multiplicity guard named force, package, mode). After: META_ITEM_SAVE_PARAMS = [force, package, mode], placed just before the multiplicity guard (after the capability, walled-catalog and 501 gates). member is refused, not read (body-addressed).",
"GET {metaPath}/:type/:name/references — read points: none (findReferencesToMeta({type,name,environmentId?}); resolveProtocol reads route, hostname, X-Environment-Id). Before: none. After: META_ITEM_REFERENCES_PARAMS = [] (first statement of the handler).",
"GET {metaPath}/:type/:name/layers — read points: package, a frame down in serveMetaItemLayered (metaItemPackageBinding(req.query?.package)); resolveObjectMasker and the read-gate sources read none. Before: none (only serveMetaItemLayered's repeat guard on package). After: META_ITEM_LAYERS_PARAMS = [package] (first statement).",
"GET {metaPath}/:type/:name/audit — read points: limit (readDeclaredQueryNumber over AuditMetaItemRequestSchema.shape.limit); refuseNonAuthoringCaller and eventDoorRefusal read the session. Before: none (repeat guard on limit). After: META_ITEM_AUDIT_PARAMS = [limit], just before the repeat guard.",
"GET {metaPath}/:type/:name/diff — read points: from / to (req.query?.from != null, req.query?.to != null) and fromVersion / toVersion (readDeclaredQueryNumber with the chosen name). Before: none (repeat guard on the four). After: META_ITEM_DIFF_PARAMS = [from, fromVersion, to, toVersion], just before the repeat guard.",
"GET {metaPath}/:type/:name/published — read points: none (resolveObjectMasker, resolveProtocol, getMetaItemLayered({type,name}), resolveMetadataService, getPublished, the read gate). Before: none. After: META_ITEM_PUBLISHED_PARAMS = [] (first statement)."
],
"census": [
"@objectstack/client meta.saveItem, both clients, one metaSaveQuery builder (packages/client/src/index.ts:787) — sends force, package, mode — green",
"@objectstack/client getReferences (:2146) / getPublished (:2059) — sends nothing — green",
"@objectstack/client getAudit (:2168) limit / diffItem (:2226) from, to — green",
"objectui at the pin 1fb1ced36243 (shallow fetch, read-only), @object-ui/data-objectstack MetadataClient: save (force, mode, package), layered (package), references (nothing), diff (from, to), audit (limit); no /published caller — green",
"objectui UnpublishedAppBar.tsx and metadata-admin/external/api.ts PUT /api/v1/meta/... by fetch — no query — green",
"dogfood: 9 files PUT /meta with a query string (package x21, mode x16, force x1) plus the 7 /layers and /published callers (no query) plus email-template-two-locales and flow-shipped-name-by-name-read — 18 files run, Test Files 18 passed (18), Tests 156 passed (156) at d813364 — green",
"packages/rest/src/meta-item-save-capability-gate.test.ts "query/body-smuggled organization ids do not reach the write either" — sent ?organizationId= to PUT, a name the door never read — INERT: red under the new set (the one red of the whole rest run at 52b332b). Fixed in this PR (d813364): the body half stays 200 with organizationId absent from the save request, the query half is now the located 400 with saveMetaItem never called.",
"rest and runtime unit suites otherwise: no other caller sends a name outside the new sets (rest local 275/276 at 52b332b, then whole local again at the final head; runtime local 363/363 and repo 3/3, which boot RestServer and drive /layers with package probes)."
],
"spec_lane_finding": [
"/references: NO, permanently. findReferencesToMeta sweeps the graph for items naming (type, name); a reference names an item, never one member of a bundle, so there is nothing to select. The refusal is the right answer, not a gap.",
"/layers: YES with threading. The reader resolves; findServedOverlayRow, lookupArtifactItem and registry.getItem already take member; getMetaItemLayered passes none. Missing: readItemFromMetadataService needs getMetaItem's addressed-member check (MetadataService keys by name), overlayLockLayerAt reads every member by name, GetMetaItemLayeredRequestSchema needs the field.",
"/audit: NO with the reader alone. sys_metadata_audit has no member column (type, name, organization_id, operation, outcome, ...), so every member's attempts share one trail; serving member needs a column, the writer stamping it, and AuditMetaItemRequestSchema.member.",
"/diff: YES with threading. historyRowsOfMember(type, rows, member) already takes a member and diffMetaItem passes none; the default to (the active version) would need the member-scoped active row (findServedOverlayRow takes member); RestServer's per-caller gate reads the canonical current document (fetchCurrentMetaDocument). No spec request is declared for this door.",
"/published: PARTLY. The overlay half rides getMetaItemLayered (as /layers); the code/package snapshot fallback getPublished reads MetadataService, which keys by name and holds one member per name."
],
"dispatcher": "Before = after (unchanged in this PR). packages/runtime/src/domains/meta.ts handleMetadataRequest, item GET (two-segment branch, ~:1236-1750 on bcebd84): reads state, preview, package (metaItemPackageBinding), layers (wantsMetaItemLayers) and locale (metaRequestLocale via createMetaItemAnswer); member never read or forwarded; no closed set. One source: @objectstack/runtime imports @objectstack/rest's single "." entry, whose index exports metaItemPackageBinding, metaSaveRequestOptions, repeatedQueryParamMessage and the meta-item-read-gate family, and does NOT export META_ITEM_READ_PARAMS, META_ITEM_MEMBER_PARAM, metaMemberSelector, refuseUnknownQueryParams or unknownQueryParamMessage (packages/rest/src/index.ts on bcebd84). Reaching them = a new public export => stopped, per the dispatch. Envelope: RestServer writes { error: { code: VALIDATION_ERROR, message } }; the dispatcher's deps.error(message, 400) writes { success: false, error: { code: VALIDATION_ERROR, message, ... } } via buildApiError; the rest index already records that split for the multiplicity rule (a dispatcher domain takes the message and builds its own body), so the same answer = same error.code and same message, each in its transport's body. Extra cost of honouring member there, measured from source: three getMetaItem calls (object branch readFromProtocol, generic branch, readPendingDraft); two lookups wrap getMetaItem in a bare catch that would swallow the protocol's located 400 for a bad selector and fall through to the registry / MetadataService; the MetadataService fallback getItem(type, name, packageId) keys by name alone and would serve the canonical member for a member the protocol answers absent; the ?layers= + member refusal sentence also lives only in rest-server.ts. The dispatcher's /published (:1094 area) is NOT in this card's population (the card names the item GET only); its /layers and PUT branches have no closed set either (out_of_scope_findings[0]).",
"tests": [
"BUILD (lock, VERDICT command-exit 0): pnpm turbo run build --filter='@objectstack/dogfood^...' --filter='@objectstack/runtime' --concurrency=2 -> Tasks: 64 successful, 64 total (43 cached), at f0f7780. Then the 8 packages check:dual-build-cjs-loads named unbuilt: 58 cached, 58 total, BUILD8_EXIT=0.",
"REST TYPECHECK (lock): pnpm --filter @objectstack/rest run typecheck -> TYPECHECK_EXIT=0; check:test-typecheck: OK ... 0 file(s) / 0 error(s).",
"REST TEST whole local project at 52b332b (lock): Test Files 1 failed | 275 passed (276), Tests 1 failed | 5781 passed | 326 skipped (6108); the one red = the inert census caller meta-item-save-capability-gate.test.ts. Whole rest local re-run at the final head d813364: NOT MEASURED, reason: three lock acquisitions (same slot issue-23072-rest-final) each ended exit 99 queue-timeout behind holder pid 2151 (issue-23088 round1-tests.sh, ALIVE, held at least 1507s) — declared narrowing instead: between 52b332b and d813364 only meta-item-save-capability-gate.test.ts changed (packages/rest/src otherwise byte-identical), and that file plus the new pins plus the ruled-doors file were re-run green at d813364 (next line); CI runs the full set.",
"REST TARGETED at d813364 (lock, verbose): rest-server-meta-door-closed-sets + meta-item-save-capability-gate + rest-server-meta-member-selector -> Test Files 3 passed (3), Tests 72 passed (72); the 21 new pins listed one by one. REST repo project: Test Files 5 passed (5), Tests 213 passed | 2 skipped (215). Lock VERDICT command-exit 0.",
"RUNTIME (lock, VERDICT command-exit 0, at d813364 with rest dist from f0f7780): typecheck TYPECHECK_EXIT=0 (check:test-typecheck OK, 27 file(s) / 190 error(s) / 68 pinned held, unchanged ledger); vitest --project local: Test Files 363 passed (363), Tests 5174 passed | 19 skipped (5193); --project repo (incl. meta-list-projection-parity.test.ts and its /layers read census over rest-server.ts): Test Files 3 passed (3), Tests 751 passed (751).",
"CONTROL, the five ruled doors: rest-server-meta-member-selector.test.ts unchanged, green inside the 72 above.",
"DOGFOOD (lock, VERDICT command-exit 0, at d813364; first attempt exit 99 queue-timeout = NOT MEASURED, re-run with the same slot): pnpm exec vitest run --maxWorkers=2 over the 18 census files -> Test Files 18 passed (18), Tests 156 passed (156).",
"ABLATION (lock, on committed d813364, scripts/ablation-replace.mjs wrap mode + a bash EXIT/INT/TERM restore trap on the absolute path): drop PUT's refuseUnknownQueryParams(req, res, META_ITEM_SAVE_PARAMS) -> marker ABLATION_23072_SAVE_RECOGNITION_DROPPED; counts before anchor 1 / marker 0, during anchor 0 / marker 1, blob 9a64fac24d3c -> f1b89f68cd07; result Tests 3 failed | 18 passed (21), exactly PUT's three refusal pins red; restored blob 9a64fac24d3c == HEAD, git diff HEAD empty, after anchor 1 / marker 0. The rest test imports ./rest-server.js from src/, so no dist/ preflight applies. Expected direction red; observed red. The dispatcher member-forward ablation was not run: no dispatcher code was built (needs_decision).",
"LINT, proven narrowing (whole-repo pnpm lint is CI's): (1) population read from eslint.config.mjs: files ['/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] minus NEVER_LINTED; ESLint API isPathIgnored=false for the 4 touched .ts files. (2) eslint --no-inline-config --format json: 4 files, errorCount 0, warningCount 0, fatal 0, exit 0, at d813364. (3) invariance: resolved parserOptions.project=null, projectService=null (no type-aware linting); the enabled rules are per-file AST rules, so this diff cannot move an untouched file's verdict."
],
"gates_head": "union at HEAD d813364 / union done at HEAD d813364",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
"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 (.changeset/11509-element-fla",
"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 #7005 diff scoping over real te",
"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) the build-core job'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 11168 tracked file(s), all registered).",
"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 specimen). A new one reds here",
"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]: 8927 files, 0 disagree, 0 unparseable, 136.3s (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, 613 text-family fields; ",
"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 on its own schema.",
"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 8341 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits beside a real destroy() (0 kno",
"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 reds, every excluded name sta",
"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, over 148 anchors and 7 file-level",
"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/, 3064 non-test src files, 2564 @objectstack/* specifiers (0 assembled, not judged); 1 ledger row(s), all",
"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 3069 source file(s) all carry their unit in the key name (or in a sibling unit, or under a declared exemption: 6 declared durati",
"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 scoping over real te",
"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_WALK_RADII; 3093 tes",
"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), 94914 string(s) read in 1324 parsed source(s) (seen floor 40000 strings / 600 sources), no growt",
"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, never investmen",
"pnpm check:dts-closure :: exit 0 :: check-dts-closure self-test: all assertions passed.",
"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 condition(s) res",
"pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: OK — 992 pinned, 122 in the DEBT ledger, 3 exempt.",
"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 (11168 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 red AND gr",
"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: 3365 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 11159 text file(s) -- 11159 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: 478 double(s) graded, 287 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 (3387 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 3390 sources under packages/, examples/, apps/ all reach the kernel through a discoverable declaration (: Page or definePage()).",
"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 table including the inconclusive",
"pnpm check:published-files :: exit 0 :: ✓ check:published-files — 71 publishable package(s) of 82 workspace member(s) declare a files whitelist that covers every entry point plus CHANGELOG.md and admits no test, test-harness config or bui",
"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 by pnpm lint.",
"pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8921 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 exempt, 0 v",
"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 self-test: all assertions passed.",
"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/4311), 2 exempt.",
"pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 16.8s, 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 array of quoted",
"pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 499 matcher(s) discovered, 499 answer the combinator battery correctly or refuse it loudly (320 refuse)."
],
"gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list at d813364 :: exit 0 :: Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN; ✓ 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). Plus, beyond the derived list: pnpm check:route-ledger-census :: exit 0 :: ✓ check:route-ledger-census — all 1 census sentence(s) match their arrays (d813364). An earlier pass at 52b332b/d813364338 had check:dual-build-cjs-loads at exit 3 (PREREQUISITE NOT MET, 8 packages unbuilt); after building them it exits 0, and the final union records exit 0.",
"gates_derivation": "Lead: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack over the claim paths + the planned test/changeset paths on bcebd84: 65 commands (the PM's count). Final: the same with no paths on d813364 (change set 5 paths, +351/-3): the identical 65. It warned STALE TREE (origin/main moved 4 commits to 0d43004; stale file scripts/engine-double-contract.pinned.json); answered by deriving over this PR's 5 paths on a throwaway detached worktree at 0d43004: the identical 65 (worktree removed). Added beyond the list, as the dispatch asked: pnpm check:route-ledger-census and pnpm check:route-envelope (both exit 0, the latter is also in the 65).",
"line_budget": "+351 / -3 = 354 changed lines in 5 files (dispatch-gates changed-lines count), container M, under the 3,000-line human-merge threshold. Of it: 219 the new pin file, 93 rest-server.ts (6 named sets with their measured docblocks + 6 gate lines), 15 the census pin split, 10 query-allowlist.ts, 17 the changeset.",
"deviations": [
"Status needs_decision and PR line 1 "Part of #23072", not the dispatched "Fixes #23072": the dispatcher half needs a new @objectstack/rest public export, which the dispatch said to stop and report; the role file forbids Fixes on a card that is going to the decision box (merging would close it with the dispatcher half undone). Line 2 is "Clause-②: no" as dispatched.",
"File surface: packages/rest/src/query-allowlist.ts (+10/-1, unknownQueryParamMessage) is outside the claimed surface. Reason: /references and /published read no query parameter, so their measured sets are empty, the first empty sets in the repo, and the shared message then ended "Supported parameters: ." — a release-fixed refusal sentence this PR would otherwise ship. The empty case now reads "This endpoint takes no query parameters."; every non-empty sentence is byte-identical (its three existing pins pass unchanged). Same defect class and same gate family as the card, mechanical, and no other claim holds the file (the claim read 9 open PRs; none touches it). Declared in the PR body. The seat may amend the claim's surface.",
"File surface: packages/rest/src/meta-item-save-capability-gate.test.ts edited: the census found it sending the inert ?organizationId= to PUT (the dispatch allows fixing such a test caller).",
"No packages/runtime change and no @objectstack/runtime entry in the changeset: the dispatcher half is the open question; the changeset is @objectstack/rest patch only, carrying Clause-②: no.",
"Not merged with origin/main: it moved 4 commits during the run (to 0d43004: spec, metadata-protocol, objectql, plugin-security), none touching rest-server.ts, query-allowlist.ts or domains/meta.ts; dispatch-gates over this PR's paths at 0d43004 derives the identical 65. Declared in the PR Acceptance notes; CI runs on the merge ref.",
"Runs on two heads: the build and the rest/runtime suites were measured on source f0f7780 / 52b332b; d813364 adds only the census pin's split (test file), and the targeted rerun, the whole rest local rerun, the dogfood, the ablation, lint and the gate union are at d813364.",
"Two lock acquisitions returned exit 99 queue-timeout (dogfood attempt 1) and are NOT MEASURED; re-run with the same OS_VERIFY_LOCK_SLOT.",
"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)."
],
"files_changed": [
".changeset/23072-meta-doors-closed-sets.md",
"packages/rest/src/meta-item-save-capability-gate.test.ts",
"packages/rest/src/query-allowlist.ts",
"packages/rest/src/rest-server-meta-door-closed-sets.test.ts",
"packages/rest/src/rest-server.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 #23133, body read back 15268 sent = 15268 stored, identical) + POST /repos//issues/23133/assignees ['os-steve'] (read back: os-steve), run 38339613713; (2) POST /repos//issues/23072/comments (this os-dev-report, scripts/pm/post-stamped.mjs). 3 REST writes in all. Zero label writes: the dispatch named no label and skip-changeset is not owed (the PR carries a publishing changeset). No PATCH, no claim, no card-assignee write. Not REST: 4 git pushes of the branch (the empty landing marker, then 3 commits).",
"open_questions": [
{
"question": "The runtime dispatcher's /meta item GET must honour ?member= (GetMetaItemRequest declares it) and refuse what RestServer's META_ITEM_READ_PARAMS refuses, from ONE source for the set and the sentence. None of META_ITEM_READ_PARAMS, unknownQueryParamMessage, META_ITEM_MEMBER_PARAM / metaMemberSelector or the ?layers=+member sentence is on @objectstack/rest's published entry (single "." export; index.ts on bcebd84). Which way?",
"options": [
"A — export the portable halves from @objectstack/rest's index, the repeatedQueryParamMessage precedent (the index already documents that split): META_ITEM_READ_PARAMS, unknownQueryParamMessage, the member reading (metaMemberSelector or META_ITEM_MEMBER_PARAM) and the layered-member sentence; the dispatcher composes them and writes deps.error(message, 400). Cost: @objectstack/rest minor (3-4 additive exports, a public-surface widening, no accept set widened beyond the declared selector); dispatcher work: member into 3 getMetaItem calls, the two bare catches must let the protocol's 400 out, the MetadataService name-keyed fallback must not answer a named member, the layered refusal; pins + extending meta-list-projection-parity's ITEM_PROBES with member. Estimate 300-500 lines, one card.",
"B — one portable DECISION in meta-item-read-gate.ts, the refuseUnknownMetaListType / metaTypeReadRefusal precedent: e.g. a metaItemReadQueryRefusal(query) returning { code, status, message } | undefined plus the member reading, exported once; RestServer's item GET is re-pointed at it too, so both transports call one function by construction. Cost: @objectstack/rest minor (1-2 exports); the same dispatcher work as A; RestServer's ruled GET door is touched in spelling only (its answers byte-identical, pinned by the existing member-selector file). Estimate 350-550 lines, one card.",
"C — no new export: the dispatcher forwards member as the literal spec key and keeps NO closed set. Cost: small (~100 lines); but it restates metaMemberSelector (meta.ts's own header: imported, never restated), leaves every other unknown name dropped on that transport, and does not meet the card's "same refusal"."
],
"recommendation": "B. Four axes: (1) real business need — measured, no in-repo or objectui producer sends ?member= at all today (the SDK and Studio clients carry no selector; the showcase serves /meta through RestServer), so the pull is the published @objectstack/hono createHonoApp composition, whose /meta answers through the dispatcher alone: anyone (an AI caller first) who sends the declared selector there gets a plausible canonical answer; the cost of not acting is a silent wrong row, not a missing feature. (2) long-term soundness — B makes the set and the member reading one decision both transports call, the pattern meta-item-read-gate.ts already uses for the list's unknown type and the type-level read refusal, so the next name added to the item read cannot drift between transports; A exports raw parts and leaves order and composition to each transport; C keeps two answers. (3) AI-error resistance — A and B both make a misspelled or undeclared name loud on the dispatcher; C keeps the silent drop for every name but one. (4) startup no-sprawl — B adds the fewest exports (1-2) for a selector the spec already declares (spec declaration > implementation: declared-but-not-honoured on one transport is an implementation gap to close, not a capability to add), and adds no gate. If the maintainer wants zero new exports, C is the only option, and it should then be recorded as a known open set on the dispatcher, not as parity."
}
],
"out_of_scope_findings": [
"class: b · reach: read from source on bcebd84 (packages/runtime/src/domains/meta.ts), not measured on a dispatcher-only host · the dispatcher's /layers, /published and PUT /metadata/:type/:name branches declare no closed query set either; its PUT reads package and mode (metaSaveRequestOptions) and drops ?member= (and, by its own settled ruling, ?force=), so a dispatcher PUT ?member=zh-CN with an en-US body writes en-US exactly as RestServer's did before this PR. Same family as #23072, outside its population (the card names the item GET only): fold into #23072's open dispatcher half rather than a single-point card. Seam: spec:GetMetaItemRequest.member / SaveMetaItemRequest -> runtime:handleMetadataRequest PUT and /layers and /published branches · dedupe words: dispatcher meta closed query set; dispatcher PUT member dropped; meta.ts layers published query allowlist",
"carrier: 承接者:无 · noted, not filed — the cloud host's kernel-resolver strategy (RestRequestEnvResolver behind resolveProtocol) is not in this tree, so whether it reads a query key on these doors was not measured; no in-repo resolver reads one."
]
}
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 1fb1ced36243 (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 12, 2026
Triage: first grade,
bug·priority:p2·domain:cli·area:i18n, intopm:queue. Each door declares its closed query set:memberis honoured or refused, never droppedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T17:54Z. ⛔ Not a claim, ⛔ not a dispatch.PUT, where a caller's?member=is ignored and the body's member is written./metaitem GET,PUT /meta/:type/:name, and the five by-name doors outside the ruling's list each declare a closed query set, measured from their real read points.PUTstays body-addressed under ruling6116165299, so it refusesmemberwith a located 400. ⛔ It does not start reading it.memberwith the reader PR feat(metadata-protocol): one language of an email template is addressed by name — the by-name /meta doors accept a bundle member selector (#22877, stage 2) #23070 added. One that cannot refuses it.RestServer.packages/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'srest-server.tssplit is queued. Whichever lands second mergesmain.packages/restandpackages/runtime/src/domains/meta.ts(domain:cli); rationale: the doors are the lane's.objectstack-fleet commentedon Oct 12, 2026
Serial note from
domain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-12T18:20Z. ⛔ Not a claim, ⛔ no label change.This card stays in
pm:queuebehind three things. This seat claims it when all of them are clear:domain:engine, in a patch round): it adds thememberreader, and triage's direction measures each by-name door "with the reader PR feat(metadata-protocol): one language of an email template is addressed by name — the by-name /meta doors accept a bundle member selector (#22877, stage 2) #23070 added". Until it lands, the reader does not exist onmain.6129108871,domain:engineunder the cross-domain exception; declared on [PM seat] domain:cli · seat 2 — ⏳ vacant #22648 as6129132964): it edits the_migrate-storedhandler inpackages/rest/src/rest-server.tsand the same door inpackages/runtime/src/domains/meta.ts. Both files are this card's.packages/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 (therest-server.tssplit,pm:queue): whichever of [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 slices and this card claims second mergesmain. The fold-or-serial answer is serial. This card changes behaviour at the doors, while [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 is move-only by ruling (6126748916). The two fail the folding gate's first condition (the same defect shape and the same fix).Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Serial note update from
domain:cliseat 2 (#22648) ·os-steve· sessionsession_01VoSxBQujKLZKPwK2u5ehQ6· 2026-10-12T22:59Z. ⛔ Not a claim, ⛔ no label change. It updates6130072486.memberreader is onmain) and PR fix(metadata-protocol): a migrate-stored pass records the user who ran it, or nobody — never a label #23085 (metadata-protocol: the migrate-stored pass and its REST, runtime and CLI doors pass a label as PutOptions.actor; the contract says null, and the label lands in recorded_by (and updated_by after PR #23067) #23068) have both landed.6131875131,domain:engine, declared on [PM seat] domain:cli · seat 2 — ⏳ vacant #22648 as6131894045) edits theHttpDispatcherPUT /metadata/:type/:namebranch inpackages/runtime/src/domains/meta.ts, one of this card's two files. This card claims after metadata: the remaining PutOptions.actor producers that give a label or drop the user (plugin-security 'system', metadata-fs 'fs', the runtime dispatcher's PUT); one family card after #23068 #23086's PR lands.packages/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 slice 1 (claim6134225153) movesregisterDataActionEndpointsout ofrest-server.ts. That region is disjoint from this card's doors inregisterMetadataEndpointsInner, and whichever lands second mergesmain.PUTclosed set (hourly full run: red on main (CI) #23084's dev,6134161286):PUT {metaPath}/:type/:nameREADSpackage(metaItemPackageBinding(req.query?.package)), and alsoforceandmode(viametaSaveRequestOptions). Its repeat guard names['force','package','mode']. So the closed set measured from its read points includes those three, and refusesmemberwith a located 400.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 9 · 2026-10-13T01:33Z
Session:
session_01VoSxBQujKLZKPwK2u5ehQ6Account:
os-steve(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23072-meta-doors-closed-setsWorktree:
objectstack-issue-23072Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on
origin/mainbcebd8409d; stop on a breach and explain it in the report):packages/rest/src/rest-server.ts, inregisterMetadataEndpointsInneronly:PUT {metaPath}/:type/:name(about:6942) declares its closed query set, measured from its read points (package,forceandmode, measured on hourly full run: red on main (CI) #23084,6134161286), through the file's ownrefuseUnknownQueryParams.memberand any other unrecognised name answer a located 400. ⛔ PUT stays body-addressed (ruling6116165299), and does not start readingmember./references(about:6100),/layers(about:6207),/audit(about:7480),/diff(about:7947) and/published(about:8299). Each declares its closed set from its real read points, and refusesmemberwith a located 400.member. Their protocol requests do not declare it (packages/spec/src/api/protocol.zod.tsdeclares it onGetMetaItem,DeleteMetaItem,PublishMetaItemandHistoryMetaItemonly), so honouring it would widen a published accept set, which is spec-lane work. The dev reports which of the five could honour it with PR feat(metadata-protocol): one language of an email template is addressed by name — the by-name /meta doors accept a bundle member selector (#22877, stage 2) #23070's reader, as a finding.packages/runtime/src/domains/meta.ts: the dispatcher's/metaitem GET (about:1496-:1660) forwardsmembertogetMetaItemasRestServer's item GET does (the request declares it), and refuses whatRestServer'sMETA_ITEM_READ_PARAMSrefuses. One source for the set, ⛔ never a second hand-kept list. The other dispatcher branches the card names (DELETE, publish, history, rollback answer 405 there) are unchanged.?member=either honoured or refused, never dropped, inpackages/rest/src/andpackages/runtime/src/. Plus any existing test or dogfood caller that sends a name outside a new closed set, measured (below)..changeset/23072-*.md(@objectstack/restand@objectstack/runtimepatch), naming each door's new refusal with its remedy, carryingClause-②: no.packages/spec,packages/metadata-protocol,packages/client, any/metadoor already in the ruling's list, and any otherregister*Endpointssection ofrest-server.ts([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 later slices).Container & model:
M(seven doors over two transports, plus a caller census),mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). CeilingCONTRACT_REVIEW_TIER, reason: the card NARROWS published accept sets (a name these doors dropped now answers 400), so it isClause-②: no, stays in its lane, and owes one contract-review-tier review before the queue. The dispatcher honouringmemberimplements a selector the spec already declares, so it widens nothing.check-governed-merges --testover the claimed paths: not governed.Clause-②: no
Responsibility:
packages/rest rest-server.ts and packages/runtime domains/meta.ts (this lane): PUT /meta/:type/:name, five by-name /meta doors and the dispatcher's item GET declare no closed query set, so a caller's ?member= is dropped with no signal | PR #23070 (038d4c9588) gave the five doors in ruling 6116165299's list closed sets and the member reader, and refuseUnknownQueryParams is the file's own refusal | a PUT ?member=zh-CN with an en-US body writes en-US, and a dispatcher-only host answers GET ?member=zh-CN with the canonical member, read from source by #22877's dev (6127796890), not measured on a live hostThread-read: 6134242418
Serial constraints cleared:
memberreader) and PR fix(metadata-protocol): a migrate-stored pass records the user who ran it, or nobody — never a label #23085 (metadata-protocol: the migrate-stored pass and its REST, runtime and CLI doors pass a label as PutOptions.actor; the contract says null, and the label lands in recorded_by (and updated_by after PR #23067) #23068) landed, as6130072486required.meta.tsbranch) landed ata3d47c2c59. metadata: the remaining PutOptions.actor producers that give a label or drop the user (plugin-security 'system', metadata-fs 'fs', the runtime dispatcher's PUT); one family card after #23068 #23086 is nowpm:blockedon a position outside both files.packages/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 slice 1 (PR refactor(rest): move registerDataActionEndpoints into data-action-routes.ts (#23050 slice 1) #23116) landed atbcebd8409d, 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 for this card (serial, not folded,6130072486).rest-server.tsorpackages/runtime/src/domains/meta.ts. PR test: the stage-8 test half — suites stop depending on the four catalog row objects (#15204 stage 8, S8-T) #22962 touches one otherpackages/resttest file.apisanswer 404 ENDPOINT_NOT_FOUND there whileos serveof the same stack answers them — its "what serve composes" rule (#22301, ruling A) has a gap #23088 (packages/verify/src/harness.ts) and verify: classify hotcrm's "platform-semantics pins" — each becomes a derived proof family in@objectstack/verifyor a platform regression test in dogfood, never an app test (epic hotcrm#1579, step 5c) #15953 slice 5 (packages/verify,packages/cli).Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23072,
"status": "needs_decision",
"branch": "claude/issue-23072-meta-doors-closed-sets",
"pr": "#23133",
"session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (subagent of the domain:cli seat 2 PM; the parent's id, as the dispatch names it)",
"premise_still_valid": true,
"summary": "The RestServer half is delivered as a draft PR (Part of #23072, Clause-② no). PUT /meta/:type/:name and the five by-name sub-doors outside ruling 6116165299's list (/references, /layers, /audit, /diff, /published) each open with refuseUnknownQueryParams over a set measured from their read points (save: force, package, mode; references: empty; layers: package; audit: limit; diff: from, fromVersion, to, toVersion; published: empty). ?member=, and any name a door does not read, is a located 400 VALIDATION_ERROR in the nested envelope, the protocol never called and nothing saved. The dispatcher half is NOT built: honouring member and refusing what META_ITEM_READ_PARAMS refuses needs META_ITEM_READ_PARAMS and unknownQueryParamMessage (and the member reading) from @objectstack/rest, and none is on its published entry, so reaching them is a new public export, which the dispatch said to stop and report. That decision is open_questions[0]. One census caller was inert (a rest pin sending ?organizationId= to PUT), and it is split and fixed. One edit is outside the claimed surface (query-allowlist.ts: an empty set's message), see deviations.",
"head_measured": "d813364338 (base bcebd84; the rest and runtime src measured is f0f7780, byte-identical in packages/rest/src/rest-server.ts and query-allowlist.ts to d813364)",
"doors": [
"PUT {metaPath}/:type/:name — read points: force (req.query?.force, handler), package (metaItemPackageBinding(req.query?.package), handler), mode (metaSaveRequestOptions -> http.query?.mode, meta-save-request.ts:118); resolveExecCtx / walledCatalogRefusal / resolveProtocol / resolveMetaWriteActor read no query key. Before: no closed set (the multiplicity guard named force, package, mode). After: META_ITEM_SAVE_PARAMS = [force, package, mode], placed just before the multiplicity guard (after the capability, walled-catalog and 501 gates). member is refused, not read (body-addressed).",
"GET {metaPath}/:type/:name/references — read points: none (findReferencesToMeta({type,name,environmentId?}); resolveProtocol reads route, hostname, X-Environment-Id). Before: none. After: META_ITEM_REFERENCES_PARAMS = [] (first statement of the handler).",
"GET {metaPath}/:type/:name/layers — read points: package, a frame down in serveMetaItemLayered (metaItemPackageBinding(req.query?.package)); resolveObjectMasker and the read-gate sources read none. Before: none (only serveMetaItemLayered's repeat guard on package). After: META_ITEM_LAYERS_PARAMS = [package] (first statement).",
"GET {metaPath}/:type/:name/audit — read points: limit (readDeclaredQueryNumber over AuditMetaItemRequestSchema.shape.limit); refuseNonAuthoringCaller and eventDoorRefusal read the session. Before: none (repeat guard on limit). After: META_ITEM_AUDIT_PARAMS = [limit], just before the repeat guard.",
"GET {metaPath}/:type/:name/diff — read points: from / to (req.query?.from != null, req.query?.to != null) and fromVersion / toVersion (readDeclaredQueryNumber with the chosen name). Before: none (repeat guard on the four). After: META_ITEM_DIFF_PARAMS = [from, fromVersion, to, toVersion], just before the repeat guard.",
"GET {metaPath}/:type/:name/published — read points: none (resolveObjectMasker, resolveProtocol, getMetaItemLayered({type,name}), resolveMetadataService, getPublished, the read gate). Before: none. After: META_ITEM_PUBLISHED_PARAMS = [] (first statement)."
],
"census": [
"@objectstack/client meta.saveItem, both clients, one metaSaveQuery builder (packages/client/src/index.ts:787) — sends force, package, mode — green",
"@objectstack/client getReferences (:2146) / getPublished (:2059) — sends nothing — green",
"@objectstack/client getAudit (:2168) limit / diffItem (:2226) from, to — green",
"objectui at the pin 1fb1ced36243 (shallow fetch, read-only), @object-ui/data-objectstack MetadataClient: save (force, mode, package), layered (package), references (nothing), diff (from, to), audit (limit); no /published caller — green",
"objectui UnpublishedAppBar.tsx and metadata-admin/external/api.ts PUT /api/v1/meta/... by fetch — no query — green",
"dogfood: 9 files PUT /meta with a query string (package x21, mode x16, force x1) plus the 7 /layers and /published callers (no query) plus email-template-two-locales and flow-shipped-name-by-name-read — 18 files run, Test Files 18 passed (18), Tests 156 passed (156) at d813364 — green",
"packages/rest/src/meta-item-save-capability-gate.test.ts "query/body-smuggled organization ids do not reach the write either" — sent ?organizationId= to PUT, a name the door never read — INERT: red under the new set (the one red of the whole rest run at 52b332b). Fixed in this PR (d813364): the body half stays 200 with organizationId absent from the save request, the query half is now the located 400 with saveMetaItem never called.",
"rest and runtime unit suites otherwise: no other caller sends a name outside the new sets (rest local 275/276 at 52b332b, then whole local again at the final head; runtime local 363/363 and repo 3/3, which boot RestServer and drive /layers with package probes)."
],
"spec_lane_finding": [
"/references: NO, permanently. findReferencesToMeta sweeps the graph for items naming (type, name); a reference names an item, never one member of a bundle, so there is nothing to select. The refusal is the right answer, not a gap.",
"/layers: YES with threading. The reader resolves; findServedOverlayRow, lookupArtifactItem and registry.getItem already take member; getMetaItemLayered passes none. Missing: readItemFromMetadataService needs getMetaItem's addressed-member check (MetadataService keys by name), overlayLockLayerAt reads every member by name, GetMetaItemLayeredRequestSchema needs the field.",
"/audit: NO with the reader alone. sys_metadata_audit has no member column (type, name, organization_id, operation, outcome, ...), so every member's attempts share one trail; serving member needs a column, the writer stamping it, and AuditMetaItemRequestSchema.member.",
"/diff: YES with threading. historyRowsOfMember(type, rows, member) already takes a member and diffMetaItem passes none; the default to (the active version) would need the member-scoped active row (findServedOverlayRow takes member); RestServer's per-caller gate reads the canonical current document (fetchCurrentMetaDocument). No spec request is declared for this door.",
"/published: PARTLY. The overlay half rides getMetaItemLayered (as /layers); the code/package snapshot fallback getPublished reads MetadataService, which keys by name and holds one member per name."
],
"dispatcher": "Before = after (unchanged in this PR). packages/runtime/src/domains/meta.ts handleMetadataRequest, item GET (two-segment branch, ~:1236-1750 on bcebd84): reads state, preview, package (metaItemPackageBinding), layers (wantsMetaItemLayers) and locale (metaRequestLocale via createMetaItemAnswer); member never read or forwarded; no closed set. One source: @objectstack/runtime imports @objectstack/rest's single "." entry, whose index exports metaItemPackageBinding, metaSaveRequestOptions, repeatedQueryParamMessage and the meta-item-read-gate family, and does NOT export META_ITEM_READ_PARAMS, META_ITEM_MEMBER_PARAM, metaMemberSelector, refuseUnknownQueryParams or unknownQueryParamMessage (packages/rest/src/index.ts on bcebd84). Reaching them = a new public export => stopped, per the dispatch. Envelope: RestServer writes { error: { code: VALIDATION_ERROR, message } }; the dispatcher's deps.error(message, 400) writes { success: false, error: { code: VALIDATION_ERROR, message, ... } } via buildApiError; the rest index already records that split for the multiplicity rule (a dispatcher domain takes the message and builds its own body), so the same answer = same error.code and same message, each in its transport's body. Extra cost of honouring member there, measured from source: three getMetaItem calls (object branch readFromProtocol, generic branch, readPendingDraft); two lookups wrap getMetaItem in a bare catch that would swallow the protocol's located 400 for a bad selector and fall through to the registry / MetadataService; the MetadataService fallback getItem(type, name, packageId) keys by name alone and would serve the canonical member for a member the protocol answers absent; the ?layers= + member refusal sentence also lives only in rest-server.ts. The dispatcher's /published (:1094 area) is NOT in this card's population (the card names the item GET only); its /layers and PUT branches have no closed set either (out_of_scope_findings[0]).",
"tests": [
"BUILD (lock, VERDICT command-exit 0): pnpm turbo run build --filter='@objectstack/dogfood^...' --filter='@objectstack/runtime' --concurrency=2 -> Tasks: 64 successful, 64 total (43 cached), at f0f7780. Then the 8 packages check:dual-build-cjs-loads named unbuilt: 58 cached, 58 total, BUILD8_EXIT=0.",
"REST TYPECHECK (lock): pnpm --filter @objectstack/rest run typecheck -> TYPECHECK_EXIT=0; check:test-typecheck: OK ... 0 file(s) / 0 error(s).",
"REST TEST whole local project at 52b332b (lock): Test Files 1 failed | 275 passed (276), Tests 1 failed | 5781 passed | 326 skipped (6108); the one red = the inert census caller meta-item-save-capability-gate.test.ts. Whole rest local re-run at the final head d813364: NOT MEASURED, reason: three lock acquisitions (same slot issue-23072-rest-final) each ended exit 99 queue-timeout behind holder pid 2151 (issue-23088 round1-tests.sh, ALIVE, held at least 1507s) — declared narrowing instead: between 52b332b and d813364 only meta-item-save-capability-gate.test.ts changed (packages/rest/src otherwise byte-identical), and that file plus the new pins plus the ruled-doors file were re-run green at d813364 (next line); CI runs the full set.",
"REST TARGETED at d813364 (lock, verbose): rest-server-meta-door-closed-sets + meta-item-save-capability-gate + rest-server-meta-member-selector -> Test Files 3 passed (3), Tests 72 passed (72); the 21 new pins listed one by one. REST repo project: Test Files 5 passed (5), Tests 213 passed | 2 skipped (215). Lock VERDICT command-exit 0.",
"RUNTIME (lock, VERDICT command-exit 0, at d813364 with rest dist from f0f7780): typecheck TYPECHECK_EXIT=0 (check:test-typecheck OK, 27 file(s) / 190 error(s) / 68 pinned held, unchanged ledger); vitest --project local: Test Files 363 passed (363), Tests 5174 passed | 19 skipped (5193); --project repo (incl. meta-list-projection-parity.test.ts and its /layers read census over rest-server.ts): Test Files 3 passed (3), Tests 751 passed (751).",
"CONTROL, the five ruled doors: rest-server-meta-member-selector.test.ts unchanged, green inside the 72 above.",
"DOGFOOD (lock, VERDICT command-exit 0, at d813364; first attempt exit 99 queue-timeout = NOT MEASURED, re-run with the same slot): pnpm exec vitest run --maxWorkers=2 over the 18 census files -> Test Files 18 passed (18), Tests 156 passed (156).",
"ABLATION (lock, on committed d813364, scripts/ablation-replace.mjs wrap mode + a bash EXIT/INT/TERM restore trap on the absolute path): drop PUT's refuseUnknownQueryParams(req, res, META_ITEM_SAVE_PARAMS) -> marker ABLATION_23072_SAVE_RECOGNITION_DROPPED; counts before anchor 1 / marker 0, during anchor 0 / marker 1, blob 9a64fac24d3c -> f1b89f68cd07; result Tests 3 failed | 18 passed (21), exactly PUT's three refusal pins red; restored blob 9a64fac24d3c == HEAD, git diff HEAD empty, after anchor 1 / marker 0. The rest test imports ./rest-server.js from src/, so no dist/ preflight applies. Expected direction red; observed red. The dispatcher member-forward ablation was not run: no dispatcher code was built (needs_decision).",
"LINT, proven narrowing (whole-repo pnpm lint is CI's): (1) population read from eslint.config.mjs: files ['/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] minus NEVER_LINTED; ESLint API isPathIgnored=false for the 4 touched .ts files. (2) eslint --no-inline-config --format json: 4 files, errorCount 0, warningCount 0, fatal 0, exit 0, at d813364. (3) invariance: resolved parserOptions.project=null, projectService=null (no type-aware linting); the enabled rules are per-file AST rules, so this diff cannot move an untouched file's verdict."
],
"gates_head": "union at HEAD d813364 / union done at HEAD d813364",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
"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 (.changeset/11509-element-fla","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 #7005 diff scoping over real te",
"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) thebuild-corejob'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 11168 tracked file(s), all registered).",
"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 specimen). A new one reds here",
"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]: 8927 files, 0 disagree, 0 unparseable, 136.3s (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, 613 text-family fields; ",
"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 on its own schema.",
"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 8341 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits beside a real destroy() (0 kno",
"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 reds, every excluded name sta",
"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, over 148 anchors and 7 file-level",
"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/, 3064 non-test src files, 2564 @objectstack/* specifiers (0 assembled, not judged); 1 ledger row(s), all",
"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 3069 source file(s) all carry their unit in the key name (or in a sibling
unit, or under a declared exemption: 6 declared durati","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 scoping over real te",
"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_WALK_RADII; 3093 tes",
"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), 94914 string(s) read in 1324 parsed source(s) (seen floor 40000 strings / 600 sources), no growt",
"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, never investmen",
"pnpm check:dts-closure :: exit 0 :: check-dts-closure self-test: all assertions passed.",
"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 condition(s) res",
"pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: OK — 992 pinned, 122 in the DEBT ledger, 3 exempt.",
"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 (11168 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 red AND gr",
"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: 3365 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 11159 text file(s) -- 11159 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: 478 double(s) graded, 287 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 (3387 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 3390 sources under packages/, examples/, apps/ all reach the kernel through a discoverable declaration (
: PageordefinePage()).","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 table including the inconclusive",
"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 config or bui","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 by
pnpm lint.","pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8921 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 exempt, 0 v",
"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 self-test: all assertions passed.",
"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/4311), 2 exempt.",
"pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 16.8s, 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 array of quoted",
"pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 499 matcher(s) discovered, 499 answer the combinator battery correctly or refuse it loudly (320 refuse)."
],
"gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list at d813364 :: exit 0 :: Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN; ✓ 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). Plus, beyond the derived list: pnpm check:route-ledger-census :: exit 0 :: ✓ check:route-ledger-census — all 1 census sentence(s) match their arrays (d813364). An earlier pass at 52b332b/d813364338 had check:dual-build-cjs-loads at exit 3 (PREREQUISITE NOT MET, 8 packages unbuilt); after building them it exits 0, and the final union records exit 0.",
"gates_derivation": "Lead: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack over the claim paths + the planned test/changeset paths on bcebd84: 65 commands (the PM's count). Final: the same with no paths on d813364 (change set 5 paths, +351/-3): the identical 65. It warned STALE TREE (origin/main moved 4 commits to 0d43004; stale file scripts/engine-double-contract.pinned.json); answered by deriving over this PR's 5 paths on a throwaway detached worktree at 0d43004: the identical 65 (worktree removed). Added beyond the list, as the dispatch asked: pnpm check:route-ledger-census and pnpm check:route-envelope (both exit 0, the latter is also in the 65).",
"line_budget": "+351 / -3 = 354 changed lines in 5 files (dispatch-gates changed-lines count), container M, under the 3,000-line human-merge threshold. Of it: 219 the new pin file, 93 rest-server.ts (6 named sets with their measured docblocks + 6 gate lines), 15 the census pin split, 10 query-allowlist.ts, 17 the changeset.",
"deviations": [
"Status needs_decision and PR line 1 "Part of #23072", not the dispatched "Fixes #23072": the dispatcher half needs a new @objectstack/rest public export, which the dispatch said to stop and report; the role file forbids Fixes on a card that is going to the decision box (merging would close it with the dispatcher half undone). Line 2 is "Clause-②: no" as dispatched.",
"File surface: packages/rest/src/query-allowlist.ts (+10/-1, unknownQueryParamMessage) is outside the claimed surface. Reason: /references and /published read no query parameter, so their measured sets are empty, the first empty sets in the repo, and the shared message then ended "Supported parameters: ." — a release-fixed refusal sentence this PR would otherwise ship. The empty case now reads "This endpoint takes no query parameters."; every non-empty sentence is byte-identical (its three existing pins pass unchanged). Same defect class and same gate family as the card, mechanical, and no other claim holds the file (the claim read 9 open PRs; none touches it). Declared in the PR body. The seat may amend the claim's surface.",
"File surface: packages/rest/src/meta-item-save-capability-gate.test.ts edited: the census found it sending the inert ?organizationId= to PUT (the dispatch allows fixing such a test caller).",
"No packages/runtime change and no @objectstack/runtime entry in the changeset: the dispatcher half is the open question; the changeset is @objectstack/rest patch only, carrying Clause-②: no.",
"Not merged with origin/main: it moved 4 commits during the run (to 0d43004: spec, metadata-protocol, objectql, plugin-security), none touching rest-server.ts, query-allowlist.ts or domains/meta.ts; dispatch-gates over this PR's paths at 0d43004 derives the identical 65. Declared in the PR Acceptance notes; CI runs on the merge ref.",
"Runs on two heads: the build and the rest/runtime suites were measured on source f0f7780 / 52b332b; d813364 adds only the census pin's split (test file), and the targeted rerun, the whole rest local rerun, the dogfood, the ablation, lint and the gate union are at d813364.",
"Two lock acquisitions returned exit 99 queue-timeout (dogfood attempt 1) and are NOT MEASURED; re-run with the same OS_VERIFY_LOCK_SLOT.",
"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)."
],
"files_changed": [
".changeset/23072-meta-doors-closed-sets.md",
"packages/rest/src/meta-item-save-capability-gate.test.ts",
"packages/rest/src/query-allowlist.ts",
"packages/rest/src/rest-server-meta-door-closed-sets.test.ts",
"packages/rest/src/rest-server.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 #23133, body read back 15268 sent = 15268 stored, identical) + POST /repos//issues/23133/assignees ['os-steve'] (read back: os-steve), run 38339613713; (2) POST /repos//issues/23072/comments (this os-dev-report, scripts/pm/post-stamped.mjs). 3 REST writes in all. Zero label writes: the dispatch named no label and skip-changeset is not owed (the PR carries a publishing changeset). No PATCH, no claim, no card-assignee write. Not REST: 4 git pushes of the branch (the empty landing marker, then 3 commits).",
"open_questions": [
{
"question": "The runtime dispatcher's /meta item GET must honour ?member= (GetMetaItemRequest declares it) and refuse what RestServer's META_ITEM_READ_PARAMS refuses, from ONE source for the set and the sentence. None of META_ITEM_READ_PARAMS, unknownQueryParamMessage, META_ITEM_MEMBER_PARAM / metaMemberSelector or the ?layers=+member sentence is on @objectstack/rest's published entry (single "." export; index.ts on bcebd84). Which way?",
"options": [
"A — export the portable halves from @objectstack/rest's index, the repeatedQueryParamMessage precedent (the index already documents that split): META_ITEM_READ_PARAMS, unknownQueryParamMessage, the member reading (metaMemberSelector or META_ITEM_MEMBER_PARAM) and the layered-member sentence; the dispatcher composes them and writes deps.error(message, 400). Cost: @objectstack/rest minor (3-4 additive exports, a public-surface widening, no accept set widened beyond the declared selector); dispatcher work: member into 3 getMetaItem calls, the two bare catches must let the protocol's 400 out, the MetadataService name-keyed fallback must not answer a named member, the layered refusal; pins + extending meta-list-projection-parity's ITEM_PROBES with member. Estimate 300-500 lines, one card.",
"B — one portable DECISION in meta-item-read-gate.ts, the refuseUnknownMetaListType / metaTypeReadRefusal precedent: e.g. a metaItemReadQueryRefusal(query) returning { code, status, message } | undefined plus the member reading, exported once; RestServer's item GET is re-pointed at it too, so both transports call one function by construction. Cost: @objectstack/rest minor (1-2 exports); the same dispatcher work as A; RestServer's ruled GET door is touched in spelling only (its answers byte-identical, pinned by the existing member-selector file). Estimate 350-550 lines, one card.",
"C — no new export: the dispatcher forwards member as the literal spec key and keeps NO closed set. Cost: small (~100 lines); but it restates metaMemberSelector (meta.ts's own header: imported, never restated), leaves every other unknown name dropped on that transport, and does not meet the card's "same refusal"."
],
"recommendation": "B. Four axes: (1) real business need — measured, no in-repo or objectui producer sends ?member= at all today (the SDK and Studio clients carry no selector; the showcase serves /meta through RestServer), so the pull is the published @objectstack/hono createHonoApp composition, whose /meta answers through the dispatcher alone: anyone (an AI caller first) who sends the declared selector there gets a plausible canonical answer; the cost of not acting is a silent wrong row, not a missing feature. (2) long-term soundness — B makes the set and the member reading one decision both transports call, the pattern meta-item-read-gate.ts already uses for the list's unknown type and the type-level read refusal, so the next name added to the item read cannot drift between transports; A exports raw parts and leaves order and composition to each transport; C keeps two answers. (3) AI-error resistance — A and B both make a misspelled or undeclared name loud on the dispatcher; C keeps the silent drop for every name but one. (4) startup no-sprawl — B adds the fewest exports (1-2) for a selector the spec already declares (spec declaration > implementation: declared-but-not-honoured on one transport is an implementation gap to close, not a capability to add), and adds no gate. If the maintainer wants zero new exports, C is the only option, and it should then be recorded as a known open set on the dispatcher, not as parity."
}
],
"out_of_scope_findings": [
"class: b · reach: read from source on bcebd84 (packages/runtime/src/domains/meta.ts), not measured on a dispatcher-only host · the dispatcher's /layers, /published and PUT /metadata/:type/:name branches declare no closed query set either; its PUT reads package and mode (metaSaveRequestOptions) and drops ?member= (and, by its own settled ruling, ?force=), so a dispatcher PUT ?member=zh-CN with an en-US body writes en-US exactly as RestServer's did before this PR. Same family as #23072, outside its population (the card names the item GET only): fold into #23072's open dispatcher half rather than a single-point card. Seam: spec:GetMetaItemRequest.member / SaveMetaItemRequest -> runtime:handleMetadataRequest PUT and /layers and /published branches · dedupe words: dispatcher meta closed query set; dispatcher PUT member dropped; meta.ts layers published query allowlist",
"carrier: 承接者:无 · noted, not filed — the cloud host's kernel-resolver strategy (RestRequestEnvResolver behind resolveProtocol) is not in this tree, so whether it reads a query key on these doors was not measured; no in-repo resolver reads one."
]
}
Generated by Claude Code
2 remaining items
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 1fb1ced36243 (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