Skip to content

fix(formula,lint,mcp): a fail-closed date verdict says the write is refused, and a time-relative sweep's run-fails verdict names no firing write - #30492

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

维护者速读

  • 改了什么:两处判词改成说真话。一是失败即拒的校验规则(及字段 requiredWhen/readonlyWhen、选项 visibleWhen、hook 条件)里对日期做加减,判词不再说「字段静默得到 null」,改说「写入被拒」,新码 date-arithmetic-fail-closed / date-text-arithmetic-fail-closed。二是定时相对扫描流程的条件里文本和数字比大小,判词不再说「触发它的写入照常落库」(扫描没有写入),改说「这一行的运行失败,别的什么都不发生」,新码 type-mismatch-sweep-run-fails。
  • 为什么改:两句话说的都是该位置不会发生的事:校验规则出错会拒写,不会得到 null;扫描由定时任务逐行启动,不是写入触发的。
  • 风险与回滚:分级不变,发布门不收也不放,只换文案和码。按旧码 date-arithmetic / date-text-arithmetic 识别失败即拒站点、按 type-mismatch-run-fails 识别扫描流程的使用方需改认新码。公式字段、advisory 校验规则、记录触发流程原码原句不动。回滚即 revert,无数据迁移。
  • 先看:packages/formula/src/validate.ts(dateArm / dateArmFault 和 TYPE_MISMATCH_SWEEP_ARM)、packages/lint/src/validate-expressions.ts(FlowBareNames.timeRelativeSweep)、packages/mcp/src/mcp-http-tools.ts(一行传键)。
  • 两分钟验证:pnpm --filter @objectstack/lint exec vitest run src/validate-expressions.test.ts -t 30250,预期 2 条通过:扫描流程判词不含 still lands,记录触发对照仍含;失败即拒规则的日期判词说写入被拒,公式字段对照仍说 null。
  • 需要你做的:无,席位落地。

Fixes #30250

