Found while implementing #13995 (which is not this, and does not close this). Same class as the #13973 driver-materialisation census — class (c), genuinely wrong on one side — but a site the census's seven do not include: #13997 covers MetadataItem.authoredAt in sys-metadata-repository.ts and MetadataStats.mtime in database-loader.ts, and neither is this one.
The site
packages/metadata-protocol/src/protocol.ts, listCommits. Its declared return type says:
}>): Promise(Array({
...
createdAt?: string;}))(spelled loosely above to keep angle brackets out of this body; the real declaration is the Promise of an Array of that object literal, and the member is verbatim createdAt?: string.)
The mapping assigns the RAW driver value:
constmapped=rows.map((r)=>({
...
...(r.created_at ? {createdAt: r.created_at} : {}),}));Why it is wrong on the production default driver
created_at is a builtin audit column: not in datetimeFields, and SqlDriver#formatOutput repairs it only inside if (this.isSqlite). Pinned in packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts — the live dialects hand it out of the record read door as a JS Date, SQLite as canonical ISO-Z text.
So on Postgres and MySQL, listCommits hands every in-process consumer a Date in a field the type says is a string, on every call, silently.
Why nothing reports it
- The type system cannot see it.
rows is any[], so the assignment is unchecked; every consumer then reads a string that is really a Date, and TypeScript agrees with the declaration rather than with the value. - The REST door hides it.
GET /packages/:id/commits returns through deps.success({ commits }), and JSON.stringify renders a Date as canonical ISO-Z text — so the WIRE shape is accidentally correct and no HTTP-level test can catch this. Only in-process consumers see the Date. - Every test drives SQLite or memory, where the value really is an ISO string.
That combination is exactly what let #13995 exist: the in-process consumer that read this field and compared it as a string was rollbackToPackageCommit, and the declaration told everyone reading the code that a string was what arrived.
Why this is not repaired inside #13995
#13995's ruled route is "compare instants at the consumer", and it is deliberately minimal: it changes the two COMPARISONS and nothing about what listCommits emits. Repairing this one means choosing what the emitted value should be, and that is a judgement call with a blast radius, not a mechanical edit:
⛔ Unassigned. Filed for triage to grade and route; it is one of the shapes #13973's option B would delete outright, so it may be worth holding against that ruling.
Generated by Claude Code
Found while implementing #13995 (which is not this, and does not close this). Same class as the #13973 driver-materialisation census — class (c), genuinely wrong on one side — but a site the census's seven do not include: #13997 covers
MetadataItem.authoredAtinsys-metadata-repository.tsandMetadataStats.mtimeindatabase-loader.ts, and neither is this one.The site
packages/metadata-protocol/src/protocol.ts,listCommits. Its declared return type says:(spelled loosely above to keep angle brackets out of this body; the real declaration is the
Promiseof anArrayof that object literal, and the member is verbatimcreatedAt?: string.)The mapping assigns the RAW driver value:
Why it is wrong on the production default driver
created_atis a builtin audit column: not indatetimeFields, andSqlDriver#formatOutputrepairs it only insideif (this.isSqlite). Pinned inpackages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts— the live dialects hand it out of the record read door as a JSDate, SQLite as canonical ISO-Z text.So on Postgres and MySQL,
listCommitshands every in-process consumer aDatein a field the type says is astring, on every call, silently.Why nothing reports it
rowsisany[], so the assignment is unchecked; every consumer then reads astringthat is really aDate, and TypeScript agrees with the declaration rather than with the value.GET /packages/:id/commitsreturns throughdeps.success({ commits }), andJSON.stringifyrenders aDateas canonical ISO-Z text — so the WIRE shape is accidentally correct and no HTTP-level test can catch this. Only in-process consumers see theDate.That combination is exactly what let #13995 exist: the in-process consumer that read this field and compared it as a string was
rollbackToPackageCommit, and the declaration told everyone reading the code that a string was what arrived.Why this is not repaired inside #13995
#13995's ruled route is "compare instants at the consumer", and it is deliberately minimal: it changes the two COMPARISONS and nothing about what
listCommitsemits. Repairing this one means choosing what the emitted value should be, and that is a judgement call with a blast radius, not a mechanical edit:createdAtbecomes the canonical ISO-Z spelling on every dialect). Makes declared = actual and is what the type already promises, but it CHANGES the value every in-process consumer receives on Postgres and MySQL — a behaviour change on a shipped method, and arguably the producer-side normalisation [finding] Sweep: which consumers compare or format a value whose runtime type differs between the Date-materialising drivers and the ISO-text ones #13973 reserves for the maintainer.Date. Honest about today, but pushes the two-shapes problem onto every consumer — which is the state the OCC seam was in before rest/OCC: postgres 驱动下乐观锁必现假冲突 409 —— normaliseVersionToken 对 Date 做 String() 丢毫秒后与 ISO 字符串严格比较 #13382, and the thing [finding] Sweep: which consumers compare or format a value whose runtime type differs between the Date-materialising drivers and the ISO-text ones #13973 says the contract question exists to avoid.⛔ Unassigned. Filed for triage to grade and route; it is one of the shapes #13973's option B would delete outright, so it may be worth holding against that ruling.
Generated by Claude Code