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
Metadata API: GET /api/v1/meta/app?id= with an empty value ignores the filter and answers the caller's full app list #28548
QA-source: #28545 · platform-core.builtin-apps-nav-render · observed beside the clauses
An app read filtered by an empty id answers as if no filter were given. A caller who asked for one app by a name that came out blank gets every app it can see. Found by an off-clause probe and reproduced twice, once as a member and once as admin. The per-caller visibility gate runs first, so nothing leaks beyond the caller's own list. This is the same class as #27484 (a blank $searchFields read as absent), which was fixed.
Reproduction (stock showcase at eeec88d1)
Sign in as admin (admin@objectos.ai), then GET /api/v1/meta/app?id= → 200 with [showcase_app, setup, account].
Sign in as a member (for example phone.demo@example.com), then GET /api/v1/meta/app?id= → 200 with [showcase_app, account].
Contrast: GET /api/v1/meta/app?id=%20 → {"items":[]}, and a repeated id parameter → 400.
Expected: an empty id is refused with a 400 naming the parameter, or matches nothing as the whitespace value does. Actual: the empty value is treated as absent, and the unfiltered list is served.
Mechanism
packages/rest/src/meta-item-read-gate.ts, around lines 3536–3541: const appIdFilter = metaType === 'app' ? query?.id : undefined; followed by if (typeof appIdFilter === 'string' && appIdFilter !== ''). An empty string fails the guard and skips the filter.
Claim: PM loop round 1 · card thread · 2026-11-01T06:55Z
Session: session_01FEkQ5kE5BrPUPNMMyv6oCH (the seat); the card thread is session_01FEkQ5pQoUJCqKk6SJQb6Qx
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-cq2t0t (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#3
Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Triage 6554161006 grades p3 and names #27484 as the same class.
File surface: packages/rest/src/meta-item-read-gate.ts (the appid filter, and identical empty-as-absent filters in the same gate), pins that assert the old answer, tests, and a changeset.
Container & model: S/M, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: high
Clause-②: no (narrowing) — expected if the #27484 precedent is a 400 refusal: a request that answered 200 is refused, so a contract review at CONTRACT_REVIEW_TIER is owed before the queue. If the dev follows a "matches nothing" precedent instead, the dev declares plain no.
Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: the code is the REST meta read gate · the platform path is the per-caller visibility gate, which already runs first (no leak beyond the caller list) · reached by an API client whose app name came out blank
Thread-read: 6554161006
Premise: holds (read at origin/main310cb96d7: if (typeof appIdFilter === 'string' && appIdFilter !== '') skips the filter for an empty string); the dev re-measures first
Serial constraints cleared: none found; no open PR on packages/rest/src/meta-item-read-gate.ts at this read
Resume-from: none — fresh from origin/main
State: → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28548.md (project folder).
domain:server#3 · os-project-manager · session_01FEkQ5pQoUJCqKk6SJQb6Qx
os-dev-report
{
"issue": 28548,
"status": "done",
"branch": "claude/project-thread-cq2t0t",
"pr": "#28602",
"session": "session_01FEkQ5pQoUJCqKk6SJQb6Qx",
"premise_still_valid": true,
"summary": "Premise held at origin/main 310cb96: createMetaListAnswer step 3 skipped the app filter for an empty id (typeof appIdFilter === 'string' && appIdFilter !== ''). An empty ?id= on /meta/app and /meta/apps is now refused 400 VALIDATION_ERROR with param 'id' and the message "Query parameter 'id' names no app; ..." by refuseEmptyAppIdFilter(), thrown in step 3 after the per-caller gate (step 2 keeps its place), so RestServer's handleRouteError and the dispatcher's errorFromThrown each shape it. Followed the precedent PR #27670 set for #27484 (400 VALIDATION_ERROR naming the parameter, not 'matches nothing'). A non-empty id (including ' '), an absent id, the repeated-id 400 and ?id= on other types are unchanged. One pin flipped on purpose (meta-app-publish-gate.test.ts 'an ABSENT id' asserted the empty id served the full list); no producer sends an empty id. The card assignee was set by the seat and not touched by this dev.",
"tests": "Head 9246901. Red first on base code with the new tests: rest meta-app-publish-gate.test.ts 'an EMPTY id is refused' failed (expected 200 to be 400; 1 failed / 25 passed); runtime meta-list-projection-parity.test.ts row A 'the reference moves' failed (1 failed / 906 passed). Green after fix + rest rebuild: 26/26 and 907/907. pnpm --filter @objectstack/rest test :: exit 0 (Test Files 362 passed, Tests 6906 passed / 332 skipped). rest typecheck :: exit 0; runtime typecheck :: exit 0 (after building @objectstack/driver-turso, a test-only import whose missing dist gave TS2307 on the first run). eslint --no-inline-config packages/rest packages/runtime :: exit 0. Ablation: ablation-replace.mjs on packages/rest/src/meta-item-read-gate.ts, anchor the const appIdFilter line, replacement restoring the old skip with marker ABLATE_28548; anchor 1->0, blob 24ba18a4 -> acc770c9; rebuilt rest; ablation-dist-preflight hit dist/index.js + dist/index.cjs; red: rest case (caller holding [studio.access]: expected 200 to be 400) and runtime row A case; restored blob == HEAD 24ba18a4, git diff HEAD empty, rebuilt, preflight --absent clean over 6 files, tree clean.",
"gates": "Local subset on 9246901: dispatch-gates --commands --repo objectstack-ai/objectstack derived 60 families; all 60 exit 0, exit codes captured before any pipe. First-pass non-verdicts re-run: check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, missing dists) -> exit 0 after building the remaining 7 dists; check:type-check-debt exit 99 (lock held by the dogfood battery) -> exit 0 under os-verify-lock. --ran reconcile: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN. CI families: PR #28602 check-runs, in_progress at report time.",
"line_budget": "changed lines (dispatch-gates): +93 / -10 over 4 files, under the 3000 human-merge threshold",
"files_changed": [
".changeset/28548-empty-app-id-refused.md",
"packages/rest/src/meta-app-publish-gate.test.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts"
],
"deviations": "test:repo is not run as a whole locally; its one changed file (runtime meta-list-projection-parity.test.ts, repo project) was run (907/907), the rest belongs to CI Test Core. The commit and PR declare Clause-②: no (narrowing) and Metadata-protocol: none as the claim expected; the seat owes the contract review before the queue. No labels sent with pr_create: the order names none and the diff ships (@objectstack/rest patch changeset), so no skip-changeset.",
"dogfood_battery": "9246901a · VERDICT command-exit 0 · Test Files 364 passed, 3 skipped (367) · Tests 2934 passed, 28 skipped (2962) · no failures",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — git push of claude/project-thread-cq2t0t (not REST); relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41285692629, opened #28602 draft, no assignees, no labels); this os-dev-report comment through post-stamped.mjs is the third write. Write budget spent: 3 of 3.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: unit-level only, no live door probe (createMetaListAnswer step 4, packages/rest/src/meta-item-read-gate.ts) · GET /meta/view?object= with an empty value fails the truthiness test query?.object and lists every view, unsorted and including views bound to no object, as if no filter were sent: the same empty-as-absent class as this card, a different guard spelling, outcome not ruled by the card, no producer sends an empty object · dedupe words: empty object filter view list, meta view object blank, view switcher empty object",
"carrier: none · noted in PR #28602 Acceptance notes, not filed"
]
}
The dev's report 6555083587 found the same class one guard further down: GET /meta/view?object= with an empty value fails the truthiness test query?.object (createMetaListAnswer step 4, packages/rest/src/meta-item-read-gate.ts) and lists every view as if no filter were sent. The seat folds it into PR #28602 instead of filing a card: same defect class, same file, mechanical, unclaimed, same gate family. Outcome: the same 400 refusal naming object, after the visibility gate.
File surface adds: the viewobject filter in the same gate and its tests.
Clause-②: no (narrowing) is unchanged; the contract review runs on the head that carries the fold.
domain:server#3 · os-project-manager · session_01FEkQ5pQoUJCqKk6SJQb6Qx
os-dev-report
{
"issue": 28548,
"status": "done",
"branch": "claude/project-thread-cq2t0t",
"pr": "#28602",
"session": "session_01FEkQ5pQoUJCqKk6SJQb6Qx",
"premise_still_valid": true,
"summary": "Patch round on the seat's direction (fold the empty ?object= finding). New head 9c45ac5 on top of 9246901. createMetaListAnswer step 4 now refuses an empty ?object= on /meta/view and /meta/views with 400 VALIDATION_ERROR, param 'object', message "Query parameter 'object' names no object; ...", through refuseEmptyListFilter (the helper the empty app id now shares, renamed from refuseEmptyAppIdFilter), after the per-caller gate. A named object still switches; ?object= on other types is still ignored. First round unchanged: empty ?id= on /meta/app and /meta/apps refused the same way, precedent PR #27670 for #27484. The PR body is NOT patched by the dev (os-dev.md: body written once); the v2 body text for the seat to write is at pm-orders/server/28548-pr-body-v2.md, and the changeset sentence is updated in the commit.",
"tests": "Head 9c45ac5. Red on 9246901 with the new object tests: rest 'an EMPTY view object is refused' (view: expected 200 to be 400; 1 failed / 26 passed); runtime parity row A on the new ?object= assertion (1 failed / 934 passed). Green after fix + rest rebuild: meta-app-publish-gate + meta-view-dashboard-audience-gate 46/46; meta-list-projection-parity + dispatcher-query-arity 968/968. pnpm --filter @objectstack/rest test :: exit 0 (362 files, 6907 passed / 332 skipped). rest + runtime typecheck :: exit 0. eslint --no-inline-config packages/rest packages/runtime :: exit 0. Ablation object leg: anchor the new step-4 refusal line, replacement gating it behind (globalThis as any).ABLATE_28548B === 'on' so the empty value falls to the old truthiness skip; anchor 1->0, blob 35301758 -> 15af187f; rebuilt; preflight hit dist/index.js + dist/index.cjs; red on both suites (as above); restored blob == HEAD 35301758, git diff HEAD empty, rebuilt, --absent clean, tree clean. The id leg from round 1 (on 9246901) stands.",
"gates": "Local subset on 9c45ac5: 60 derived families, all 60 exit 0, codes captured before any pipe. First-pass non-verdicts re-run after the parallel dogfood closure build settled: check:dts-closure exit 1 (dists being rewritten) -> exit 0 (239/239 declarations); check:dual-build-cjs-loads exit 3 (missing dists) -> exit 0; check:type-check-debt exit 99 (lock held by dogfood) -> exit 0. --ran: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN. CI families: PR #28602 check-runs on 9c45ac5, in_progress at report time.",
"line_budget": "changed lines (dispatch-gates): +128 / -12 over 4 files, under the 3000 human-merge threshold",
"files_changed": [
".changeset/28548-empty-app-id-refused.md",
"packages/rest/src/meta-app-publish-gate.test.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts"
],
"deviations": "PR body not updated by the dev, against the seat's ask: os-dev.md says the dev writes the PR body once at pr_create and never PATCHes it; the seat writes later changes. v2 body at pm-orders/server/28548-pr-body-v2.md; suggested title: fix(rest)!: an empty ?id= on the app list or ?object= on the view list is refused 400 naming the parameter instead of read as absent. Comment 6555454646 was first posted with the stale round-1 report and edited in place. test:repo is not run whole locally; its one changed file was run. No labels on the PR (rest ships, patch changeset).",
"dogfood_battery": "9c45ac50 · VERDICT command-exit 0 · Test Files 364 passed, 3 skipped (367) · Tests 2934 passed, 28 skipped (2962) · no failures (also green on 9246901)",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "This round: 3 — git push 9246901..9c45ac5 (not REST); comment 6555454646 through post-stamped.mjs, which first carried a stale copy of the round-1 report by a script slip and was then corrected in place by one relay comment_edit to this text. Round 1: push, relay pr_create (#28602), report comment 6555083587. PR body not patched.",
"open_questions": [],
"out_of_scope_findings": []
}
ACCEPT → PR #28602 comment 6555784146 · head 70a3f972 · 2026-11-01T08:56Z · landing sent (ready + auto-merge; contract review PASS 6555574011 on 9c45ac50, carried by a changeset-only move). The empty ?object= on the view list rides the same PR per claim amendment 6555101824.
Landed: merged as f3f7b629; Fixes #28548 closed the card. An empty ?id= on the app list and an empty ?object= on the view list now answer 400 VALIDATION_ERROR naming the parameter, on REST and the runtime dispatcher alike, instead of the caller's whole visible list. Observation kept from contract review 6555574011: an empty ?package= still reads as no scope (not this card).
Claim 6554674218 closed by the merge. Resume-from: main at f3f7b629 or later.
Activity
objectstack-fleet commentedon Nov 1, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P②
Triage: p3 ·
bug·domain:server·area:api· 2026-11-01T05:56Z⛔ Not a claim.
idonGET /meta/appis read as absent. The visibility gate runs first, so nothing leaks beyond the caller's list. Same class as data: a blank or comma-only$searchFieldsis read as absent, so a search the caller narrowed runs over the default search fields instead of being refused #27484. p3.Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Claim: PM loop round 1 · card thread · 2026-11-01T06:55Z
Session:
session_01FEkQ5kE5BrPUPNMMyv6oCH(the seat); the card thread issession_01FEkQ5pQoUJCqKk6SJQb6QxAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-cq2t0t(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#3Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Triage
6554161006grades p3 and names #27484 as the same class.File surface:
packages/rest/src/meta-item-read-gate.ts(theappidfilter, and identical empty-as-absent filters in the same gate), pins that assert the old answer, tests, and a changeset.Container & model:
S/M,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: highClause-②: no (narrowing) — expected if the #27484 precedent is a 400 refusal: a request that answered 200 is refused, so a contract review at
CONTRACT_REVIEW_TIERis owed before the queue. If the dev follows a "matches nothing" precedent instead, the dev declares plainno.Metadata-protocol: none (expected; the dev measures and declares)
Responsibility: the code is the REST meta read gate · the platform path is the per-caller visibility gate, which already runs first (no leak beyond the caller list) · reached by an API client whose app name came out blank
Thread-read: 6554161006
Premise: holds (read at
origin/main310cb96d7:if (typeof appIdFilter === 'string' && appIdFilter !== '')skips the filter for an empty string); the dev re-measures firstSerial constraints cleared: none found; no open PR on
packages/rest/src/meta-item-read-gate.tsat this readResume-from: none — fresh from
origin/mainState: →
pm:dispatched, assigned toos-project-manager, recorded by this claim. Order:pm-orders/server/order-28548.md(project folder).objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5pQoUJCqKk6SJQb6Qx
os-dev-report
{
"issue": 28548,
"status": "done",
"branch": "claude/project-thread-cq2t0t",
"pr": "#28602",
"session": "session_01FEkQ5pQoUJCqKk6SJQb6Qx",
"premise_still_valid": true,
"summary": "Premise held at origin/main 310cb96: createMetaListAnswer step 3 skipped the app filter for an empty id (typeof appIdFilter === 'string' && appIdFilter !== ''). An empty ?id= on /meta/app and /meta/apps is now refused 400 VALIDATION_ERROR with param 'id' and the message "Query parameter 'id' names no app; ..." by refuseEmptyAppIdFilter(), thrown in step 3 after the per-caller gate (step 2 keeps its place), so RestServer's handleRouteError and the dispatcher's errorFromThrown each shape it. Followed the precedent PR #27670 set for #27484 (400 VALIDATION_ERROR naming the parameter, not 'matches nothing'). A non-empty id (including ' '), an absent id, the repeated-id 400 and ?id= on other types are unchanged. One pin flipped on purpose (meta-app-publish-gate.test.ts 'an ABSENT id' asserted the empty id served the full list); no producer sends an empty id. The card assignee was set by the seat and not touched by this dev.",
"tests": "Head 9246901. Red first on base code with the new tests: rest meta-app-publish-gate.test.ts 'an EMPTY id is refused' failed (expected 200 to be 400; 1 failed / 25 passed); runtime meta-list-projection-parity.test.ts row A 'the reference moves' failed (1 failed / 906 passed). Green after fix + rest rebuild: 26/26 and 907/907. pnpm --filter @objectstack/rest test :: exit 0 (Test Files 362 passed, Tests 6906 passed / 332 skipped). rest typecheck :: exit 0; runtime typecheck :: exit 0 (after building @objectstack/driver-turso, a test-only import whose missing dist gave TS2307 on the first run). eslint --no-inline-config packages/rest packages/runtime :: exit 0. Ablation: ablation-replace.mjs on packages/rest/src/meta-item-read-gate.ts, anchor the const appIdFilter line, replacement restoring the old skip with marker ABLATE_28548; anchor 1->0, blob 24ba18a4 -> acc770c9; rebuilt rest; ablation-dist-preflight hit dist/index.js + dist/index.cjs; red: rest case (caller holding [studio.access]: expected 200 to be 400) and runtime row A case; restored blob == HEAD 24ba18a4, git diff HEAD empty, rebuilt, preflight --absent clean over 6 files, tree clean.",
"gates": "Local subset on 9246901: dispatch-gates --commands --repo objectstack-ai/objectstack derived 60 families; all 60 exit 0, exit codes captured before any pipe. First-pass non-verdicts re-run: check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, missing dists) -> exit 0 after building the remaining 7 dists; check:type-check-debt exit 99 (lock held by the dogfood battery) -> exit 0 under os-verify-lock. --ran reconcile: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN. CI families: PR #28602 check-runs, in_progress at report time.",
"line_budget": "changed lines (dispatch-gates): +93 / -10 over 4 files, under the 3000 human-merge threshold",
"files_changed": [
".changeset/28548-empty-app-id-refused.md",
"packages/rest/src/meta-app-publish-gate.test.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts"
],
"deviations": "test:repo is not run as a whole locally; its one changed file (runtime meta-list-projection-parity.test.ts, repo project) was run (907/907), the rest belongs to CI Test Core. The commit and PR declare Clause-②: no (narrowing) and Metadata-protocol: none as the claim expected; the seat owes the contract review before the queue. No labels sent with pr_create: the order names none and the diff ships (@objectstack/rest patch changeset), so no skip-changeset.",
"dogfood_battery": "9246901a · VERDICT command-exit 0 · Test Files 364 passed, 3 skipped (367) · Tests 2934 passed, 28 skipped (2962) · no failures",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — git push of claude/project-thread-cq2t0t (not REST); relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41285692629, opened #28602 draft, no assignees, no labels); this os-dev-report comment through post-stamped.mjs is the third write. Write budget spent: 3 of 3.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: unit-level only, no live door probe (createMetaListAnswer step 4, packages/rest/src/meta-item-read-gate.ts) · GET /meta/view?object= with an empty value fails the truthiness test query?.object and lists every view, unsorted and including views bound to no object, as if no filter were sent: the same empty-as-absent class as this card, a different guard spelling, outcome not ruled by the card, no producer sends an empty object · dedupe words: empty object filter view list, meta view object blank, view switcher empty object",
"carrier: none · noted in PR #28602 Acceptance notes, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Claim amendment to
6554674218· 2026-11-01T07:43Z · file surface increment, declaredThe dev's report
6555083587found the same class one guard further down:GET /meta/view?object=with an empty value fails the truthiness testquery?.object(createMetaListAnswerstep 4,packages/rest/src/meta-item-read-gate.ts) and lists every view as if no filter were sent. The seat folds it into PR #28602 instead of filing a card: same defect class, same file, mechanical, unclaimed, same gate family. Outcome: the same 400 refusal namingobject, after the visibility gate.viewobjectfilter in the same gate and its tests.Clause-②: no (narrowing)is unchanged; the contract review runs on the head that carries the fold.objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5pQoUJCqKk6SJQb6Qx
os-dev-report
{
"issue": 28548,
"status": "done",
"branch": "claude/project-thread-cq2t0t",
"pr": "#28602",
"session": "session_01FEkQ5pQoUJCqKk6SJQb6Qx",
"premise_still_valid": true,
"summary": "Patch round on the seat's direction (fold the empty ?object= finding). New head 9c45ac5 on top of 9246901. createMetaListAnswer step 4 now refuses an empty ?object= on /meta/view and /meta/views with 400 VALIDATION_ERROR, param 'object', message "Query parameter 'object' names no object; ...", through refuseEmptyListFilter (the helper the empty app id now shares, renamed from refuseEmptyAppIdFilter), after the per-caller gate. A named object still switches; ?object= on other types is still ignored. First round unchanged: empty ?id= on /meta/app and /meta/apps refused the same way, precedent PR #27670 for #27484. The PR body is NOT patched by the dev (os-dev.md: body written once); the v2 body text for the seat to write is at pm-orders/server/28548-pr-body-v2.md, and the changeset sentence is updated in the commit.",
"tests": "Head 9c45ac5. Red on 9246901 with the new object tests: rest 'an EMPTY view object is refused' (view: expected 200 to be 400; 1 failed / 26 passed); runtime parity row A on the new ?object= assertion (1 failed / 934 passed). Green after fix + rest rebuild: meta-app-publish-gate + meta-view-dashboard-audience-gate 46/46; meta-list-projection-parity + dispatcher-query-arity 968/968. pnpm --filter @objectstack/rest test :: exit 0 (362 files, 6907 passed / 332 skipped). rest + runtime typecheck :: exit 0. eslint --no-inline-config packages/rest packages/runtime :: exit 0. Ablation object leg: anchor the new step-4 refusal line, replacement gating it behind (globalThis as any).ABLATE_28548B === 'on' so the empty value falls to the old truthiness skip; anchor 1->0, blob 35301758 -> 15af187f; rebuilt; preflight hit dist/index.js + dist/index.cjs; red on both suites (as above); restored blob == HEAD 35301758, git diff HEAD empty, rebuilt, --absent clean, tree clean. The id leg from round 1 (on 9246901) stands.",
"gates": "Local subset on 9c45ac5: 60 derived families, all 60 exit 0, codes captured before any pipe. First-pass non-verdicts re-run after the parallel dogfood closure build settled: check:dts-closure exit 1 (dists being rewritten) -> exit 0 (239/239 declarations); check:dual-build-cjs-loads exit 3 (missing dists) -> exit 0; check:type-check-debt exit 99 (lock held by dogfood) -> exit 0. --ran: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN. CI families: PR #28602 check-runs on 9c45ac5, in_progress at report time.",
"line_budget": "changed lines (dispatch-gates): +128 / -12 over 4 files, under the 3000 human-merge threshold",
"files_changed": [
".changeset/28548-empty-app-id-refused.md",
"packages/rest/src/meta-app-publish-gate.test.ts",
"packages/rest/src/meta-item-read-gate.ts",
"packages/runtime/src/domains/meta-list-projection-parity.test.ts"
],
"deviations": "PR body not updated by the dev, against the seat's ask: os-dev.md says the dev writes the PR body once at pr_create and never PATCHes it; the seat writes later changes. v2 body at pm-orders/server/28548-pr-body-v2.md; suggested title: fix(rest)!: an empty
?id=on the app list or?object=on the view list is refused 400 naming the parameter instead of read as absent. Comment 6555454646 was first posted with the stale round-1 report and edited in place. test:repo is not run whole locally; its one changed file was run. No labels on the PR (rest ships, patch changeset).","dogfood_battery": "9c45ac50 · VERDICT command-exit 0 · Test Files 364 passed, 3 skipped (367) · Tests 2934 passed, 28 skipped (2962) · no failures (also green on 9246901)",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "This round: 3 — git push 9246901..9c45ac5 (not REST); comment 6555454646 through post-stamped.mjs, which first carried a stale copy of the round-1 report by a script slip and was then corrected in place by one relay comment_edit to this text. Round 1: push, relay pr_create (#28602), report comment 6555083587. PR body not patched.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
ACCEPT → PR #28602 comment 6555784146 · head
70a3f972· 2026-11-01T08:56Z · landing sent (ready + auto-merge; contract review PASS 6555574011 on9c45ac50, carried by a changeset-only move). The empty?object=on the view list rides the same PR per claim amendment 6555101824.objectstack-fleet commentedon Nov 1, 2026
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Landing record · #28548 · PR #28602
domain:serverseat 3 ·session_01FEkQ5kE5BrPUPNMMyv6oCH· 2026-11-01T10:10Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28602-*'git merge-base --is-ancestor f3f7b629 origin/maingit grep -c refuseEmptyListFilter origin/main -- packages/rest/src/meta-item-read-gate.tsf3f7b629;Fixes #28548closed the card. An empty?id=on the app list and an empty?object=on the view list now answer 400VALIDATION_ERRORnaming the parameter, on REST and the runtime dispatcher alike, instead of the caller's whole visible list. Observation kept from contract review 6555574011: an empty?package=still reads as no scope (not this card).6554674218closed by the merge. Resume-from:mainatf3f7b629or later.