Clause-②: yes (widening) — three codes join @objectstack/formula's exported ExpressionRefusalCode / EXPRESSION_REFUSAL_CODES (date-arithmetic-fail-closed, date-text-arithmetic-fail-closed, type-mismatch-sweep-run-fails) and the optional key timeRelativeSweep joins the exported ExprSchemaHint. A fail-closed site's date verdicts move to the first two from date-arithmetic / date-text-arithmetic, and a time-relative sweep's type-mismatch verdict moves to the third from type-mismatch-run-fails (a consumer keyed to the old codes at those sites re-keys). Grading is unchanged at every site; what is refused, warned or passed is unchanged.
Metadata-protocol: none — what an author can write is unchanged, and os validate reports no expression refusal code: its findings come from @objectstack/lint's ExprIssue (where, message, source, severity, path), which carries no code. Only the message text changes there.
Skills-docs: none — skills/** grep for still lands and silently evaluates to null hits skills/objectstack-formula/SKILL.md:407 and skills/objectstack-formula/evals/cel-predicates-formulas.json:87, both about a record-triggered flow's null-guard edge, which still lands the write, so both stay true. The one hand-written doc sentence this PR makes false is fixed here: content/docs/data-modeling/formulas.mdx (type-soundness row).

What changes

Measured on main at c5800818 before the change, and again after origin/main was merged.

How each site learns its scope. (1) A fail-closed site already says so: @objectstack/lint's faultOutcomeOf ledger and MCP validate_expression (site validation) pass faultOutcome: 'fail-closed' at a record-scoped site, and a formula field passes none. So the date verdicts read it with no new signal. (2) A time-relative sweep did not say so. The ledger answers run-fails for a record-triggered flow and for a sweep of the typed object alike, so @objectstack/formula could not tell them apart. The new optional ExprSchemaHint.timeRelativeSweep carries that one fact. It is read only under faultOutcome: 'run-fails' at a 'flattened' site.

site before now
fail-closed record site (error validation rule, field requiredWhen / readonlyWhen, option visibleWhen, hook condition): date arithmetic date-arithmetic / date-text-arithmetic, error, "this faults at runtime, so the field silently evaluates to null." date-arithmetic-fail-closed / date-text-arithmetic-fail-closed, error: "this faults at runtime, and here an expression that cannot be evaluated fails closed: the write (or the operation a hook guards) is refused, not skipped (it does not read null)."
formula field, display predicate, advisory (warning / info) validation rule: date arithmetic date-arithmetic / date-text-arithmetic unchanged, code and sentence
time-relative sweep of the typed object (run-fails): text or boolean against a number type-mismatch-run-fails, error, "… the flow stops at this condition, the write that fired it still lands, and the work the flow exists to do is not done." type-mismatch-sweep-run-fails, error: "… a flow condition that cannot be evaluated fails the run: the time-relative sweep starts one run per row it finds, the run for this row stops at this condition and the work the flow exists to do is not done for that row, and nothing else happens (the sweep logs the failure and goes on to the next row)."
record-triggered flow (run-fails) type-mismatch-run-fails unchanged, code and sentence

Why new codes. A code names one message template (expression-refusal.ts, the contract), and each new sentence is a new template, as #30015 and #30016 did. The sweep sentence is read off packages/triggers/trigger-schedule/src/time-relative-trigger.ts: each matched row is launched with { record: row, event: 'time_relative' }, and a failed run is caught per row, logged at error and counted, and the sweep goes on.

The nullable-comparison sentence (validate-null-guards.ts OUTCOME_CLAUSE['run-fails']) is unchanged. It is never served on a sweep: validate-expressions.ts runs the null-guard twin only where triggerKind === 'record_change' (nullGuardObject), and no other caller passes run-fails to checkNullGuards / nullGuardMessage. On a record-triggered flow it is true. The new lint pin asserts that no finding on a sweep says "still lands", and that the record-triggered control still does.

Routing. @objectstack/lint: FlowBareNames gains timeRelativeSweep (triggerKind === 'time_relative'), handed to the validator at every flow site that already gets the bare-name facts (start, edge, decision, inert node conditions). @objectstack/mcp: validate_expression with site: 'flow_condition' and triggerKind: 'time_relative' passes the same key, so the tool's answer stays the gate's (the gate-parity test compares messages). The date verdicts' -run-fails sentence already names no write, so it is served on a sweep as it is.

Tests

  • packages/formula/src/validate.test.ts:
    • new: a fail-closed record site's record.start_date + 1 > 3 and record.end_date - record.start_date > duration("24h") get the -fail-closed codes and the refused-write sentence, and not "evaluates to null". Controls: a formula field (value, no outcome) and an advisory rule (fail-open) keep date-arithmetic / date-text-arithmetic and the null sentence;
    • new: name > 5 and record.name > 5 under run-fails + timeRelativeSweep get type-mismatch-sweep-run-fails, the per-row sentence, and no "still lands" / "write that fired". With no outcome the key changes nothing (type-mismatch-run-may-fail). A sweep's date verdict stays date-arithmetic-run-fails;
    • changed: the expression gate: a date-arithmetic verdict in a flow condition still says the value "silently evaluates to null", though the run fails #30016 test's last control asserted that a fail-closed rule says "silently evaluates to null", which is the defect. It now asserts that a formula field keeps that sentence.
    • unchanged: errors with the run-fails wording where the caller declares a flow condition (#29926) (the record-triggered "still lands" pin, :801 on main).
  • packages/formula/src/expression-refusal-codes.test.ts: a PINS entry for each new code (exact params and message). SWEEP_SCHEMAS gains a fail-closed record schema with a date field and a flattened run-fails + timeRelativeSweep schema, so the sweep reaches all three codes and holds their params to their messages.
  • packages/lint/src/validate-expressions.test.ts:
  • packages/mcp/src/mcp-validate-expression.test.ts: unchanged. Its gate-parity rows for triggerKind: 'time_relative' compare the tool's messages with the gate's, so they pin the MCP half (ablation A2 below).
  • packages/objectql/src/publish-type-mismatch-fail-closed.test.ts: unchanged, 12/12 passed.
Suites (head fc42e4a, under os-verify-lock, shared box)
formula test   Test Files 64 passed (64)    Tests 1649 passed (1649)
lint test      Test Files 195 passed (195)  Tests 8034 passed (8034)
mcp test       Test Files 56 passed (56)    Tests 678 passed (678)
objectql src/publish-type-mismatch-fail-closed.test.ts   Tests 12 passed (12)
typecheck (tsc --noEmit + check:test-typecheck): formula 0, lint 0, mcp 0
red first, on main's validate.ts (head 81c14bac, pins only): formula 2 failed, lint 3 failed
Ablations (scripts/ablation-replace.mjs; each restored: blob == HEAD, git diff HEAD empty, dist marker absent after rebuild)
A1  formula: the sweep arm off (`scope === 'flattened' && String(timeRelativeSweep) === 'ablated-30250-a1'`),
    rebuilt, marker present in 2 built files (ablation-dist-preflight exit 0)
    formula × on a time-relative sweep … names no write (#30250) · × type-mismatch-sweep-run-fails (PINS) · × the SWEEP
            Tests 3 failed | 179 passed (182)
    lint    × refuses a type mismatch on a time-relative sweep … (#29959, #30026) · × names no firing write … (#30250)
            Tests 2 failed | 471 passed (473)
    mcp     Tests 38 passed (38): parity holds, both halves read the same ablated validator (A2 is the MCP pin)
A2  mcp: the sweep key not passed (`? { ablated30250a2: true } : {}`)
    × site flow_condition, triggerKind time_relative · × … sweepObject objectName · × … start names no objectName
    Tests 3 failed
A3  formula: the fail-closed date arm off (`String(faultOutcome) === 'ablated-30250-a3'`), rebuilt, marker in dist
    formula (src) × on a fail-closed site says the write is refused … (#30250) · × date-arithmetic-fail-closed
            · × date-text-arithmetic-fail-closed · × the SWEEP — Tests 4 failed | 178 passed (182)
    lint (dist) × says a fail-closed validation rule's date arithmetic refuses the write … (#30250)
            Tests 1 failed | 472 passed (473)

Acceptance notes

  • An advisory (warning / info) validation rule's date arithmetic still says "silently evaluates to null". There a fault skips the rule and the write proceeds (type-mismatch-fail-open's sentence), so "evaluates to null" is not true either. Out of this card's ruling (fail-closed only), left as is and pinned as a control. Reported to the seat as a finding in the same family.
  • content/docs/data-modeling/formulas.mdx date-arithmetic row says "this always faulted to null". That describes a formula's runtime, and this PR changes no runtime, so it is left as is.
  • Serial check. The open PRs touching packages/formula/src/validate.ts, packages/lint/src/validate-expressions.ts or packages/mcp/src/mcp-http-tools.ts were not enumerated by this dev (the seat's claim cleared them at 12:18Z). origin/main was merged once at fc42e4af with no conflict, and none of the 8 incoming commits touches these packages.

Verification

All at head fc42e4af (git rev-parse --short HEAD), the commit this PR opens on (origin/main merged once).

  • Full build after the merge: pnpm install --frozen-lockfile && turbo run build --filter='!@objectstack/docs' --concurrency=2, 119/119 tasks, VERDICT command-exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 88 derived families, each run on this head, all exit 0. check-plugin-teardown-shape.mjs --self-test first answered exit 3 because the shallow clone could not read its pinned fixture commit 621a4876; after fetching that commit it answered exit 0 ("48 cases pass"), and the record carries that run. --ran: "88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED". Among them check-adr-0087-registration --base origin/main, check-changeset-no-major, check-empty-changeset, check:doc-authoring, check:docs-transcript-drift, check:test-source-alias, check:cross-package-test-inputs, check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:nul-bytes, lint check:doc-formula-expressions.
  • pnpm lint (eslint . --no-inline-config): exit 0.
  • Full dogfood: not run. The title has no ! and Clause-② has no (narrowing) arm.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Utp6CbntgiC5D1vTyg2pBV

…lative sweep sentence (red)

Claude-Session: https://claude.ai/code/session_01Utp6CbntgiC5D1vTyg2pBV
Co-authored-by: Claude <noreply@anthropic.com>
…efused; a sweep's run-fails verdict names no firing write

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

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/formula, @objectstack/lint, @objectstack/mcp, touching 20 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via validateExpression (symbol, a top-level function), time_relative (literal, a string literal in registerObjectTools; a string literal in runStackExpressionPasses))
  • content/docs/data-modeling/formulas.mdx (via validateExpression (symbol, a top-level function))
  • content/docs/permissions/capabilities.mdx (via time_relative (literal, a string literal in registerObjectTools; a string literal in runStackExpressionPasses))

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

  • content/docs/releases/v16.mdx (via time_relative (literal, a string literal in registerObjectTools; a string literal in runStackExpressionPasses))
  • content/docs/releases/v17/17-5.mdx (via time_relative (literal, a string literal in registerObjectTools; a string literal in runStackExpressionPasses))

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
  • 1 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 — 25 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 0c7e95aff5996de2313b4668760eafee2e50172d → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 0c7e95aff5996de2313b4668760eafee2e50172d

⚠️ 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 0c7e95aff5996de2313b4668760eafee2e50172d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

Contract review

Served-tier: TIER_DEFAULT
Head-sha: fc42e4af8618938b74b1720f114c5931362dc2c4
Local-runs: none

① Derived judgments

  • Exported surface: widening, and correctly declared. Three codes join ExpressionRefusalCode / EXPRESSION_REFUSAL_CODES (CODE_TABLE in expression-refusal.ts): date-arithmetic-fail-closed, date-text-arithmetic-fail-closed and type-mismatch-sweep-run-fails. Each takes the params of the code it splits from. There is also one optional key on the exported ExprSchemaHint, timeRelativeSweep, documented as "absent ⇒ every verdict is exactly as before".
  • Grades unchanged. dateArm returns fail-closed only on a record-scoped site with faultOutcome: 'fail-closed', and the date verdict there was already an error. A formula field and a fail-open (advisory) rule keep date-arithmetic and the null sentence. The sweep arm is taken only when outcome === 'run-fails' && scope === 'flattened' && timeRelativeSweep, and every other outcome reads TYPE_MISMATCH_FAULT_ARMS as before.
  • What is refused is unchanged. The PR changes only which code and sentence a refusal carries. One code names one template, following the fix(lint)!: a flow condition's text-vs-number comparison gets a true verdict on every flow, record.x read and decision branch #30015 / expression gate: a date-arithmetic verdict in a flow condition still says the value "silently evaluates to null", though the run fails #30016 precedent.
  • Consumers: lint's flow walk sets the key on time_relative flows, and MCP's validate_expression passes the same key for flow_condition + triggerKind: time_relative, so the gate-parity test stays verbatim. The null-guard sentence (validate-null-guards.ts:770) is unchanged: it runs on record_change flows only, and a pin asserts that no sweep verdict says "still lands".

② Semver level

③ Boundary flags

  • Existing assertions: three assertions were rewritten because they pinned the defective sentence (listed in the dev report). That is accepted, since they pinned the bug.
  • Out of scope, a follow-up card from the seat: an advisory (fail-open) rule's date verdict still says "silently evaluates to null", while an advisory rule is skipped and the write proceeds.
  • Skills: skills/objectstack-formula/SKILL.md:407 and evals/cel-predicates-formulas.json:87 describe record-triggered flows and remain true.

Implemented-by: claude/project-thread-pd2xvn
Reviewed-by: session_01Utp6CYVV6fcAQrNTDQRvPb

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

席位核对 · ACCEPT

Head fc42e4af8 · 2026-11-05T13:29Z

  • Matches the order and the Claim amendment.
    • The fail-closed date verdicts say the write is refused, under date-arithmetic-fail-closed / date-text-arithmetic-fail-closed.
    • A time-relative sweep's run-fails verdict names no firing write, under type-mismatch-sweep-run-fails with ExprSchemaHint.timeRelativeSweep.
    • The null-guard half's premise did not hold, so it is pinned and left unchanged.
    • MCP passes the same key.
  • Governed: NOT governed (+334 / −40). Review: the declaration-face record (TIER_DEFAULT) above, PASS.
  • Declarations: Clause-②: yes (widening), Metadata-protocol: none (measured: os validate prints lint's ExprIssue, which carries no code), Skills-docs: none, and patch changesets, all consistent.
  • Follow-up: the advisory (fail-open) date sentence is filed as formula: an advisory (warning / info) validation rule's date verdict says the field 'silently evaluates to null', but the rule is skipped and the write proceeds #30494.

Marking ready and arming auto-merge. Main is red at the moment (p0 #30467): if the merge group bounces on that, this PR re-queues once it is fixed.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

维护者速读

  • 改了什么:两处报错文案改为说实话。第一处,失败即拒的位置(error 级校验规则、字段 requiredWhen/readonlyWhen、选项 visibleWhen、hook 条件)里做日期加减时,提示改说「写入被拒」,配新码 date-arithmetic-fail-closed / date-text-arithmetic-fail-closed。第二处,定时相对扫描流程的条件里拿文本和数字比大小时,提示不再说「触发它的写入照常落库」,改说「这一行的运行失败,其它都不受影响」,配新码 type-mismatch-sweep-run-fails。
  • 为什么改:旧文案说的都是该位置根本不会发生的事。校验规则出错时会拒写,不会得到 null;扫描是定时任务逐行启动的,不是写入触发的。
  • 风险与回滚:分级不变,发布门不收紧也不放宽,只换文案和码。按旧码识别这两类位置的使用方需要改认新码。公式字段、advisory 规则和记录触发流程的码和文案都不动。回滚就是 revert,不涉及数据迁移。
  • 先看:packages/formula/src/validate.ts 里的 dateArm / dateArmFault / TYPE_MISMATCH_SWEEP_ARM;packages/lint/src/validate-expressions.ts 里的 timeRelativeSweep;packages/mcp/src/mcp-http-tools.ts 里传键的那一行。
  • 两分钟验证:运行 pnpm --filter @objectstack/lint exec vitest run src/validate-expressions.test.ts -t 30250,预期 2 条通过:扫描流程的提示里不再有 still lands,记录触发流程的对照仍有。
  • 需要你做的:无,席位落地。

席位意见:同意落地。这是放宽型改动(新增码和可选键),declaration 面记录 PASS(6659206175),钉住 head fc42e4af8。advisory 规则的同类文案已另立跟进卡 #30494。

席位核对

Head fc42e4af8618938b74b1720f114c5931362dc2c4 · 2026-11-05T13:49Z

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb

Requeue · 2026-11-05T14:52Z

The merge group gh-readonly-queue/main/pr-30492-c045c341… was dequeued with CI_FAILURE on one job, Live E2E UI (run 42045320193, job 127306898079).

  • The failing test is studio-customize-journey.spec.ts:121. Its step "Customize a view" failed with apiRequestContext.get: read ECONNRESET on GET /api/v1/meta/view/showcase_task.in_progress.
  • The other 16 live specs passed, both before and after it in the same server run, so the server stayed up.
  • This PR changes refusal wording and codes in packages/formula / lint / mcp only. It touches no meta read, no view and no Studio path.
  • Live E2E UI is green on the PR head fc42e4af8.

This is not this PR's failure. Requeued once. If the same test fails again in the next group, this re-queue is the only one, and the failure is treated as real and investigated.

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Live E2E UI — 失败步骤: Run the live e2e specs(日志不可读,点进 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,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

  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 67d5d53 Nov 5, 2026
66 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-pd2xvn branch November 5, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant