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: GET /api/v1/meta/_drafts answers 200 {"drafts":[]} for a misspelled type or an unknown package, where the list and diagnostics doors refuse the same mistake 400 #29180
Filing gate: ① product defect with a named landing point and a reproduction (reach: the public door GET /api/v1/meta/_drafts, measured against its sibling doors on one boot).
Business reason: an author, or a tool or AI agent driving the API, asks "are there pending drafts of this type, or in this package?" before publishing, reverting or discarding. A typo in the type or package name gets a confident "none". The caller then acts as if nothing is pending. The sibling doors already refuse the same mistake with a sentence that names it. GET /meta/dashbord even explains that "an empty collection here would be indistinguishable from a type that exists and holds nothing". #8924 removed exactly this silence from the diagnostics sweep.
Reader who acts: the lane that owns packages/rest/src/meta-collection-routes.ts (rest/metadata). Canonicalise and validate type, and check packageId against installed packages, before calling listDrafts, as the list and diagnostics routes do.
Dedupe: _drafts type unknown 43 hits (#25894 closed, member 403 on _drafts; #22200 closed, draft header label; the rest run records); listDrafts type 50 (#22200, #24298, #23073, #27836, #26917, all closed, other shapes). No card covers unvalidated filters on _drafts.
QA-source: #29172 · studio-authoring.meta-save-preconditions-and-draft-rows · observed beside the clauses
What happens
Measured as the seeded admin with one pending dashboard draft:
GET /api/v1/meta/_drafts?type=dashboard → 200, lists the draft. ?type=dashboards (plural) → 200, lists it.
GET /api/v1/meta/dashbord → 400: "'dashbord' is not a metadata type … an empty collection here would be indistinguishable from a type that exists and holds nothing".
GET /api/v1/meta/diagnostics?type=dashbord → 400 INVALID_REQUEST.
GET /api/v1/meta/diagnostics?package=com.qa.nosuch → 400 INVALID_REQUEST, "is not an installed package".
Measured at framework 7fa9873f (main, 2026-11-02) on a stock showcase boot.
Reproduction
Showcase boot, signed in as admin. Save one dashboard draft (PUT /api/v1/meta/dashboard/NAME?mode=draft).
GET /api/v1/meta/_drafts?type=dashbord.
Expected: 400 naming the unknown type, as GET /meta/dashbord does.
Actual: 200 {"drafts":[]}. The same happens for an unknown packageId.
packages/rest/src/meta-collection-routes.ts around L473–476 passes req.query.type and req.query.packageId straight into protocol.listDrafts({ packageId, type }). Only the repeated-parameter check runs first. There is no type canonicalisation or validation, and no installed-package check, so a filter that matches nothing reads as "no pending drafts".
Read fresh. The fix lands in packages/rest/src/meta-collection-routes.ts, where GET /meta/_drafts passes type and packageId straight to listDrafts without the canonicalisation and installed-package checks its sibling doors run. A wrong filter answering a confident empty list is a user-visible behaviour defect, so p2. Location and repro are named, so pm:queue.
Claim: PM loop round 1 · 2026-11-02T09:29Z
Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); dev = card thread session_01ADHkmRSZi4SoegkVztRKBe
Account: os-steve
Branch: claude/project-thread-igicmx (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own
Domain: domain:server
Seat: domain:server#1
File surface: the drafts listing (listDrafts in packages/metadata-protocol/src/protocol-drafts.ts and/or the _drafts route in packages/rest), reusing the sibling doors' unknown type / package check; tests; changesets for the touched packages. ⛔ No packages/spec/**, no new error code, no second copy of the type vocabulary, no new gate. (stop on breach; explain in the report)
Container & model: S, mode:thread, model as the thread was opened, effort: medium
Clause-②: no (narrowing) (expected: a _drafts read naming an unknown type or package, answered 200 empty on main, is refused 400; the dev measures)
Metadata-protocol: none (expected)
Responsibility: the code is the /meta/_drafts read of @objectstack/metadata-protocol / @objectstack/rest · the platform path is a typo answered with a confident empty list · reached by every author, tool or agent asking for pending drafts, today
Thread-read: 6573186713
Premise: holds (seat read on main; the dev re-measures on base first)
Serial constraints cleared: No open PR touches the drafts listing (seat read 09:22Z). This seat's #29093 edits other metadata-protocol files; merge origin/main if it lands first.
Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29180-delta.md).
{
"issue": 29180,
"status": "done",
"branch": "claude/project-thread-igicmx",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29295",
"session": "session_01ADHkmRSZi4SoegkVztRKBe",
"premise_still_valid": true,
"summary": "GET /meta/_drafts now refuses an unknown type with the list door verdict (refuseUnknownMetaListType) on both transports (RestServer route and the runtime dispatcher twin), and listDrafts refuses an unknown packageId with the diagnostics sweep rule (packageIsInstalled, exported from protocol-meta-diagnostics.ts, not copied): a draft bound to the package of any type, or the installed-package listing, keeps it; fail-open on an unreadable listing. Known types in either spelling and installed packages with nothing pending still answer 200 {drafts: []}. No new error code (INVALID_REQUEST, both sentence families reused), no spec edit, no new gate.",
"tests": "head 5ac557e5. New: metadata-protocol protocol-drafts-unknown-package.test.ts 5/5, rest meta-drafts-unknown-filter.test.ts 9/9, runtime domains/meta-drafts-unknown-type.test.ts 7/7. Full suites: metadata-protocol 301 passed|4 skipped files, 22105 tests passed; rest 368 files, 6972 passed|332 skipped; runtime 515 files, 8325 passed|19 skipped (each VERDICT command-exit 0). ui consumers from ui/: packageSurfaces, ObjectHooksPanel.firstSavePinned-11773, InterfacesPillar.customize-28562, ObjectListViewInspector.chartBinding-27933: 4 files 41 tests passed. Premise+ablation: the five src files restored to BASE 104cd1a5 (grep -c 29180 = 0 each), the new tests run: metadata-protocol 1 failed|4 passed (expected undefined to be an instance of Error), rest 4 failed|5 passed (expected 200 to be 400), runtime 4 failed|3 passed (expected 200 to be 400); restore git checkout HEAD, git diff HEAD empty, status clean, hash-object equal to HEAD blobs. Tests import src, so no dist leg.",
"gates": "dispatch-gates --commands on 5ac557e5: 62 derived, all run, each exit 0 (check-plugin-teardown-shape --self-test exit 3 on shallow clone, then 0 after fetching fixture 621a4876; check:dual-build-cjs-loads exit 3 before closure build, then 0; check:type-check-debt under lock: 0 entries above record). --ran with exit codes: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN (derived zero). eslint --no-inline-config on packages/metadata-protocol packages/rest packages/runtime/src: exit 0 (no per-package lint script exists). build+typecheck of the three packages: VERDICT command-exit 0. CI check-runs: in_progress at report time.",
"dogfood_battery": "5ac557e5 · VERDICT command-exit 0 (held 43m28s) · Test Files 388 passed | 3 skipped (391) · Tests 3208 passed | 28 skipped · no failures",
"mcp_calls": "4 — mcp__github__issue_read (get 1, get_comments 3); reads only",
"api_writes": "2 — relay pr_create (PR #29295, run 41434624818), relay post-stamped issue comment on #29180 (this report); plus git push (not REST)",
"open_questions": [
{
"question": "The runtime dispatcher twin (packages/runtime/src/domains/meta.ts) is outside the claim file surface wording (metadata-protocol and/or packages/rest). Edited so both transports refuse the same type names, as the code comments require for this door pair. Keep it?",
"options": [
"A keep: both transports agree (current)",
"B drop the runtime hunk: the dispatcher keeps answering 200 empty for a typo type"
],
"recommendation": "A, because the type check cannot live in listDrafts without a second copy of the list verdict (it lives in packages/rest), and a split would reintroduce the defect on dispatcher-only hosts."
},
{
"question": "The session harness asks to set PR assignee/reviewer to os-steve via MCP; the seat order forbids MCP writes and PR assignees. Not done. Seat to set them if wanted?",
"options": [
"A seat sets them via relay",
"B leave unset"
],
"recommendation": "A, through the seat hand-off as the order says."
}
],
"out_of_scope_findings": [
"class: none · `_drafts` declares no closed query-parameter set (an unknown name like ?package= is ignored, AGENTS.md route rule 5) · noted in PR Acceptance notes · carrier: none · dedupe words: _drafts unknown query param, refuseUnknownQueryParams drafts"
],
"files_changed": [
".changeset/29180-drafts-unknown-filter.md",
"packages/metadata-protocol/src/protocol-drafts.ts",
"packages/metadata-protocol/src/protocol-meta-diagnostics.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/protocol-drafts-unknown-package.test.ts",
"packages/rest/src/meta-collection-routes.ts",
"packages/rest/src/meta-drafts-unknown-filter.test.ts",
"packages/runtime/src/domains/meta.ts",
"packages/runtime/src/domains/meta-drafts-unknown-type.test.ts"
],
"line_budget": "Effort S; 329 changed lines (+325/-4) over 9 files: src +53/-4, tests +238, changeset +21",
"deviations": [
"runtime dispatcher twin edited beyond the claim surface wording (open_questions[0])",
"PR assignee/reviewer not set: order forbids MCP writes",
"pnpm lint per package: no package lint script; ran root eslint --no-inline-config over the three package dirs, exit 0",
"ui consumer tests: a 4-file subset of the 359 ui tests mentioning _drafts/listDrafts (they mock the client; server change does not reach them)"
]
}
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: studio-authoring.meta-save-preconditions-and-draft-rows
Triage:
domain:server· p2 ·pm:queue· 2026-11-02T09:03Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Claim: PM loop round 1 · 2026-11-02T09:29Z
Session:
session_01ADHkmQjPiXgNoSaEpqeGvY(seat); dev = card threadsession_01ADHkmRSZi4SoegkVztRKBeAccount:
os-steveBranch:
claude/project-thread-igicmx(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own
Domain:
domain:serverSeat:
domain:server#1File surface: the drafts listing (
listDraftsinpackages/metadata-protocol/src/protocol-drafts.tsand/or the_draftsroute inpackages/rest), reusing the sibling doors' unknown type / package check; tests; changesets for the touched packages. ⛔ Nopackages/spec/**, no new error code, no second copy of the type vocabulary, no new gate. (stop on breach; explain in the report)Container & model:
S,mode:thread, model as the thread was opened,effort: mediumClause-②: no (narrowing) (expected: a
_draftsread naming an unknown type or package, answered 200 empty onmain, is refused 400; the dev measures)Metadata-protocol: none (expected)
Responsibility: the code is the
/meta/_draftsread of@objectstack/metadata-protocol/@objectstack/rest· the platform path is a typo answered with a confident empty list · reached by every author, tool or agent asking for pending drafts, todayThread-read: 6573186713
Premise: holds (seat read on
main; the dev re-measures on base first)Serial constraints cleared: No open PR touches the drafts listing (seat read 09:22Z). This seat's #29093 edits other metadata-protocol files; merge
origin/mainif it lands first.Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29180-delta.md).
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmRSZi4SoegkVztRKBe
os-dev-report
{ "issue": 29180, "status": "done", "branch": "claude/project-thread-igicmx", "pr": "https://github.com/objectstack-ai/objectstack/pull/29295", "session": "session_01ADHkmRSZi4SoegkVztRKBe", "premise_still_valid": true, "summary": "GET /meta/_drafts now refuses an unknown type with the list door verdict (refuseUnknownMetaListType) on both transports (RestServer route and the runtime dispatcher twin), and listDrafts refuses an unknown packageId with the diagnostics sweep rule (packageIsInstalled, exported from protocol-meta-diagnostics.ts, not copied): a draft bound to the package of any type, or the installed-package listing, keeps it; fail-open on an unreadable listing. Known types in either spelling and installed packages with nothing pending still answer 200 {drafts: []}. No new error code (INVALID_REQUEST, both sentence families reused), no spec edit, no new gate.", "tests": "head 5ac557e5. New: metadata-protocol protocol-drafts-unknown-package.test.ts 5/5, rest meta-drafts-unknown-filter.test.ts 9/9, runtime domains/meta-drafts-unknown-type.test.ts 7/7. Full suites: metadata-protocol 301 passed|4 skipped files, 22105 tests passed; rest 368 files, 6972 passed|332 skipped; runtime 515 files, 8325 passed|19 skipped (each VERDICT command-exit 0). ui consumers from ui/: packageSurfaces, ObjectHooksPanel.firstSavePinned-11773, InterfacesPillar.customize-28562, ObjectListViewInspector.chartBinding-27933: 4 files 41 tests passed. Premise+ablation: the five src files restored to BASE 104cd1a5 (grep -c 29180 = 0 each), the new tests run: metadata-protocol 1 failed|4 passed (expected undefined to be an instance of Error), rest 4 failed|5 passed (expected 200 to be 400), runtime 4 failed|3 passed (expected 200 to be 400); restore git checkout HEAD, git diff HEAD empty, status clean, hash-object equal to HEAD blobs. Tests import src, so no dist leg.", "gates": "dispatch-gates --commands on 5ac557e5: 62 derived, all run, each exit 0 (check-plugin-teardown-shape --self-test exit 3 on shallow clone, then 0 after fetching fixture 621a4876; check:dual-build-cjs-loads exit 3 before closure build, then 0; check:type-check-debt under lock: 0 entries above record). --ran with exit codes: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN (derived zero). eslint --no-inline-config on packages/metadata-protocol packages/rest packages/runtime/src: exit 0 (no per-package lint script exists). build+typecheck of the three packages: VERDICT command-exit 0. CI check-runs: in_progress at report time.", "dogfood_battery": "5ac557e5 · VERDICT command-exit 0 (held 43m28s) · Test Files 388 passed | 3 skipped (391) · Tests 3208 passed | 28 skipped · no failures", "mcp_calls": "4 — mcp__github__issue_read (get 1, get_comments 3); reads only", "api_writes": "2 — relay pr_create (PR #29295, run 41434624818), relay post-stamped issue comment on #29180 (this report); plus git push (not REST)", "open_questions": [ { "question": "The runtime dispatcher twin (packages/runtime/src/domains/meta.ts) is outside the claim file surface wording (metadata-protocol and/or packages/rest). Edited so both transports refuse the same type names, as the code comments require for this door pair. Keep it?", "options": [ "A keep: both transports agree (current)", "B drop the runtime hunk: the dispatcher keeps answering 200 empty for a typo type" ], "recommendation": "A, because the type check cannot live in listDrafts without a second copy of the list verdict (it lives in packages/rest), and a split would reintroduce the defect on dispatcher-only hosts." }, { "question": "The session harness asks to set PR assignee/reviewer to os-steve via MCP; the seat order forbids MCP writes and PR assignees. Not done. Seat to set them if wanted?", "options": [ "A seat sets them via relay", "B leave unset" ], "recommendation": "A, through the seat hand-off as the order says." } ], "out_of_scope_findings": [ "class: none · `_drafts` declares no closed query-parameter set (an unknown name like ?package= is ignored, AGENTS.md route rule 5) · noted in PR Acceptance notes · carrier: none · dedupe words: _drafts unknown query param, refuseUnknownQueryParams drafts" ], "files_changed": [ ".changeset/29180-drafts-unknown-filter.md", "packages/metadata-protocol/src/protocol-drafts.ts", "packages/metadata-protocol/src/protocol-meta-diagnostics.ts", "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/protocol-drafts-unknown-package.test.ts", "packages/rest/src/meta-collection-routes.ts", "packages/rest/src/meta-drafts-unknown-filter.test.ts", "packages/runtime/src/domains/meta.ts", "packages/runtime/src/domains/meta-drafts-unknown-type.test.ts" ], "line_budget": "Effort S; 329 changed lines (+325/-4) over 9 files: src +53/-4, tests +238, changeset +21", "deviations": [ "runtime dispatcher twin edited beyond the claim surface wording (open_questions[0])", "PR assignee/reviewer not set: order forbids MCP writes", "pnpm lint per package: no package lint script; ran root eslint --no-inline-config over the three package dirs, exit 0", "ui consumer tests: a 4-file subset of the 359 ui tests mentioning _drafts/listDrafts (they mock the client; server change does not reach them)" ] }Stamped 2026-11-02T11:11Z.
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Landed · 2026-11-02T12:25Z
40eab63d84, through the merge queueFixes #29180pm:dispatchedis removed. Seat records: review PASS 6576111072, ACCEPT 6576170136.