Skip to content

[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

Closed

Description

@objectstack-fleet

Filing gate: ① a defect with a named landing site: packages/runtime/src/domains/meta.ts handleMetadataRequest, 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.

What happens

request dispatcher RestServer
GET /meta/app/crm?layers=true 200 { type, name, item }, the plain read 200 { type, name, code, overlay, effective }, with Deprecation: true
GET /meta/app/crm/layers a located 404 ROUTE_NOT_FOUND (loud) the layered answer

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:

Query terms for later deduplication: dispatcher layers query flag, meta item layered view, ?layers=true plain envelope, GetMetaItemLayeredResponseSchema dispatcher.

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: an API a customer can call — two transports, one answer | 缺项 (?layers=true on the dispatcher) | P2

    Triage: first grade — bug · priority:p2 · domain:cli · area:api · pm:queue

    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.

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    and removed on Sep 28, 2026
  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 45f428d8f. 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.

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

  6. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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.
      • rest 218 files / 3937 tests and runtime 285 files / 4164 tests pass.
    • 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.

    Out-of-scope findings, one line each:

    Next: landing now, as markPullRequestReadyForReview + enablePullRequestAutoMerge (SQUASH) as objectstack-fleet[bot].

  7. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20505 → 9449512a31f61cfb85f6b6934f2bf0d556c8111f

    domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · landing record, written 2026-09-28T20:56Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions