Skip to content

fix(driver-sql): a date field still on a Postgres timestamptz column reads the day it was written - #33000

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #32851

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Postgres 上把日期时间字段改成日期字段并发布后,该列仍是 timestamptz;现在这种字段的写入、读取、按日期过滤、按天分组统一按 UTC 日计算,四处答案一致。列类型不动,存量数据不改写。
  • 为什么改:数据库服务器时区在 UTC 以东(如上海)时,写入 2026-11-12 读回是 2026-11-11,且按日期过滤、按天分组各给出不同的日子,用户看到的日期整体早一天,没有任何报错。
  • 风险与回滚:只影响"Postgres + 日期字段 + 列仍是 timestamptz"这一种组合;日期时间字段、SQLite、MySQL 的行为不变。修复前已经写进去的错误日子保持原样(仍早一天),本 PR 不修数据。回滚即 revert。
  • 先看:packages/drivers/driver-sql/src/date-on-instant-column.ts(规则与理由),其次 sql-driver-rotation.ts 里登记哪些列是 timestamptz 的那段,再看 sql-driver-filter-helpers.ts 的 filterColumnExpr 新分支。
  • 两分钟验证:OS_TEST_POSTGRES_URL=… TZ=America/New_York pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-32851-date-on-instant-column.test.ts(服务器时区设为 Asia/Shanghai),预期 Postgres 与 SQLite 两格全绿。
  • 需要你做的:无,席位落地。

What changes

Chosen shape: (b), read every door in a fixed zone (UTC), not (a), narrowing the column.

  • New module packages/drivers/driver-sql/src/date-on-instant-column.ts: the registry type, the predicate isDateOnInstantColumn, the write binding utcMidnightOfDay and the SQL utcCalendarDaySql (((col) AT TIME ZONE 'UTC')::date), with the measurement and the reasoning in its header.
  • SqlDriver.dateFieldsOnInstantColumn (protected registry): per table, the declared Field.date fields whose Postgres column is timestamp with time zone. initObjects (the call syncSchema and the publish's storage sync make) records it from the columnInfo() it already reads for an existing table; it is reset to empty for every table it syncs, so a table created here, a non-Postgres dialect, deferred DDL or a guest datasource keeps the plain date path exactly as before.
  • Write (formatInput): such a field binds the day as its UTC midnight (2026-11-12T00:00:00.000Z) instead of the bare day the server read in its own zone. This is the Oracle arm's rule (oracleDayBinding).
  • Filter (filterColumnExpr, so every comparison operator, the object shorthand, cross-field and now-anchor comparisons) and the outside-the-builder twin temporalFilterColumnSql (analytics native SQL): the column is read as its UTC calendar day; the comparand stays the YYYY-MM-DD day it already was.
  • Date bucket (buildDateBucketExpr, so aggregate() and the dateBucketSql echo): such a field buckets as its UTC calendar day instead of ::date in the session zone.
  • Read: unchanged. It already folds the stored instant by its UTC components (temporalStorageForm), which is the zone every other door now uses.

Why (b) and not (a):

  • (a) narrows only "when that loses nothing", which is only an empty column or one whose every value is a UTC midnight. The card's own case is a live object: rows written while the field was a datetime carry a time of day, and rows written after the publish on a server ahead of UTC are stored at 16:00Z of the previous day. Narrowing those changes stored values, which the order routes to needs_decision; skipping them leaves the defect in place. So (a) alone does not fix the reported case, and once (b) is in, (a) adds nothing.
  • (b) changes no stored value and no column, makes all four doors agree for every stored value (including a row written as a datetime before the publish, which a comparand-only fix would still miss), and leaves datetime fields untouched.
  • A session-wide TimeZone = UTC on every Postgres connection (the MySQL withUtcSession shape) was considered and rejected as wider: it moves every raw statement and host query on the pool, and it still would not make an equality filter match a stored instant with a time of day.

Existing rows: a day written into such a column before this fix, on a server ahead of UTC, was stored as the previous day's instant (2026-11-11T16:00:00Z for 2026-11-12). It keeps reading, filtering and bucketing as that previous day, now consistently. It is not repaired: from the column alone it cannot be told apart from a datetime-era value at the same instant, so a repair needs a data migration and a decision (see Acceptance notes). Rows written while the field was a datetime read, filter and bucket as their UTC day.

Tests

  • New packages/drivers/driver-sql/src/sql-driver-32851-date-on-instant-column.test.ts, on every dialect cell of the live matrix (SQLite always; Postgres and MySQL where provisioned, which the Temporal Conformance (live PG + MySQL) job does with both servers at Asia/Shanghai and the process at America/New_York). It creates the table with due: datetime, writes a datetime-era row (2026-11-12T20:00:00Z, 04:00 the next day in Shanghai), publishes due: date through syncSchema + applyPublishedColumnChanges, then:
    • Postgres premise: the column is still timestamptz and the server's offset is ahead of UTC (fails, with a remedy, if the server is at UTC);
    • every cell: create({ due: '2026-11-12' }) answers and reads 2026-11-12, an update to that day reads it back, due = day and $gte/$lte day find both, $lt day does not;
    • every cell: a datetime field beside it (seen) still reads and filters as its exact instant;
    • Postgres: the datetime-era row reads 2026-11-12, the equality filter returns all three rows, and aggregate() by day answers one bucket 2026-11-12 of 3;
    • Postgres: temporalFilterColumnSql + temporalFilterValue, and the dateBucketSql echo, answer the same.
Pre-fix measurement on main (b4f6971), live Postgres 16 at Asia/Shanghai, TZ=America/New_York
col timestamp with time zone Asia/Shanghai America/New_York
create answers 2026-11-11
raw [{"due":"2026-11-12 21:00:00+08"} (datetime-era), {"due":"2026-11-12 00:00:00+08"} (written as 2026-11-12)]
read a 2026-11-12 read b 2026-11-11
filter eq ["b"]
filter range ["b"]
bucket [{"due":"2026-11-12","n":2}]

After the fix: create answers 2026-11-12, stored 2026-11-12 08:00:00+08 (UTC midnight), both rows read 2026-11-12, filter eq ["a","b"], bucket 2026-11-12 of 2.

Ablation (each leg through scripts/ablation-replace.mjs; restore verified blob == HEAD and git diff HEAD empty)
registry population removed (sql-driver-rotation.ts)   exit 1 · 3 failed: read-back ("2026-11-12" expected, "2026-11-11" received), datetime-era row, analytics doors
write binding removed (sql-driver-sqlite-serialisation.ts) exit 1 · 4 failed: read-back ("2026-11-11"), filter, datetime-era row, analytics doors
filterColumnExpr arm removed (sql-driver-filter-helpers.ts) exit 1 · 2 failed: filter by day, datetime-era row
date bucket arm removed (sql-driver-date-bucket.ts)      exit 1 · 2 failed: datetime-era row bucket, dateBucketSql echo
temporalFilterColumnSql arm removed (sql-driver-filter-helpers.ts) exit 1 · 1 failed: analytics doors

Acceptance notes

  • Days already written into such a column before this fix on a server ahead of UTC stay one day early (now consistently on every door). Repairing them needs a data migration that shifts values by the server's offset, and only rows written after the publish need it, which the column cannot tell apart. Not done here; the seat can raise it as a decision if wanted.
  • Found, not fixed (pre-existing, every dialect, unrelated to time zones): on SQLite and MySQL a row written while the field was a datetime (an instant with a time of day) reads as its day but is not matched by an equality filter on that day, because the comparand is the bare day and the column holds the instant. Postgres now matches it through the UTC-day column read. Reported to the seat in the os-dev report.
  • The registry is filled by the schema sync. A driver that never syncs the table (deferred DDL on os migrate plan, a guest datasource with schemaMode other than managed) keeps the pre-fix behaviour for such a column.
  • A timestamp without time zone column under a date field is not recorded: this driver never builds one for a datetime field.
  • An index on such a column no longer serves a filter on the field (the column is read through an expression). Only columns left by a publish that changed a field from datetime to date are affected.

Verification

  • One origin/main merge before the PR (583b1e38, main at c1a645e2; no packages/drivers/driver-sql change came with it).
  • Final head 6c557952: node scripts/pm/dispatch-gates.mjs --commands derived 70 families; every one run after a turbo run build of the @objectstack/dogfood^... + @objectstack/runtime^... + driver-sql closure (exit 0), plus pnpm --filter @objectstack/driver-sql typecheck and pnpm lint. All 72 exit 0.
  • --ran reconciliation at 6c557952: ✓ dispatch-gates --ran: 70 derived famil(ies) accounted for — 70 run, 0 NOT-MEASURED.
  • The first pass at 583b1e38 was green except check:query-options-erasure (the new test cast six query bags as any); 6c557952 types them and the full union was re-run on it.
  • pnpm --filter @objectstack/driver-sql test with live Postgres 16 (server Asia/Shanghai, TZ=America/New_York): 261 files passed, 4 skipped; 5319 tests passed, 122 skipped (run at 89ffef34, same driver-sql source as the final head except the pin's casts; the pin re-run on 6c557952: 9 passed, 4 skipped).
  • NOT MEASURED locally: the MySQL cell (no MySQL server in this container). CI's Temporal Conformance (live PG + MySQL) job runs it.
  • Dogfood battery: not owed (no ! in the title, Clause-②: no without a narrowing arm).
--ran file (COMMAND :: exit CODE) at 6c55795
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:error-code-provenance :: exit 0
pnpm check:adr-symbol-anchors :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-conformance :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:facade-delegation-only :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:object-def-param-keys :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:optional-error-sink :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:route-envelope :: exit 0
pnpm check:settings-bind-window :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:tenant-chokepoint :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vouch-for-reference-callers :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --filter @objectstack/driver-sql typecheck :: exit 0
pnpm lint :: exit 0

@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 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql, touching 65 documentable anchor(s).

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

  • content/docs/data-modeling/drivers.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/queries.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/permissions/tenant-audit-census.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/lifecycle.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/query-syntax.mdx (via SqlDriver (symbol, a top-level class), storageDatetimeValue (literal, a string literal in formatInput))
  • content/docs/protocol/objectql/types.mdx (via SqlDriver (symbol, a top-level class))

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

  • content/docs/releases/v17/17-0.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-5.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-6.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v18/18-1.mdx (via SqlDriver (symbol, a top-level class))

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
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 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 — 14 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 d4b14a85048f45e52622eb520a00aaea2fe5bda2 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 d4b14a85048f45e52622eb520a00aaea2fe5bda2 → 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#6 · os-elon-musk · session_016WV4cz8zK9vJDPeeVKJ4XP

维护者速读

席位核对 — ACCEPT · domain:server#6 · head 6c557952db635f7c4b4575b2d0dbc1c494429c0f · 2026-11-13T03:44Z

  1. Shape: 8 files, +330 −7 (new date-on-instant-column.ts +98, filter / date-bucket / write / initObjects wiring in four facets and sql-driver.ts, a new test +156, one @objectstack/driver-sql patch changeset); file surface held (claim 6841691366, re-post 6842383131). Not Tier H: no governed path, far under 3000 lines.
  2. Premise measured: re-measured by the dev on main b4f69716 with live Postgres 16 at Asia/Shanghai and the process at America/New_York: 2026-11-12 read back 2026-11-11, and the filter and the day bucket disagreed with the read (os-dev report 6842768512).
  3. Gates: every check-run on this head concluded success (35) or an expected skip (16). Among the skips, Temporal Conformance (live PG + MySQL) is skipped on pull_request and merge_group by design (ci.yml 2414, push and schedule only), so CI's live-Postgres reading comes after merge; the dev ran the driver-sql suite against live Postgres locally (5319 pass) with 5 ablation legs red and restored; the MySQL cell was not measured locally and MySQL's path is unchanged by the diff (the registry is recorded for Postgres only). The dev's --ran file: 70 derived families plus typecheck and lint exit 0.
  4. Contract review: Clause-②: no, Metadata-protocol: none: no contract face, none owed. The fix restores the declared date semantics without moving any datetime read.
  5. Accepted deviation / out of scope: shape (b) chosen over narrowing the column, since narrowing rewrites stored values. Existing rows written a day early before the fix stay a day early, now consistently: decision card driver-sql (Postgres): days written a day early into a datetime-to-date column before #32851 stay a day early — repair or not #33001. SQLite / MySQL equality filter on former datetime rows: driver-sql (SQLite, MySQL): after a datetime-to-date publish, an equality filter on a day misses rows written while the field was datetime #33002. Read-path registry is refreshed by every syncSchema (the publish's storage sync calls initObjects).
  6. Landing: K = 9 behind main (< 15), no conflict, no new locale pack: no merge before queueing. Ready, then auto-merge into the queue.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 13, 2026 03:45
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Dogfood Regression Gate (2/3) — 失败步骤: Boot example apps and exercise real user flows

    @objectstack/dogfood:test:  FAIL   isolated  test/approval-decide-over-mcp.dogfood.test.ts > an approver's agent decides a pending approval request over MCP (#32442) > ⭐ an AI service account acting f
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: {
    

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

分诊清单:

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

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

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

2 participants