Skip to content

test(driver-sql): give the seven unconditionally-live hooks an explicit 60_000 budget - #14629

Open
os-musk wants to merge 2 commits into
mainfrom
claude/issue-14213-driver-sql-live-hook-budgets
Open

test(driver-sql): give the seven unconditionally-live hooks an explicit 60_000 budget#14629
os-musk wants to merge 2 commits into
mainfrom
claude/issue-14213-driver-sql-live-hook-budgets

Conversation

@os-musk

@os-musk os-musk commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #14213

Seven hooks in packages/drivers/driver-sql construct a driver against an unconditionally live cell and carried no explicit budget. Each now takes the third argument, 60_000, matching the precedent landed for #14100 in sql-driver-backend-fault-envelope.test.ts (PR #14212).

The measured reframing this card carries

A hook does not inherit testTimeout. It inherits hookTimeout. This package's vitest config sets neither, so both are vitest's own defaults, and the card's leg B3/B4 measured the difference: an unbudgeted hook dies at 10000ms (Hook timed out in 10000ms), not at the 5000ms an unbudgeted it() gets, and the third argument demonstrably does lift that ceiling (leg B4). Every comment added below records 10000ms as the hook figure.

Verified independently here: no testTimeout or hookTimeout appears in packages/drivers/driver-sql/vitest.config.ts, and the repo has no root vitest.config.ts for one to be inherited from.

The seven sites

Each row's argument reading comes from a per-site AST walk over the hook's own new SqlDriver(...) argument — not a file-level grep (the card's method note; #13902's original script pre-filtered whole files that contained any budget line and hid a true positive that way). Line numbers are on this branch's head.

# File Hook Argument reading Budget
1 sql-driver-datetime-mysql-storage.test.ts:78 beforeEach MYSQL_CELL.config() 60_000
2 sql-driver-datetime-mysql-storage.test.ts:178 beforeEach MYSQL_CELL.config() 60_000
3 sql-driver-datetime-mysql-storage.test.ts:263 beforeEach MYSQL_CELL.config() 60_000
4 sql-driver-datetime-postgres-timezone.test.ts:64 beforeAll PG_CELL.config() 60_000
5 sql-driver-datetime-postgres-timezone.test.ts:75 beforeEach PG_CELL.config() 60_000
6 sql-driver-json-binding-without-ddl.test.ts:114 beforeAll PG_CELL.config() 60_000
7 sql-driver-time-live-dialects.test.ts:167 beforeEach MYSQL_CELL.config() twice (two live connections per test) 60_000

All are named live-cell constants, never a parametrised cell.config(). Five of the seven are beforeEach, so the cost is paid per test rather than once — the heaviest shape in the population.

The walk, before and after

The same script, run on the tree before and after the edit:

files scanned: 160
hook calls seen: 337
hooks constructing SqlDriver: 111

BEFORE  { "not-unconditionally-live": 104, "LIVE-UNBUDGETED": 7 }
AFTER   { "not-unconditionally-live": 104, "LIVE-BUDGETED":   7 }

LIVE-UNBUDGETED goes 7 to 0 and LIVE-BUDGETED goes 0 to 7, with the 104 out-of-population sites unchanged in both directions.

Found, not fixed

A third scan resolving one level of helper indirection found exactly one further site package-wide, and it is not in this PR: the beforeAll of the #3942 suite in sql-driver-datetime-mysql-storage.test.ts reaches a live driver through the module-level rawDriver() one-liner rather than an inline argument, so its own argument reading is not a live cell constant. Recorded as #14628, unassigned, for the seat to decide. Folding it in silently is exactly the package-wide creep this card's lineage rules out.

Prose correction: none was due

The dispatch expected "5000ms" prose at the touched sites to need correcting to 10000ms. Measured before and after: the four files contained exactly one "5000ms" mention, at sql-driver-json-binding-without-ddl.test.ts in the note on the §3 it() site landed by #14212. For an it(), 5000ms is the correct figure, and that site is not one of the seven. So nothing was corrected, and the comment added at site 6 says explicitly that the neighbouring §3 figure is right for an it() and would be wrong for a hook.

