Skip to content

refactor(spec): share the form object-name resolver with lint - #25979

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-xsr2kc
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-xsr2kc

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #25756

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

What

The rung order that resolves the object an open anonymous form submits into (the form's data.object, else the view's list.data.object, form.data.object, object) was typed twice: as anonymousFormObjectName in @objectstack/metadata-core and as candidateObjectName in the lint rule validate-public-form-file-fields. It now lives once, in @objectstack/spec/ui beside anonymousFormIntakeCandidates.

  • packages/spec/src/ui/anonymous-form-intake.ts: declares and exports anonymousFormObjectName; the docblock no longer says it stays in metadata-core.
  • packages/metadata-core/src/anonymous-form-intake.ts: re-exports the spec function. The consumers (rest form-routes.ts, rest-server.ts, metadata-protocol runtime-authoring-gate.ts) import from metadata-core unchanged.
  • packages/lint/src/validate-public-form-file-fields.ts: imports it from @objectstack/spec/ui; candidateObjectName is gone.
  • Pins: the metadata-core identity test now also holds anonymousFormObjectName to be the spec binding itself (module and barrel); a lint test pins that the rule imports it from @objectstack/spec/ui and keeps no retyped copy.
  • Generated: api-surface/ui.json and export-origins/ui.json (one added export); one changeset.

Behaviour effect (stated, as ordered)

Lint's old helper went through viewObjectName (view-walk.ts), which also reads objectName on a raw input and skips non-string or empty values. The shared function is the doors' own raw read and does neither, so lint now judges exactly the object the doors resolve. A form that sets objectName (not a spec key) or an empty or non-string object no longer binds an object in this rule; the doors never honoured those either, so the rule no longer judges a form the doors cannot reach. No producer in the repo was found relying on the old reading.

