Skip to content

Commit c5b7d84

Browse files
os-muskclaude
andauthored
docs: correct nine more out-of-package comments that still gated formatOutput's timestamp passes on isSqlite (#16866)
* docs: correct eight out-of-package comments that still gated formatOutput's timestamp passes on isSqlite WIP checkpoint before the verification laps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg * chore: changeset for the cli bundle bytes Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg * docs(metadata): correct the migration's numbered step now that #16834 has landed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg * chore: changeset names only the package whose dist bytes change Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 10d05bb commit c5b7d84

10 files changed

Lines changed: 209 additions & 62 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
Correct the remaining out-of-package comments that still described
6+
`SqlDriver#formatOutput`'s two timestamp passes as gated on `if (this.isSqlite)`.
7+
8+
Since ADR-0053 D-F1 (#13973) both passes — the `AUDIT_TIMESTAMP_COLUMNS` pass and the
9+
`normalizeSqliteDatetimeOutput` pass over `datetimeFields` — run on every dialect, so the
10+
record read door presents the builtin audit columns and every declared `Field.datetime`
11+
as canonical ISO-8601-`Z` text on Postgres and MySQL as well as SQLite. Measured on the
12+
tree rather than recalled: in `packages/drivers/driver-sql/src/sql-driver.ts` the
13+
`if (this.isSqlite)` arm inside `formatOutput` opens at line 16965 and closes at 17026,
14+
covering only the JSON codec and the numeric-scalar repair, while the audit-column loop
15+
(17046) and the `normalizeSqliteDatetimeOutput` loop (17061) both sit at the method's top
16+
level, below that closing brace.
17+
18+
Two of the corrected comments were load-bearing rather than merely stale. The
19+
`service-storage` one drew a conclusion for a live read door from the false premise, and
20+
it also claimed that folding at the driver's read boundary "would reverse the deliberate
21+
`withPostgresCalendarDayAsText` decision" — which is what #13973 ruled and did. The two
22+
`packages/cli` ones attached the wrong reason to a true fact: the holder probe reads
23+
through the raw-SQL seam, so `formatOutput` never runs on that path at all, and the
24+
dialect divergence there survives the ruling for that reason and not because of a gate.
25+
26+
Comments only — no runtime behaviour, no exported symbol and no public type changes.
27+
`@objectstack/cli` is the one package named here because its per-file build carries the
28+
amended text verbatim into `dist/commands/migrate/duplicates.js` and
29+
`dist/commands/migrate/duplicates.d.ts`, so its published output changes.
30+
`@objectstack/metadata-protocol` is deliberately NOT named: its edits are all in test
31+
files, which are not published. `@objectstack/service-storage` and `@objectstack/metadata`
32+
are deliberately NOT named either: their source edits are JSDoc blocks on the internal
33+
`usableCreatedAt` and `canonicalTimestampText`, and both bundles strip them — measured
34+
absent from `dist/`, with each package's identifier found in the same `dist/` (and the
35+
exported `StrandedOrphanInventoryEngine` docblock present in `dist/index.d.ts`) as the
36+
firing control that the probe works.
37+
38+
Three carve-outs are preserved rather than flattened: `withPostgresCalendarDayAsText` is
39+
untouched by that ruling (D-F2 — the client library still materialises `timestamptz` /
40+
`DATETIME(3)` as a `Date`); the Invalid `Date` residue still stands (D-F3 — the one `Date`
41+
shape with no canonical text leaves the read door unchanged, so no sentence claims the
42+
read door never hands out a `Date`); and the ruled-B consumer arms stay, with only the
43+
prose explaining why they exist corrected.

packages/cli/src/commands/migrate/duplicates.created-at-canonical.test.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
*
88
* `DuplicateHolder.createdAt` is declared `string | null`, and the mapper built
99
* it with `String(row.created_at)`. `created_at` is a BUILTIN audit column — not
10-
* in `datetimeFields`, and `SqlDriver#formatOutput` repairs it only inside its
11-
* `if (this.isSqlite)` arm — and the holder probe reads through the raw-SQL seam,
12-
* so no presentation runs on this path at all. The dialect therefore decides what
13-
* arrives:
10+
* in `datetimeFields`, so no declared-field coercion reaches it — and the holder
11+
* probe reads through the raw-SQL seam, so `formatOutput` does not run on this
12+
* path at all. The dialect therefore decides what arrives:
1413
*
1514
* - **Postgres / MySQL** materialise a JS `Date`, so `String()` ran
1615
* `Date.prototype.toString`: `Sun Aug 30 2026 18:19:25 GMT+0800 (China
@@ -43,8 +42,18 @@
4342
* expression really produced.
4443
*
4544
* ⛔ Not a `??` fallback and not a driver change: `withPostgresCalendarDayAsText`
46-
* is a deliberate driver decision and is untouched. The CLI is a leaf consumer
47-
* with a declared `string | null`, so the canonical spelling is owed here.
45+
* is a deliberate driver decision and is untouched ([ADR-0053 D-F2]). The CLI is
46+
* a leaf consumer with a declared `string | null`, so the canonical spelling is
47+
* owed here.
48+
*
49+
* ⚠️ What keeps this file live is the SEAM, ⛔ not an `if (this.isSqlite)` gate
50+
* inside `formatOutput`. That gate is gone: #13973 ([ADR-0053 D-F1]) lifted both
51+
* of `formatOutput`'s timestamp passes — the `AUDIT_TIMESTAMP_COLUMNS` pass and
52+
* the `normalizeSqliteDatetimeOutput` pass over `datetimeFields` — out of it and
53+
* they run on EVERY dialect, so the RECORD read door presents the canonical text
54+
* everywhere. The holder probe does not go through that door; §B3 of the pin
55+
* named above reads the same row raw through knex and still gets the dialect's
56+
* `Date` on the live cells, which is the fact §A1 drives.
4857
*/
4958

5059
import { describe, it, expect, beforeAll, afterAll } from 'vitest';

packages/cli/src/commands/migrate/duplicates.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -578,14 +578,23 @@ export function answeringSeam(exec: SeedTenancyExec): SeedTenancyExec {
578578
/**
579579
* The canonical `createdAt` spelling for one holder row.
580580
*
581-
* `created_at` is a BUILTIN audit column: it is not in `datetimeFields`, and
582-
* `SqlDriver#formatOutput` repairs it only inside its `if (this.isSqlite)` arm —
583-
* and the holder probe reads through the raw-SQL seam anyway, so no presentation
584-
* runs on this path at all. The DIALECT therefore decides what lands in
585-
* `row.created_at`, and both sides of that asymmetry are pinned in
586-
* `packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`:
587-
* Postgres and MySQL materialise a JS `Date`, SQLite and its siblings hand back
588-
* canonical ISO-8601 UTC text.
581+
* `created_at` is a BUILTIN audit column: it is not in `datetimeFields`, so no
582+
* declared-field coercion reaches it — and the holder probe reads through the
583+
* raw-SQL seam anyway, so `formatOutput` does not run on this path at all. The
584+
* DIALECT therefore decides what lands in `row.created_at`: Postgres and MySQL
585+
* materialise a JS `Date`, SQLite and its siblings hand back canonical ISO-8601
586+
* UTC text. Pinned in
587+
* `packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`
588+
* §B3, which reads the same row raw through knex and still gets the dialect's
589+
* `Date` on the live cells.
590+
*
591+
* ⚠️ The reason is the SEAM, ⛔ not an `if (this.isSqlite)` gate inside
592+
* `formatOutput`. That gate is gone: #13973 ([ADR-0053 D-F1]) lifted both of
593+
* `formatOutput`'s timestamp passes — the `AUDIT_TIMESTAMP_COLUMNS` pass and
594+
* the `normalizeSqliteDatetimeOutput` pass over `datetimeFields` — out of it,
595+
* and they run on EVERY dialect now, so the record read door presents the
596+
* canonical text everywhere. This path simply never reaches that door, which is
597+
* why the divergence survives HERE after the driver closed it there.
589598
*
590599
* `DuplicateHolder.createdAt` is declared `string | null`, so this leaf consumer
591600
* is the side that owes the canonical spelling — the same form the `occurredAt`

packages/metadata-protocol/src/protocol-14038-list-commits-created-at-iso.test.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,27 @@
1010
* ## The defect
1111
*
1212
* `created_at` is an engine-injected audit column: it is not in
13-
* `datetimeFields`, and `SqlDriver#formatOutput` repairs it (both the
14-
* builtin-audit-column repair and the `datetimeFields` fold) only inside its
15-
* `if (this.isSqlite)` arm (`sql-driver.ts`, `formatOutput`). Postgres and
16-
* MySQL therefore hand this column out of the record read door as a JS
17-
* `Date`, while the SQLite family hands out canonical ISO-Z text — pinned
18-
* live in
19-
* `packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`.
20-
* So on the production default driver, `listCommits` handed every
21-
* in-process consumer a `Date` in a field the type says is a `string`.
13+
* `datetimeFields`, and when this landed `SqlDriver#formatOutput` repaired it
14+
* (both the builtin-audit-column repair and the `datetimeFields` fold) only
15+
* inside its `if (this.isSqlite)` arm (`sql-driver.ts`, `formatOutput`).
16+
* Postgres and MySQL therefore handed this column out of the record read door
17+
* as a JS `Date`, while the SQLite family handed out canonical ISO-Z text. So
18+
* on the production default driver, `listCommits` handed every in-process
19+
* consumer a `Date` in a field the type says is a `string`.
20+
*
21+
* #13973 ([ADR-0053 D-F1]) has since lifted BOTH passes out of that gate — they
22+
* run on EVERY dialect now, so the record read door presents the canonical
23+
* text — and the pin that recorded the asymmetry records that contract instead
24+
* (`packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`
25+
* §B1, inverted on purpose).
26+
*
27+
* ⚠️ That does not make the cases below historical. `withPostgresCalendarDayAsText`
28+
* is untouched by that ruling ([ADR-0053 D-F2]) — the CLIENT still materialises
29+
* `timestamptz` / `DATETIME(3)` as a `Date`, and what moved is where the driver
30+
* folds it — and the `Date` domain at this mapper did not close: `driver-sql`
31+
* hands an INVALID `Date` through unchanged ([ADR-0053 D-F3]) and non-SQL
32+
* drivers materialise their own. What these cases own is the mapper's behaviour
33+
* per INPUT SHAPE, which outlives the dialect fact.
2234
*
2335
* ## Why the fixture drives a hand-made `Date`
2436
*

packages/metadata-protocol/src/protocol.commit-timeline-instant-order.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@
88
// The defect
99
// ---------------------------------------------------------------------------
1010
// `created_at` is an engine-injected audit column: it is not in `datetimeFields`
11-
// and `SqlDriver#formatOutput` repairs it only inside `if (this.isSqlite)`, so
12-
// the live SQL dialects hand it out of the record read door as a JS `Date` while
13-
// the SQLite family hands out canonical ISO-Z text. Pinned one layer down by
14-
// `packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`.
11+
// and, when this landed, `SqlDriver#formatOutput` repaired it only inside
12+
// `if (this.isSqlite)`, so the live SQL dialects handed it out of the record
13+
// read door as a JS `Date` while the SQLite family handed out canonical ISO-Z
14+
// text. #13973 ([ADR-0053 D-F1]) has since lifted both of `formatOutput`'s
15+
// timestamp passes out of that gate — they run on EVERY dialect now — and the
16+
// pin one layer down records that contract instead
17+
// (`packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`
18+
// §B1, inverted on purpose).
19+
//
20+
// The `Date` this file drives is still a shape `compareAuditInstants` receives,
21+
// so what is pinned below stays a live comparator arm: `withPostgresCalendarDayAsText`
22+
// is untouched by that ruling ([ADR-0053 D-F2]) so the CLIENT still materialises
23+
// the column as a `Date`, `driver-sql` hands an INVALID `Date` through unchanged
24+
// ([ADR-0053 D-F3]), and non-SQL drivers materialise their own.
1525
//
1626
// Both timeline consumers in `protocol.ts` compared `String(created_at)`:
1727
//

packages/metadata-protocol/src/sys-metadata-repository-13997-authored-at-canonicalisation.test.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@
1717
* - `rowToItem()` (reached by `get()`) — `updated_at` / `created_at`, the
1818
* BUILTIN audit columns.
1919
*
20-
* On Postgres and MySQL both arrive out of the record read door as a JS
21-
* `Date`: `SqlDriver#formatOutput` repairs the audit columns and folds
22-
* declared `datetime` columns only inside its `if (this.isSqlite)` arm, and
23-
* `withPostgresCalendarDayAsText` leaves `timestamptz` / `timestamp`
24-
* deliberately untouched. That dialect fact is pinned live in
25-
* `packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`.
20+
* When this landed, both arrived out of the record read door as a JS `Date` on
21+
* Postgres and MySQL: `SqlDriver#formatOutput` repaired the audit columns and
22+
* folded declared `datetime` columns only inside its `if (this.isSqlite)` arm.
23+
* #13973 ([ADR-0053 D-F1]) has since lifted both passes out of that gate — they
24+
* run on EVERY dialect — and the pin that recorded the asymmetry records the
25+
* canonical-text contract instead
26+
* (`packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`
27+
* §B1, inverted on purpose).
28+
*
29+
* ⚠️ `withPostgresCalendarDayAsText` is untouched by that ruling
30+
* ([ADR-0053 D-F2]) and still leaves `timestamptz` / `timestamp` deliberately
31+
* alone — the CLIENT still hands back a `Date`; what changed is that the driver
32+
* folds it at its own read boundary. The `Date` this file plants therefore
33+
* still reaches both adapters: `driver-sql` hands an INVALID `Date` through
34+
* unchanged ([ADR-0053 D-F3]) and non-SQL drivers materialise their own, so
35+
* what is pinned below is a live adapter arm rather than a historical one.
2636
*
2737
* ## Why nothing reported it, and what that costs THIS file
2838
*

packages/metadata/src/loaders/database-loader.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,10 +1514,20 @@ describe('MetadataManager auto-configuration', () => {
15141514
* (`packages/spec/src/system/metadata-persistence.zod.ts`) — stricter than the
15151515
* sibling `MetadataItem.authoredAt`, which is a bare `z.string()`. `stat()`
15161516
* built it from `record.updatedAt ?? record.createdAt`, and `created_at` /
1517-
* `updated_at` are BUILTIN audit columns: not in `datetimeFields`, and
1518-
* `SqlDriver#formatOutput` repairs them only inside its `if (this.isSqlite)`
1519-
* arm. On Postgres and MySQL they arrive as a JS `Date`, pinned live in
1520-
* `packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`.
1517+
* `updated_at` are BUILTIN audit columns: not in `datetimeFields`, and when
1518+
* this landed `SqlDriver#formatOutput` repaired them only inside its
1519+
* `if (this.isSqlite)` arm, so on Postgres and MySQL they arrived as a JS
1520+
* `Date`. #13973 ([ADR-0053 D-F1]) has since lifted both of `formatOutput`'s
1521+
* timestamp passes out of that gate — they run on EVERY dialect — and the pin
1522+
* that recorded the asymmetry records the canonical-text contract instead
1523+
* (`packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts`
1524+
* §B1, inverted on purpose).
1525+
*
1526+
* ⚠️ The `Date` these cases drive is still a shape `stat()` receives, so they
1527+
* pin a LIVE arm: `withPostgresCalendarDayAsText` is untouched by that ruling
1528+
* ([ADR-0053 D-F2]) so the CLIENT still materialises the column as a `Date`,
1529+
* `driver-sql` hands an INVALID `Date` through unchanged ([ADR-0053 D-F3]),
1530+
* and non-SQL drivers materialise their own.
15211531
*
15221532
* ⚠️ `rowToRecord` reaches `createdAt` / `updatedAt` through an unchecked
15231533
* `row.created_at as string | undefined` cast, so the `string` in

packages/metadata/src/migrations/migrate-sys-notification-to-event.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -491,20 +491,32 @@ async function recordNotificationEventReceipt(
491491
* `formatOutput`, so none of its repairs apply here on any dialect:
492492
*
493493
* - `created_at` is a BUILTIN audit column, so it is never in `datetimeFields`
494-
* and no declared-field coercion reaches it; `formatOutput` repairs it only
495-
* inside its `if (this.isSqlite)` arm (`repairNaiveUtcAuditTimestamp` over
496-
* `AUDIT_TIMESTAMP_COLUMNS`).
494+
* and no declared-field coercion reaches it; what repairs it is
495+
* `formatOutput`'s own `AUDIT_TIMESTAMP_COLUMNS` pass
496+
* (`repairNaiveUtcAuditTimestamp`) at the RECORD read door — a door this
497+
* path does not go through.
497498
* - `read_at` is a LEGACY column ADR-0030 removed from the object, so it is
498499
* not declared either — it can never enter `datetimeFields`, and it is not
499500
* an audit column, so no arm of `formatOutput` could reach it even at the
500501
* record read door.
501502
*
503+
* ⚠️ The reason no repair reaches this path is the SEAM, ⛔ not an
504+
* `if (this.isSqlite)` gate inside `formatOutput`. Both of `formatOutput`'s
505+
* timestamp passes — the `AUDIT_TIMESTAMP_COLUMNS` pass and the
506+
* `normalizeSqliteDatetimeOutput` pass over `datetimeFields` — sat inside that
507+
* arm until #13973 ([ADR-0053 D-F1]) lifted them out, and they run on EVERY
508+
* dialect now. So the record read door presents canonical text everywhere while
509+
* this raw-SQL door still hands back whatever the client materialised, which is
510+
* why the divergence below survives the ruling HERE and nowhere upstream of it.
511+
*
502512
* On SQLite both arrive as canonical ISO text and `String()` is the identity —
503513
* which is why every test in this directory stayed green. On Postgres and
504514
* MySQL an instant column materialises as a JS `Date`
505-
* (`withPostgresCalendarDayAsText` leaves the instant types alone deliberately;
506-
* pinned in `sql-driver-13567-audit-stamp-materialisation.test.ts`), and
507-
* `String(date)` spells
515+
* (`withPostgresCalendarDayAsText` leaves the instant types alone deliberately,
516+
* [ADR-0053 D-F2]; pinned in
517+
* `sql-driver-13567-audit-stamp-materialisation.test.ts` §B3, which reads the
518+
* same row raw through knex and still gets the dialect's `Date` on the live
519+
* cells), and `String(date)` spells
508520
*
509521
* Sun Aug 30 2026 18:19:25 GMT+0800 (China Standard Time)
510522
*

packages/services/service-storage/src/stranded-orphan-inventory.test.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -513,11 +513,23 @@ describe("[#10950] the sweep cannot nominate a stranded orphan — the card's pr
513513
* ## Why this file could not have caught the defect before
514514
*
515515
* `created_at` is a BUILTIN audit column, so no declared-field coercion
516-
* reaches it and `SqlDriver#formatOutput` repairs it only inside its
517-
* `if (this.isSqlite)` arm. The record read door therefore hands it back as
518-
* canonical ISO-Z TEXT on SQLite and as a JS `Date` on Postgres and MySQL —
519-
* pinned at that door, per dialect, in driver-sql's
520-
* `sql-driver-13567-audit-stamp-materialisation.test.ts` (§B0/§B1).
516+
* reaches it and `SqlDriver#formatOutput` USED TO repair it only inside its
517+
* `if (this.isSqlite)` arm. While that gate stood, the record read door handed
518+
* it back as canonical ISO-Z TEXT on SQLite and as a JS `Date` on Postgres and
519+
* MySQL. #13973 ([ADR-0053 D-F1]) has since lifted both of `formatOutput`'s
520+
* timestamp passes out of that gate — they run on EVERY dialect now — and the
521+
* pin that recorded the asymmetry records the canonical-text contract instead
522+
* (driver-sql's `sql-driver-13567-audit-stamp-materialisation.test.ts`
523+
* §B0/§B1, inverted on purpose).
524+
*
525+
* ⚠️ The `Date` these cases drive is still a shape `usableCreatedAt` receives,
526+
* so they pin a LIVE arm rather than a historical one: `driver-sql` hands an
527+
* INVALID `Date` through unchanged ([ADR-0053 D-F3] — the one shape with no
528+
* canonical text) and a non-SQL driver materialises its own.
529+
* `withPostgresCalendarDayAsText` is untouched by that ruling
530+
* ([ADR-0053 D-F2]), so the CLIENT still materialises `timestamptz` /
531+
* `DATETIME(3)` as a `Date`; what changed is that the driver folds it at its
532+
* own read boundary.
521533
*
522534
* ⚠️ This repo's default test backend is SQLite, and every fixture above
523535
* spells `created_at` as an ISO STRING — the one shape the old

0 commit comments

Comments
 (0)