Skip to content

[finding] Sweep: which consumers compare or format a value whose runtime type differs between the Date-materialising drivers and the ISO-text ones #13973

Description

@claude

UNBLOCKED 2026-09-07T09:26Z — engine execution seat, session session_01ADLdAs2pVcH17h9tZKWMBg, R18. The Blocked-by: #14078 line is removed and the 2026-09-02 hold below is discharged. ⛔ Not released on "the blocker closed": released on the condition that hold was actually ABOUT, verified in the tree.

The hold said the ruling assumed #14078's NaN guard "makes the conversion total" and that the guard was measured absent from main. Re-measured on origin/main 0344f40: the guard is present in every named spellingmetadata-protocol/src/protocol.ts:1730, sys-metadata-repository.ts:152 and :205, rest/src/rest-server.ts:774 and :838, metadata/src/loaders/database-loader.ts:94 and :150 — landed by PR #16427 ("a total Date arm on the shared canonical-ISO spelling — all five arms at once"), with pins sys-metadata-repository-14078-invalid-date-total-arm.test.ts, database-loader-14078-invalid-date-total-arm.test.ts and sql-driver-14078-invalid-date-materialisation.test.ts beside them. The hold's second limb is also spent: #14078 closed completed carrying bug · priority:p1 · domain:engine and no pm:retriage, so the grading dispute that made it undispatchable is over.

放行双查, both answered before the write: ① the condition released is the one carried by the most recent conversion comment (the 2026-09-02T18:20Z note below — nothing newer exists on this card); ② the card carries no merged PR newer than that comment (closed_by_pull_requests = 0, and the card's last event before this one was 2026-09-02T18:18Z).

⚠️ Carried forward for whoever picks this up, because it changes the census rather than the card: PR #16427 gave five arms a total Date arm. Sites that were class (b) — correct only because the value happens to arrive as text in every test it has — may now be class (a) at those five arms specifically. ⛔ Do not inherit any pre-16427 verdict; criterion 2's search expression is re-run on the current tree, and #16427's arms are re-verdicted, not assumed.

Superseded hold note (2026-09-02T18:20Z) — kept as history, ⛔ no longer current

⚠️ State note (engine execution seat, 2026-09-02T18:20Z). This card is RULED (B1 narrow, comment 5507803003) and would otherwise be dispatchable. It is held because the ruling's own execution note says #14078's NaN guard "makes the conversion total", and that guard is measured absent from main — see the measurement comment below. #14078's fix half carries pm:retriage, so it is undispatchable while its grading is disputed, and this lane will not absorb another card's ruled work under an unresolved dispute. The unlock scan returns this card to pm:queue when #14078 closes. ⛔ Do not dispatch B1 on the assumption that the guard is present.


Recording the sweep #13567 deliberately declined to answer, so it does not close with that card. Filed by the seat that implemented #13567's option 1, per the triage note on that card: the question currently exists only as a paragraph of prose inside another issue's body, where it is invisible to candidate queries and staleness alerts.

The question

updated_at is not the only value whose RUNTIME TYPE differs between the drivers that materialise a JS Date (Postgres, MySQL, MongoDB) and the ones that hand back ISO text (the SQLite family, memory). The OCC seam was a production bug precisely because a consumer compared such a value with String(v) on both sides while every test it had drove the text side.

So: which other consumers compare, sort, key or format one of these values, while only ever being exercised against the text-side drivers?

Why this is worth a card and not a paragraph

The class is the expensive one AGENTS.md already names — a green suite that is not a suite, on exactly the path a double was introduced for. It is invisible to the type system: on the Date drivers the value is a Date, on the text drivers it is a string, and both flow through the same any-shaped record. A consumer that happens to be correct for one is not thereby correct for the other, and nothing today reports which consumers are in that position.

The two facts that make it findable are now pinned rather than assumed, in packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts: the live dialects hand updated_at out of the record read door as a Date, and SQLite hands it out as canonical ISO-8601-Z text.

Executable criteria for whoever picks this up

This is a SWEEP — a census with a verdict per site — not a fix. Done means:

  1. Enumerate the values. Every field or builtin column whose materialised JS type is driver-dependent, derived rather than recalled: the audit columns (created_at, updated_at), Field.datetime, and anything else the drivers do not normalise on read. SqlDriver#formatOutput is the authority for driver-sql — note which of its repairs sit inside if (this.isSqlite) and which run for every dialect, because that gating IS the divergence.
  2. Enumerate the consumers. Every site that does String(v), v1 === v2, .localeCompare, template interpolation, .startsWith, a sort comparator, a Map key, or a date format on one of those values, across packages/** — with the search expression recorded so the census can be re-run.
  3. Give each site a verdict: (a) correct on both shapes; (b) correct only because the value happens to arrive as text in every test it has; (c) genuinely wrong on one side. Class (b) is the finding — it is the state the OCC seam was in.
  4. Report which consumers are tested against a Date-materialising driver at all. A consumer that has never seen one is class (b) by construction.
  5. Class (c) sites become their own cards. Class (b) sites become a decision: pin the composed fact where a live server exists, normalise at the producer, or record the acceptance.

Explicitly NOT part of this

Backlinks

Recorded from #13567 (the OCC coverage gap) and the repair that preceded it, #13382. Neither is addressed by this card, and this card does not close either.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions