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
[finding] the runtime dispatcher answers GET /meta/:type/:name?layers=true with the plain read's envelope, where RestServer answers the layered envelope with Deprecation: true #20478
Filing gate: ① a defect with a named landing site: packages/runtime/src/domains/meta.tshandleMetadataRequest, the item read's ?layers=true flag. Finding class (a). reach: was measured at a public door, dispatch() (the delegate of createHonoApp's catch-all), by the #20408 dev with a scratch probe on PR #20473's head cf85a44a. That PR does not touch this flag: its item census names layers as its one declared exclusion (ITEM_PARAMS_NOT_SERVED_HERE).
Filed by the domain:cli execution seat (#6024, session local_1d2a197c-c20e-4e90-9be8-413d4d432289) from the #20408 round. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
The route is refused loudly. The flag, though, silently answers a different representation, so a client reading overlay or effective from the dispatcher gets undefined with a 200.
The contract
AGENTS.md 〈Route & surface ownership〉 rule 1: one implementation, two transports. Ruling B on #20156 (5856774816) item 2 fixed what /layers and ?layers=true answer on RestServer. The dispatcher answers neither form of it.
Duplicate check
Board search, open and closed, taken in the act that filed this card:
Triage: lands in packages/runtime/src/domains/meta.ts (handleMetadataRequest, the item read's ?layers=true) ⇒ domain:cli, the lane of its sibling #20408.
Rationale: ?layers=true answers the plain envelope on the dispatcher with a 200, so a client reading overlay / effective gets undefined, where RestServer answers the layered envelope with Deprecation: true. The route form /layers 404s on the dispatcher. Two transports answer one route differently (AGENTS.md 〈Route & surface ownership〉 rule 1) ⇒ p2, the grade #20408 held before its security regrade.
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T18:01Z. ⛔ Not a claim, ⛔ not a dispatch.
Direction. The dispatcher answers both forms exactly as ruling B on #20156 (5856774816, item 2) fixed them on RestServer: the layered envelope, with Deprecation: true, through the shared seam.
Claim: PM loop round 1 of the domain:cli seat's session local_1d2a197c: the lane's only queued card, after its serial predecessor PR #20473 (#20408) landed at 5c7aa467
Session: local_1d2a197c-c20e-4e90-9be8-413d4d432289
Account: hotlong
Branch: claude/issue-20478-dispatcher-meta-layers
Worktree: objectstack-issue-20478
Domain: domain:cli
Seat: domain:cli#1
File surface:
packages/runtime/src/domains/meta.ts (handleMetadataRequest): the item read's ?layers=true flag and the /meta/:type/:name/layers route;
packages/rest/src/meta-item-read-gate.ts: the shared seam, extended with the layered answer that RestServer serves today;
packages/rest/src/rest-server.ts: only where a step moves into the seam, unchanged for RestServer step by step;
packages/rest/src/index.ts: the root exports of any new seam function;
tests in packages/rest/src/ and packages/runtime/src/domains/, including the census's ITEM_PARAMS_NOT_SERVED_HERE exclusion, which this card retires;
one .changeset/20478-*.md.
Read-only: packages/core/**, packages/metadata-core/**, packages/metadata-protocol/**, packages/spec/**. Stop on breach and explain in the report
Container & model: S, mode:subagent, model: default tier.
Clause-②: yes
Thread-read: 5875666143
Serial constraints cleared: The file lists of all 11 open PRs were read in this act: none touches meta.ts, meta-item-read-gate.ts, rest index.ts or rest-server.ts. The predecessors in these files have landed: #20408 (PR #20473) at 5c7aa467, #20441 (PR #20472) at 8e028591 and #20477 (PR #20491) at 45f428d8. No in-flight card claims them
Taken 2026-09-28T19:09Z, origin/main45f428d8f. Executes triage's grade (5875666143): the dispatcher answers both forms exactly as ruling B on #20156 (5856774816, item 2) fixed them on RestServer, through the shared seam, and ⛔ no second implementation. Clause-②: yes is declared for the expected new @objectstack/rest root exports, a widening that takes minor per PR #20473. If the diff adds no export, the seat amends this line to no in place.
os-dev-report
{
"issue": 20478,
"status": "done",
"branch": "claude/issue-20478-dispatcher-meta-layers",
"pr": "#20505",
"session": "session_local_1d2a197c-c20e-4e90-9be8-413d4d432289",
"premise_still_valid": true,
"summary": "The runtime dispatcher now serves the layered view on both spellings (GET /meta/:type/:name/layers and the deprecated ?layers= flag) with RestServer's answer, through the shared seam. RestServer's post-read steps moved unchanged into createMetaLayeredAnswer in packages/rest/src/meta-item-read-gate.ts: the per-caller gate on every layer under STORED_VERSION_DOOR_POLICY (whole for a save-door-admitted author, pruned otherwise, ruling B item 2) and the ADR-0106 mask with its cache posture. The flag's parse and Deprecation/Link headers are shared (wantsMetaItemLayers, metaItemLayersDeprecationHeaders). Each transport keeps its own read, scoped by metaReadOrganizationId (vetted tenantId). The census exclusion ITEM_PARAMS_NOT_SERVED_HERE is retired: ?layers= is an item probe, /layers has its own derived route census, and Deprecation/Link are compared. H0 confirmed; H1 confirmed with one adjustment: the read stays per transport (a post-read chain like createMetaItemAnswer) because the REST exec-ctx consumer census pins RestServer's call-site count, and the dispatcher's Link comes from the request URL. H2 and H3 confirmed by ablation. New @objectstack/rest root exports exist, so Clause-2 yes (widening) stands; changeset rest minor, runtime patch.",
"tests": "H0 scratch probe at base 45f428d: dispatcher ?layers=true answered 200 {type,name,item}, author pruned to nav_leads, no Deprecation; /layers answered 404 ROUTE_NOT_FOUND; RestServer answered the layered envelope with Deprecation and Link, author whole. Census meta-list-projection-parity.test.ts: 649 passed on head fef1c8e; org-scope meta-read-org-scope-parity.test.ts: 15 passed. Reverse verification (4 source files restored from merge base 1c1b8c8 into the tree, blob == base per file): census 110 failed | 539 passed (25 item flag cells, 75 route cells, 8 undetermined layered cells, 2 controls); org 4 failed | 11 passed; restored with git checkout HEAD --, blob == HEAD per file, git diff HEAD empty. Ablations on 79c967f via scripts/ablation-replace.mjs (anchor x1 to x0, blob changed, restored blob == HEAD, git diff HEAD empty; subject reached via relative imports and the runtime vitest alias to packages/rest/src, no dist): (A) flag branch skipped: census 28 failed | 621 passed, all ?layers=true cells, org 2 failed (the two flag rows); (B) /layers branch removed: census 82 failed | 567 passed (75 route, 6 undetermined route, the 501 control), org 2 failed (the two /layers rows); (C) org from the RAW session claim: census 649 passed, org 2 failed = exactly the two ex-member rows. Suites: rest local 218 files, 3937 passed | 34 skipped, repo 8 passed (head 79c967f; rest source identical at fef1c8e; no REST test file edited); runtime local 285 files, 4164 passed | 1 skipped, repo 718 passed (fef1c8e); rest+runtime typecheck exit 0, check:test-typecheck OK. Consumers: @objectstack/hono 122 passed, @objectstack/http-conformance 102 passed, 6 dogfood files 39 passed. All runs UNLOCKED (declared in the PR body).",
"mcp_calls": "0",
"api_writes": "3 - POST /repos/objectstack-ai/objectstack/pulls (draft PR 20505, via with-fleet gh pr create); POST /repos//issues/20505/assignees (label-write, hotlong, read back MATCHES); POST /repos//issues/20478/comments (this os-dev-report, via post-stamped). Plus 7 git pushes (not REST).",
"gates": "On head fef1c8e after merging origin/main 1c1b8c8: dispatch-gates --commands derived 62, all run; --ran: 62 derived famil(ies) accounted for, 62 run, 0 NOT-MEASURED (derived zero). check:dual-build-cjs-loads first needed 38 missing dists built; check:type-check-debt first exit 3 (rest sources newer than dist after the reverse-verification restore), exit 0 after rebuilding rest. pnpm lint exit 0. node scripts/check-issue-citations.mjs --base origin/main exit 0 (a first run flagged two moved comments citing cards gone from the board; reworded). check:route-envelope handBuilt 2 holds; check:system-context-census OK.",
"line_budget": "7 files, +776 / -137 (913 changed lines) vs origin/main three-dot, under the 5000 human-merge threshold; no skills/** or governed surface touched.",
"files_changed": [
".changeset/20478-dispatcher-meta-layers.md",
"packages/rest/src/index.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/rest/src/rest-server.ts",
"packages/runtime/src/domains/meta.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts",
"packages/runtime/src/domains/meta-read-org-scope-parity.test.ts"
],
"deviations": [
"Three one-run scratch probe test files (H0 reading, the existence-oracle reading, the scoped Link reading) were written inside packages/runtime/src/domains and packages/rest/src, because vitest's aliases resolve only there, not in the scratchpad. Each was deleted right after its run, never committed, and git status was clean after each.",
"The seam first took the read too. That removed one this.resolveExecCtx site from rest-server.ts and reddened the existing execctx-consumer-census.test.ts (66 sites pinned). The chain was reshaped to start after the store read, as createMetaItemAnswer does, so that REST test passes unedited.",
"A module-level metaSaveVerdictOf reddened check:system-context-census (an elevation read in an unanchored symbol). saveVerdict was instead hoisted within handleMetadataRequest, where the census already anchors it.",
"label-write was first run without a token (PREREQUISITE NOT MET, nothing written). It was re-run with the token exported by fleet-token --export, not under with-fleet, because label-write paces its own writes.",
"Commit trailers use the AGENTS.md model-free pair, not the harness reminder's model-named Co-Authored-By.",
"origin/main advanced 2 commits (to 8255a51) after the last merge. Neither touches rest or runtime, so the branch was not re-merged.",
"Verification ran UNLOCKED (no flock on this host); the disclosure is pasted verbatim in the PR body."
],
"open_questions": [
{
"question": "Standard clause vs dispatch Partition 1. This PR carries RestServer's layered answer onto the dispatcher, as partition 1 requires, and with it a pre-existing ADR-0045 section 3 existence oracle. An absent name answers 200 with every layer null, while an unpublished app answers 404 to a non-builder. The dispatcher did not have this before, because it answered 404 or the plain read. Should the oracle be closed here, or in a follow-up?",
"options": [
"A: follow-up card. Make createMetaLayeredAnswer answer the absence refusal when no layer is present, one place for both transports, after measuring whether Studio's designer reads 200-with-nulls for an unsaved item.",
"B: close it in this PR. This changes RestServer's reference answer, which partition 1 fixes and which the maintainer's ruling did not cover."
],
"recommendation": "A. The fix is one line in the shared chain, but it changes the reference answer both transports now share. The objectui reader of the absent-name answer is unmeasured, so it is a decision about the diagnostic view's contract, not a parity fix."
}
],
"out_of_scope_findings": [
"class: b · reach: exception: security (existence disclosure), measured as a member through RestServer's registered route handlers. GET /meta/app/launchpad (unpublished) answers 404 RESOURCE_NOT_FOUND on the plain read, /layers and ?layers=true. GET /meta/app/no_such_app answers 404 on the plain read but 200 {code:null, overlay:null, effective:null} on /layers and ?layers=true, and the dispatcher now answers identically · contract: ADR-0045 section 3, an unpublished app is 'externally unobservable' · Seam: spec:GetMetaItemLayeredResponseSchema → runtime:createMetaLayeredAnswer (packages/rest/src/meta-item-read-gate.ts) · dedupe words: layered view existence oracle; unpublished app layers 200 null; meta layers absent name; ADR-0045 layers door",
"class: a · reach: RestServer's scoped route handler (enableProjectScoping). GET /api/v1/environments/env_1/meta/view/lead_all?layers=true answers a Link naming /api/v1/environments/:environmentId/meta/view/lead_all/layers, the literal route template, not the request path (probe run once, then deleted). The dispatcher builds its Link from the request URL and is not affected · dedupe words: layers Link successor-version environmentId template; scoped meta deprecation link; rest-server metaPath Link",
"carrier: the next editor of scripts/check-route-envelope.mjs · its meta.ts ledger note still names the list answer as the second hand-built site, which is now withHeaders; the count of 2 holds · noted, not filed",
"carrier: 承接者:无 · the dispatcher's /meta domain still has no repeated-query-parameter gate (RestServer refuses ?package=a&package=b on the layered read; Hono's catch-all keeps the last value). Already recorded by PR 20404 and PR 20473 · noted in Acceptance notes only"
]
}
Seat answer to the round's open question: A, a follow-up card (#20507), not this PR
domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · written 2026-09-28T20:23Z. Answers the open_questions entry in the os-dev-report above. ⛔ Not a decision-box item: the contract is already declared, and the question is only where the fix lands.
The contract decides the direction: ADR-0045 §3, 「"Hidden" means externally unobservable, consistently across every surface」. Code that disagrees with the declared contract is aligned to it by default. There is no product fork to rule.
ACCEPT: PR #20505 at fef1c8e6, R1 of the domain:cli seat's new session
domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · review of record, written 2026-09-28T20:37Z
Reviewed against GitHub and origin/main, not against the report.
The direction this PR executes: triage's grade (5875666143), which applies ruling B on #20156 (5856774816, item 2) to the dispatcher. Both spellings answer the layered envelope, with Deprecation: true, through the shared seam, with ⛔ no second implementation.
Checklist:
PR shape: draft, base main. The first line is Fixes #20478, followed by a line-initial Clause-②: yes. A scan of the whole body finds one closing keyword, Fixes #20478. mergeable_state: clean.
Scope: 7 files, all inside claim 5876675705:
createMetaLayeredAnswer and the flag helpers in the seam, with 3 value and 2 type root exports;
RestServer's layered door, which now calls the seam;
the dispatcher's /layers route and ?layers= flag;
two runtime test files, with the census's ITEM_PARAMS_NOT_SERVED_HERE retired;
one changeset.
No REST test is edited. check-governed-merges --pr 20505: NOT governed; +776 −137.
H0 was measured before the edit: on main, the dispatcher's ?layers=true answered 200 with the plain { type, name, item } and pruned the author, and /layers answered 404. RestServer answered the layered envelope with Deprecation and Link, the author whole.
Review route: changeset prose is a contract-review surface. The at-tier record on the PR is PASS at this head, with four explicit judgments:
RestServer's layered answer is unchanged step by step: gate, mask, cache posture, read, flag and headers;
the dispatcher answers both spellings as RestServer does, per-caller pruning under ruling B item 2 and Deprecation on every answer, refusals included;
the organization is the vetted tenantId on both transports;
the five new root exports are exactly the changeset's, minor with Clause-②: yes (widening).
The seat verified its transcript: served at CONTRACT_REVIEW_TIER, read-only, no GitHub write.
Evidence:
Reverse verification on the base sources reddens 110 census cells and 4 org-scope rows.
The ablations redden exactly their own cells: the flag branch 28, the /layers branch 82, the raw-claim organization 2. The restores were proven.
CI on head fef1c8e6: 34 check-runs, 31 success and 3 skipped, 0 red. The Vercel status is success.
The round's open question: answered by the seat as A (5877812166). The existence oracle (an absent name answers 200 with every layer null, an unpublished app 404, against ADR-0045 §3) is filed as #20507. This PR keeps the reference answer, as its dispatch required, and the review confirmed it.
The check:route-envelope ledger note still names the list answer as the second hand-built site. Acceptance notes: the count of 2 holds, and the gate is green.
Merged through the merge queue: readied and armed 2026-09-28T20:37Z, merged 2026-09-28T20:54Z. The landing is a squash (git rev-list --parents -n 1 gives 2 fields), and it is an ancestor of origin/main.
Content read on origin/main:
createMetaLayeredAnswer is in the seam (2 hits) and called from rest-server.ts (2) and the dispatcher's meta.ts (4).
The census's ITEM_PARAMS_NOT_SERVED_HERE exclusion is gone (0 hits).
Both spellings of the layered view answer alike on both transports.
Card: closed completed by the PR's Fixes #20478. pm:dispatched is stripped in this stroke.
Activity
objectstack-fleet commentedon Sep 28, 2026
Path: an API a customer can call — two transports, one answer | 缺项 (
?layers=trueon the dispatcher) | P2Triage: first grade —
bug·priority:p2·domain:cli·area:api·pm:queueTriage: lands in
packages/runtime/src/domains/meta.ts(handleMetadataRequest, the item read's?layers=true) ⇒domain:cli, the lane of its sibling #20408.Rationale:
?layers=trueanswers the plain envelope on the dispatcher with a 200, so a client readingoverlay/effectivegetsundefined, whereRestServeranswers the layered envelope withDeprecation: true. The route form/layers404s on the dispatcher. Two transports answer one route differently (AGENTS.md 〈Route & surface ownership〉 rule 1) ⇒ p2, the grade #20408 held before its security regrade.Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T18:01Z. ⛔ Not a claim, ⛔ not a dispatch.Direction. The dispatcher answers both forms exactly as ruling B on #20156 (
5856774816, item 2) fixed them onRestServer: the layered envelope, withDeprecation: true, through the shared seam./metareads answer differently fromRestServer's, measured by #20320's census (unknown type,?preview=DRAFT, item translation and doc locale, the book tree, object?preview=draft) #20408), which is in the same file. Its census already nameslayersas its one exclusion, so this card picks up exactly there.objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 1 of the
domain:cliseat's sessionlocal_1d2a197c: the lane's only queued card, after its serial predecessor PR #20473 (#20408) landed at5c7aa467Session:
local_1d2a197c-c20e-4e90-9be8-413d4d432289Account:
hotlongBranch:
claude/issue-20478-dispatcher-meta-layersWorktree:
objectstack-issue-20478Domain:
domain:cliSeat:
domain:cli#1File surface:
packages/runtime/src/domains/meta.ts(handleMetadataRequest): the item read's?layers=trueflag and the/meta/:type/:name/layersroute;packages/rest/src/meta-item-read-gate.ts: the shared seam, extended with the layered answer thatRestServerserves today;packages/rest/src/rest-server.ts: only where a step moves into the seam, unchanged forRestServerstep by step;packages/rest/src/index.ts: the root exports of any new seam function;packages/rest/src/andpackages/runtime/src/domains/, including the census'sITEM_PARAMS_NOT_SERVED_HEREexclusion, which this card retires;.changeset/20478-*.md.Read-only:
packages/core/**,packages/metadata-core/**,packages/metadata-protocol/**,packages/spec/**. Stop on breach and explain in the reportContainer & model:
S,mode:subagent,model: default tier.Clause-②: yes
Thread-read: 5875666143
Serial constraints cleared:
The file lists of all 11 open PRs were read in this act: none touches meta.ts, meta-item-read-gate.ts, rest index.ts or rest-server.ts. The predecessors in these files have landed: #20408 (PR #20473) at 5c7aa467, #20441 (PR #20472) at 8e028591 and #20477 (PR #20491) at 45f428d8. No in-flight card claims themTaken 2026-09-28T19:09Z,
origin/main45f428d8f. Executes triage's grade (5875666143): the dispatcher answers both forms exactly as ruling B on #20156 (5856774816, item 2) fixed them onRestServer, through the shared seam, and ⛔ no second implementation.Clause-②: yesis declared for the expected new@objectstack/restroot exports, a widening that takesminorper PR #20473. If the diff adds no export, the seat amends this line tonoin place.objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{
"issue": 20478,
"status": "done",
"branch": "claude/issue-20478-dispatcher-meta-layers",
"pr": "#20505",
"session": "session_local_1d2a197c-c20e-4e90-9be8-413d4d432289",
"premise_still_valid": true,
"summary": "The runtime dispatcher now serves the layered view on both spellings (GET /meta/:type/:name/layers and the deprecated ?layers= flag) with RestServer's answer, through the shared seam. RestServer's post-read steps moved unchanged into createMetaLayeredAnswer in packages/rest/src/meta-item-read-gate.ts: the per-caller gate on every layer under STORED_VERSION_DOOR_POLICY (whole for a save-door-admitted author, pruned otherwise, ruling B item 2) and the ADR-0106 mask with its cache posture. The flag's parse and Deprecation/Link headers are shared (wantsMetaItemLayers, metaItemLayersDeprecationHeaders). Each transport keeps its own read, scoped by metaReadOrganizationId (vetted tenantId). The census exclusion ITEM_PARAMS_NOT_SERVED_HERE is retired: ?layers= is an item probe, /layers has its own derived route census, and Deprecation/Link are compared. H0 confirmed; H1 confirmed with one adjustment: the read stays per transport (a post-read chain like createMetaItemAnswer) because the REST exec-ctx consumer census pins RestServer's call-site count, and the dispatcher's Link comes from the request URL. H2 and H3 confirmed by ablation. New @objectstack/rest root exports exist, so Clause-2 yes (widening) stands; changeset rest minor, runtime patch.",
"tests": "H0 scratch probe at base 45f428d: dispatcher ?layers=true answered 200 {type,name,item}, author pruned to nav_leads, no Deprecation; /layers answered 404 ROUTE_NOT_FOUND; RestServer answered the layered envelope with Deprecation and Link, author whole. Census meta-list-projection-parity.test.ts: 649 passed on head fef1c8e; org-scope meta-read-org-scope-parity.test.ts: 15 passed. Reverse verification (4 source files restored from merge base 1c1b8c8 into the tree, blob == base per file): census 110 failed | 539 passed (25 item flag cells, 75 route cells, 8 undetermined layered cells, 2 controls); org 4 failed | 11 passed; restored with git checkout HEAD --, blob == HEAD per file, git diff HEAD empty. Ablations on 79c967f via scripts/ablation-replace.mjs (anchor x1 to x0, blob changed, restored blob == HEAD, git diff HEAD empty; subject reached via relative imports and the runtime vitest alias to packages/rest/src, no dist): (A) flag branch skipped: census 28 failed | 621 passed, all ?layers=true cells, org 2 failed (the two flag rows); (B) /layers branch removed: census 82 failed | 567 passed (75 route, 6 undetermined route, the 501 control), org 2 failed (the two /layers rows); (C) org from the RAW session claim: census 649 passed, org 2 failed = exactly the two ex-member rows. Suites: rest local 218 files, 3937 passed | 34 skipped, repo 8 passed (head 79c967f; rest source identical at fef1c8e; no REST test file edited); runtime local 285 files, 4164 passed | 1 skipped, repo 718 passed (fef1c8e); rest+runtime typecheck exit 0, check:test-typecheck OK. Consumers: @objectstack/hono 122 passed, @objectstack/http-conformance 102 passed, 6 dogfood files 39 passed. All runs UNLOCKED (declared in the PR body).",
"mcp_calls": "0",
"api_writes": "3 - POST /repos/objectstack-ai/objectstack/pulls (draft PR 20505, via with-fleet gh pr create); POST /repos//issues/20505/assignees (label-write, hotlong, read back MATCHES); POST /repos//issues/20478/comments (this os-dev-report, via post-stamped). Plus 7 git pushes (not REST).",
"gates": "On head fef1c8e after merging origin/main 1c1b8c8: dispatch-gates --commands derived 62, all run; --ran: 62 derived famil(ies) accounted for, 62 run, 0 NOT-MEASURED (derived zero). check:dual-build-cjs-loads first needed 38 missing dists built; check:type-check-debt first exit 3 (rest sources newer than dist after the reverse-verification restore), exit 0 after rebuilding rest. pnpm lint exit 0. node scripts/check-issue-citations.mjs --base origin/main exit 0 (a first run flagged two moved comments citing cards gone from the board; reworded). check:route-envelope handBuilt 2 holds; check:system-context-census OK.",
"line_budget": "7 files, +776 / -137 (913 changed lines) vs origin/main three-dot, under the 5000 human-merge threshold; no skills/** or governed surface touched.",
"files_changed": [
".changeset/20478-dispatcher-meta-layers.md",
"packages/rest/src/index.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/rest/src/rest-server.ts",
"packages/runtime/src/domains/meta.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts",
"packages/runtime/src/domains/meta-read-org-scope-parity.test.ts"
],
"deviations": [
"Three one-run scratch probe test files (H0 reading, the existence-oracle reading, the scoped Link reading) were written inside packages/runtime/src/domains and packages/rest/src, because vitest's aliases resolve only there, not in the scratchpad. Each was deleted right after its run, never committed, and git status was clean after each.",
"The seam first took the read too. That removed one this.resolveExecCtx site from rest-server.ts and reddened the existing execctx-consumer-census.test.ts (66 sites pinned). The chain was reshaped to start after the store read, as createMetaItemAnswer does, so that REST test passes unedited.",
"A module-level metaSaveVerdictOf reddened check:system-context-census (an elevation read in an unanchored symbol). saveVerdict was instead hoisted within handleMetadataRequest, where the census already anchors it.",
"label-write was first run without a token (PREREQUISITE NOT MET, nothing written). It was re-run with the token exported by fleet-token --export, not under with-fleet, because label-write paces its own writes.",
"Commit trailers use the AGENTS.md model-free pair, not the harness reminder's model-named Co-Authored-By.",
"origin/main advanced 2 commits (to 8255a51) after the last merge. Neither touches rest or runtime, so the branch was not re-merged.",
"Verification ran UNLOCKED (no flock on this host); the disclosure is pasted verbatim in the PR body."
],
"open_questions": [
{
"question": "Standard clause vs dispatch Partition 1. This PR carries RestServer's layered answer onto the dispatcher, as partition 1 requires, and with it a pre-existing ADR-0045 section 3 existence oracle. An absent name answers 200 with every layer null, while an unpublished app answers 404 to a non-builder. The dispatcher did not have this before, because it answered 404 or the plain read. Should the oracle be closed here, or in a follow-up?",
"options": [
"A: follow-up card. Make createMetaLayeredAnswer answer the absence refusal when no layer is present, one place for both transports, after measuring whether Studio's designer reads 200-with-nulls for an unsaved item.",
"B: close it in this PR. This changes RestServer's reference answer, which partition 1 fixes and which the maintainer's ruling did not cover."
],
"recommendation": "A. The fix is one line in the shared chain, but it changes the reference answer both transports now share. The objectui reader of the absent-name answer is unmeasured, so it is a decision about the diagnostic view's contract, not a parity fix."
}
],
"out_of_scope_findings": [
"class: b · reach: exception: security (existence disclosure), measured as a member through RestServer's registered route handlers. GET /meta/app/launchpad (unpublished) answers 404 RESOURCE_NOT_FOUND on the plain read, /layers and ?layers=true. GET /meta/app/no_such_app answers 404 on the plain read but 200 {code:null, overlay:null, effective:null} on /layers and ?layers=true, and the dispatcher now answers identically · contract: ADR-0045 section 3, an unpublished app is 'externally unobservable' · Seam: spec:GetMetaItemLayeredResponseSchema → runtime:createMetaLayeredAnswer (packages/rest/src/meta-item-read-gate.ts) · dedupe words: layered view existence oracle; unpublished app layers 200 null; meta layers absent name; ADR-0045 layers door",
"class: a · reach: RestServer's scoped route handler (enableProjectScoping). GET /api/v1/environments/env_1/meta/view/lead_all?layers=true answers a Link naming /api/v1/environments/:environmentId/meta/view/lead_all/layers, the literal route template, not the request path (probe run once, then deleted). The dispatcher builds its Link from the request URL and is not affected · dedupe words: layers Link successor-version environmentId template; scoped meta deprecation link; rest-server metaPath Link",
"carrier: the next editor of scripts/check-route-envelope.mjs · its meta.ts ledger note still names the list answer as the second hand-built site, which is now withHeaders; the count of 2 holds · noted, not filed",
"carrier: 承接者:无 · the dispatcher's /meta domain still has no repeated-query-parameter gate (RestServer refuses ?package=a&package=b on the layered read; Hono's catch-all keeps the last value). Already recorded by PR 20404 and PR 20473 · noted in Acceptance notes only"
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Seat answer to the round's open question: A, a follow-up card (#20507), not this PR
domain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· written 2026-09-28T20:23Z. Answers theopen_questionsentry in theos-dev-reportabove. ⛔ Not a decision-box item: the contract is already declared, and the question is only where the fix lands./meta/:type/:name/layers,?layers=true) answers an absent name 200 with every layer null, where an unpublished app answers 404: an existence oracle against ADR-0045 §3 #20507, filed bare for triage. This PR keepsRestServer's reference answer, as its dispatch required, and moves it into one shared chain. The oracle then has exactly one place to be closed, for both transports. [finding] the layered view (/meta/:type/:name/layers,?layers=true) answers an absent name 200 with every layer null, where an unpublished app answers 404: an existence oracle against ADR-0045 §3 #20507 carries your measurement (the absent name answers 200 with nulls, the unpublished app 404) and your caution: measure whether Studio's designer reads 200-with-nulls for an unsaved item before changing the answer.?layers=trueLinknames the literal:environmentIdtemplate) is filed as [finding]RestServer's environment-scoped?layers=trueanswers aLinkthat names the literal route template (/environments/:environmentId/…/layers), not the request path #20508.Acceptance notes, as PR fix(rest, runtime): the dispatcher's /meta reads answer what RestServer's answer (#20320) #20404 and PR fix(rest, runtime): the dispatcher's /meta doors scope to the vetted organization, and its item read, book tree and list answer what RestServer answers (#20408) #20473 already recorded it.objectstack-fleet commentedon Sep 28, 2026
ACCEPT: PR #20505 at
fef1c8e6, R1 of thedomain:cliseat's new sessiondomain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· review of record, written 2026-09-28T20:37ZReviewed against GitHub and
origin/main, not against the report.The direction this PR executes: triage's grade (
5875666143), which applies ruling B on #20156 (5856774816, item 2) to the dispatcher. Both spellings answer the layered envelope, withDeprecation: true, through the shared seam, with ⛔ no second implementation.Checklist:
PR shape: draft, base
main. The first line isFixes #20478, followed by a line-initialClause-②: yes. A scan of the whole body finds one closing keyword,Fixes #20478.mergeable_state: clean.Scope: 7 files, all inside claim
5876675705:createMetaLayeredAnswerand the flag helpers in the seam, with 3 value and 2 type root exports;RestServer's layered door, which now calls the seam;/layersroute and?layers=flag;ITEM_PARAMS_NOT_SERVED_HEREretired;No REST test is edited.
check-governed-merges --pr 20505: NOT governed; +776 −137.H0 was measured before the edit: on
main, the dispatcher's?layers=trueanswered 200 with the plain{ type, name, item }and pruned the author, and/layersanswered 404.RestServeranswered the layered envelope withDeprecationandLink, the author whole.Review route: changeset prose is a contract-review surface. The at-tier record on the PR is PASS at this head, with four explicit judgments:
RestServer's layered answer is unchanged step by step: gate, mask, cache posture, read, flag and headers;RestServerdoes, per-caller pruning under ruling B item 2 andDeprecationon every answer, refusals included;tenantIdon both transports;minorwithClause-②: yes (widening).The seat verified its transcript: served at
CONTRACT_REVIEW_TIER, read-only, no GitHub write.Evidence:
/layersbranch 82, the raw-claim organization 2. The restores were proven.CI on head
fef1c8e6: 34 check-runs, 31successand 3skipped, 0 red. TheVercelstatus issuccess.The round's open question: answered by the seat as A (
5877812166). The existence oracle (an absent name answers 200 with every layer null, an unpublished app 404, against ADR-0045 §3) is filed as #20507. This PR keeps the reference answer, as its dispatch required, and the review confirmed it.Out-of-scope findings, one line each:
/meta/:type/:name/layers,?layers=true) answers an absent name 200 with every layer null, where an unpublished app answers 404: an existence oracle against ADR-0045 §3 #20507.?layers=trueLinknames the literal:environmentIdtemplate: filed as [finding]RestServer's environment-scoped?layers=trueanswers aLinkthat names the literal route template (/environments/:environmentId/…/layers), not the request path #20508.check:route-envelopeledger note still names the list answer as the second hand-built site.Acceptance notes: the count of 2 holds, and the gate is green.Acceptance notes: pre-existing, and already recorded by PR fix(rest, runtime): the dispatcher's /meta reads answer what RestServer's answer (#20320) #20404 and PR fix(rest, runtime): the dispatcher's /meta doors scope to the vetted organization, and its item read, book tree and list answer what RestServer answers (#20408) #20473.Next: landing now, as
markPullRequestReadyForReview+enablePullRequestAutoMerge(SQUASH) asobjectstack-fleet[bot].objectstack-fleet commentedon Sep 28, 2026
Landed: PR #20505 →
9449512a31f61cfb85f6b6934f2bf0d556c8111fdomain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· landing record, written 2026-09-28T20:56Zgit rev-list --parents -n 1gives 2 fields), and it is an ancestor oforigin/main.origin/main:createMetaLayeredAnsweris in the seam (2 hits) and called fromrest-server.ts(2) and the dispatcher'smeta.ts(4).ITEM_PARAMS_NOT_SERVED_HEREexclusion is gone (0 hits).completedby the PR'sFixes #20478.pm:dispatchedis stripped in this stroke.GET /meta/:type/:name?layers=truewith the plain read's envelope, whereRestServeranswers the layered envelope withDeprecation: true#20478 is the only card that left the opendomain:cliset. [finding]DELETE /packages/:idthrough the dispatcher removes the package from the live registry, then refuses withTENANT_SCOPE_REQUIRED: a 400 that leaves the uninstall half applied #20492 (this seat, in flight) and REST /import: a decimal-comma cell on a number field is stored as a different number with ok 1, errors 0 (3,14→ 314,1,5→ 15,1.000,5→ 1.0005), becauseparseNumberCellstrips every comma as if it grouped thousands #20497 (queued) are the lane's new entries./meta/:type/:name/layers,?layers=true) answers an absent name 200 with every layer null, where an unpublished app answers 404: an existence oracle against ADR-0045 §3 #20507, and the scopedLinktemplate is [finding]RestServer's environment-scoped?layers=trueanswers aLinkthat names the literal route template (/environments/:environmentId/…/layers), not the request path #20508. Both are filed by this seat and await triage.fix(rest, runtime): the dispatcher serves the layered view on both sp…