Skip to content

test(driver-sql): make the update-stamp precision guard deterministic — drive §3's clock instead of racing it - #12112

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-11572-update-stamp-precision-determinism
Aug 25, 2026
Merged

test(driver-sql): make the update-stamp precision guard deterministic — drive §3's clock instead of racing it#12112
os-warren merged 2 commits into
mainfrom
claude/issue-11572-update-stamp-precision-determinism

Conversation

@os-warren

@os-warrenos-warren commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#11572

The number the card asked for, before any edit

The card said the frequency was unmeasured — one observation on one runner — so this
started by counting. Three measurements on this container, all on the SQLite cell (no
live PG/MySQL is provisioned here, so those two cells report as named skips):

measurementresult
ROUNDS (6) sqlite update+read round-trips, warm path, 2000 trialsmedian 0.63 ms; 41.9% of trials produced a single distinct stamp
40 runs of the real test file, as it actually executesnever red — but distribution size=2 (12 runs), 3 (18), 4 (8), 5 (2)
the cell's actual marginone clock tick: 30% of runs sat at exactly size=2, span=1 ms

§3 asserted size > 1. So on this hardware nearly a third of runs cleared the bar by a
single millisecond, and a machine one tick faster lands on size === 1 — which is what
CI reported on PR #11570, a diff touching no file under packages/drivers/.

That is the whole case for building something rather than re-running: the guard's margin
was never more than one tick, and its red is byte-identical to the defect it guards.

What was built — direction 1, done so the assertion gets stronger

The clock is now driven and verified rather than raced. awaitClockAdvance()
re-reads the clock and keeps waiting until the clock itself agrees the gap elapsed, so §3
never assumes a tick happened — a timer is a request, and what the cell needs is a fact.
MIN_GAP_MS = 5 is sized against both bounds §3 sits between: 5x above the millisecond
the audit columns are declared at, and three orders of magnitude below the second
truncation would need.

The assertion got stronger, not weaker: ROUNDS spaced updates must now yield
ROUNDS distinct stamps, where the old cell asked for two out of six. §1, §2 and the
span < 1_000 non-vacuity guard are untouched.

