Skip to content

fix(rest)!: GET /meta/_drafts refuses an unknown type or package filter instead of answering an empty list (#29180) - #29295

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-igicmx
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-igicmx

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Fixes #29180

Clause-②: no (narrowing) — measured: on base 5ac557e5^ (104cd1a5) _drafts?type=dashbord|dashboardes|viewes|nosuchtype and ?packageId=com.qa.nosuch answer 200 {"drafts":[]} (the refusal pins below go red there); on head they answer 400 INVALID_REQUEST, on both transports. Nothing is widened.
Metadata-protocol: none — measured: no spec key, value or type moves; packages/spec is untouched.
Skills-docs: none — no skills/** page or docs page documents the _drafts filters (the two docs mentions, content/docs/ui/apps.mdx L455/L461, cite only its capability gate).

维护者速读

  • 改了什么:GET /meta/_drafts 的 type 写错(如 dashbord)或 packageId 指向不存在的包时,不再回 200 空列表,而是回 400,文案与列表门、诊断门对同一错误的拒绝一致。
  • 为什么改:作者或 AI 在发布、回滚、丢弃前问"有没有待发布草稿",拼错就得到一个自信的"没有",随后按"无待办"行事。
  • 风险与回滚:这是收窄(原先 200 的请求现在 400);正确的类型(单复数皆可)和已安装但无草稿的包仍回 200 空列表。回滚即 revert 本 PR。
  • 先看:packages/metadata-protocol/src/protocol-drafts.ts(包检查)、packages/rest/src/meta-collection-routes.ts(类型检查)、packages/runtime/src/domains/meta.ts(派发器孪生门)。
  • 两分钟验证:pnpm --filter @objectstack/rest exec vitest run src/meta-drafts-unknown-filter.test.ts,预期 9 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • Type (packages/rest/src/meta-collection-routes.ts, packages/runtime/src/domains/meta.ts): both _drafts doors ask refuseUnknownMetaListType, the list door's own verdict (static spelling contract, then the live type set, fail-open), before listDrafts. No second copy of the type vocabulary: it is the same function GET /meta/:type and GET /meta/diagnostics?type= call, so the drafts answer is pinned EQUAL to the list door's answer for the same name.
  • Package (packages/metadata-protocol/src/protocol-drafts.ts): listDrafts asks the diagnostics sweep's rule (packageIsInstalled, now exported from protocol-meta-diagnostics.ts; nothing re-exports that module, so the published surface does not move): an empty answer for a packageId that no pending draft (of any type) is bound to and that the installed-package listing does not carry is refused 400 INVALID_REQUEST, same code and sentence family as the sweep's is not an installed package. Fails open when the listing cannot be read. Living in listDrafts, it covers both transports.
  • DraftsHost gains getMetaItems (the class hands it over as the other moved bodies do); the method docblock names the refusal.
  • No new error code, no new gate, no packages/spec edit.

Tests

New files only; no existing test was edited (no forced test edits).

  • packages/metadata-protocol/src/protocol-drafts-unknown-package.test.ts (5): refusal (status + code + first clause naming the package, with and without type); controls: installed package with nothing pending (no type, dashboard, view) → {drafts: []}; uninstalled package with a bound draft is served and, under another type filter, answers empty; listing unreadable or shapeless → fail-open empty; no package named → listing never asked.
  • packages/rest/src/meta-drafts-unknown-filter.test.ts (9): four misspellings → 400 equal to GET /meta/:type's body for the same name, listDrafts never called; controls dashboard/dashboards/view → 200 empty with the arguments listDrafts received; known package → 200 empty; the package refusal leaves the route a 400 INVALID_REQUEST.
  • packages/runtime/src/domains/meta-drafts-unknown-type.test.ts (7): the dispatcher twin, same pins against its own list branch.

Acceptance notes

  • ?package= (the card's wording in one place) is not a _drafts parameter; the route reads packageId and the console sends packageId. Unknown parameter names on _drafts are not refused today (no closed set declared); out of scope here.
  • Console consumer reading (ui/packages/app-shell, 20 listDrafts call sites): every call that sends type pairs it with client.list(type) on GET /meta/:type, which already refuses the same names with the same verdict, so the new check refuses nothing the console's paired read does not already refuse. The literal types sent (view, hook, object, app, permission) all pass the static contract. packageId comes from the Studio package switcher, which lists /api/v1/packages (installed packages); most call sites also .catch the drafts read.

Verification

Ablation + premise (base code, head tests)

The five src files restored to base with git restore --source=BASE (tree only, grep -c 29180 = 0 on each), the three new test files run, then git checkout HEAD -- restore; git diff HEAD empty, git status --porcelain empty, every path's git hash-object equal to its HEAD blob.

  • metadata-protocol: × refuses an unknown package with nothing bound to it — AssertionError: type=undefined: expected undefined to be an instance of Error; 1 failed | 4 passed (5).
  • rest: × ?type=dashbord|dashboardes|viewes|nosuchtype … — AssertionError: expected 200 to be 400; 4 failed | 5 passed (9).
  • runtime: same four — expected 200 to be 400; 4 failed | 3 passed (7).

Every refusal pin red on base, every control green. Tests import their package's src; no dist resolution in these paths.

Suites, typecheck, lint (head 5ac557e)
  • pnpm --filter @objectstack/{metadata-protocol,rest,runtime} build + typecheck: VERDICT command-exit 0.
  • @objectstack/metadata-protocol test: 301 passed | 4 skipped files; 22105 passed | 26 skipped tests.
  • @objectstack/rest test: 368 files passed; 6972 passed | 332 skipped tests.
  • @objectstack/runtime test: 515 files passed; 8325 passed | 19 skipped tests.
  • ui consumers (cd ui && vitest run packageSurfaces, ObjectHooksPanel.firstSavePinned-11773, InterfacesPillar.customize-28562, ObjectListViewInspector.chartBinding-27933): 4 files, 41 tests passed.
  • eslint --no-inline-config over packages/metadata-protocol packages/rest packages/runtime/src: exit 0.
  • Dogfood (closure build + pnpm --filter @objectstack/dogfood test, no shard): VERDICT command-exit 0; Test Files 388 passed | 3 skipped (391); Tests 3208 passed | 28 skipped.
Derived gates (head 5ac557e)

node scripts/pm/dispatch-gates.mjs --commands derived 62 families; all 62 run, each exit 0 (check-plugin-teardown-shape --self-test first exited 3 on the shallow clone, green after fetching its pinned fixture; check:dual-build-cjs-loads first exited 3 before the closure build, green after; check:type-check-debt run under the lock: 0 entries above record). --ran reconciliation: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).

🤖 Generated with Claude Code

https://claude.ai/code/session_01ADHkmRSZi4SoegkVztRKBe


Generated by Claude Code

… of answering an empty list

GET /meta/_drafts?type=TYPO and ?packageId=UNKNOWN answered 200
{"drafts":[]}. The type is now judged by the list door's verdict
(refuseUnknownMetaListType) on both transports, and listDrafts judges
the package by the diagnostics sweep's packageIsInstalled rule.

Claude-Session: https://claude.ai/code/session_01ADHkmRSZi4SoegkVztRKBe
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/rest, @objectstack/runtime, touching 19 documentable anchor(s).

27 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 39951d2a26590ff93226d3c0cf4ecfdc4136031e.

⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 42 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 39951d2a26590ff93226d3c0cf4ecfdc4136031e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f7371cd62cf4fcba6769fea8dd05d108d701faa8 — the merge of head 5ac557e5ee88b430a8f9cbb3e47d24427c65ab77 into base 39951d2a26590ff93226d3c0cf4ecfdc4136031e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f7371cd62cf4fcba6769fea8dd05d108d701faa8 && git checkout f7371cd62cf4fcba6769fea8dd05d108d701faa8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 39951d2a26590ff93226d3c0cf4ecfdc4136031e 5ac557e5ee88b430a8f9cbb3e47d24427c65ab77 && git checkout -B drift-repro 39951d2a26590ff93226d3c0cf4ecfdc4136031e && git merge --no-ff 5ac557e5ee88b430a8f9cbb3e47d24427c65ab77

node scripts/docs-audit/affected-docs.mjs --json 39951d2a26590ff93226d3c0cf4ecfdc4136031e

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 39951d2a26590ff93226d3c0cf4ecfdc4136031e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

Contract-review-claim: session_01ADHkmQjPiXgNoSaEpqeGvY · head 5ac557e5ee88b430a8f9cbb3e47d24427c65ab77 · 2026-11-02T11:12Z

The lane seat runs this PR's review at CONTRACT_REVIEW_TIER (declared Clause-②: no (narrowing)), in an isolated reviewer.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5ac557e5ee88b430a8f9cbb3e47d24427c65ab77
Local-runs: none
Scope: packages/metadata-protocol/src/protocol-drafts.ts, packages/metadata-protocol/src/protocol-meta-diagnostics.ts, packages/metadata-protocol/src/protocol.ts, packages/rest/src/meta-collection-routes.ts, packages/runtime/src/domains/meta.ts, packages/metadata-protocol/src/protocol-drafts-unknown-package.test.ts, packages/rest/src/meta-drafts-unknown-filter.test.ts, packages/runtime/src/domains/meta-drafts-unknown-type.test.ts + packages/rest/src/meta-item-read-gate.ts (refuseUnknownMetaListType, metaTypeIsLive), packages/metadata-protocol/src/protocol.ts getMetaItems package branch (L10801) and its disabled-package filter (L10720-10750), packages/metadata-protocol/src/index.ts, packages/metadata-protocol/src/protocol-save-door-refusals.ts (L505, L724), packages/client/src/index.ts (listDrafts, L2199), ui/packages/app-shell/src/views/studio-design/packageSurfaces.ts and StudioDesignSurface.tsx (the 24 listDrafts call sites), packages/spec/src/api/protocol.zod.ts (ListDraftsResponseSchema docblock), scripts/check-adr-0087-registration.mjs (CATEGORIES)

Read on the head tree via git show 5ac557e5:PATH and git diff origin/main...5ac557e5; nothing checked out. Inputs: card #29180 body and its three comments (triage, claim, dev report), PR #29295 body, file list, comments (docs-drift, review claim), and the head's check-runs. The PR body's account was not the evidence; the tree was.

Merge state. git merge-tree --write-tree origin/main 5ac557e5 on today's origin/main (39951d2a, 44 commits past the base 104cd1a5) returns a tree with no conflict, so the dev's "protocol.ts changed elsewhere" note is a clean three-way merge; the net diff against origin/main is the nine files above (+325/-4).

Check-runs on the head. 48 check-runs, all completed: 35 success, 13 skipped, 0 failure, 0 cancelled (final tally read after the eight Test Core shards, Build Core, the Type Check and Lint families finished). Green among them: Check Changeset (carries the ADR-0087 disposition gate), Governed Surface Queue Guard, PR-body guards, Check PR Size, Lint · ESLint, Lint · repo & tooling gates, Type Check · workspace, Type Check · consumer gates, Type Check · source gates, Type Check · debt ledger, Test Core 1-8, Dogfood Regression Gate, Live E2E UI. The skipped runs are the UI build/test family and opt-in harnesses whose path filters this diff does not reach. These conclusions are the derived-gate verdicts; nothing was run locally.

① Derived judgments

  1. Accept set — GET /meta/_drafts?type=X (both transports). Before: any type string narrowed the repo query and answered 200 {"drafts":[]} when nothing matched. Now: refuseUnknownMetaListType(p, type) runs before listDrafts — static spelling contract (unrecognisedMetaTypeRefusal), then the live getMetaTypes set folded through canonicalMetaUrlType, fail-open when the listing is unreadable or the protocol lacks getMetaTypes. A name the list door refuses is refused here with the identical thrown error, shaped by each transport's own catch (handleRouteError / errorFromThrown). Read in meta-item-read-gate.ts L2676-2690 and L2706-2726. Narrowing; right — it is the card's acceptance ("as GET /meta/dashbord does") and no second copy of the vocabulary exists: the same function GET /meta/:type (rest L664, runtime L2221) and GET /meta/diagnostics?type= (rest L375) call. Plural spellings pass (dashboards → canonicalMetaUrlType fold), pinned by both transport tests.
  2. Accept set — listDrafts({ packageId }) in @objectstack/metadata-protocol. Before: an unmatched packageId answered empty. Now: when the filtered result is empty, packageIsInstalled(this, packageId) is asked — the identical rule the diagnostics sweep applies to its package= filter (protocol-meta-diagnostics.ts L261-272), with a draft bound to the package (any type) as proof enough, read BEFORE the disabled-package filter so an uninstalled or disabled package with real draft rows is served, and fail-open on an unreadable or shapeless listing. Narrowing; right. Two break attempts failed: (a) a disabled package with nothing pending — getMetaItems deliberately never filters package records ("the Packages page must list disabled packages", protocol.ts L10726), so it is listed and keeps its 200 {"drafts":[]}; (b) a Studio-created scope-less base — registered through POST /api/v1/packages, so it is among the type: 'package' records the listing returns. The type-filtered empty path costs one extra type-less repo.listDrafts({ packageId }) read, only on the empty answer; acceptable.
  3. Public surface — @objectstack/metadata-protocol. packageIsInstalled gains export, and DraftsHost gains getMetaItems; index.ts re-exports neither protocol-meta-diagnostics.ts nor protocol-drafts.ts (verified: no export * and no named export from either), so the published surface does not move. ObjectStackProtocolImplementation.listDrafts's declared signature is unchanged; only its docblock grows. Right; Metadata-protocol: none holds — packages/spec is untouched and no authorable key, value or type moves.
  4. Protocol-internal callers. protocol-save-door-refusals.ts L505 and L724 call this.listDrafts({ type: 'datasource' | 'object' }) with no packageId, so the package rule never reaches them; the type rule lives in the transports and they bypass it with known types. Unaffected; right.
  5. Consumers. packages/client listDrafts forwards packageId/type and unwraps the envelope — a 400 now rejects where it resolved []. In ui/, 24 call sites: every one pairs packageId from the Studio package switcher (installed packages) with a type in {view,hook,object,app,permission,metaType} and, except StudioDesignSurface.tsx L4855 (inside a try), carries .catch(() => []); each paired client.list(type, { packageId }) already refuses the same type names. No consumer refusal the paired read did not already make; the dev's reading holds.
  6. Not changed. Unknown query-parameter NAMES on _drafts (e.g. ?package=) stay ignored — the dev names it out of scope and the card's own ?package= wording is a slip (the route reads packageId). Correct scoping.

② Semver level

.changeset/29180-drafts-unknown-filter.md: patch on @objectstack/metadata-protocol, @objectstack/rest, @objectstack/runtime — the three packages whose behaviour the diff moves, and no others. Line-initial Clause-②: no (narrowing) and Metadata-protocol: none in both the changeset and the PR body. Per the Post-Task Checklist level rule a none kind takes patch, and a non-protocol breaking change ships patch with its BREAKING banner and ADR-0087 disposition — present: the BREAKING banner, adr-0087: not-required (no-migration-prescription) (a CATEGORIES member, L147 of the gate), and the migration ("The remedy": fix the misspelled value; GET /api/v1/meta/types, GET /api/v1/packages). Check Changeset (which runs check-adr-0087-registration.mjs --base) is green on the head. Level and declarations match what the diff publishes. Clause-②: no (narrowing) — confirmed by reading: nothing widens; two previously-200 request shapes become 400 on both transports.

③ Boundary flags

  • Dev open_questions[0] — runtime dispatcher twin edited beyond the claim's file-surface wording. Answered: A, keep — right. The rest route's own [#23214] comment at meta-collection-routes.ts L465-466 ("the runtime dispatcher's twin refuses the same names") and the gate's "Both transports" docblock (meta-item-read-gate.ts L2661-2667) bind the _drafts pair; the type verdict lives in @objectstack/rest, so it cannot be placed in listDrafts without a second copy, which the claim forbids. Dropping the hunk would leave a dispatcher-only host at 200 {"drafts":[]} for the typo — the defect, half-fixed. The claim's "and/or the _drafts route in packages/rest" named the door; the twin IS that door on the other transport. Deviation accepted.
  • Dev open_questions[1] — PR assignee/reviewer not set (order forbids MCP writes). Outside the contract; no bearing on the verdict. Escalated to the seat: set via relay if wanted, otherwise leave unset.
  • Dev deviation — ui consumer tests run as a 4-file subset. Accepted: those tests mock the client, so the server change cannot reach them; the consumer reading in ①.5 is the evidence that matters.
  • Skills-docs: none. Verified: git grep _drafts over content/docs/**, skills/**, .claude/skills/** finds only the maintenance-mount row (references/api/rest-server.mdx L228) and the 403 capability citation (ui/apps.mdx L455, L461); no page documents the type/packageId filters. The docs-drift comment is advisory and lists pages by package mention. Holds.
  • Card premise. Holds against the base tree: meta-collection-routes.ts passed req.query.type/packageId straight into listDrafts after the arity check alone; the dev's base-tree ablation (refusal pins red, controls green) is consistent with that reading.
  • No packages/spec edit, no new error code, no new gate — the claim's three stops are honoured.

④ Design judgment

  • packages/metadata-protocol/src/protocol-drafts.ts — holds. Order of operations is right: the repo read, then the package verdict on the UNFILTERED rows (so a disabled package's drafts count as bound, matching the docblock "a disabled package's drafts still publish and discard"), then the disabled-package projection. The bound re-read is only taken when a type narrowed the first read, and skipped when it could not change the answer. The refusal reuses the sweep's status, code and sentence family. Should-fix (doc): the module header still says "A MOVE, not a redesign. Each function below is the body ... as it stood, re-indented one level and otherwise unedited" and accounts for exactly two imports from protocol.ts; listDrafts now carries a [#29180] hunk and a third import from protocol-meta-diagnostics.ts. One sentence noting the 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 edit keeps the header true for the next reader. Not blocking.
  • packages/metadata-protocol/src/protocol-meta-diagnostics.ts — holds. export only; the body is unchanged, the host Pick (getMetaItems) is the one member DraftsHost adds, typed as the same indexed access on the class, so the call type-checks without a cast.
  • packages/metadata-protocol/src/protocol.ts — holds. The host literal adds getMetaItems: (...a) => this.getMetaItems(...a) in the closure shape the module header prescribes (a spy set on the instance is still reached); the docblock names the refusal and says the type half is the transports', which is true.
  • packages/rest/src/meta-collection-routes.ts — holds. The call sits after the authoring gate, the 501 probe and the arity refusal, inside the try, exactly where the diagnostics door (L375) asks the same question; || undefined keeps type= (empty) as "no filter" as before; draftsType is the one value both the verdict and listDrafts see.
  • packages/runtime/src/domains/meta.ts — holds; the twin mirrors the rest placement (after capability gate, protocol probe, arity), inside the try that errorFromThrown shapes, and the test pins the drafts body equal to the list branch's.
  • Tests — protocol-drafts-unknown-package.test.ts pins both halves of the rule including the fail-open shapes and the "listing never asked" controls; the two transport tests pin equality with the sibling list door's body for four misspellings, listDrafts never called, and the controls in both spellings. meta-drafts-unknown-filter.test.ts also pins that a thrown package refusal leaves the rest route at 400. Each is a new file; no existing pin was edited.
  • Scenario the card accepts against — a typo'd type or packageId on _drafts is refused 400 with the sibling doors' sentence; a known type in either spelling and an installed package with nothing pending keep 200 {"drafts":[]}. Met on both transports.

Must-fix: none. Should-fix: one (stale module header in protocol-drafts.ts).

Implemented-by: claude/project-thread-igicmx
Reviewed-by: session_01ADHkmQjPiXgNoSaEpqeGvY

VERDICT: PASS

Stamped 2026-11-02T11:33Z.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

席位核对 · ACCEPT · domain:server seat 1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY · 2026-11-02T11:35Z

Head 5ac557e5ee, report 6575684316 (on #29180), claim 6573676255. Clause-②: no (narrowing) → contract review owed at CONTRACT_REVIEW_TIER: record 6576111072 on this head, PASS, no must-fix.

item reading
Shape Draft, base main, Fixes #29180. 9 files: GET /api/v1/meta/_drafts refuses an unknown type with the list door's refuseUnknownMetaListType on both transports (rest/meta-collection-routes.ts, runtime/domains/meta.ts); listDrafts refuses an unknown packageId with the #27514 packageIsInstalled rule (protocol-drafts.ts, protocol-meta-diagnostics.ts export, protocol.ts). Three tests, a changeset (patch ×3, BREAKING banner, ADR-0087 not-required (no-migration-prescription)). Metadata-protocol: none. No packages/spec/**, no new code (INVALID_REQUEST). Not governed.
Rule An unknown type or an uninstalled package answers 400 instead of 200 {"drafts":[]}. A disabled package and a draft bound to an installed package keep 200. The rule fails open where the list door fails open.
Accepted deviation The runtime dispatcher twin was edited so both transports stay equal (the review judges it right: the type verdict lives in @objectstack/rest, and leaving the dispatcher out would half-fix dispatcher-only hosts).
Evidence Base: 200 {drafts:[]}; head: 400. Ablation red then restored. metadata-protocol 301 files, rest 368, runtime 515, ui consumers 41, dogfood VERDICT 0, gates 62/62. CI on this head: 48 runs, 35 green, 13 skipped, none red; check-expected-skips OK; merge-tree against today's main clean (review ①).
Should-fix, not filed The protocol-drafts.ts module header still calls the file "a move … otherwise unedited" with two imports. It now carries a #29180 hunk and a third import. That's internal comment drift with no user-visible cost, so it isn't a card under the filing gate. The next PR that edits this file should correct the header sentence.
Open question PR assignee/reviewer unset: not needed, the seat lands it.
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay.

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant