Skip to content

feat(spec): an action param can name the widget that draws it (permission-ref picker) - #30321

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

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

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Requested by Yibo Wang · project thread

domain:spec#3 · os-support-ai · session_01WGufzhmTfrR3b13itPTmu3

Fixes #30259
Part of #30117 (this is slice 1, the spec half; #30260 adds the console reader, and #30117 keeps the server switch of Create User's param)

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up owed once #30260 lands (the skills lane is paused, and the key does nothing in the console until then, so teaching it now would teach an inert key). Owed lines: skills/objectui/rules/protocol.md:309, the inline-param widget-config list (options, multiple, accept, …) gains widget; skills/objectstack-ui/rules/actions.md:174-175 says a field-backed param takes its "widget" from the object metadata, which the console does not do today (its resolver copies no field widget onto a param, measured on #30117), so that line is #30260's to make true or to correct.
Prior rulings read: permission-ref,actionparamschema,action param widget → 1 hits; ADR-0104 D2; thread: none; repo: objectstack-ai/objectstack
ADR-0087: not needed. A new optional key; nothing is removed, renamed or narrowed, and no stored param changes meaning, so no conversion is owed.

维护者速读

  • 改了什么: 动作参数(ActionParamSchema)新增可选键 widget,与字段上的 widget 同名、同词汇、同含义:指定用哪个已注册的控件来画这个参数,type 仍是值的类型。Studio 的动作表单参数列表多一列「控件」,四种语言都有标签。
  • 为什么改: 新建用户时「权限集」是一个只认机器名的文本框(Create User: the permission set is a free text box that only accepts the machine name #30117)。授权表上的同一列早已用 permission-ref 选择器(ADR-0131 D4),但动作参数没有任何方式请求这个选择器。这是拆分后的第一片:先让规范能表达。
  • 风险与回滚: 低。纯新增可选键:不写它的参数解析结果与之前完全一样;未注册的控件名会被保留并退回 type 的默认控件,和字段一致。控制台在 ui: resolveActionParams and paramToField pass a param widget through, so an action dialog draws the permission-set picker (#30117 slice 2) #30260 落地前不读它,所以现在声明了也只是照旧显示文本框;键的说明写明了这一点,os validate / os lint 也会提醒写了它的作者。回滚即还原本 PR。
  • 先看: packages/spec/src/ui/action.zod.ts(新键与 TSDoc);packages/spec/src/ui/action-param-widget.test.ts(与字段键逐值同判的钉);packages/spec/liveness/action.json(params 钻取后的逐成员判定,widget 为 planned 并提醒作者)。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/ui/action-param-widget.test.ts,预期 17 passed。
  • 需要你做的: 无,席位落地。

What changes

  • @objectstack/spec: ActionParamSchema.widget, z.string().optional(), placed after type. Same key, vocabulary and meaning as FieldSchema.widget (data/field.zod.ts): it names a registered field component, looked up as field: plus the name, that draws the param instead of the type default. type stays the value's type. The TSDoc cites ADR-0131 D4 and names the case: widget: 'permission-ref' on a text param lists the security catalog's permission sets and submits the chosen machine name. The .describe() carries the same contract for the generated docs and JSON Schema.
  • The action form (ui/action.form.ts): the params repeater offers widget as a row property (label Widget), and @objectstack/platform-objects' metadata-form bundles label it in en / zh-CN / ja-JP / es-ES, reusing each locale's existing word for the field form's widget row (控件覆盖, ウィジェット指定, Control del campo).
  • Liveness, action/params drilled: liveness/action.json's params row now carries a children map, one verdict per member, and action/params leaves the containers list of scripts/liveness/undrilled-containers.baseline.json (the debt shrinks by 19 child keys). 18 members are live, each with dated in-repo evidence anchored on the consuming symbol: the console resolver resolveActionParam, the dialog (ActionParamDialog, filterVisibleParams, CarryOverParam, serializeParamValues), paramToField, and the dispatcher's ADR-0104 D2 path (resolveDeclaredActionParams, validateActionParams, summarizeActionParams); requiresFeature cites lowerRequiresFeature, its parse-time lowering, as the action-level row does. options is itself a container, so it is drilled too (label, value, visibleWhen, all live). widget is planned with authorWarn and an authorHint saying the dialog does not read it yet (the row's note names ui: resolveActionParams and paramToField pass a param widget through, so an action dialog draws the permission-set picker (#30117 slice 2) #30260 as the reader; author-facing text carries no issue id, per check:doc-authoring), so os validate / os lint warn an author who writes it (rule liveness-planned-property); its note records what each reader does with it today. state-counts/action.md moves 51/2/54 → 70/3/74 (live/planned/classified). Two measured notes ride on rows: objectOverride is read by the console only (the dispatcher resolves a field-backed param against the action's own object), and options.visibleWhen is client-side only.
  • Describe: the key's .describe() (and so the generated reference) now says it is not read yet: until the console's dialog passes it through, every param is drawn with its type renderer. A // comment beside the key says the sentence comes off with ui: resolveActionParams and paramToField pass a param widget through, so an action dialog draws the permission-set picker (#30117 slice 2) #30260.
  • Generated: authorable-surface/ui.json gains ui/ActionParam:widget; content/docs/references/ui/action.mdx gains the row (both regenerated, not hand-edited).
  • Count pins moved by the new form row (platform-objects): action-body-panel-echo-decisions.test.ts (params children 18 → 19) and object-lifecycle-panel-echo-decisions.test.ts (positive control 701 → 702), each with a dated comment line.
  • Not changed: sys-user.object.ts. Its permissionSet param switches to widget: 'permission-ref' in Create User: the permission set is a free text box that only accepts the machine name #30117's server PR, once ui: resolveActionParams and paramToField pass a param widget through, so an action dialog draws the permission-set picker (#30117 slice 2) #30260 gives the key a reader; declaring it now would ship a key the console ignores. BulkActionParamSchema does not get the key: no bulk action asks for a picker of this kind today.

The choice: widget on the param, not permission-ref as a param type

Measured on origin/main 959c26be:

  • permission-ref is a widget hint, not a field type. It is not in FieldType. The grant column declares it as Field.text({ widget: 'permission-ref' }) (plugin-security/src/objects/sys-user-permission-set.object.ts:104), the console registers it as field:permission-ref beside object-ref and position-ref, and both the console's changelog and ui/packages/app-shell/src/utils/paramValueShape.ts:181 say it is "widget-hint only — not a declarable param type". A param type reads FieldType, so the type route means either adding permission-ref to FieldType (which also makes it a declarable FIELD type, against ADR-0131 D4's text-column-plus-widget shape) or forking a param-only type vocabulary.
  • The type route would loosen the server's value check. ADR-0104 D2 (the one prior ruling the check found) makes type the enforced value contract: the dispatcher validates each param through valueSchemaFor(type). A type it does not know falls to the explicitly-open branch (field-value.zod.ts:797, z.unknown()), so type: 'permission-ref' would stop checking that the value is a string. With widget, type stays text and D2's check is unchanged.
  • widget is one key with the field's own meaning, so an author who knows the field key already knows this one, and the console's resolver can pass it through to the same field:WIDGET lookup the record form uses.

Value verdict: kept, as on the field. FieldSchema.widget is an open z.string(): an unregistered name parses and degrades to the type renderer. The param key makes the same promise, and the pin checks seven values (three registered names, one unregistered, a number, a boolean, null) get the same verdict on both schemas.

Who reads action params, and what each does with the key today

Reader What it does with widget before #30260
Spec parse door: ActionParamSchema (strict) behind PUT /meta/action, MetadataManager.validate, defineStack, os validate Before this PR: refused as an unrecognized key. After: kept in the parsed param.
Console resolver ui/packages/fields/src/resolveActionParams.ts Builds ActionParamDef from named members only; ActionParamDef has no widget, so the key is not carried. The param is still resolved and drawn.
Console adapter ui/packages/app-shell/src/utils/paramToField.ts Sets a field widget only for boolean (checkbox); otherwise draws the type renderer. A text param stays a text box and its value is submitted as before. Nothing is dropped.
REST / MCP dispatch (packages/runtime/src/action-execution.ts → validateActionParams) Resolves each param to { name, type, multiple, required, options } (ResolvedActionParam) and validates the value by type (ADR-0104 D2). widget is not read; the value check is unchanged.
AI action tools (cloud service-ai action-tools.ts#buildParametersSchema, the params row's evidence) NOT MEASURED: closed repo, not in this container. Per the ledger row it builds one JSON Schema property per param from its declared shape; an extra string key gives it nothing new to misread.
os i18n extract (packages/cli/src/utils/i18n-extract.ts) Emits label / helpText / placeholder / options leaves only; widget is a machine name, not translatable copy.
@object-ui/types ActionParam Derived from the spec schema's z.input type, so it gains the member with no edit; its parity pins are subset (direction) checks. Not run locally (ui is out of scope here); CI's UI job runs them.

Tests

  • New packages/spec/src/ui/action-param-widget.test.ts (17 cases): accepts permission-ref on an inline text param and keeps it with type unchanged; accepts it on a field-backed param; it is a declared key (no unrecognized_keys); per-value parity with FieldSchema.widget over seven values; an unregistered name is kept; a non-string is refused on the widget path (invalid_type); five existing param shapes (Create User's current inline permissionSet among them) parse with no widget member added.

  • New pin in packages/lint/src/lint-liveness-properties.test.ts: an action whose params name widget draws exactly one liveness-planned-property warning (through the shipped ledger and the array fan-out); its live siblings stay silent; an action whose params name no widget draws nothing.

  • Suites, under the verify lock:

    • pnpm --filter @objectstack/spec test: Test Files 813 passed (813), Tests 25592 passed, 1 todo, on the drilled tree (4fe77e8's content). pnpm --filter @objectstack/spec typecheck: exit 0 (first head).
    • On 1d215c5c (the describe and hint rewording): lint-liveness-properties.test.ts 113/113; action-param-widget.test.ts + action.test.ts 168/168.
    • pnpm --filter @objectstack/metadata-protocol test: Test Files 312 passed, 4 skipped (316), Tests 22354 passed, 26 skipped (first head; the later commits touch no metadata-protocol input).
    • pnpm --filter @objectstack/platform-objects test: the first run had 2 failures, both count pins the new form row moves (action-body-panel-echo-decisions.test.ts 18 → 19, object-lifecycle-panel-echo-decisions.test.ts 701 → 702). Both moved in the second commit; the two files then pass (57/57), and the other 108 files passed.
    • Ablation of the key (first head, ablation-replace.mjs, see Verification). The ledger drill is data, so it is not ablated; the lint pin is its red-without-it check.
Derived gates on the final head 1d215c5: 107 derived, 107 run, all exit 0
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 107 families against merge base 959c26b; a runner recorded CMD :: exit N for each (with NODE_OPTIONS=--max-old-space-size=4096, after a full turbo run build), and --ran reconciled: "107 derived famil(ies) accounted for — 107 run, 0 NOT-MEASURED".
  • check:liveness: "every governed-type property, at every depth the ledger drills, is classified … state-counts/ is current".
  • check:dual-build-cjs-loads: "116 published require entry point(s) across 71 package(s) load; 2456 emitted CommonJS file(s) parse" (NOT MEASURED on the first head; measured here after the full build).
  • check:doc-authoring: "21066 customer-facing string(s) … clean — no internal issue-id references". Its first run on 4fe77e88 refused the describe's objectstack#30260; 1d215c5c moved that pointer to a // comment and out of the authorHint.
  • check:docs: "240 generated files in sync with packages/spec".
  • check-changeset-no-major --base origin/main: no major bump; the declaration axis reads this body in CI.

Acceptance notes

Verification

  • Metadata-protocol: added (corrected from the malformed additive the first head carried, which Check Changeset read as no declaration) is read off this body by CI's Check Changeset; locally check-changeset-no-major found no major bump. The changeset bumps @objectstack/spec and @objectstack/platform-objects minor and carries both declaration lines.
  • Ablation, one run on the first head 792c7abe through scripts/ablation-replace.mjs (spec tests import the schema from src, so no build sits between): renaming the key to widgetAblated (anchor 1 → 0, blob 8cd40792 → 893ac68c) turns action-param-widget.test.ts red, 9 failed / 8 passed of 17 (the 8 are the five existing-param cases and the three values both schemas refuse). Restored: blob equals HEAD 8cd40792 and git diff HEAD is empty.
  • Reverse check of the parity pin's premise: the field key's verdicts are read from the built schema in the same test run, so if FieldSchema.widget ever narrows to an enum the per-value cases go red rather than silently diverging.

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 is left to CI (the repo-wide run is CI's).
  • 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 maintainer brief is in Chinese by the repo's rule).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzhmTfrR3b13itPTmu3

…sion-ref picker)

ActionParamSchema gains an optional `widget`, the same key, vocabulary and
meaning as FieldSchema.widget, so a param can ask for the permission-set
picker ADR-0131 D4 gives the grant column. `type` stays the value's type;
an unregistered name is kept and degrades to the type renderer. The action
form's Params repeater offers the key, labelled in four locales. The
console reader is #30260.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzhmTfrR3b13itPTmu3
…e the two row-label counts for params.widget

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzhmTfrR3b13itPTmu3
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 2 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/action.json, packages/spec/liveness/state-counts/action.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

⛔ 2 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via ActionParamSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-0.mdx (via ActionParamSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/action.json, packages/spec/liveness/state-counts/action.md) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 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 — 170 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 cfb602c4595795c696dd07763abca3104dd4cf80 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json cfb602c4595795c696dd07763abca3104dd4cf80

⚠️ 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 cfb602c4595795c696dd07763abca3104dd4cf80 → 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:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head 792c7ab · 2026-11-05T06:04Z

Full review of the first head at CONTRACT_REVIEW_TIER (a widening of a published schema: widget on ActionParamSchema).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 792c7abe12b255d6f11d325b15083a7af76f1b05
Local-runs: none
Scope: the full diff vs merge-base 959c26b (13 files); PR #30321 body and comments (6651034779, 6651041734); #30259 and comments (6649924151, 6649959478, 6651029910); #30117 and comments; #30260; check-runs on the head and the Check Changeset job log 127080786433; at origin/main: packages/spec/src/ui/action.zod.ts, data/field.zod.ts:2360-2370, data/field-value.zod.ts:770-800, ui/bulk-action.zod.ts:191-330, ui/action-params.zod.ts:42, packages/runtime/src/action-execution.ts:3103-3119, ui/packages/fields/src/resolveActionParams.ts:290-350, ui/packages/app-shell/src/utils/paramToField.ts:111, ui/packages/app-shell/src/utils/paramValueShape.ts:175-182, ui/packages/core/src/actions/ActionRunner.ts:848-905, packages/lint/src/validate-object-references.ts:428-434, packages/lint/src/lint-liveness-properties.ts:60-91,261-280, packages/spec/scripts/liveness/check-liveness.mts:55-95,495-560,1160-1175, packages/spec/scripts/liveness/undrilled-containers.baseline.json:1-14,61, packages/spec/liveness/README.md:690-762, packages/spec/liveness/action.json:86-98,262-272, app.json:132-137, api-surface/ui.json, json-schema.manifest/ui.json, authorable-defaults/ui.json, platform-objects sys-user.object.ts:355-372, AGENTS.md:655-700,1019-1030, scripts/check-changeset-no-major.mjs:905-915, docs/adr/0131 D4 (:411-430), 0104 D2 (:230-275), 0049 (:1-34), 0087 (:732-765), 0078 header, skills/objectui/rules/protocol.md:309, skills/objectstack-ui/rules/actions.md:174-175.

① Derived judgments

  • Choice: widget on the param is the right surface. permission-ref is outside FieldType and the console itself marks it "Widget-hint only — not a declarable param type" (paramValueShape.ts:181). The ADR-0104 D2 argument holds at main: a type route would need permission-ref in FieldType, after which valueSchemaFor has no branch for it and returns the explicitly open z.unknown() (field-value.zod.ts:793-797), so the dispatcher's value check (action-execution.ts:3103-3119 resolves {name,type,multiple,required,options} and validates by type) would stop checking for a string. With widget, type stays text and D2 is untouched.
  • Open z.string() matches FieldSchema.widget exactly (field.zod.ts:2370, z.string().optional(), "Degrades to the type renderer when unregistered"); no widget registry or vocabulary lint exists in packages/spec at main, so refusing or warning on an unknown name here would be a new, param-only regime diverging from the field key. Accepting anything is the consistent verdict, and the pin checks parity over seven values.
  • Readers at main with a param carrying widget: spec parse door kept it (was unrecognized_keys before); runtime dispatcher ignores it (action-execution.ts:3108-3117 copies five named members); console resolver ignores it silently, and widget is not in RESOLVED_ONLY_PARAM_KEYS (resolveActionParams.ts:311-327) so no dev-mode warning fires; paramToField sets field.widget only for boolean (paramToField.ts:111); i18n-extract and validate-object-references (:428-434) read label/reference/type only; the AI tool builder is cloud-side (not measurable here). No reader drops or refuses the param.
  • Pins (action-param-widget.test.ts, 17 cases): declared key (no unrecognized_keys), kept value, type unchanged, per-value parity with FieldSchema.widget, non-string refused at path widget, five existing shapes parse with no widget member. They would fail without the change (9/17 red under the reported key-rename ablation, which is consistent with the cases).
  • TSDoc (action.zod.ts, the new block) cites ADR-0131 D4 and names ui: resolveActionParams and paramToField pass a param widget through, so an action dialog draws the permission-set picker (#30117 slice 2) #30260 as the reader. The .describe(), which is what the docs reference and JSON Schema hand to authors, does not: it says "picks a permission set by name" and "Degrades … when unregistered", which is not true today for any name (see ④).
  • Generated surfaces: authorable-surface/ui.json (+ui/ActionParam:widget), content/docs/references/ui/action.mdx (three tables) and the four *.metadata-forms.generated.ts bundles are in the diff. api-surface/ui.json records export existence only (ActionParamSchema (const) unchanged), json-schema.manifest/ui.json lists schema names, authorable-defaults/ui.json lists defaulted keys only; none is moved by an optional key with no default. Count pins moved honestly (18→19, 701→702).
  • Scope: sys-user.object.ts untouched and the body names Create User: the permission set is a free text box that only accepts the machine name #30117's server PR as the switch. BulkActionParamSchema (bulk-action.zod.ts:191ff) is a divergent dialect (help/default/object) with no widget; the card asked for one surface, and skipping bulk parity is justified and stated.
  • Body matches code: Fixes #30259, Part of #30117, Prior rulings read:, Skills-docs: (both cited skills lines verified at main), 维护者速读, reader table. One overstatement: "the params row records widget as PLANNED" — the row's status is live; PLANNED is a sentence in its note.
  • Check-runs on the head: Check Changeset FAILURE (see ②); Spec property liveness, PR-body guards, dispatch-gates green; build/test/typecheck lanes in progress at review time. check:dual-build-cjs-loads NOT MEASURED locally per the report; noted, not failed on.

② Semver level

  • Clause-②: yes (widening): correct. Every existing param parses unchanged (pinned); the strictObject accepts one more optional key.
  • minor on @objectstack/spec and @objectstack/platform-objects: correct level for a new authorable key on moved packages (AGENTS.md:1023-1026).
  • MUST-FIX: Metadata-protocol: additive is not in the closed set added|narrowed|semantic|none (AGENTS.md:1023; check-changeset-no-major.mjs:907-911). The gate on this head reads it as "MALFORMED, not a declaration", reports "LEVEL AXIS: NOT MEASURED", and Check Changeset is red (job 127080786433). The PR body and .changeset/30259-action-param-widget.md both carry additive; AGENTS.md requires the line in both. Change both to Metadata-protocol: added (the body edit clears the check on the next edited event; the changeset needs a commit).
  • ADR-0087: no conversion owed — nothing removed, renamed or narrowed; the adr-0087 comment marker is required only on declared-breaking changesets and check-adr-0087-registration is green.

③ Boundary flags

④ Design judgment

The surface choice is sound and the smallest one that keeps ADR-0104 D2 intact and reuses the field key's vocabulary and degradation. The widening is honest and fully pinned, and every generated surface a new optional key moves is regenerated. The two defects are declaration and record hygiene, not design: the protocol line is malformed and blocks the level gate, and the ADR-0049-style reader obligation is held by an unscanned sentence rather than by the ledger. dual-build-cjs-loads was not measured locally; CI runs it.

Implemented-by: claude/project-thread-evl2a5
Reviewed-by: session_01WGufzfgvGexKn9vLHsy89K

VERDICT: FAIL

…with an author warning, declare Metadata-protocol: added

The contract review asked for the drill: each ActionParamSchema member gets
its own verdict with in-repo evidence (options drilled too), action/params
leaves the undrilled baseline, and params.widget is planned + authorWarn
naming #30260. The key's describe says it is not read until #30260. The
changeset's Metadata-protocol line takes the closed-set value added.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzhmTfrR3b13itPTmu3
check:doc-authoring refuses an issue id in author-facing prose; the
pointer to the reader card moves to a // comment beside the key.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzhmTfrR3b13itPTmu3
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head 1d215c5 · 2026-11-05T07:35Z

Delta review of fix round 1 (792c7abe..1d215c5c) at CONTRACT_REVIEW_TIER, against the FAIL record 6651175841.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1d215c5caf8c191915f424e74dda7715c01af0fc
Local-runs: none
Scope: the delta 792c7abe..1d215c5c (7 files) and the full diff vs merge-base 959c26b (16 files); PR #30321 body and comments (6651034779, 6651041734, 6651175841, 6652453553) and the os-dev-report 6652427201 on #30259; #30260 body; check-runs on the head, the Check Changeset job logs 127097588033 (failure) and 127114043465 (success); at origin/main: packages/spec/liveness/README.md:76-130,581-760, action.json:146-150,263-290, app.json:132-137, scripts/liveness/check-liveness.mts:536,925-945,1098-1116,1142-1150,1205-1230, scripts/liveness/undrilled-containers.baseline.json:1-14,61, packages/lint/src/lint-liveness-properties.ts:60-95,431-460,468-487,542-544, ui/packages/fields/src/resolveActionParams.ts:252-282,580-700, ui/packages/app-shell/src/views/ActionParamDialog.tsx:251-275,329-360,398-412,487-900, ui/packages/app-shell/src/utils/paramToField.ts:60-160, packages/runtime/src/action-execution.ts:2967-3010,3103-3120, packages/spec/src/ui/action-params.zod.ts:197-240, packages/spec/src/data/field-value.zod.ts:735, ui/packages/core/src/evaluator/optionRules.ts:93-107,217-227, packages/spec/src/kernel/public-auth-features.ts:421, scripts/check-doc-authoring.mjs:101,385-410,516; at the head: packages/spec/src/ui/action.zod.ts:150-330,527,618, liveness/action.json params subtree, lint-liveness-properties.test.ts:72,190-216; AGENTS.md:14-17,1019-1036.

① Derived judgments

  • 6651175841, finding by finding. MUST (Metadata-protocol: additive): closed — the changeset line reads added (delta, .changeset/30259-action-param-widget.md:9) and the live body carries Metadata-protocol: added. Check Changeset on this head: the 06:52Z run 127097588033 failed reading the then-unpatched body (its log quotes Metadata-protocol: additive); the 07:35Z pull_request run 127114043465 completed success at 07:36:53Z. Green now. SHOULD (ledger prose): closed by the drill (below). SHOULD (describe): closed — the describe and all three regenerated action.mdx tables carry "Not read yet: until the console's action dialog passes it through, the dialog draws every param with its type renderer whatever this names." SHOULD (body "PLANNED"): closed — the body now says the row is planned with authorWarn and names the drill.
  • The drill, checked member by member at main (all 22, not a sample). Every anchor resolves to an exported or module-level symbol in the cited file (resolveActionParam :580, resolveOptionLabels :252, ActionParamDialog :487, filterVisibleParams :251, serializeParamValues :329, CarryOverParam :398, paramToField :83, resolveDeclaredActionParams :3103, summarizeActionParams :2967, validateActionParams :197, resolveCascadingOptions :217, lowerRequiresFeature :421), and each symbol reads the member it is cited for: resolveActionParam reads name/field/objectOverride(:56 ownerName)/label/type/required/options/placeholder/helpText/defaultValue/visible/carryOver/multiple/accept/maxSize/reference(→referenceTo)/defaultFromRow(:14); the dialog keys state and errors by name, seeds defaultValue (:83), blocks on required (:118), localises label/helpText/option label (:219-223), branches on carryOver (:251; serializeParamValues :7,:31), filterVisibleParams reads p.visible; paramToField copies type/required/placeholder/options/multiple/accept/maxSize and degrades a targetless reference (paramDegradesWithoutTarget); the dispatcher copies name/field/type/multiple/required/options (:3107-3116) and summarizeActionParams reads label/helpText; validateActionParams hands type/multiple/options to valueSchemaFor (:32), whose membership is over o.value (field-value.zod.ts:735); requiresFeature is the ActionParamSchema .transform at action.zod.ts:618. The widget verdict is right: widget appears nowhere in resolveActionParams.ts, paramToField.ts sets field.widget only for boolean, and action-execution.ts names neither widget nor objectOverride — so the planned grade and the objectOverride "console only" note are both measured truth. Nit, no fix owed: options.value / options.visibleWhen anchor the exported entry resolveCascadingOptions; the actual read is one call down in resolveVisibleOptions (optionRules.ts:93-107), same file.
  • Shape against the ledger's rules and precedents: file#symbol anchor grammar (README:93-110), verifiedAt 2026-11-05 on every child (today, not future), evidenceScope: in-repo, planned + authorWarn: true + authorHint opening "Keep it, but nothing reads it yet:" mirrors app.json:132-137 navigation.dataSource exactly, and the PLANNED RECORD (…) note naming the carrier mirrors action.json:263-270 overrides. requiresFeature uses the #symbol form where the action-level row (:148) still uses a parenthetical — an improvement, not a divergence. No child inherits by fan-out; the container row keeps its own cross-repo AI-tool evidence and its 2026-09-15 date, which is honest since that pointer was not re-read.
  • Counts and baseline: drillChildren (check-liveness.mts:1098-1110) grades leaves only, so params and options stop counting as rows: 51 − 2 + 21 live = 70, planned 2 + 1 = 3, dead 1, classified 74. action/params is the only baseline row naming that coordinate at main (:61) and none remains at the head; Spec property liveness is green on the head, which is the gate's own word that anchors resolve, key mentions hold, the baseline has no stale row and the shards reconcile.
  • Author lint: ledgerRows flattens every children depth (:75-93), getNested fans a dotted path over array levels (:468-487), action/actions is in TYPE_COLLECTIONS (:544), the message is sets \${path}`(:450) and the hint isentry.authorHint ?? defaultHint(:447). The pin (test :190-216) asserts exactly oneliveness-planned-propertyfinding whose hint contains "nothing reads it yet", six live siblings silent byruleOf, and a widget-less action silent. Test Core shards are green on the head. The pin would go red if the row lost authorWarn`, the drill, or the array fan-out.
  • Delta scope: the seven delta files are the changeset line, the describe + // pointer, the drill, the counts shard, the baseline row, the regenerated reference and the lint pin — nothing beyond the review's asks. The objectOverride server-side gap found while drilling is recorded on the row and in the dev report as an observation with no carrier; it is outside this card and not changed here.

② Semver level

  • Unchanged from 6651175841: Clause-②: yes (widening) correct, minor on @objectstack/spec and @objectstack/platform-objects correct, and Metadata-protocol: added is now a member of the closed set in both the body and the changeset (AGENTS.md:1023). ADR-0087: nothing owed.

③ Boundary flags

④ Design judgment

The surface choice stands as reviewed. The fix round turned the reader obligation from an unscannable sentence into 22 measured verdicts, each anchored on the symbol that reads the member, with the one planned member warning its authors through the shipped ledger and a pin that keeps it warning; the declaration axis is well-formed and green. The remaining gap is hand-off hygiene on the successor issue, not this diff.

Implemented-by: claude/project-thread-evl2a5
Reviewed-by: session_01WGufzfgvGexKn9vLHsy89K

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

维护者速读

  • 改了什么: 动作参数 ActionParamSchema 新增可选键 widget,和字段上的 widget 同名、同词汇,用来指定画这个参数的控件(比如权限集选择器 permission-ref)。type 仍是值的类型。
  • 为什么改: 新建用户时「权限集」只是一个认机器名的文本框(Create User: the permission set is a free text box that only accepts the machine name #30117)。授权表那一列早就用 permission-ref 选择器(ADR-0131 D4),动作参数却没有办法请求它。这是拆分后的第一片:先让规范能表达。
  • 风险与回滚: 纯新增可选键,不写的参数照旧解析。控制台要等 ui: resolveActionParams and paramToField pass a param widget through, so an action dialog draws the permission-set picker (#30117 slice 2) #30260 才读它,所以存活度账本把它记为 planned,作者写了会收到 lint 提醒。回滚即还原本 PR。
  • 先看: packages/spec/src/ui/action.zod.ts;packages/spec/liveness/action.json(params 逐成员判定);action-param-widget.test.ts。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/ui/action-param-widget.test.ts,预期 17 passed。
  • 需要你做的: 无,席位落地。

席位意见:fable 契约复审两轮。

  • 首轮 FAIL:Metadata-protocol: additive 不在合法取值里,导致 Check Changeset 变红。这个写法最早出自席位自己的派单。
  • 修正后 PASS:存活度从一句备注改成逐成员钻取,widget 标为 planned 并提醒作者。

席位核对 — ACCEPT · domain:spec#3 · head 1d215c5caf8c191915f424e74dda7715c01af0fc

  • Shape: 13+ files on origin/main (+334 −6):
    • ActionParamSchema.widget, an optional open z.string() with the same vocabulary as FieldSchema.widget, with TSDoc citing ADR-0131 D4;
    • the action/params liveness container drilled into 19 members plus options' 3, each with dated in-repo evidence, and widget marked planned with authorWarn and authorHint; the undrilled baseline row is removed (state counts 51/2/54 → 70/3/74);
    • pins, including the author-lint warning on params.widget;
    • the authorable surface, the docs reference and the four locale bundles, regenerated;
    • a minor changeset for @objectstack/spec and @objectstack/platform-objects.
    • sys-user.object.ts and BulkActionParamSchema untouched.
  • Premise measured: at origin/main, ActionParamSchema had no widget key and no permission-ref type. The review confirmed the choice: permission-ref is outside FieldType, and an unknown param type falls to valueSchemaFor's open branch, losing the ADR-0104 D2 check. Every reader at main ignores the new key safely.
  • Gates: the dev ran all 107 derived gates at 1d215c5c, all exit 0, and --ran reconciles 107/107, including check:dual-build-cjs-loads after a full build. Spec 813 files green, lint pin 113/113, action tests 168/168. Check-runs on this head: 58 success, 10 skipped. The one failure is a stale Check Changeset run (127097588033) that read the pre-patch body; its re-run on the patched body (127114043465) is green.
  • Contract review:
    • Full review, 6651175841 on 792c7abe: FAIL. The must-fix was the malformed Metadata-protocol: additive. The should-fixes were liveness recorded as prose only, a .describe() promising an unread picker, and the body's wording.
    • Delta review, 6652561835 on this head: PASS. All four are closed, Check Changeset is green, and all 22 drilled members were verified at main against their readers.
  • Declarations: Clause-②: yes (widening), Metadata-protocol: added, minor for both moved packages. ADR-0087 not needed.
  • Accepted deviations:
  • Carried:
  • Landing: not governed (check-governed-merges --pr 30321: 340 lines). The hand-off is pr_ready plus automerge_enable, through the merge queue.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

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

队列构建 41989486500 红了。队列跑的是全量套件(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,按失败测试文件聚合):

  • scripts/liveness/check-liveness.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Merge-queue failure on run 41989486500 is not this PR's. Test Core (2/8) failed in check-liveness.test.ts, and the gate's only finding is query/searchFields → packages/objectql/src/engine.ts: a cited file that no longer names the property. That file moved under #30341 (f672e0dc, merged to main at 07:26Z), and the query ledger is outside this diff. main itself carries the same finding. The fix is open as PR #30368, which repoints the row at engine-find.ts#expandSearchOnAst. The seat re-arms auto-merge here once #30368 lands; no change to this PR is needed.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

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

队列构建 41990912046 红了。队列跑的是全量套件(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)

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit c7d1367 Nov 5, 2026
69 of 70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-evl2a5 branch November 5, 2026 10:04
os-support-ai pushed a commit that referenced this pull request Nov 5, 2026
#30321 and #30259 landed first. The four metadata-forms catalogs
auto-merged and `check-i18n-bundles --write` on the merged tree
reproduces them byte for byte. The echo-ledger positive control is
main's 702 plus this branch's thirty view row labels: 732.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv
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 protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: an action param can name the widget its picker draws (permission-ref), so Create User asks for a permission set by picker (#30117 slice 1)

1 participant