Skip to content

feat(rest)!: serve ?names= on the /meta/:type list and close its query set - #30709

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ibfnsp
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ibfnsp

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

domain:server#3 · os-elon-musk · session_016WV4cyaiN5dg5cw1zSyihP

Fixes #29236

Clause-②: yes (narrowing)
Metadata-protocol: none

维护者速读

  • 改了什么:/meta/:type 列表接口新增 ?names=a,b,c,一次请求按名字读多个元数据项(package 列表除外,它的条目按 id 而非名字区分,带 ?names= 会返回 400);同时列表只认七个查询参数,拼错或不认识的参数改为返回 400,不再静默返回整张表。
  • 为什么改:裁决 A(6580261740)的服务端一半。控制台 Home 的 roster 门现在每个对象发一次请求(app-crm 上 8 次),有了批量读就能并成一次;拼错的过滤参数以前看起来像“匹配了全部”,现在会被明确拒绝。
  • 风险与回滚:收窄是唯一的破坏性变化:带未声明参数的脚本会收到 400(控制台和 client SDK 只发已声明参数)。名字过滤在权限门之后、对象字段掩码之前执行,不改变谁能看到什么。回滚即 revert。
  • 先看:packages/rest/src/meta-item-read-gate.ts 的 META_LIST_QUERY_PARAMS、metaListNames、列表链第 2a 步;packages/runtime/src/domains/meta.ts 的分发器对等实现。
  • 两分钟验证:pnpm --filter @objectstack/rest exec vitest run src/meta-list-names-filter.test.ts src/meta-query-arity-doors.test.ts,预期全部通过。
  • 需要你做的:在验收时确认 locale 加入封闭集合(裁决原文是六个键,见下方偏差 1);其余无,席位验收、契约复核通过、CI 全绿后落地。

What changes

@objectstack/rest, the list door GET /api/v1/meta/:type (and its environment-scoped twin).

  • ?names=a,b,c narrows every type's list to the items of those names, except package's: a package list item is keyed by its id and carries no name, so the package list refuses ?names= with a located 400 VALIDATION_ERROR (NAMES_ON_PACKAGE_LIST_MESSAGE) on both transports, before any read. One comma-separated value, as the data routes spell fields; each name is trimmed, blanks are dropped and repeats count once (metaListNames). A name that matches no item is simply absent (no 404). A value that names no item (?names=, ?names=,) is refused 400 VALIDATION_ERROR with param: 'names', before the store read. A repeated ?names= is refused like the list's other single-valued keys (META_QUERY_ARITY_DOORS.list gains names).
  • The narrowing is chain step 2a of createMetaListAnswer: after the per-caller gate (a named item the caller may not see stays withheld) and before the object field mask and translation, which now run on the named items only.
  • The names are pushed down to the store read as GetMetaItemsRequest.names. The chain step still narrows, so a store that does not filter answers the same list.
  • The list declares its closed query set, META_LIST_QUERY_PARAMS = package, preview, object, include, id, names, locale, judged by the same unknownQueryParamRefusalMessage that refuseUnknownQueryParams uses, before the arity check and before any read (metaListUnknownParamRefusal / metaListQueryRefusal). Bracketed keys that already have a located refusal keep it: package[ everywhere, id[ on app, object[ on view. Any other bracketed key (?names[]=) is refused as a name the list does not read.
  • Three new exports, the decision both transports ask: META_LIST_QUERY_PARAMS, metaListQueryRefusal, metaListNames.

@objectstack/runtime, the dispatcher twin (packages/runtime/src/domains/meta.ts): the same recognition and arity refusal, the same empty-value 400, and the same push-down to protocol.getMetaItems, all asking the rest exports. The parity census (meta-list-projection-parity.test.ts) derives the read keys from both sources and now also pins that META_LIST_QUERY_PARAMS equals what the handlers actually read.

@objectstack/metadata-protocol: getMetaItems / readFlattenedMetaItems take names?: readonly string[] and keep only those items once every source is merged, before the app nav contributions, locks, governance and serving decorations run.

@objectstack/client: both meta.getItems methods (unscoped and environment-scoped) take { names } and send it as one ?names=a,b value.

@objectstack/spec, description only: GetMetaItemsRequestSchema.names drops its [PLANNED — not yet served] prefix and its trailing "no list door reads it yet" sentence; the marker test is deleted. content/docs/references/api/protocol.mdx is regenerated (gen:docs).

Docs: one paragraph in content/docs/protocol/kernel/http-protocol.mdx › List All Objects on ?names= and the closed set.

BREAKING accept-set narrowing at the metadata list (@objectstack/rest, @objectstack/runtime). Before: GET /meta/object?name=account, ?ids=a,b or ?filter=x answered 200 with every object. Now: a name outside the seven is refused 400 with the nested { "error": { "code": "VALIDATION_ERROR", "message" } } body naming the parameter and the supported ones, and nothing is read. Who moves: a script that sends an undeclared name; the Console and the client SDK send only declared names. Remedy: drop the parameter, or use ?names=a,b, ?id=APP, ?object=OBJECT or ?package=ID. The changeset carries the same section and an adr-0087: not-required (no-migration-prescription) disposition: no schema key moves, and nothing at rest records the route's answer.

Why

Ruling A on #29236 (6580261740): one spelling, ?names=a,b,c, on every /meta/:type list, narrowing after the per-caller gate and before the object mask, pushed down as GetMetaItemsRequest.names, with the list's closed query set declared in the same server PR. The spec half (#29441, PR #29722) put names on GetMetaItemsRequestSchema with the planned marker; this PR serves it and retires the marker. Measured on main before this PR (6576480506): ?names=, ?name=, ?ids=, ?filter= on /meta/object each answered the full 115-item, 746 KB list with 200.

The Console follow-up, folding the Home roster gate's by-name reads into one ?names= request, is its own card, #29444 (domain:ui). This PR does not touch ui/**.

Deviations (named, accepted by the seat)

  1. locale joins the closed set. The ruling's line reads: "The same server PR declares the list door's closed set of query parameters (package, preview, object, include, id, names)". The list door already reads ?locale= (extractLocale in the translation step, a frame below the handler), so a six-key set would refuse ?locale=zh-CN, which answers 200 today. Keeping it is keeping behaviour, not widening. Pinned: ?locale=zh-CN still answers 200 on both transports, and the census derives locale from the source. The seat puts this to the maintainer at ACCEPT as a veto point.
  2. Patch levels, not minor. The ruling sketched rest / metadata-protocol / client minor; check-changeset-no-major requires every entry to be patch when the PR declares Metadata-protocol: none, which this is (an API key, not authorable metadata).
  3. The docs paragraph in http-protocol.mdx is outside the ruling's file list; the seat added it to the claim.
  4. Two existing controls flipped. In meta-app-publish-gate.test.ts, a bracketed id[] on the view list and object[] on the app list used to answer 200 (ignored); they are now recognition refusals (400), as the closed set requires.
  5. ?names= is refused on the package list (contract review 6672872746, seat ruling): package items carry manifest.id, no name, so a names filter there could only answer 200 []. It is a located 400 instead; matching packages by id is a different behaviour, left for its own card if someone needs it. The describe and http-protocol.mdx say "every type except package".

Verification

Main's behaviour is the card's measurement (6576480506) and the ablations below: with the recognition, the chain step and the push-down off, the new pins go red the way main answers (the whole list, 200, no names sent to the store).

  • packages/rest/src/meta-list-names-filter.test.ts (new): the reading of ?names=; the chain step on object (the mask and the translation see only the named items), an unknown name absent, an empty value rejected before the mask, app narrowed after the per-caller gate (a withheld app stays withheld); the door: push-down, a non-filtering store on object and view (and with ?object=), no names sent when absent, empty 400 with no store read, unknown name 200 with an empty list.
  • packages/rest/src/meta-query-arity-doors.test.ts: the closed set, recognition before arity, every declared key accepted, undeclared and bracketed keys refused, ?locale=zh-CN 200.
  • packages/metadata-protocol/src/protocol.meta-list-names.test.ts (new, 5): narrowing on object and app, nav contributions only for the named apps, unknown names absent, each named item served as the whole list serves it.
  • packages/runtime/src/domains/dispatcher-query-arity.test.ts and meta-list-projection-parity.test.ts: the dispatcher's recognition, push-down, empty refusal and locale, and REST/dispatcher parity on five new probes (names narrowing, names=nope, empty names, names[], ?name=crm).
  • packages/client/src/client.test.ts: the ?names= URL, unscoped and environment-scoped.
  • The package refusal: meta-query-arity-doors.test.ts (the sentence, every value spelling, recognition outranking it, controls on four other types, the door answering the nested 400 with no protocol read) and dispatcher-query-arity.test.ts (the dispatcher's twin). Ablated (the check off): 2 red in rest, 1 red in runtime.
  • Ablations, each red: rest recognition off (12), locale dropped from the set (3), chain step off (5), chain step moved after the mask (3), rest empty refusal off (5), rest push-down off (1); dispatcher recognition off (census red), dispatcher push-down off, dispatcher empty refusal off, protocol filter off (4), client names off. All green at head.
  • Gates (dispatch-gates --commands), first measured at c2b596eb before the main merge: 106 derived families plus the extras and pnpm lint, 112 run, 109 exit 0. @objectstack/spec check:generated failed only because check:test-typecheck ran out of heap; with an 8 GB heap it is green at b0872d29 (all 14 artifacts current). check:skill-examples and check:dual-build-cjs-loads exited 3 (NOT MEASURED: they need a whole-tree build). The full re-run at the merged head, after a whole-tree build, goes in the os-dev report on the card.

Skills-docs: follow-up #30710

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cyaiN5dg5cw1zSyihP

…y set

The metadata list read no names filter, so a client that needed several
items of one type paid one by-name request per item (the Console Home
roster gate: 8 on app-crm). Ruling A on #29236:

- `?names=a,b,c` on every `/meta/:type` list, one comma-separated value
  (`metaListNames`): an empty value is refused 400 VALIDATION_ERROR, an
  unknown name is simply absent. The chain narrows after the per-caller
  gate and before the object mask and the translation; both transports
  push the names down to the store read (`GetMetaItemsRequest.names`), and
  `readFlattenedMetaItems` keeps the named items once every source is
  merged, before the lock, governance and decoration work.
- The list declares its closed query set (`META_LIST_QUERY_PARAMS`:
  package, preview, object, include, id, names, and locale, which the
  list already reads), asked before arity by both transports
  (`metaListQueryRefusal`), so a misspelt filter is a located 400 instead
  of the whole list with 200.
- `@objectstack/client` `meta.getItems(type, { names })`.
- The spec describe of `GetMetaItemsRequestSchema.names` drops its
  planned marker, with its pin; the api reference is regenerated.

Fixes #29236

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyaiN5dg5cw1zSyihP
…ngeset names both halves

Refs #29236

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyaiN5dg5cw1zSyihP
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/client, @objectstack/metadata-protocol, @objectstack/rest, @objectstack/runtime, @objectstack/spec, touching 50 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/rest/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

38 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 447226b222467709ed74ca99eaf808c178656b33.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/rest/src/index.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: /meta/:type (route, 40 pages)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 181 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 447226b222467709ed74ca99eaf808c178656b33 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f177a461b7327709169153c9f694cc019e136979 — the merge of head 621212ae86bbac8cb5f6a5a434b8a9c9e2dae120 into base 447226b222467709ed74ca99eaf808c178656b33, 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 f177a461b7327709169153c9f694cc019e136979 && git checkout f177a461b7327709169153c9f694cc019e136979
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 447226b222467709ed74ca99eaf808c178656b33 621212ae86bbac8cb5f6a5a434b8a9c9e2dae120 && git checkout -B drift-repro 447226b222467709ed74ca99eaf808c178656b33 && git merge --no-ff 621212ae86bbac8cb5f6a5a434b8a9c9e2dae120

node scripts/docs-audit/affected-docs.mjs --json 447226b222467709ed74ca99eaf808c178656b33

⚠️ 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 447226b222467709ed74ca99eaf808c178656b33 → 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#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5f55d49c574d697676d4c93a0a95fbc72d17675f
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts + packages/spec/src/api/protocol.test.ts (the describe and the deleted marker pin), packages/rest/src/meta-item-read-gate.ts (META_LIST_QUERY_PARAMS, metaListQueryRefusal, metaListUnknownParamRefusal, metaListNames, chain step 2a, META_QUERY_ARITY_DOORS.list), packages/rest/src/meta-collection-routes.ts (the list door), packages/rest/src/index.ts (three new exports), packages/runtime/src/domains/meta.ts (the dispatcher twin), packages/metadata-protocol/src/protocol.ts + protocol-meta-list-reads.ts (the push-down), packages/client/src/index.ts (meta.getItems({ names }), both surfaces), the six test files beside them, .changeset/29236-meta-list-names-filter.md, content/docs/protocol/kernel/http-protocol.mdx, content/docs/references/api/protocol.mdx (generated); read for the judgments, unchanged on main: packages/rest/src/query-allowlist.ts (unknownQueryParamRefusalMessage, the ingress policy), packages/rest/src/rest-server.ts (extractLocale), metaRequestLocale and META_ITEM_READ_PARAMS in the gate file, scripts/pm/clause2-line.mjs (readArmToken), scripts/check-changeset-no-major.mjs (the level axis), scripts/check-adr-0087-registration.mjs (CATEGORIES), packages/objectql/src/registry.ts (the package item shape).

Inputs read: card #29236 body and all 19 comments (triage 6575623583; the measurement 6576480506; the decision 6579872432; the ruling 6580261740, letter A, quoted here as written; seat 5's execution 6580443235 and release 6591626191; the spec seat's inherited items 6594515725 and the blocker landing 6595098031; seat 3's claim 6670429730 with amendments 6670501210, 6671057079 and the re-posted claim 6671541845; os-dev report 6672708990); PR #30709 title, body, 19-file list and net diff vs main (merge-base 4bf9a2d7, 3 ahead; origin/main is f2ea1931, 20 commits past the merge-base at read time, mergeability unknown); the docs-drift comment 6671701513; follow-up cards #30710 and #29444; the seat's dispatch order; the 65 check-runs on this head; the code cited above on main at the merge-base.

① Derived judgments

  • The ruling's seven binding sentences, each against the code. (a) ?names=a,b,c on every /meta/:type list as one comma-separated value: metaListNames reads query.names as one string, splits on ,, trims, drops blanks, folds repeats (the data routes' own fields / searchFields shape, data-action-routes.ts), and both transports call it; TRUE. (b) The narrowing runs after the per-caller gate and before the object mask and translation: step 2a of createMetaListAnswer sits between the gate (step 1) and the app ?id= step 3, ahead of the mask and translateMetaList; the pin records the masker being asked for the named items only, and a withheld app named in the filter stays withheld; TRUE. (c) An empty value is refused 400 like ?id=: metaListNames throws status 400 / VALIDATION_ERROR / param names on '', ',' and ' , ', caught by handleRouteError on REST and errorFromThrown(e, 400) on the dispatcher, before any store read (pinned: getMetaItems never called); TRUE. (d) An unknown name is simply absent, no 404: a Set filter on both the chain and the store read, 200 [] when none match; TRUE. (e) Pushed down to the store read as GetMetaItemsRequest.names: readFlattenedMetaItems filters once every source is merged and unseated rows are dropped, before the env-wide join, the package writable decoration, lock selection, governance and the artifact envelope; the "served exactly as the whole list serves it" pin holds equality; TRUE. (f) The closed set with the refuseUnknownQueryParams pattern: metaListUnknownParamRefusal asks unknownQueryParamRefusalMessage (the same sentence refuseUnknownQueryParams writes) before refuseRepeatedQueryParams, on both transports, so recognition precedes arity as AGENTS.md's rule 5 orders; TRUE. (g) ?id= keeps its one-app meaning: untouched; TRUE.
  • The closed set is measured, not copied. I re-measured the list path's read points on main at the merge-base: the handler reads package (metaItemPackageBinding), preview, and hands req.query to the chain, which reads id (app), object (view), include (doc content, position grants), and locale through requestLocale → metaRequestLocale, which reads http.query.locale after Accept-Language for every translated list answer and the doc locale collapse. No middleware reads the query on this path. So the set is exactly package, preview, object, include, id, names, locale, and the parity census now derives the read set from meta-collection-routes.ts plus createMetaListAnswer / metaRequestLocale / translateMetaList / metaListNames and pins META_LIST_QUERY_PARAMS equal to it, so a seventh-plus read cannot land unlisted.
  • The locale deviation is sound, and under AGENTS.md it is required. Route & surface ownership rule 5 names this exact case: "a parameter such as the export route's ?locale= is read a frame down (extractLocale), never in the handler body", and orders the set measured from the read points, "never from the docs or the card". The item read's META_ITEM_READ_PARAMS already carries locale for the same reason. A six-member set would turn every ?locale=zh-CN list read, which answers 200 today, into a 400, which is the "loud outage" the rule warns against. The ruling's six is a list of the filters; it neither names nor denies locale. The PR pins ?locale=zh-CN 200 on both transports and the ablation "locale dropped from the set" goes red (3). Presenting it to the maintainer as a veto point in the PR's 速读 is the right form.
  • Bracketed spellings. package[ on every type, id[ on app and object[ on view keep the located sentences the arity step and the chain already answer; every other bracketed key (names[], id[] on a non-app type, object[] on a non-view type) is an undeclared name and is refused by recognition. The two meta-app-publish-gate.test.ts controls that flipped 200 → 400 are exactly these; the flip is the closed set working, not a regression.
  • Consumer census (mine, on main at the merge-base). Producers of /meta/:type list query strings: ui/** sends only client.meta.getItems(type) / (type, { packageId }), GET /meta/view?object=, GET /meta/object?package=…&preview=draft, GET /meta/position?include=grants (the _drafts and diagnostics doors are other routes); skills/**, content/docs/** outside references/ and releases/, examples/**, packages/{qa,dogfood,cli,mcp,verify,client} send no key outside the seven. No real caller starts getting 400; the dispatch order's fork condition is not met. Test UI 16/16 and the dogfood battery are green at this head.
  • packages/spec/src/** exports and .describe() text. No export is added, removed or renamed in the spec; check:api-surface has nothing to move and the file list carries no api-surface/ or authorable-surface/ change. The one .describe() that changes, GetMetaItemsRequestSchema.names, now states: "only the named items of the type are answered, among those the list would answer without it" (TRUE, (b) and (e)); "Sent over HTTP as one comma-separated value (?names=a,b,c)" (TRUE, (a); the client joins with ,); "A name that matches no item is simply absent from the answer (no 404)" (TRUE, (d)); "An empty value is refused with 400 rather than read as every item; leave the key out to list every item" (TRUE, (c); absent names sends no filter and the store receives no names member, pinned). The .min(1) on the array stays and no transport can produce an empty array (the empty string is refused ahead of it). content/docs/references/api/protocol.mdx carries the regenerated row and nothing else in the generated tree names the marker for names. Declaration equals enforcement; retiring the [PLANNED — not yet served] prefix, the trailing sentence and the marker it(...) is exactly what 6594515725 asked.
  • Three new @objectstack/rest exports (META_LIST_QUERY_PARAMS, metaListQueryRefusal, metaListNames): the widening half, needed by the dispatcher twin; declared in the PR body and the changeset. unknownQueryParamRefusalMessage was already exported on main, so the runtime test's import resolves.
  • Dispatcher parity. The twin asks metaListQueryRefusal(listType, query) before any read, refuses the unknown type, reads names once (empty → 400), pushes it to protocol.getMetaItems, and the metadata-service and registry exits that take no names are still narrowed by the chain inside answerMetaList. The census's five new probes and the META_LIST_QUERY_PARAMS == derived.params pin cover both transports.
  • package list items carry no name (InstalledPackageSchema, keyed by manifest.id / id), so ?names= on /meta/package always answers 200 []. Deviation 5 notes it and changes nothing; see should-fix below.

② Semver level

Changeset .changeset/29236-meta-list-names-filter.md: @objectstack/rest, @objectstack/runtime, @objectstack/metadata-protocol, @objectstack/client, @objectstack/spec, each patch; body opens feat(rest)!: …, carries Clause-②: yes (narrowing + widening) — … and Metadata-protocol: none, a **BREAKING** section with Before / Now / Who moves / Remedy, and one <!-- adr-0087: not-required (no-migration-prescription) … --> marker, a member of CATEGORIES. The PR body carries the same two lines, and the title carries !.

  • Level. Metadata-protocol: none is right: names is an API query key, not a key a metadata author writes; the spec change is description text on an already-declared member (added by feat(spec): GetMetaItemsRequestSchema declares names, the batch by-names read of a /meta/:type list #29722 as added), and no authorable-surface/ or authorable-defaults/ shard moves. AGENTS.md Post-Task Checklist step 3: the level follows the kind alone, none takes patch, and "a non-protocol change, widening or breaking, ships patch and keeps its BREAKING banner and ADR-0087 disposition". check-changeset-no-major enforces the same ("a declared none must grade every entry it introduces patch"), so deviation 2 (patch, not the ruling's sketched minor) is the rule and the gate, not a choice; the ruling's own Metadata-protocol: none already implies it.
  • The narrowing is declared and pinned. The arm: readArmToken takes the first token inside the parenthetical, so yes (narrowing + widening) reads as value yes, arm narrowing, the documented "widens one surface and narrows another" shape; check-adr-0087-registration therefore asks for the banner and the disposition, and both are present. Title !, BREAKING section with migration (drop the key, or spell it as one of the seven), docs paragraph naming the closed set, and the refusal pinned on both transports with the store never called. Complete.
  • Check-runs on 5f55d49c: 65 runs read, every one completed; 57 success, 8 skipped (Lint UI, UI Published Artifacts, Dogfood Verify CLI, the opt-in tarball and 18.x harnesses, the live-PG temporal conformance, the sharded dogfood matrix label, the schedule gate), none in_progress, none failed. Check Changeset, Lint & Repo Gates, TypeScript Type Check, Test Core (1–8/8), Build Core, Governed Surface Queue Guard, Type Check UI, Test UI (1–16/16), Build & E2E UI, Live E2E UI, Dogfood Regression Gate, Spec property liveness, Build Docs all success.

③ Boundary flags

  • open_questions: none. Deviations 1–5 judged: 1 (locale) sound and required, see ①; 2 (patch) sound, see ②; 3 (the http-protocol.mdx paragraph) the seat added it to the claim 6671057079 before the PR, in scope; 4 (two controls flipped) correct, see ①; 5 (package list) honest but unfiled, should-fix below.
  • Skills-docs: the PR body still reads follow-up owed — skills/objectstack-api/SKILL.md; the report names skills/objectstack-api: teach the /meta/:type list door's ?names= filter and its closed query set (follow-up of #29236) #30710. The seat rewrites the body line to Skills-docs: follow-up #30710 before ACCEPT (pm-dispatch claim template). The deferral itself is honest: the skill today names only declared keys (?preview=draft, ?package=, ?include=content), so no skill reader is broken by this PR; it merely does not yet teach ?names=. skills/objectstack-api: teach the /meta/:type list door's ?names= filter and its closed query set (follow-up of #29236) #30710 is Tier H (skills/**), p3, serial after this PR.
  • console(home): fold the roster gate's by-name object reads into one ?names= request (#29236 ruling A follow-up) #29444 (ui follow-up) is the consumer's adoption of the new key, Blocked-by this card; nothing a consumer needs now is deferred into it: the client SDK ships { names } in this PR.
  • The dev's Local-runs-class statements (106 gate families, ablations, dogfood) are not relied on here; the check-runs and the diff are.
  • Author is objectstack-fleet[bot]; no out-of-relay login. The PR is a draft. origin/main moved 20 commits past the merge-base at read time; the landing merges main and rebuilds (Multi-agent discipline §10), no contract effect.
  • Independence: rendered by an isolated contract-review subagent fed the card, the PR, the diff, the cited code on main, the gates' sources and the check-runs, not the seat's or the dev's conclusions; adopted by the seat named in Reviewed-by:.

④ Design judgment

  • protocol.zod.ts — the describe states only what both transports now do; the first consumer (the Console roster gate, console(home): fold the roster gate's by-name object reads into one ?names= request (#29236 ruling A follow-up) #29444) reads exactly the sentences a client needs (one value, absent unknowns, empty refused). Holds.
  • meta-item-read-gate.ts — one decision (metaListQueryRefusal) for both transports, recognition before arity, the three bracketed exceptions enumerated by the type whose step reads them, the chain step placed where the ruling put it, and the census pinning the set to the source. The design is the one AGENTS.md rule 5 prescribes and the sibling item read already uses. Holds.
  • protocol-meta-list-reads.ts — the filter sits after the merge and before every per-item decoration, so the saving the ruling wanted (filter before decorating) is real and the per-item verdicts are unchanged. Holds.
  • client/src/index.ts — names !== undefined sends ?names= for an empty array, which the server refuses; the docblock says so. Deliberately not dropped into "every item". Holds.
  • The narrowing is a breaking change the maintainer ordered; its remedy is one of the seven names; no alias or lenient fallback is added anywhere. Holds.

Findings (none must-fix):

  • should-fix — packages/spec/src/api/protocol.zod.ts, the TSDoc paragraph "Declared ahead of its producer. No list door reads names yet, so its describe opens [PLANNED — not yet served]; the server PR on Metadata API: no batch by-names read, so the Console roster gate costs one /meta/object request per name #29236 drops the marker in the change that serves the member (the pin is in protocol.test.ts)." is false on this head: this PR drops the marker and deletes that pin. Rewrite it to say the member is served, or delete the paragraph. Not in the published JSON Schema, so not must-fix, but it is the one false sentence left in the hit file.
  • should-fix — ?names= on GET /meta/package can never match (deviation 5): package items carry manifest.id / id, no name, so a caller gets 200 [], the silent-zero shape the closed set exists to remove, while the describe and the docs say "every type". Either refuse ?names= on the package list with a located 400, or match packages by their id, or the seat files a card before ACCEPT (Prime Directive 10: a declared≠enforced trap is an issue, not a note).
  • should-fix (seat, before ACCEPT) — rewrite the PR body's Skills-docs: follow-up owed — skills/objectstack-api/SKILL.md to Skills-docs: follow-up #30710.
  • nit — Clause-②: yes (narrowing + widening): the parser reads the arm narrowing and treats + widening as trailing reasoning, and yes already says widening; the canonical spelling is yes (narrowing).

Implemented-by: claude/project-thread-ibfnsp
Reviewed-by: session_016WV4cytQjG9X1CrCgytUM5

VERDICT: PASS

…by id; retire the stale TSDoc paragraph

Contract review 6672872746 should-fix items, per the seat's ruling: the
package list refuses ?names= with a located 400 on both transports (pinned,
ablated), the GetMetaItemsRequestSchema TSDoc says the member is served,
the describe and http-protocol.mdx name the package exception, and the
changeset spells Clause-② canonically.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyaiN5dg5cw1zSyihP
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 621212ae86bbac8cb5f6a5a434b8a9c9e2dae120
Local-runs: none
Scope: packages/rest/src/meta-item-read-gate.ts (metaListUnknownParamRefusal, NAMES_ON_PACKAGE_LIST_MESSAGE, MetaItemQueryRefusal.rule), packages/spec/src/api/protocol.zod.ts (the GetMetaItemsRequestSchema TSDoc and the names describe), packages/rest/src/meta-query-arity-doors.test.ts, packages/runtime/src/domains/dispatcher-query-arity.test.ts, .changeset/29236-meta-list-names-filter.md, content/docs/protocol/kernel/http-protocol.mdx, content/docs/references/api/protocol.mdx (generated); read for the judgments, unchanged by the delta: packages/rest/src/index.ts, packages/rest/src/meta-collection-routes.ts and packages/runtime/src/domains/meta.ts (the two call sites), packages/rest/src/rest-server.ts (metaTypeSingular), packages/spec/src/meta-spelling/metadata-url-spelling.ts + meta-url-data.generated.ts (unrecognisedMetaTypeRefusal, canonicalMetaUrlType), packages/metadata-protocol/src/protocol-meta-list-reads.ts (the package branch), the PR body on this head.

Delta review of record 6672872746 (PASS on 5f55d49c): exactly one commit, 621212ae "fix(rest): refuse ?names= on the package list, whose items are keyed by id; retire the stale TSDoc paragraph", parent 5f55d49c, 7 files, +74/-13 (net vs merge-base 4bf9a2d7: still 19 files, +850/-49). Nothing else moved: the three new @objectstack/rest exports, META_LIST_QUERY_PARAMS, the chain step, the push-down, the client and the census are byte-identical to the reviewed head, and that record's judgments stand. The commit folds in the three should-fix items and the nit of 6672872746 under the seat's ruling for a located 400 on the package list rather than matching by id.

① Derived judgments

  • The new refusal is honest. On main a package list item is InstalledPackageSchema (keyed by manifest.id / id, no name), so ?names= there could only answer 200 [] for every value. metaListUnknownParamRefusal now answers { status: 400, code: 'VALIDATION_ERROR', message: NAMES_ON_PACKAGE_LIST_MESSAGE, rule: 'names-on-package' } when metaType === 'package' and the query owns a names key, whatever its value ('crm', '', an array), pinned for all four spellings. It sits where the closed-set refusal sits, inside the one decision both transports ask: RestServer's door calls metaListUnknownParamRefusal(listType, req.query) and the dispatcher calls metaListQueryRefusal(listType, query), which delegates to it first, so the refusal runs on both transports before the arity check, before refuseUnknownMetaListType and before any protocol read (pinned on both: getMetaItems never called). Same code (VALIDATION_ERROR), same nested envelope (REST writes { error: { code, message } }, the dispatcher queryRefusalAnswer), same status.
  • Ordering versus the unknown-key refusal is right. Recognition still outranks it: { names: 'crm', zeta: '1' } on package answers the unknown-key sentence for zeta (pinned), so a caller fixes the misspelt key first and the located package sentence second, and the arity refusal (metaQueryArityRefusal('list', …)) stays after both, as before. names stays in META_LIST_QUERY_PARAMS (it IS read on every other type), so the parity census's META_LIST_QUERY_PARAMS == derived.params pin is undisturbed.
  • Message voice is consistent with its siblings, not with refuseUnknownQueryParams, and that is right. The key is declared, so the "not supported by this endpoint" sentence would be false; the new sentence takes the voice of the list's own per-type refusals (metaListNames's empty refusal, refuseEmptyListFilter): what was asked, why it cannot be served ("package items are keyed by id, not by name, so no name could match"), that "The list was not served", and the remedy ("Leave the parameter out to list every package you can see"). No tracker number in the runtime string.
  • Type coverage. package is not a declared URL spelling (META_URL_TO_SINGULAR has no entry; CANONICAL_META_TYPES lacks it) but a live-registered type that refuseUnknownMetaListType admits through metaTypeIsLive, and the registry lists it under the singular, so /meta/package is the one list spelling that reaches the protocol's package branch; metaType === 'package' covers it on both transports (metaTypeSingular / pluralToSingular pass the singular through). The protocol's request.type === 'packages' tolerance is an internal branch no admitted list URL reaches.
  • .describe() and docblock now true. The TSDoc paragraph reads "Served. Both list doors … read ?names= and push it down here, on every type except package … refuses ?names= with 400" — true against the two call sites and the new refusal. The describe adds one sentence, "Served on every type except package, whose items are keyed by id; the package list refuses it with 400." — true, and every other sentence of the describe is unchanged and was judged TRUE in 6672872746. Declaration equals enforcement for every type now, including the one it formerly over-claimed.
  • Public surface. NAMES_ON_PACKAGE_LIST_MESSAGE is exported from meta-item-read-gate.ts only; packages/rest/src/index.ts is unchanged in the delta and does not re-export it, nor the MetaItemQueryRefusal type whose rule union gains 'names-on-package'. So the package's published surface does not move; the test imports it by relative path. No spec export moves. Clause-② remains yes (narrowing) with no new widening.
  • Generated artifact is a faithful regen. The names row of content/docs/references/api/protocol.mdx on this head is the describe text verbatim (compared byte for byte, the three + fragments concatenated); nothing else in the file moved. check:docs ran green in TypeScript Type Check.
  • Docs. http-protocol.mdx adds the exception sentence beside the ?names= paragraph; consistent with the describe and the changeset.

② Semver level

Changeset: the five patch entries, Metadata-protocol: none, the ADR-0087 marker and the **BREAKING** section are unchanged; Clause-② is now spelled canonically yes (narrowing) (readArmToken reads the same arm as before), and the by-names paragraph names the package exception. The level does not move: refusing ?names= on the package list is the same class the BREAKING section already declares (a query the list does not read, answered 200 on main, now a located 400), names on package was never served, and no authorable key, export or schema shape changes. Metadata-protocol: none stands.

Check-runs on 621212ae at read time (2026-11-06T00:53Z): 69 runs, every one completed; 59 success, 10 skipped (the opt-in and UI-artifact rows, Lint UI, Dogfood Verify CLI, the dogfood matrix label, the schedule gate, the temporal conformance); none in_progress, none failed. Check Changeset, Lint & Repo Gates, TypeScript Type Check, Test Core (1–8/8), Build Core, Governed Surface Queue Guard, Type Check UI, Test UI (1–16/16), Build & E2E UI, Live E2E UI, Dogfood Regression Gate, Spec property liveness, Build Docs all success.

③ Boundary flags

  • 6672872746 should-fix (a) closed: the stale TSDoc paragraph is rewritten to the served state. (b) closed by the seat's ruling (located 400), with the describe, the docs and the changeset carrying the exception. (c) closed: the PR body reads Skills-docs: follow-up #30710, Clause-②: yes (narrowing), and deviation 5 is rewritten to the refusal and cites the record. The nit (canonical arm spelling) is closed in both the body and the changeset.
  • open_questions: none. The commit is the dev card thread's own (session_016WV4cyaiN5dg5cw1zSyihP), trailer pair present, no model name.
  • Author is objectstack-fleet[bot]; no out-of-relay login. The PR is still a draft. origin/main is still ahead of the merge-base; the landing merges main and rebuilds (Multi-agent discipline §10).
  • Independence: rendered by the same isolated contract-review subagent that rendered 6672872746, fed the delta diff, the new head's check-runs and the cited code, not the dev's or the seat's conclusions; adopted by the seat named in Reviewed-by:.

④ Design judgment

The refusal lives in the one decision both transports already share, so the twins cannot drift; it refuses at the key's own granularity (the type whose items have no name), the way the list already refuses an empty ?id= or a bracketed ?object[]= by type, rather than growing a second vocabulary or a lenient match-by-id alias. Matching packages by id would be a new behaviour and is rightly left for its own card if anyone pulls for it. Holds.

Findings (none must-fix):

  • nit — the changeset's **BREAKING** Before/Now lines name only undeclared keys; a one-clause addition ("and ?names= on /meta/package") would make the banner enumerate every 200 → 400 flip. The by-names paragraph two lines above already states it, so a reader of the entry is not misled.

Implemented-by: claude/project-thread-ibfnsp
Reviewed-by: session_016WV4cytQjG9X1CrCgytUM5

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

ACCEPT · #29236 · 2026-11-06T00:55Z · Head-sha: 621212ae86bbac8cb5f6a5a434b8a9c9e2dae120

席位核对

  1. Shape: 19 files, +850/−49: GET /meta/:type serves ?names=a,b,c (chain step 2a, after the per-caller gate and before the field mask / translation, pushed down as GetMetaItemsRequest.names), and the list declares its closed query set META_LIST_QUERY_PARAMS (package, preview, object, include, id, names, locale), refusing any other key 400 before arity and before any read, on both transports; ?names= on the package list is refused with a located 400 (its items are keyed by id). Client SDK sends { names }. Diff read at this head.
  2. Premise, measured: contract review 6672872746 (PASS, CONTRACT_REVIEW_TIER, head 5f55d49c) checked all seven binding sentences of ruling 6580261740 against the code and measured the closed set from read points; delta review 6673652674 (PASS, this head) covers the one should-fix commit (TSDoc, the package refusal, docs regen, canonical Clause-②). No must-fix or should-fix open.
  3. Gates: 106 derived, 106 exit 0 at this head plus 6 extras incl. pnpm lint (--ran reconciled, 0 NOT MEASURED); the full unsharded dogfood battery exit 0 at 5f55d49c (title carries !). Check-runs on this head: 59 success, 10 skipped, 0 failed; all ten required contexts green.
  4. Accepted deviations: (1) locale joins the ruling's six keys: the list already reads ?locale= a frame down, so a six-key set would turn every working ?locale=zh-CN read into a 400; AGENTS.md route-ownership rule 5 orders the set measured from read points; pinned on both transports. ⚠️ Veto point for the maintainer — the landing waits for the maintainer's word on it. (2) patch levels, not the ruling's sketched minor: forced by check-changeset-no-major under Metadata-protocol: none. (3) the http-protocol.mdx paragraph, added to the claim before the PR. (4) the package refusal, the seat's ruling on review should-fix (b); matching packages by id is left for its own card if anyone needs it.
  5. Out of scope: Skills-docs: follow-up #30710 (Tier H, serial after this PR; the skill today names only declared keys, so nothing breaks); ui adoption console(home): fold the roster gate's by-name object reads into one ?names= request (#29236 ruling A follow-up) #29444. Reviewer nit kept as is: the changeset's Before/Now lines name only undeclared keys; the by-names paragraph above them states the package flip.
  6. Landing: check-governed-merges --pr 30709: NOT governed, 899 lines. Ordinary queue landing by this seat, held until the maintainer rules on deviation (1); then ready + auto-merge (merging main first if the queue asks).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

Ruled · 2026-11-06T03:49Z · deviation (1) of ACCEPT 6673684157: locale joins the list door's closed query set (seven keys).

Maintainer: 「同意,七个键落地」, on the seat's decision card in the seat thread, 2026-11-06T03:49Z. The landing hold is lifted: ready + auto-merge in this stroke on head 621212ae86bbac8cb5f6a5a434b8a9c9e2dae120.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 03:50
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 6, 2026 03:50
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 42159059506 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (4/8) — 失败步骤: Run this shard's tests

    @objectstack/cli:test:  FAIL   unit  test/validate-build-gate-parity.test.ts > os validate is the read-only superset of os build (#3782, #4409) > the command sources use no call shape this scan cannot
      ↳ 失败原因: @objectstack/cli:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 3 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 42163420456 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (2/8) — 失败步骤: Run this shard's tests

    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — evidence pointers (#5623) > is green against a verbatim copy of the shipped ledgers
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — evidence pointers (#5623) > stays green when the missing path is attributed to ANOTHER repo
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — evidence pointers (#5623) > never bounds a citation attributed to ANOTHER repo
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — evidence pointers (#5623) > prints the citation count and how many are in range, in the documented two
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — symbol anchors (#12516) > stays GREEN on the drifted BEFORE-state — the honest residual this grammar e
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — symbol anchors (#12516) > prints the anchor count and how many resolve, equal on a green run
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — the evidence-scan population (#13041) > stays GREEN when a `dead` entry carries the SAME rotted pointe
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — the evidence-scan population (#13041) > declares every status either scanned or explicitly unscanned, 
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — the live-elsewhere criteria (#13483) > is green on the shipped ledgers and publishes the population be
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — the live-elsewhere criteria (#13483) > is green on a planted live-elsewhere row and counts it as carry
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — the README state table (#7257) > is green against a verbatim copy, and says how many rows it checked
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    @objectstack/spec:test:  FAIL   local  scripts/liveness/check-liveness.test.ts > check:liveness — the generated count artifact (#7377, sharded #20361) > is green against a verbatim copy, says the shar
      ↳ 失败原因: @objectstack/spec:test: AssertionError: Spec liveness gate (registry-rooted) — governed types: object, field, flow, action, hook, permission, position, agent, tool, skill, dataset, page, view, report,
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 14 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit c17719d Nov 6, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ibfnsp branch November 6, 2026 05:15
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/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Metadata API: no batch by-names read, so the Console roster gate costs one /meta/object request per name

2 participants