Deliberately out of scope

Verification

Head bec55ae54. origin/main was merged before opening (never rebased, never force-pushed), and the gate union below was derived and run on that head.

Green:

  • pnpm --filter '@objectstack/driver-sql^...' build — lock VERDICT command-exit 0.
  • pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 over the four files — Test Files 1 passed | 3 skipped (4), Tests 1 passed | 39 skipped (40).
  • pnpm --filter @objectstack/driver-sql typecheck (tsc --noEmit) — lock VERDICT command-exit 0. The test layer is inside this package's tsc program: tsc --listFiles reports all four edited files, 1 occurrence each, so the green covers the edits rather than skipping past them.
  • The 27-command gate union derived on this head by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 25 green.
  • Always-runs: pnpm lint (whole repo, eslint . --no-inline-config, not narrowed) exit 0; pnpm check:nul-bytes exit 0; pnpm check:error-status-conformance exit 0.

NOT MEASURED, in each tool's own words, neither green nor red:

  • The seven hook sites themselves. OS_TEST_POSTGRES_URL and OS_TEST_MYSQL_URL are unset in this container, so all three live suites skip. CI's Temporal Conformance (live PG + MySQL) job is the authority on whether these hooks execute.
  • node scripts/check-test-completeness.mjs — exit 3, PREREQUISITE NOT MET: it grades a saved turbo run test log and none was named.
  • pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. It needs a whole-repo build.
  • pnpm check:type-check-debt — exit 3, PREREQUISITE NOT MET: 49 workspace dependencies have no built type entry point, so measuring would measure a different world.

Exit codes were captured after a redirect, never through a pipe.

Changeset

skip-changeset: nothing published changes. Every touched path is a *.test.ts, and pnpm check:published-files passes with 69 publishable packages whose files whitelist "admits no test, test-harness config or build script".


🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…it 60_000 budget

A per-site AST walk over all 160 test files in packages/drivers/driver-sql/src
(111 hooks that construct a SqlDriver) classifies each site by READING its
`new SqlDriver(...)` argument rather than by filename. Seven hooks take an
unconditionally live cell argument (`PG_CELL.config()` / `MYSQL_CELL.config()`,
not a parametrised `cell.config()`) and carried no explicit budget. Each now
takes the third argument, matching the precedent set in
sql-driver-backend-fault-envelope.test.ts.

A hook inherits `hookTimeout`, NOT `testTimeout`. This package's config sets
neither, so both are vitest's own defaults: an unbudgeted hook dies at 10000ms,
not at the 5000ms an unbudgeted it() gets. The comments at each site record
that measured figure. No existing prose needed correcting: the only "5000ms"
mention already in these four files describes an it() site, where 5000ms is
the correct number.

The `declareDialectCell(...)` hook population is deliberately untouched: those
take a parametrised `cell.config()` and budgeting them trades against keeping
the sqlite path fast.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions github-actions Bot added the size/s label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 87ad30c103bb4e8c5d7b8a399634b59716f3c21fpackageMentionDocs.

@os-musk os-musk added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026 — with Claude
@github-actions github-actions Bot added the tests label Sep 2, 2026
@os-musk
os-musk marked this pull request as ready for review September 2, 2026 16:12
@os-musk
os-musk enabled auto-merge September 2, 2026 16:13

