Skip to content

fix(objectql,cli): os migrate stored-expressions fills a stored computed field on rows stored before it was declared, and the boot names it - #30227

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

objectstack-fleet[bot] merged 11 commits into
mainfrom
claude/project-thread-jp5zxi

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

domain:server#2 · os-bill · session_01Utp6Cb2FL7H75wB1wA6PpZ

维护者速读

  • 改了什么: 新增 os migrate stored-expressions:给"在已有数据的对象上新加的存储型计算字段"补算存量行(默认只补 NULL,--changed 再重算表达式改过后不一致的值),先报告、--apply 才写;另在启动时抽查每个声明了存储型计算字段的对象的前 100 行,发现这种 NULL 就告警一次并点名命令。
  • 为什么改: 存储型计算字段只在写入时计算,升级后的库里新字段在所有旧行上一直是 NULL(hotclm 121/121 行),分析看板把 NULL 当 0 天,静默多算超期。
  • 风险与回滚: 命令默认只读;写入走 engine.update(保留 updated_at),钩子、汇总、审计照常触发;表达式在某行算不出而该行已有值时整行跳过并列出,不会用 NULL 覆盖。启动抽查只读、有上限、出错不影响启动。回滚即撤 PR。
  • 先看: packages/objectql/src/stored-expression-backfill.ts(分类与写入规则)、packages/objectql/src/engine.ts 新方法 computeStoredExpressionsOver、packages/objectql/src/plugin.ts 的 kernel:bootstrapped 一行。
  • 两分钟验证: pnpm --filter @objectstack/objectql exec vitest run src/stored-expression-backfill.test.ts,预期 29 个用例全过(含"加字段后旧行 NULL → --apply 补齐 → 再跑为 0")。
  • 需要你做的: 无,席位落地。

Fixes #29961

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

Why

See the first two bullets of 维护者速读 and #29961: a stored computed field added to an object that already has rows stays NULL on every existing row until each row is next written, and an analytics view reading it treats the NULL as 0.

What changes

