Skip to content

feat(spec): a record:path block may opt in to allowStageChange, so a stage can be pressed to move the record there - #28198

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-asm4hj
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-asm4hj

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #28061

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — skills/objectstack-ui/rules/pages.md teaches record:path as statusField + stages and every taught use stays true; teaching allowStageChange waits for the console half that makes it do something, so the skill line belongs to that follow-up.

维护者速读

  • 改了什么:record:path 区块的 spec(RecordPathProps)新增一个可选布尔键 allowStageChange,作者打开后,路径上的阶段可以被点击来请求把记录改到该状态;不写或写 false 时仍是今天的只读进度条。
  • 为什么改:HotCRM 销售测试反馈(console: suggestions from a full HotCRM browser pass on 18.2.0: clickable path steps, chart labels and empty charts, record-page Delete, list inline edit without edit rights, public web forms, and phone layouts #27355 第 1 行):想把商机推进到下一阶段只能打开编辑表单改下拉框。本 PR 只做 spec 半边,声明这个开关和它的含义;控制台渲染(plugin-detail 的 record-path.tsx)是另一张 domain:ui 卡。
  • 风险与回滚:纯加宽,没有 schema 默认值,已有页面解析结果完全不变;服务端不改,状态机校验和字段编辑权限照旧由普通的记录更新判定。回滚即 revert 本 PR。
  • 先看:packages/spec/src/ui/component.zod.ts 的 allowStageChange(describe 写明含义)、packages/spec/src/ui/component.test.ts 新增的三条测试、ui/apps/console/src/__tests__/registry-inputs-spec-parity.test.ts 新增的一条 UNPUBLISHED_EXEMPTIONS 豁免。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/ui/component.test.ts -t allowStageChange,预期 3 条全部通过。
  • 需要你做的:无,席位落地(等契约复审记录)。

What changes

  • RecordPathProps (ComponentPropsMap['record:path']) declares allowStageChange: z.boolean().optional(), with no schema default (the RecordChatterProps reason: a default would write false onto every parsed component). Absent or false is the read-only progress rail, as today. The .describe() and docblock state the console contract from the card: with the key on, each stage the reader may move the record to is a button, offered only to a reader who may edit statusField; a press confirms, then saves statusField through the same record update record:details uses, with the record's concurrency token, so the object's state_machine validation (ADR-0020) and field edit rights decide; a refusal is shown in place. No server change.
  • Regenerated: packages/spec/authorable-surface/ui.json (+1 row, ui/RecordPathProps:allowStageChange) and content/docs/references/ui/component.mdx (+1 row).
  • ui/apps/console/src/__tests__/registry-inputs-spec-parity.test.ts: one UNPUBLISHED_EXEMPTIONS entry, 'record:path.allowStageChange', citing objectstack#28061. Without it the gate's reverse direction (a key the spec declares must be published in the block's registry inputs) reds Test UI, because ui/ consumes this PR's spec through workspace:^. The key is not published because the path renderer does not read it yet, the file's own bar for an entry. When the console half publishes the input, the file's stale check demands the entry's deletion in that change. Seat ruling A (this card's thread) widened the file surface by exactly this entry; nothing else under ui/ changes.
  • Changeset 28061-record-path-allow-stage-change.md: @objectstack/spec minor, both declaration lines.

Console follow-up for the seat to file (domain:ui): ui/packages/plugin-detail/src/renderers/record-path.tsx honours allowStageChange (stage buttons gated by the reader's edit verdict on statusField, confirm, write through the record:details update path with the concurrency token, refusal shown in place), the record:path registration publishes the input, the UNPUBLISHED_EXEMPTIONS entry above is deleted, and skills/objectstack-ui/rules/pages.md teaches the key.

Tests

packages/spec/src/ui/component.test.ts, new block "RecordPathProps allowStageChange — opt-in, off unless authored":

  • accepts the opt-in and keeps the authored value: safeParse succeeds and allowStageChange is true.
  • leaves an existing path unchanged: no default is written (control): the showcase-shaped statusField + stages input parses to an object equal to the input, with no allowStageChange key.
  • refuses a non-boolean at the key, with the code and path: 'yes' gives exactly one issue, code: 'invalid_type', path: ['allowStageChange'].
Local runs
  • pnpm --filter @objectstack/spec typecheck and pnpm --filter @objectstack/spec exec vitest run on d9b26f02, under the verify lock: VERDICT command-exit 0; Test Files 843 passed (843), Tests 23972 passed, 1 todo.
  • pnpm --dir ui exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts on d9b26f02: Test Files 1 passed, Tests 261 passed (261).
  • Control for the exemption entry: with the 'record:path.allowStageChange' entry removed (grep -c 0 after the edit), the same file fails exactly one case, "record:path publishes every top-level key its spec props schema declares" (expected [ 'allowStageChange' ] to deeply equal []), 1 failed and 260 passed. Restored with git checkout HEAD --; git diff HEAD empty.
  • After merging origin/main (4 commits, packages/spec changes in the automation domain only), on 526055d6: workspace build exit 0, pnpm --filter @objectstack/spec check:generated "All 14 generated artifacts are up to date", src/ui/component.test.ts 353/353 and the parity file 261/261.
  • Dogfood not run: no ! in the title and no (narrowing) arm.

Acceptance notes

  • The key name is the card's "name for review"; the contract review judges it. It is named for the act (changing the stage), not the widget.
  • The order's file surface was packages/spec only. The ui parity gate made that impossible without a red Test UI; reported to the seat before any code and ruled A (one exemption entry).
  • content/docs/ui/components/record.mdx is generated from the registry inputs, so it does not list the key until the console half publishes it.

Verification

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 106 commands on d9b26f02. All 106 ran with stdin from /dev/null, exit codes captured before any pipe: 106 exited 0. Commands 60 to 106 first ran while a dts-less build of the console's dependencies was rewriting dist/; they were re-run after a full declaration build and all 47 exited 0 again.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE: "106 derived famil(ies) accounted for — 106 run, 0 NOT-MEASURED".
  • The gate sweep was taken on d9b26f02; the merge with origin/main (526055d6) brought in 4 commits, none touching packages/spec/src/ui or ui/.
  • test:repo not run locally (CI's Test Core).
  • CI on this PR: not yet read; the seat reads the check-runs.

Generated by Claude Code

A record:path block may opt in to changing the record's stage by pressing
a stage on the path. Off unless authored, no schema default; the spec
declares the opt-in and its meaning only. The console parity gate books
the key as unpublished until the path renderer reads it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibxNQzFQH8nBAaoSUkx
… allowStageChange

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

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/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
  • 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 58 of 210 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 — 168 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 70209b892a9baa5849cd887bf14107a28d668c7f → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 70209b892a9baa5849cd887bf14107a28d668c7f

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head e0c5c2fd6f7e43e12e7bb8055b5ec55a50b9db7c · 2026-10-30T16:48Z

At CONTRACT_REVIEW_TIER, by the maintainer's dedicated contract-review thread for the lanes (「开一个新的 fable thread,30分钟定时,负责车道的契约复审」, 2026-10-30T15:39Z), on the lane seat's behalf; the reviewer is not the drafter (session_01L9qibxNQzFQH8nBAaoSUkx).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e0c5c2fd6f7e43e12e7bb8055b5ec55a50b9db7c
Local-runs: none
Scope: packages/spec/src/ui/component.zod.ts + ui/apps/console/src/tests/registry-inputs-spec-parity.test.ts (the gate the new key trips, read as one change with it), and the cited code read on the head: packages/spec/src/ui/component.zod.ts (RecordPathProps L2054-2105 strictObject, RecordChatterProps L2032 and the maxVisible no-default precedent L1504), ui/packages/plugin-detail/src/renderers/record-path.tsx (L133, L271-290, the readout with no handler), ui/apps/console/src/tests/registry-inputs-spec-parity.test.ts (the reverse direction and the carries no stale unpublished-key exemption case, L1181-1260), packages/spec/liveness/field.json L97 (stages[].terminal / option kind precedent), packages/spec/liveness/object.json L60.

Inputs: card #28061 body and its three comments (claim 6526133189, claim correction 6526163273 with the seat's ruling A, os-dev-report 6527812635); PR #28198 body, 6-file list and the diff against main (+74/-0); the check-runs on e0c5c2fd (read 2026-10-30T17:11Z): 65 runs over 65 names, all completed, 58 success, 7 skipped, 0 failure; among the successes Auto Label, Build & E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Spec property liveness, Test Core, Test UI, Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter, Test UI 1/16-16/16, Test Core 1/8-8/8. Author user.login is objectstack-fleet[bot], so no login note is owed. The reviewer is not the drafter (session_01L9qibxNQzFQH8nBAaoSUkx).

① Derived judgments

  1. RecordPathProps (component.zod.ts L2054, a strictObject with statusField, stages[], aria) gains one optional key allowStageChange: z.boolean() with no schema default. The accept set widens by exactly this key on record:path and nowhere else (ComponentPropsMap['record:path'] is the only consumer of the schema); a non-boolean is refused at path allowStageChange with invalid_type; an existing path parses to an object equal to its input with no allowStageChange key written. Right, and the no-default choice is the file's own rule (RecordChatterProps, maxVisible L1504: a default turns "the author said nothing" into a declared value and inverts a renderer merge). Pinned by the three new component.test.ts cases (accept, no default written, refusal with code and path).
  2. Generated surfaces: authorable-surface/ui.json adds ui/RecordPathProps:allowStageChange; content/docs/references/ui/component.mdx adds the row with the .describe() text. check:generated was 14/14 on the dev's merged head and Lint · repo & tooling gates runs it on this one. Right.
  3. registry-inputs-spec-parity.test.ts: one UNPUBLISHED_EXEMPTIONS entry record:path.allowStageChange, with the measured reason (record-path.tsx draws a read-only rail and reads no such key, L271-290). Without it the reverse direction (record:path publishes every top-level key its spec props schema declares) reds Test UI, because ui/ consumes this tree's spec through workspace:^; the dev's control run shows exactly that one failure. The entry is self-clearing: the file's carries no stale unpublished-key exemption case (L1181-1260) reports it STALE the moment the console registration publishes the input, so the console card must delete it in the same change. This is the file's own documented bar for an entry, and the seat's claim correction 6526163273 widened the file surface by exactly this entry. Right. No other ui/** path changes.
  4. No liveness ledger carries component props (packages/spec/liveness/ has no component ledger; the only record:path mentions are notes on field.json L97 and object.json L60), so no planned row is owed here, unlike a view or report key. The contract-first precedent on this same block is stages[].terminal / option kind (Spec: a select option can declare its lifecycle kind (open / won / lost) so stage paths draw terminal states apart #23809, field.json L97: declared ahead of the renderer under the maintainer's ruling). Right.
  5. No server change and no new refusal: the describe says the press writes statusField through the ordinary record update, so ADR-0020 state_machine validation and field edit rights judge it as any other edit. Nothing in the diff touches the engine, runtime or permissions, so the sentence is a description of the existing update path, not a new promise. Right.

② Semver level

.changeset/28061-record-path-allow-stage-change.md: @objectstack/spec minor, one optional key on a published schema, nothing removed or tightened. Clause-②: yes (widening) and Metadata-protocol: added (record:path allowStageChange) sit line-initial on the PR body and the changeset. Consistent.

③ Boundary flags

  • open_questions: none declared.
  • Deviation 1 (file surface widened by one UNPUBLISHED_EXEMPTIONS entry): answered in ①.3; reported before code and ruled A by the seat (6526163273).
  • Deviation 2 (gates 60-106 re-run after a dts-less build overlapped): both sweeps exit 0; a reporting note, not a contract matter.
  • Deviation 3 (test:repo not run locally): CI's Test Core, read above.
  • Skills-docs: none with the reason (every taught record:path use stays true; the key is taught with the console half): accepted, the skill would otherwise teach a control that does nothing.
  • Console follow-up (domain:ui: record-path.tsx honours the key, the registration publishes the input, the exemption entry is deleted, pages.md teaches it): the seat's card, as the PR body names it. The exemption's stale check is what makes landing the spec half alone sound.
  • Serial constraint: open PR feat(spec): a calendar view can declare the date it opens on, calendar.initialDate #28138 edits the same file far from RecordPathProps (claim 6526133189); no overlap in the hunks.
  • Author: objectstack-fleet[bot]; no login note.

④ Design judgment

packages/spec/src/ui/component.zod.ts against the card (#28061: an opt-in key on RecordPathProps, off by default, name for review; the console half to follow) and its first consumer:

  • The key holds against its consumer. record-path.tsx (L271-290) registers record:path, reads statusField and stages off ComponentPropsMap['record:path'], and draws a readout with no handler; its channel (useRecordContext) exposes no field write. The console card is therefore a renderer change plus an input publication, with the spec row already typed boolean | undefined, which is the smallest contract that can carry the feature.
  • Name. allowStageChange names the act the reader is allowed (changing the stage), matches the allow* opt-in spelling the component surface already uses, and does not collide with any key on RecordPathProps or the shared AriaPropsSchema. The card left the name for review; it is a sound choice.
  • Off unless authored, no default. This keeps The record page's lifecycle path looks like a control and is inert: stageField renders as a segmented button group with no handler, no cursor, and no tab stop #11243's inert look for every existing page, and the no-default rule is the file's own (RecordChatterProps, maxVisible). Right.
  • Meaning is complete but not over-specified. The describe fixes what the reader sees (pressable stages only for a reader who may edit statusField), the confirm, the write path (the record:details update with the concurrency token) and the refusal surface (in place). Each of those is a property of the existing update path, so the console half cannot honour the key in a way that bypasses state_machine validation without contradicting the spec. Right.
  • Strictness holds. RecordPathProps stays a strictObject; an undeclared key is still refused, and the new key's refusal carries invalid_type at its own path (pinned).
  • Nothing is retroactive. Existing pages carry no allowStageChange; their parse and drawing are byte-identical.

Should-fix (none blocks the contract):

  • S1 The describe text is a 70-word sentence that repeats the docblock; the reference table row (component.mdx) inherits it whole. A shorter describe (the opt-in and "off unless authored") with the behaviour kept in the docblock would read better in the generated reference; can ride the console card.

Implemented-by: claude/project-thread-asm4hj
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm

维护者速读

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · 2026-10-30T17:15Z

Head e0c5c2fd6f7e43e12e7bb8055b5ec55a50b9db7c. Card #28061, claim 6526133189, claim correction 6526163273 (the ui surface), os-dev-report 6527812635. The maintainer's ruling, verbatim: 「按 pm-dispatch 审过的pr你可以自主落地」.

  • Shape: draft to main, made by the relay, with Fixes #28061. Clause-②: yes (widening) and Metadata-protocol: added sit line-initial. The changeset is @objectstack/spec minor.
  • Contract review: record 6528734955 from the contract-review lane at CONTRACT_REVIEW_TIER. It is a PASS with no must-fix. Its should-fix S1 (the describe is long and repeats the docblock) has no reader-facing defect and rides console card Record path: a stage press requests that status when record:path sets allowStageChange (console half of #28061) #28199.
  • Diff read by the seat: allowStageChange: z.boolean().optional(), with no schema default, which is the RecordChatterProps reason. The docblock and describe say that absent or false is today's read-only rail. The ui/ surface is exactly the ruled single UNPUBLISHED_EXEMPTIONS entry record:path.allowStageChange. It cites objectstack#28061, and the stale check makes the console card delete it.
  • Head move: 526055d6 → e0c5c2fd was a merge of main only, to pick up test(ui): pin the 19th flow value-ledger entry, connector_action input.* #28193's ledger-pin fix. The PR's own diff is unchanged, and the record is on e0c5c2fd.
  • Gates: the newest run of every check on this head is success or skipped. The Check Changeset re-run from the label pull is the one pending, and landing waits for it. check-governed-merges --pr 28198: NOT governed, +74/−0.
  • Out of scope: the console half is filed as Record path: a stage press requests that status when record:path sets allowStageChange (console half of #28061) #28199. Skills-docs: reads none.
  • Landing: pr_ready + automerge_enable once the Check Changeset re-run is green.

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Test UI (5/16) — 失败步骤: Run the ui tests (shard 5/16)

    × the node cell and the Problems list both name the blank expression, in the same words 2521ms
    FAIL   dom  packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx > Automations page — Problems and the node agree on a blank expression (objectstack#27799) > the n
      ↳ 失败原因: AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''
    

↳ 失败原因 是判读的关键:超时(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 队列共有 7 个失败构建(不含本次)。

分诊清单:

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

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Test UI (3/16) — 失败步骤: (无步骤级结论)

    × the node cell and the Problems list both name the blank expression, in the same words 2369ms
    FAIL   dom  packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx > Automations page — Problems and the node agree on a blank expression (objectstack#27799) > the n
      ↳ 失败原因: AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''
    
  • Test UI — 失败步骤: (无步骤级结论)(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(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 队列共有 9 个失败构建(不含本次)。

分诊清单:

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

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Test UI (1/16) — 失败步骤: (无步骤级结论)

    × the node cell and the Problems list both name the blank expression, in the same words 2772ms
    FAIL   dom  packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx > Automations page — Problems and the node agree on a blank expression (objectstack#27799) > the n
      ↳ 失败原因: AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''
    
  • Test UI — 失败步骤: (无步骤级结论)(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(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 已在队列失败 2 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 12 个失败构建(不含本次)。

分诊清单:

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

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

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Test UI (1/16) — 失败步骤: (无步骤级结论)

    × the node cell and the Problems list both name the blank expression, in the same words 2618ms
    FAIL   dom  packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx > Automations page — Problems and the node agree on a blank expression (objectstack#27799) > the n
      ↳ 失败原因: AssertionError: expected 'flows[0].nodes[1].config.fields.id' to contain 'node \'write\''
    
  • Test UI — 失败步骤: (无步骤级结论)(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(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 已在队列失败 3 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 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 fb4e0bb Oct 30, 2026
69 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-asm4hj branch October 30, 2026 20:25
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

Development

Successfully merging this pull request may close these issues.

Record path: a stage can be clicked to request that status, judged by the field's validation and state machine

1 participant