You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
MetadataItem.authoredAt is declared z.string() but receives a JS Date on Postgres/MySQL — a silent declared-contract violation, because the schema is parsed only in its own test #13997
Found by the driver-materialisation consumer census on #13973 (class (c) — genuinely wrong on one side). That sweep is not addressed by this card and does not close it.
The spec side has already answered which side owes the canonical spelling. MetadataItem is z.infer<typeof MetadataItemSchema>, so authoredAt is string to every consumer.
The two producers that violate it
1. packages/metadata-protocol/src/sys-metadata-repository.ts:1752, in rowToItem:
created_at / updated_at are builtin audit columns: not in datetimeFields, and SqlDriver#formatOutput repairs them only inside if (this.isSqlite). Pinned in packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts — the live dialects hand them out of the record read door as a JS Date, SQLite as canonical ISO-Z text. So on Postgres and MySQL a Date lands in a field declared and consumed as an ISO-8601 string.
MetadataItemSchema is never parsed on any production path. Measured — the only .parse call sites in the repo are in packages/metadata-core/test/types.test.ts:70,74,78, the schema's own unit test, which feeds a hand-made sample. So the runtime validator that would have caught this is a phantom check for this seam: it evaluates only against inputs that were never near a driver.
Every sibling producer of the same field spells it canonically and is class (a) — sys-metadata-repository.ts:417 and :606, remote-loader.ts:108, memory-loader.ts:72, in-memory-repository.ts:120. The two sites above are exactly the two that pass a driver value straight through.
Why this is not a ?? fallback
Per #13973's standing prohibition, the question is which side owes the canonical spelling — and here, unusually for this census, the contract already says: z.string(), ISO-8601. The producer owes it. Candidate shapes:
A — canonicalise in rowToItem / the stats builder, the two places that adapt a driver row into a declared MetadataItem / MetadataStats. Smallest change, keeps the declared type honest at the adapter boundary where it is asserted.
B — normalise at the driver's read door so a builtin audit column presents one shape per dialect. Fixes every sibling site in the census at once, but reverses a deliberate driver decision (withPostgresCalendarDayAsText: "timestamptz / timestamp are deliberately untouched: those are instants, a Date is the right materialisation for them") — a maintainer call, not an implementer's.
Worth deciding alongside: whether MetadataItemSchema should be parsed somewhere on a real path, since a declared schema nothing runs cannot enforce the answer either way.
Found by the driver-materialisation consumer census on #13973 (class (c) — genuinely wrong on one side). That sweep is not addressed by this card and does not close it.
The declared contract
packages/metadata-core/src/types.ts:103:The spec side has already answered which side owes the canonical spelling.
MetadataItemisz.infer<typeof MetadataItemSchema>, soauthoredAtisstringto every consumer.The two producers that violate it
1.
packages/metadata-protocol/src/sys-metadata-repository.ts:1752, inrowToItem:2.
packages/metadata/src/loaders/database-loader.ts:917, the sibling shape onMetadataStats.mtime:created_at/updated_atare builtin audit columns: not indatetimeFields, andSqlDriver#formatOutputrepairs them only insideif (this.isSqlite). Pinned inpackages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts— the live dialects hand them out of the record read door as a JSDate, SQLite as canonical ISO-Z text. So on Postgres and MySQL aDatelands in a field declared and consumed as an ISO-8601string.Why nothing reports it
Two independent reasons, and both need naming:
rowisany. The value flows through the sameany-shaped record [finding] Sweep: which consumers compare or format a value whose runtime type differs between the Date-materialising drivers and the ISO-text ones #13973 describes, so tsc sees astringassignment that never happened.MetadataItemSchemais never parsed on any production path. Measured — the only.parsecall sites in the repo are inpackages/metadata-core/test/types.test.ts:70,74,78, the schema's own unit test, which feeds a hand-made sample. So the runtime validator that would have caught this is a phantom check for this seam: it evaluates only against inputs that were never near a driver.Every sibling producer of the same field spells it canonically and is class (a) —
sys-metadata-repository.ts:417and:606,remote-loader.ts:108,memory-loader.ts:72,in-memory-repository.ts:120. The two sites above are exactly the two that pass a driver value straight through.Why this is not a
??fallbackPer #13973's standing prohibition, the question is which side owes the canonical spelling — and here, unusually for this census, the contract already says:
z.string(), ISO-8601. The producer owes it. Candidate shapes:rowToItem/ the stats builder, the two places that adapt a driver row into a declaredMetadataItem/MetadataStats. Smallest change, keeps the declared type honest at the adapter boundary where it is asserted.withPostgresCalendarDayAsText: "timestamptz/timestampare deliberately untouched: those are instants, aDateis the right materialisation for them") — a maintainer call, not an implementer's.Worth deciding alongside: whether
MetadataItemSchemashould be parsed somewhere on a real path, since a declared schema nothing runs cannot enforce the answer either way.Re-run
Backlink: #13973 (census), #13382 (the OCC seam, the same class). Neither is addressed here.