The producer. A stored computed field (a non-formula field declaring expression) is computed by computeStoredExpressions on insert and update only. Schema sync adds its column NULL on every existing row, and nothing computes it until each row is next written. The fix leaves the write path alone and adds the missing producer for existing rows, in the shape os migrate summary-nulls and os migrate recompute-home-amounts already have: a report-first command, plus a bounded boot advisory.

  • ObjectQL.computeStoredExpressionsOver(object, rows, context?, onFault?) (engine.ts, one method): the write's own plan (planStoredExpressions) and serving (declared option labels, the one hop, the can() map) over rows already stored. Evaluates over copies, writes nothing.
  • stored-expression-backfill.ts (new): backfillStoredExpressions walks every row by keyset (id), computes each page through the method above, and classifies each (row, field):
    • stored NULL, expression gives a value → hole: written by default;
    • stored NULL, expression gives NULL (an empty reference, a null input) → already the write's value, left alone;
    • stored value differs from the expression → stale: counted on every run, written only with changed;
    • the expression does not evaluate on the row → faulted: listed; when the row holds a value there, the whole row is skipped, because the write would store NULL over it.
      A written row goes through engine.update with the computed values and the row's updated_at under preserveAudit, as recompute-home-amounts writes. The write computes every stored field of the row again, so a stale value on a filled row is rewritten with it (counted under written). Refused before any row is read: an unknown object (OBJECT_NOT_FOUND), a named object or field declaring no stored computed field, and field without exactly one object (INVALID_FIELD, 400). Idempotent; no sys_migration flag (nothing gates on it).
  • Boot advisory (announceStoredExpressionHoles, called from the objectql plugin's kernel:bootstrapped hook beside announceOpenMigrationGates): one find of at most 100 rows per object that declares a stored computed field, nothing for objects declaring none. When the sample holds a hole, it warns once, naming each object.field (n of the first m rows) and the command. It writes nothing, and a read or compute failure is logged at debug and never fails the boot. A column added to an object with rows is NULL on every row, so the first page finds it.
  • os migrate stored-expressions (packages/cli, new, discovered by oclif's pattern strategy, so no registration file): --object (repeatable), --field (needs --object), --changed, --apply, --yes, --json, --force, --max-records, --database-url; the occupancy gate, confirmation, read-only dry-run boot and SQLite journal-mode note of its siblings. Exits 1 while a row is skipped, a write failed, or --max-records truncated the walk.
  • Docs: one row in content/docs/deployment/cli.mdx's data-migration table.

Tests

Measured at c82c1c7f (branch merged with origin/main once before this PR). Head 72809e52 adds only the changeset's Clause-② line on top of it.

  • packages/objectql/src/stored-expression-backfill.test.ts (new, 29 tests): a real ObjectQL over an in-file driver. Dry run reports and writes nothing; apply fills holes, keeps updated_at, and a re-run finds 0; stale values are counted by default and written with changed; a faulted row that holds a value is skipped and listed; a refused write is isolated to its row; field narrows the walk; the three refusals; an unreadable object; the boot advisory (holes found, none found, a read failure that never throws); a sameStoredValue table.
  • packages/cli/src/commands/migrate/stored-expressions.test.ts (new, 10 tests, integration project): flags reach the walk, --field needs --object, --apply without --yes writes nothing and exits 1, an undeclared object is refused, exit 1 on skipped / failed / truncated, and the dry-run face closes with the report's line.
  • Pin and ledger rows: migrate-confirmation-exit.pin.test.ts (the new command in the confirmation roster) and federated-injected-column-readers.test.ts (the new updated_at reader skips an unprovisioned federated column).
  • Ablation: replacing the if (!apply) continue; guard turns 5 backfill tests red; restore verified blob == HEAD.

Acceptance notes

  • Boot backfill vs command, measured choice. An automatic boot backfill was not taken: each fill is a write (hooks, record-triggered flows, audit rows, roll-up recomputes) nobody ordered, a large table would hold the boot for a full walk, a remote driver pays a round trip per row, and an expression reading a lookup reads another object per page. The boot advisory is bounded to one 100-row read per object declaring a stored computed field.
  • Roll-ups have no boot notice. The triage said "as roll-ups have"; on main the roll-up backfill (summary-backfill.ts) has no boot notice (the lint's isAlwaysValuedSummary docblock says "Nothing backfills a stored NULL automatically"). The in-tree boot-advisory shape is announceOpenMigrationGates at kernel:bootstrapped, which this follows.
  • A changed expression. Covered by --changed. No boot can tell that an expression changed (the stored value is not a function of the declaration alone, and a now()-reading expression always differs), so the advisory does not report stale values; every run of the command counts them and its report says to re-run with --changed.
  • Clause-② corrected to yes (widening). The dispatch order proposed no. Measured: new published @objectstack/objectql exports (the stored-expression backfill functions and types), one new public method ObjectQL.computeStoredExpressionsOver, and a new CLI command os migrate stored-expressions; nothing narrows. Declared as feat(objectql,cli): recompute stored home-currency amounts at the rates in force now, report first, rows without a rate listed and skipped (multi-currency M2-3) #30183 declared the same shape; the changeset carries the same line from 72809e52. Metadata-protocol stays none.
  • Advisory false negatives. Holes only past the first 100 rows by id are not seen by the advisory; the command walks every row.

Verification

Reproduction on SQLite and memory, before and after

An object with 4 rows, booted; stored computed fields then declared (a doubled number, a date offset, a boolean, a text, a scale-2 decimal) and booted again over the same file. Before: 4/4 rows NULL in each new column on both drivers. os migrate stored-expressions dry run reported the holes and wrote nothing; --apply wrote the expected values (qty_double 2/4/6/8, offset -5/-5/-5/null for a row with an empty input); a re-run found 0 holes and 0 stale. Changing an expression to qty*3 then showed 4 stale in default mode, --changed --apply rewrote them, and a re-run found 0.

Suites, typechecks, lint and gates
pnpm --filter @objectstack/objectql test        :: exit 0   (549 files, 10892 tests)
pnpm --filter @objectstack/objectql typecheck   :: exit 0
pnpm --filter @objectstack/cli typecheck        :: exit 0
pnpm --filter @objectstack/cli exec vitest run --project unit :: exit 0   (382 files, 5384 tests)
cli integration: stored-expressions.test.ts     :: exit 0   (10 tests)
pnpm lint                                       :: exit 0
node scripts/pm/dispatch-gates.mjs --ran        :: 96 derived, 96 run, 0 NOT-MEASURED

Human Minimum

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

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/objectql, touching 62 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

68 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json bb86ff96cc6e3854d00c39488a0a8985fb1312d1.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • 2 cross-cutting symbol(s) contributed no route anchor: recordId (4 routes), safeParse (7 routes)
  • 1 anchor(s) matched too much of the corpus to be a work list: ObjectQL (symbol, 80 pages)
  • 19 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 — 49 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 bb86ff96cc6e3854d00c39488a0a8985fb1312d1 → packageMentionDocs.

Which tree this was computed on

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

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

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

@github-actions github-actions Bot added 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
The PR adds published @objectstack/objectql exports (the stored-expression
backfill functions and types), one public method
ObjectQL.computeStoredExpressionsOver and a new CLI command; nothing
narrows. The dispatch order proposed `no`; corrected on the seat's
review, as #30183 declared the same shape.

Claude-Session: https://claude.ai/code/session_01Utp6Cb2FL7H75wB1wA6PpZ
Co-authored-by: Claude <noreply@anthropic.com>
…rityResolversFacet

engine.ts is a registered facade whose members hold delegations only
(check-facade-delegation-only). The body moves beside serveFormulaField in
engine-security-resolvers.ts, and ObjectQL.computeStoredExpressionsOver
forwards to it in one call, as its neighbours do. No behaviour change.

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

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 3378366a175ca1ba50aa4a983ed4186b372661c3
Local-runs: none

① Derived judgments

Face = declaration only (Clause-②: yes (widening) on the PR body and changeset). No packages/spec/src/**, no governed path (check-governed-merges --pr 30227: NOT governed), no narrowing declaration, so the tier is TIER_DEFAULT, recorded by the lane seat.

② Semver level

Additive only. patch is the right level: it follows Metadata-protocol: none (AGENTS.md: a non-protocol change, widening or breaking, ships patch; precedent #30183). @objectstack/objectql gains five value exports (backfillStoredExpressions, formatStoredExpressionBackfillReport, storedExpressionBackfillComplete, announceStoredExpressionHoles, sampleStoredExpressionHoles), eight type exports, and the public method ObjectQL.computeStoredExpressionsOver, all new names; no existing export, signature or IObjectQLEngine member changes. @objectstack/cli gains the subcommand os migrate stored-expressions; no existing command or flag changes. The boot gains one warn at kernel:bootstrapped, wrapped so a failed sample logs at debug and returns (verified in announceStoredExpressionHoles): it can never fail a boot that booted before. The declaration yes (widening) is correct; no would have been wrong.

③ Boundary flags

Implemented-by: session_01Utp6Cb2FL7H75wB1wA6PpZ
Reviewed-by: session_01Utp6Cbvcmv8YWFhFJwzjKL

VERDICT: PASS

…ck family rosters

The command boots through bootSchemaStack, so the one-shot family table
(schema-migrate.one-shot-family.integration.test.ts CALLERS) and the
--json stdout purity family (json-stdout-purity.e2e.test.ts FAMILY) both
discover it; each now drives it, as summary-nulls is driven.

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

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

Contract review

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

① Derived judgments

Re-recorded on the new head: the delta from 3378366a (record 6648457517) is test-only, 2 files +10 (schema-migrate.one-shot-family.integration.test.ts CALLERS row and json-stdout-purity.e2e.test.ts FAMILY row for the new command); every judgment below stands.

Face = declaration only (Clause-②: yes (widening) on the PR body and changeset). No packages/spec/src/**, no governed path (check-governed-merges --pr 30227: NOT governed), no narrowing declaration, so the tier is TIER_DEFAULT, recorded by the lane seat.

② Semver level

Additive only. patch is the right level: it follows Metadata-protocol: none (AGENTS.md: a non-protocol change, widening or breaking, ships patch; precedent #30183). @objectstack/objectql gains five value exports (backfillStoredExpressions, formatStoredExpressionBackfillReport, storedExpressionBackfillComplete, announceStoredExpressionHoles, sampleStoredExpressionHoles), eight type exports, and the public method ObjectQL.computeStoredExpressionsOver, all new names; no existing export, signature or IObjectQLEngine member changes. @objectstack/cli gains the subcommand os migrate stored-expressions; no existing command or flag changes. The boot gains one warn at kernel:bootstrapped, wrapped so a failed sample logs at debug and returns (verified in announceStoredExpressionHoles): it can never fail a boot that booted before. The declaration yes (widening) is correct; no would have been wrong.

③ Boundary flags

Implemented-by: session_01Utp6Cb2FL7H75wB1wA6PpZ
Reviewed-by: session_01Utp6Cbvcmv8YWFhFJwzjKL

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

维护者速读

  • 改了什么: 新增 os migrate stored-expressions:给「声明前就已存在的行」补算存储型计算字段(默认试跑,--apply 才写);启动时抽样每个声明了这种字段的对象前 100 行,发现空洞就警告一次并点名这条命令。
  • 为什么改: 存储型计算字段只在插入和更新时算,给已有数据的对象新加这种字段后,老行一直是 NULL,过滤、排序、汇总、报表都读到 NULL,且不报错。新库对、升级的库错。
  • 风险与回滚: 命令默认不写;写入走正常的 engine.update 并保留 updated_at;算不出值但已有值的行只列出不写。启动抽样只读、出错只记 debug,不会让启动失败。回滚即撤本 PR。
  • 先看: stored-expression-backfill.ts 的 backfillStoredExpressions 与 announceStoredExpressionHoles,再看 engine.ts 新加的一行委派。
  • 两分钟验证: pnpm --filter @objectstack/objectql exec vitest run src/stored-expression-backfill.test.ts。
  • 需要您做的: 无,席位经合并队列落地(非受管面,1414 行)。
  • 席位意见: ACCEPT。Clause-② 改为 yes (widening)(纯新增导出和一个新方法),席位申报复核 PASS(6649065103)。

席位核对 — ACCEPT · domain:server#2 · head a1ee9670a6325bdc0f6e1d226c74464a356f136c

  • 形态: draft, author objectstack-fleet[bot] (relay pr_create), no assignee, no review request; 14 files, +1411 −3; check-governed-merges --pr 30227: NOT governed (0 of 12 paths), 1414 lines ≤ 3000. Dev write tally: 2 API writes + git push, mcp_calls 2 reads.
  • 前提实测: stored computed fields (non-formula fields declaring expression) are computed only on insert and update on origin/main, and no command or boot notice covers rows stored before the declaration (claim Premise: holds). Dev reproduced on SQLite and memory: 4/4 rows NULL after adding fields, --apply filled them, re-run 0 holes.
  • 门禁 check-runs: 50 runs on the head, 38 success, 12 skipped, 0 failing; dev ran the nightly json-stdout-purity.e2e locally, 98/98. Two reds fixed on the way: the check:facade-delegation-only N6 red on 72809e52 (method body inline in engine.ts) fixed at 3378366a: the body moved to SecurityResolversFacet.computeStoredExpressionsOver, engine.ts keeps a one-line delegation; and Test Core (1/8) at 3378366a (schema-migrate.one-shot-family.integration.test.ts: the new command was missing from the bootSchemaStack CALLERS table), fixed at a1ee9670 with the CALLERS row and the matching json-stdout-purity.e2e FAMILY row. Dev: dispatch-gates 96/96 exit 0 at c82c1c7f, pnpm lint 0, objectql 10892 tests, cli unit 5384 tests, one ablation (apply guard) 5 red then restored.
  • 契约复审: declaration face only ⇒ TIER_DEFAULT, seat record PASS on this head, comment 6649065103 (first recorded on 3378366a as 6648457517; the delta is test-only). No packages/spec, no governed path, no narrowing.
  • 启动开销 (seat-measured): one bounded read (first 100 rows) per object declaring a stored computed field, plus the related reads its expressions make; 0 statements on the blank scaffold, 1 on showcase and on crm. Accepted in this shape: bounded, read-only, never fails the boot.
  • 散文面逐句核 (TIER_DEFAULT): changeset bullets ("A NULL the expression itself answers … is left alone", "rewritten only with --changed", "exits 1 while a row is skipped or could not be written, or when --max-records cut the walk short", "reads the first 100 rows of each object declaring a stored computed field … writes nothing and never fails a boot") and the cli.mdx table row each match the code read in stored-expression-backfill.ts and stored-expressions.ts.
  • 接受的偏差: Clause-② re-declared yes (widening) in the PR body and changeset (corrected claim 6648065279); one cli.mdx table row outside the declared surface; two existing tests edited for roster and ledger rows (migrate-confirmation-exit.pin.test.ts, federated-injected-column-readers.test.ts with two skips rows); the boot advisory sits beside announceOpenMigrationGates because roll-ups have no boot notice on main (the triage's premise there was wrong, not a fork).
  • 范围外去向: none from the dev. Seat note: dispatch-gates does print check:facade-delegation-only for an engine.ts diff, in its run-or-read list outside the runnable total; the dev's 96/96 counted only the total, so the N6 red reached CI. Not a tooling gap; a reading the os-dev report shape should name, no card filed.
  • 落地条款: Clause-②: yes (widening), Metadata-protocol: none, changeset patch for @objectstack/objectql and @objectstack/cli. Ordinary queue landing by this seat: pr_ready + automerge_enable on this head.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 04:03
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 04:03
@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 构建失败 — 先分诊,再决定要不要重排

队列构建 41965463000 红了。队列跑的是全量套件(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 队列共有 7 个失败构建(不含本次)。

分诊清单:

  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 cd5f554 Nov 5, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-jp5zxi branch November 5, 2026 04:29
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

Merge-queue kick, run 41965463000 (triage comment 6649684120): one re-queue, on the three facts.

  • Signature: Live E2E UI, e2e/live/studio-customize-journey.spec.ts:121 › "Customize a view: the edit lands in the pair, and the server serves its columns", first error apiRequestContext.get: read ECONNRESET at servedColumns (:61). New signature: no ledger row, no open issue naming it.
  • ① closure disjoint: the spec is ui/e2e/live test code; this PR's diff is packages/objectql (a new backfill module, a facet method with a one-line engine.ts delegation, a read-only boot advisory) and packages/cli (a new os migrate command) plus tests. The failing request reads a customized view's served columns; nothing on that path is in the diff.
  • ② base green: Live E2E UI is success on the last four main commits (9b8a5867, 1d410377, 8eca18fd, 31260ca8).
  • ③ first error is not an assertion: a connection reset on one request, with the server still answering: the 6 specs after it in the same run passed. Transport, not a product-behaviour assertion.

Re-queued once (automerge_enable on head a1ee9670). If the same signature kicks again, this seat stops re-queuing and files it.

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/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A stored expression field added to an object that already has rows is never backfilled: existing rows stay NULL until each is next written

1 participant