Measurements

  • git grep candidateObjectName -- packages: 0 hits after the change.
  • git grep "v.list?.data?.object" -- packages still hits lint-view-refs.ts:185, an unrelated candidate list (the card's own correction).
  • Landing point as expected; no other producer package found.

Acceptance notes

Gate and test readings are in the os-dev report comment on #25756.


Generated by Claude Code

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 anchor(s) derived from 3 changed package(s); no hand-written page names any of them. ⚠️ 2 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 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; 97 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 — 144 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 a4eae06499a8f0377960bfc9c98daced5cf19258 → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7a6a2de49cae222501de355ee2bf8c82505d0241
Local-runs: none
Scope: packages/spec/src/ui/anonymous-form-intake.ts + packages/metadata-core/src/anonymous-form-intake.ts, packages/metadata-core/src/anonymous-form-intake.test.ts, packages/lint/src/validate-public-form-file-fields.ts, packages/lint/src/validate-public-form-file-fields.test.ts, packages/lint/src/view-walk.ts (viewObjectName, main), packages/lint/src/lint-view-refs.ts:185 (main), packages/rest/src/form-routes.ts:142, packages/rest/src/rest-server.ts:5889, packages/metadata-protocol/src/runtime-authoring-gate.ts:436 (main, the metadata-core consumers), packages/spec/src/ui/view.zod.ts (FormViewSchema and ViewSchema key shape, main), packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, .changeset/spec-form-object-name-shared.md. Remainder for the lane seat's ACCEPT at TIER_DEFAULT: none, every path of the diff is read here.

Inputs: card #25756 (body, comments 6394618326, 6409947695, 6410190296, 6411223236), PR #25979 body, file list and net diff against main at the head, the 48 check-runs on the head. Read-only: nothing built, run or re-run.

① Derived judgments

  1. Public surface, @objectstack/spec/ui: one new export, anonymousFormObjectName(view, form) — right. The body is byte-for-byte the former metadata-core definition (f.data?.object ?? v.list?.data?.object ?? v.form?.data?.object ?? v.object), so the doors' rung order is unchanged. api-surface/ui.json and export-origins/ui.json each gain exactly that one line, origin src/ui/anonymous-form-intake.ts. The module keeps its stated contract (pure functions, no imports, no module-load work). Placement follows the candidates-half precedent the docblock already cites (ADR-0053 D-D2: a pure read of the contract's own vocabulary is protocol, not business logic), and the docblock no longer claims the function stays in metadata-core.

  2. @objectstack/metadata-core: the name becomes a re-export of the same binding — right, and pinned as identity. The export name, signature and rung order are kept, so the three server callers (rest form-routes.ts:142, rest-server.ts:5889, metadata-protocol runtime-authoring-gate.ts:436) are untouched and still import from metadata-core. The identity test now holds anonymousFormObjectName to be the spec binding on the module and on the package barrel (toBe, not a wrapper), which is the only form "one copy" is checkable in.

  3. @objectstack/lint accept set, parsed stacks: unchanged — right. FormViewSchema is a strict object carrying data and no object or objectName; the container ViewSchema carries object and no data. On a parsed body the old helper and the shared function cannot disagree, and the fixtures of validate-public-form-file-fields.test.ts (all bind through data.object or the view's object) pass unchanged, 14/14 in Test Core.

  4. @objectstack/lint accept set, raw input (os lint hands the rule the unparsed stack): a stated change, in the card's ordered direction — right, with an incomplete statement. The retired candidateObjectName read each rung through viewObjectName (objectName ?? object ?? data.object, strings only, empty skipped). The shared function reads only the doors' four keys. So on raw input the rule stops honouring, at every rung: objectName; a bare object on the form, the list block or the default form block; and view.data.object. It also no longer steps past an empty or non-string value (a truthy non-string now short-circuits the chain; the rule's outcome is the same, the object does not resolve and the form is not judged). Every one of those is a binding the doors never resolve, so the rule now refuses exactly the forms a visitor can reach, as the card orders. The PR body names objectName and the empty or non-string case but not the bare object spellings the card itself lists; the card's own measurement (0 producers on examples, platform-objects, HotCRM) covers them, so the omission is a statement gap, not a behaviour the PR hides. Non-blocking.

  5. The done-when: one rung order, pinned — right; one measurement line is wrong. The only definition of the rung order outside the spec module is gone (the lint helper), and the lint pin reads the rule's source: imports anonymousFormObjectName from @objectstack/spec/ui, no candidateObjectName, no viewObjectName. The corrected check in 6409947695 (v.list?.data?.object hits the spec definition plus lint-view-refs.ts:185, an unrelated candidate list) reads as the premise said. But the PR body's and the os-dev report's "git grep candidateObjectName -- packages: 0 hits" is false on this tree: the new pin line expect(source).not.toContain('candidateObjectName') is itself one hit. The symbol has no definition and no call, which is what the measurement was meant to prove; the count as written is wrong. Non-blocking.

  6. Minor doc staleness in metadata-core — noted, non-blocking. The comment above the re-export block (// The candidates half of the rule ...) and the module docblock still describe the re-exports as the candidates half only, while the block now also carries the object-name half. The spec docblock and the identity test's describe name were updated; this comment was not.

② Semver level

Changeset .changeset/spec-form-object-name-shared.md: @objectstack/spec, @objectstack/metadata-core, @objectstack/lint at patch, with Clause-②: yes (widening) and Metadata-protocol: none as line-initial declarations in both the changeset and the PR body. Right. AGENTS.md Development Workflow item 3: the level follows the Metadata-protocol kind alone, none takes patch, and a non-protocol widening ships patch. Metadata-protocol: none holds, no key, value or type an author writes changes. Clause-②: yes (widening) holds: a new named export on @objectstack/spec/ui, nothing removed or renamed (metadata-core keeps the name and signature as a re-export), and the lint change on raw input refuses fewer bodies, never more, so no published accept set narrows. Check Changeset and Governed Surface Queue Guard: success on the head.

③ Boundary flags

  • open_questions: none declared. None found.
  • Deviation 1 (no HTML-comment attribution marker in the PR body): accepted, the sanitizer rule wins; the italic requested-by line is present.
  • Deviation 2 (edits on the platform-assigned thread branch in the primary checkout, no separate worktree): accepted, the branch is the one the Claim comment 6410190296 names and The card this PR closes must claim this branch is success.
  • Deviation 3 (no reviewer request): accepted, the dispatch allowed no MCP write.
  • Eight gate families the dev marked NOT MEASURED locally (engine-split-ratio, plugin-teardown-shape, skill-examples, dual-build-cjs-loads, i18n x3, lean-entry-closure, dts-closure): CI owns them, and on the head Lint & Repo Gates, Build Core, TypeScript Type Check and every sub-job are success. Of the 48 check-runs none failed; the skipped ones (Lint UI, Test UI, Dogfood Verify CLI, Temporal Conformance, Build Docs, two opt-in harnesses) are path-filtered for a diff that touches no ui/ and no docs.
  • Dogfood battery: n/a, no ! in the title and no narrowing arm. Right.
  • Premise comment 6409947695: honoured, the dispatch's corrected done-when (no candidateObjectName definition plus a shared-function pin) is what the diff delivers, modulo the miscounted grep in item 5 above.

Implemented-by: claude/project-thread-xsr2kc
Reviewed-by: session_01RyMY3prh9GRva1Tt37Di3i

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1691585fa6ee4047eb9bb46bc06952df1cfcf147
Local-runs: none
Scope: packages/spec/src/ui/anonymous-form-intake.ts + packages/metadata-core/src/anonymous-form-intake.ts, packages/metadata-core/src/anonymous-form-intake.test.ts, packages/lint/src/validate-public-form-file-fields.ts, packages/lint/src/validate-public-form-file-fields.test.ts, packages/lint/src/view-walk.ts (viewObjectName, main), packages/lint/src/authoring-rules.ts:1591 (the rule's stack-level caller, main), packages/lint/src/lint-view-refs.ts:185 (main), packages/rest/src/form-routes.ts:142, packages/rest/src/rest-server.ts:5889, packages/metadata-protocol/src/runtime-authoring-gate.ts:436 (main, the metadata-core consumers), packages/spec/src/ui/index.ts:99 and packages/metadata-core/src/index.ts:135 (the two barrels, main), packages/spec/src/ui/view.zod.ts (FormViewSchema and ViewSchema key shape, main), packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, .changeset/spec-form-object-name-shared.md. Remainder for the lane seat's ACCEPT at TIER_DEFAULT: none, every path of the diff is read here.

Inputs: card #25756 (body, comments 6394618326, 6409947695, 6410190296, 6411223236), PR #25979 body, file list and net diff against main at the head, the 48 check-runs on the head, and the prior record 6411696416 on head 7a6a2de as a prior record only. Read-only: nothing built, run or re-run. The head moved from 7a6a2de by exactly one commit, 1691585, which changes comment lines alone in packages/metadata-core/src/anonymous-form-intake.ts (+5/-2, compare 7a6a2de...1691585: ahead 1, one file); the whole head is judged below, not only the delta.

① Derived judgments

  1. Public surface, @objectstack/spec/ui: one new export, anonymousFormObjectName(view, form) — right. The function body is byte-for-byte the former metadata-core definition (f.data?.object ?? v.list?.data?.object ?? v.form?.data?.object ?? v.object, same non-object guards on both arguments), so the doors' rung order is unchanged. The ui barrel (packages/spec/src/ui/index.ts:99, export * of the module) carries it without an edit; api-surface/ui.json and export-origins/ui.json each gain exactly that one line, origin src/ui/anonymous-form-intake.ts. The module keeps its stated contract (pure functions, no imports, no module-load work), and the rewritten docblock no longer claims the function stays in metadata-core. Spec property liveness, Build Core and Lint & Repo Gates are success on the head.

  2. @objectstack/metadata-core: the name becomes a re-export of the same binding — right, and pinned as identity. The export name and signature are kept, so the three server callers (rest form-routes.ts:142, rest-server.ts:5889, metadata-protocol runtime-authoring-gate.ts:436) are untouched and still import from metadata-core, resolved through the package barrel's export * at index.ts:135. The identity test now holds anonymousFormObjectName to be the spec binding on the module and on the barrel (toBe, not a wrapper), the only form in which "one copy" is checkable. The comment-only commit 1691585 brings the module docblock and the re-export comment into line with the block, which now carries the object-name half as well as the candidates half; both sentences read as true on this tree. The staleness noted in the prior record is closed.

  3. @objectstack/lint accept set, parsed stacks: unchanged — right. FormViewSchema (view.zod.ts:4526) is a strict object carrying data and no object or objectName; the container ViewSchema (view.zod.ts:5077) is strict with object, list and form and no data. On a parsed body the retired helper and the shared function cannot disagree, the rule's fixtures all bind through data.object or the view's object, and Test Core 1 to 8 of 8 are success on the head.

  4. @objectstack/lint accept set, raw input (authoring-rules.ts:1591 hands the rule the unparsed stack): a stated change, in the card's ordered direction — right, with an incomplete statement. The retired candidateObjectName read each rung through viewObjectName (view-walk.ts:180: objectName ?? object ?? data.object, strings only, empty skipped). The shared function reads only the doors' four keys. So on raw input the rule stops honouring, at every rung: objectName; a bare object on the form, the list block or the default form block; and view.data.object. It also no longer steps past an empty or non-string value: a truthy non-string now short-circuits the chain and an empty string is returned as is, and in both cases objectName ? fieldTypes.get(objectName) : undefined leaves the form unjudged, which is what the doors do (if (!objectName) continue). Every binding dropped is one the doors never resolve, so the rule now refuses exactly the forms a visitor can reach, as the card orders; the lint accept set on raw input grows and never shrinks. The PR body names objectName and the empty or non-string case but not the bare object spellings and view.data.object the card itself lists; the card's own measurement (0 producers on examples, platform-objects, HotCRM) covers them, so the omission is a statement gap, not a behaviour the PR hides. Non-blocking. viewObjectName keeps its other two callers (validate-form-layout, validate-translatable-sections), so dropping the import leaves no dead export.

  5. The done-when: one rung order, pinned — right; one measurement line is miscounted. The only definition of the rung order outside the spec module is gone (the lint helper), and the lint pin reads the rule's source: imports anonymousFormObjectName from @objectstack/spec/ui, no candidateObjectName, no viewObjectName. The rule's header docblock at the head names no metadata-core copy either. The corrected check in 6409947695 (v.list?.data?.object hits the spec definition plus lint-view-refs.ts:185, an unrelated candidate list) reads as the premise said. The PR body's and the os-dev report's "git grep candidateObjectName -- packages: 0 hits" is false as a count on this tree: the new pin line expect(source).not.toContain('candidateObjectName') is itself one hit. The symbol has no definition and no call, which is what the measurement was meant to prove. Non-blocking.

② Semver level

Changeset .changeset/spec-form-object-name-shared.md: @objectstack/spec, @objectstack/metadata-core, @objectstack/lint at patch, with Clause-②: yes (widening) and Metadata-protocol: none as line-initial declarations in both the changeset and the PR body. Right. AGENTS.md Post-Task Checklist item 3: the level follows the Metadata-protocol kind alone, none takes patch, and a non-protocol widening ships patch. Metadata-protocol: none holds: no key, value or type an author writes changes, and the strict schemas are untouched. Clause-②: yes (widening) holds: one new named export on @objectstack/spec/ui, nothing removed or renamed (metadata-core keeps the name and signature as the same binding), and the lint change on raw input refuses fewer bodies, never more, so no published accept set narrows. Check Changeset and Governed Surface Queue Guard: success on the head.

③ Boundary flags

  • open_questions: none declared in 6411223236. None found.
  • Deviation 1 (no HTML-comment attribution marker in the PR body): accepted, the sanitizer rule wins; the italic requested-by line and project thread link are present.
  • Deviation 2 (edits on the platform-assigned thread branch in the primary checkout, no separate worktree): accepted, the branch is the one the Claim comment 6410190296 names and The card this PR closes must claim this branch is success.
  • Deviation 3 (no reviewer request): accepted, the dispatch allowed no MCP write.
  • The dev's local gate and test readings were taken on tree 7a6a2de, one comment-only commit behind this head. The head is covered by its own check-runs: of the 48 on 1691585 none failed, 40 are success (TypeScript Type Check and its four sub-jobs, Lint & Repo Gates and its seven sub-jobs, Build Core, Build & E2E UI, Type Check UI, Governed Surface Queue Guard, Check Changeset, Check PR Size, Dogfood Regression Gate, Spec property liveness, Test Core and its eight shards, the three PR-claim guards, Auto Label, docs link and drift flags), and the 8 skipped (Lint UI, Test UI, Dogfood Verify CLI, Temporal Conformance, Build Docs, the dogfood shard matrix, two opt-in harnesses) are path-filtered or opt-in for a diff that touches no ui/ and no docs.
  • Eight gate families the dev marked NOT MEASURED locally (engine-split-ratio, plugin-teardown-shape, skill-examples, dual-build-cjs-loads, i18n x3, lean-entry-closure, dts-closure): CI owns them, and Lint & Repo Gates, Build Core and TypeScript Type Check are success on the head.
  • Dogfood battery: n/a, no ! in the title and no narrowing arm. Right.
  • Premise comment 6409947695: honoured, the dispatch's corrected done-when (no candidateObjectName definition plus a shared-function pin) is what the diff delivers, modulo the miscounted grep in item 5 above.

Implemented-by: claude/project-thread-xsr2kc
Reviewed-by: session_01RyMY3prh9GRva1Tt37Di3i

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 2 · 2026-10-26T09:10Z · head 1691585fa6ee4047eb9bb46bc06952df1cfcf147

  1. Shape. Draft, base main, closes spec(ui): the anonymous-form object rung order is spelled twice (metadata-core anonymousFormObjectName and lint's public-form-file-field); keep one copy in @objectstack/spec/ui #25756. 8 files, +53/−34. check-governed-merges --pr 25979: NOT governed (0 of 8 paths hit the register).
  2. Premise and stop conditions. The premise held: the rung order was typed twice (metadata-core anonymousFormObjectName, lint candidateObjectName) and now lives once in @objectstack/spec/ui. The three server callers are untouched and still import through metadata-core, which re-exports the same binding (identity-pinned with toBe). The landing point is as ordered.
  3. Gates (check-runs pinned to this head). 48 runs: 40 success, 8 skipped, 0 failed. check-expected-skips --pr 25979: OK.
  4. Contract review. PASS at CONTRACT_REVIEW_TIER on this head, comment 6412295389. The earlier PASS (6411696416) was on 7a6a2de4; the head moved by one comment-only commit in packages/metadata-core/src/anonymous-form-intake.ts, which closes that record's staleness note. The whole head was re-judged.
  5. Widening. Clause-②: yes (widening) and Metadata-protocol: none on the PR body and the changeset; patch ×3 (spec, metadata-core, lint). The accept set on parsed stacks is unchanged; on raw input it only grows. Dogfood n/a.
  6. PR-body corrections (recorded here, the body is not edited).
    • Behaviour effect under-states what the rule stops reading. Besides objectName and an empty or non-string object, the rule also no longer reads a bare object on the form, list and form-block shapes, nor view.data.object. The doors never resolve any of these, and the card's measurement found 0 producers for each.
    • Measurements: git grep candidateObjectName -- packages returns 1 hit, not 0: the new negative pin's not.toContain line in the lint test. No definition or call remains, which is what the check meant.
  7. Landing. Ready plus auto-merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 09:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 09:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

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

    @objectstack/cli:test:  FAIL   unit  test/hook-timeout-override-refusal.test.ts > #18788 a CLI timeout override that cannot reach a project is refused, not passed > ⭐ CONTROL — a run naming no overrid
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected 1 to be +0 // Object.is equality
    

↳ 失败原因 是判读的关键:超时(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,按失败测试文件聚合):

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

历史信号:

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

分诊清单:

  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

Queue ejection · domain:spec seat 2

The merge-queue CI run 40298562907 failed one test in Test Core (2/8): packages/cli/test/hook-timeout-override-refusal.test.ts › CONTROL (runVitestList() exited 1, expected 0). This PR does not touch packages/cli or the hook-timeout path; the same test passed on this exact head (1691585f, PR CI green), and main's latest push CI (c16c00f93) is green. Re-queued once by re-arming auto-merge; a second failure is treated as real.

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/s tests tooling

Projects

None yet

2 participants