os-musk commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Landing provenance (engine execution seat, session session_0112hMx9hjJ9BgB28X97DS68): flipped ready at 16:13Z and armed auto-merge (squash) at 16:13:02Z on head bec55ae54.

  • Review: ACCEPT on the card, comment 5512246430. Clause-② no, self-read from the final diff.
  • Governed-surface test on the four changed paths: 0 of 4 governed — ordinary queue landing. skip-changeset, test-only.
  • Enqueue bar: every check run on bec55ae54 completed green or skipped (37 runs read at 16:13Z, perPage: 50). Last standing was Test Core (1/6), 15:39:45Z to 16:00:32Z — 21 minutes; Lint & Repo Gates finished 15:56Z. The window was re-armed twice rather than flipped on a partial reading.
  • The seven live-cell hooks themselves stay NOT MEASURED locally by construction (OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL unset in the dev's container, so all three live suites skip). CI's Temporal Conformance (live PG + MySQL) job on this head is green (15:44Z) and is the authority for them.
  • Landing owed by the seat at MERGED: verify by content on origin/main that all seven hooks carry the 60_000 argument, strip pm:dispatched from driver-sql: 7 unbudgeted LIVE-cell hooks outside #14100, and a measured correction — hooks inherit hookTimeout (10000ms), not testTimeout (5000ms) #14213, landing record on the card.

The eighth site the dev found and deliberately did not fold in is #14628, unassigned for triage.


Generated by Claude Code

@os-musk
os-musk added this pull request to the merge queue Sep 2, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test:  FAIL   integration  test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected 'SIGKILL' to be null
    

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

分诊清单:

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

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

os-musk commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Ejected from the merge queue at 17:45Z — not this PR's failure, and deliberately NOT re-queued

Engine execution seat (session session_0112hMx9hjJ9BgB28X97DS68), reading queue build 33660420831.

What failed: Test Core (1/6) on packages/cli/test/run-dev-unbuilt-workspace.e2e.test.tsAssertionError: expected 'SIGKILL' to be null.

Why it is not this PR's. This diff is four *.test.ts files in packages/drivers/driver-sql, adding a third argument 60_000 to seven hooks. It touches no source, no packages/cli, and no shared config; the before/after AST walk in the PR body shows the 104 out-of-population sites unchanged in both directions. There is no mechanism by which it reaches a CLI end-to-end test.

Why it is not a regression at all. The assertion is a wall-clock claim, not a behavioural one: UNREAD_HARD_CAP_MS = 40_000 at :153 and setTimeout(() => child.kill('SIGKILL'), UNREAD_HARD_CAP_MS) at :186 mean signal === 'SIGKILL' reports only that the harness's own 40-second cap beat the child's own exit. That is exactly the exception the triage comment above names — an AssertionError whose object is the experiment's validity premise rather than product behaviour, in the same load-sensitive class as a timeout.

The decisive measurement came from a sibling PR on this lane, #14649: the same test, same assertion, failed on the PR side at 17:15:22Z and then passed on a re-run of the identical head — no push, no rebase — at 17:45:27Z. A test that is red and green on a byte-identical tree is not measuring the tree. Full reading, with the three ejected diffs and the shape of a real fix, is on the queue-flake anchor #14648, comment 5513952826.

Why this PR is not being re-queued. The triage comment's own checklist, item 2: once an aggregation issue exists, re-queuing before the fix only burns another full-queue round for every PR behind it. #14648 is open, domain:cli, unassigned. So this PR stands down and waits rather than paying that cost on a coin flip. ⛔ No re-queue while #14648 is open and unfixed. ⛔ The test is not being skipped, quarantined, relaxed or re-baselined by this lane — weakening a gate stays a human act, and this seat has not asked for one.

What unblocks it: a fix on #14648. The option that actually closes the class is to assert the child's own exit reason instead of the absence of a signal, taking the wall clock out of the oracle; raising the cap or serialising the file both leave a timing oracle for the next load spike. That call belongs to domain:cli, not here.

State: PR stays open, ready, mergeable_state: clean, auto-merge not re-armed. #14213 keeps pm:dispatched — the work is done and reviewed, the landing is blocked on someone else's flake. The seat re-checks #14648 on each patrol and re-arms the moment it is closed.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

driver-sql: 7 unbudgeted LIVE-cell hooks outside #14100, and a measured correction — hooks inherit hookTimeout (10000ms), not testTimeout (5000ms)

2 participants