Coverage traded away, stated plainly: §3 no longer fires its updates at the machine's
maximum write rate — there is now ~5 ms of idle between them. That coverage did not leave
the file. §1, §1b and §2 still create and update with no sleep between them ("No sleep and
no backdating", §1), so the same-millisecond path stays measured where it belongs.
Resolution — §3's actual subject — is not a rate property.

A second, smaller guard: the count of intervals is pinned as well as their size, because
Math.min() of an empty list is Infinity, so a loop that stopped spacing its updates
would have satisfied the gap bound instead of failing it.

The closing condition: the failure now discriminates itself

Previously the discriminator was "a genuine regression reds §1, §2 and §3; a fast run
reds only §3" — knowledge living in a docblock and in a human comparing sections. §3's
failure message now carries the measured evidence and does the arithmetic itself. Actual
output, captured under ablation B below:

[#11572] the 6 updates collapsed onto 1 instant(s) — and a fast runner CANNOT explain it.
Each update was spaced by a clock-verified gap (5/5/6/5/5 ms, every one at or above 5 ms)
across a total span of 0 ms, so a stamp holding the millisecond resolution its audit column
is declared at must produce 6 distinct values here; second-precision truncation produces 1
and would need whole SECONDS, not 0 ms, to produce more. … Read this as a resolution
regression in updatedAtStamp() — not as a timing artifact, and without comparing which
other sections failed.: expected 1 to be 6

Compare the old text it replaces: expected 1 to be greater than 1.

No .skip, no quarantine, no retry, and no counted-skip bookkeeping — direction 1 removes
the probabilistic arm outright, so there is no skip to count.

Reverse verification — direction predicted in writing before either run

Both mutations were proven on disk by anchored grep counts of the text actually
changed
(removed-text occurrences → 0, injected marker → 1) before any result was read,
and both were restored under trap … EXIT INT TERM via
git checkout HEAD -- given the file's ABSOLUTE path (never a repo-relative one after a
cd, and never git checkout -- alone, which restores from the index the mutation step
itself wrote). Restore was then verified rather than trusted: git hash-object of the
working file equalled git rev-parse HEAD: that same path — both
6c12539f68c3266d371b0c0279d35c0be1cd6b8e — with git status --porcelain empty.

No rebuild is involved, and that is mechanical rather than assumed: the test reaches
the driver by a relative import (./index.js./sql-driver.js), and
packages/drivers/driver-sql/dist does not exist in this worktree at all — only the
dependency closure was built. A dist-resolved ablation could not have run here.

Ablation B — the defect class on the cell that is actually runnable.
Mutation: the SQLite branch truncated to whole seconds
(new Date().toISOString().replace(/\.\d{3}Z$/, '.000Z')).
Predicted: RED in three sections — §1, §2, §3 — with §4/§5 green.
Observed: RED in four — §1, §1b, §2, §3; §4 and §5 green (the ISO_Z shape admits
.000Z). The direction was as predicted; §1b, the bulk-door twin of §1, was
under-enumerated in the prediction and also reds. The rebuilt §3 fails on the real defect,
and fails distinguishably — the message above is its output.

Ablation A — the card's literal ask, and an honest negative result.
Mutation: main's pre-#11224 body restored
(this.isMysql ? this.knex.fn.now(3) : this.knex.fn.now()this.knex.fn.now()).
Predicted: NO CHANGE — the SQLite cell stays green.Observed: green, vitest exit 0.
This is not evidence the guard works: #11224 changed only the NON-sqlite branch, so
restoring main's body is a no-op on the only cell this container can run. The file's
own docblock already says the reverse verification reds "on the live MySQL cell ONLY".
Ablation B is what makes A's green readable as a genuine no-op rather than an unreached
mutation — same file, same edit mechanism, and B reds.

The head docblock now records both, so the next reader is not left to rediscover that the
documented ablation is inert without a live MySQL.

Verification — all at the final commit 32638fbd3f

Every exit code captured before any pipe; each family's own verdict line quoted.

  • pnpm --filter @objectstack/driver-sql typechecktsc --noEmit, no output, exit 0
  • targeted file — Test Files 1 passed (1), Tests 7 passed | 2 skipped (9) (the 2 skips are the unprovisioned live cells)
  • full package suite — Test Files 131 passed | 8 skipped (139), 0 FAIL
  • determinism of the rebuilt cell — 32/32 completed runs green, zero [#11572] messages, zero FAIL lines. A 33rd run was cut off mid-startup by the container's ~10-minute foreground cap (its log ends at the vitest banner, never reaching an assertion) — reported as a harness event, not a test outcome. Because the assertion is toBe(ROUNDS), each green is the statement that the run observed exactly 6 distinct stamps.
  • pnpm lintrun in full, not narrowed: eslint . --no-inline-config, 5154 files, 0 errors, 0 warnings, VERDICT command-exit 0

Gate union derived, not recalled (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack,
re-derived at 32638fbd3f; 8 path-matched + the adds or edits a test file convention kind).
All green:

familyits own verdict line
check:driver-conformanceOK — 45 covered cell(s), 0 in the DEBT ledger, 0 exempt.
check:published-files✓ … 69 publishable package(s) of 78 workspace member(s) …
check:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new · baseline key set verified against 8450eea: no files added.
check:test-source-aliasOK — 72 packages with tests scanned
check:type-source-resolutionOK — 93 tsc program(s) across 77 packages scanned
check-plugin-teardown-shape✓ … 63 Plugin implementation(s) across 4675 source(s)
check-affected-docs / check-drift-commentexit 0 · ✓ check-drift-comment: 56 cases pass across 5 fixture diff(s).
check:query-options-erasure✓ query-options-erasure ratchet holds: … none new · baseline key set verified against 8450eea: no files added.
check:type-check-coverageOK — 65/78 workspace packages type-checked
check:engine-double-contractOK — 408 pinned, 133 in the DEBT ledger, 2 exempt.
check:cross-package-test-inputsOK: 16 package(s) read outside themselves, all declared
check:where-matcher0 silently-wrong and 0 unjudged matcher(s) … none new. · baseline key set verified against 8450eea: no files added.
check-nul-bytesOK (scanned 6707 text file(s) … no raw ASCII control bytes)

Changeset

None — graded against check-empty-changeset.mjs's own rule rather than assumed. Its
header states the route directly: for a PR that releases nothing, "delete the changeset
and apply the skip-changeset label"
; an empty-frontmatter changeset is a real input
to changesets/action and an all-empty set stalls the release silently and greenly
(#4898). This diff is one test file — no shipped behaviour, no public export moves — so it
takes the label and writes no .changeset/*.md.

Scope

One file: packages/drivers/driver-sql/src/sql-driver-11224-update-stamp-precision.test.ts.
No sql-driver.ts source edit — the two mutations above were ablation-only and both
restored byte-identically. The measurement found no evidence the product stamping is
wrong: §1, §2, §4, §5 and §6 are green throughout on the unmutated tree.

Generated by Claude Code

…racing it
The precision guard's section 3 fired ROUNDS updates back to back and required
that at least two landed on different values, which needs the wall clock to
tick during the run -- a property of the machine, not of the code. Its whole
margin was measured at one clock tick on this container, and it had already
reddened CI on an unrelated diff with entirely correct stamping.
The gap between updates is now forced and, more importantly, verified by
re-reading the clock rather than trusting a timer, so the distinctness the
section asserts is entailed by the spacing rather than hoped for. The
assertion got stronger in the process (ROUNDS spaced updates must yield ROUNDS
distinct stamps, where the old cell asked for two out of six), and the failure
message now carries the measured gaps, the measured span and the arithmetic
that rules a fast runner out -- so the red discriminates itself instead of
being byte-identical to the defect it guards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o
…ss vacuously
Math.min() of an empty list is Infinity, so a loop that stopped spacing its
updates would satisfy the gap assertion instead of failing it. Pin the count of
intervals as well as their size.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o
@github-actions

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 a933ed720ec240e154fdfe433a7aced48b557700packageMentionDocs.

@os-warrenos-warren added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed tests labels Aug 25, 2026 — with Claude
@os-warren
os-warren marked this pull request as ready for review August 25, 2026 09:37
@os-warren
os-warren added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit d2cacbcAug 25, 2026
41 checks passed
@os-warren
os-warren deleted the claude/issue-11572-update-stamp-precision-determinism branch August 25, 2026 10:08
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-warren@claude