fix(metadata-protocol): listCommits emits the ISO-8601 string createdAt declares - #15008
fix(metadata-protocol): listCommits emits the ISO-8601 string createdAt declares#15008os-musk wants to merge 7 commits into
Conversation
…At declares `listCommits`'s declared return type says `createdAt?: string`, but the mapping assigned the raw driver value straight through. `created_at` is an engine-injected audit column, and `SqlDriver#formatOutput` repairs it only inside its `if (this.isSqlite)` arm, so Postgres and MySQL hand it out of the record read door as a JS `Date` — a value every in-process consumer received in a field the type promised was a `string`. Follows #14037's precedent: a narrow per-site `isoFromValidDate` helper converts the one measured shape (a valid `Date`) and returns every other shape, including an Invalid `Date`, unchanged — deliberately not the shared `canonicalIsoInstant` spelling, which raises RangeError on an Invalid `Date` reachable on both live dialects (#14078, on which #13973 is blocked). Fixes #14038 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
check:system-context-census pins doc-table anchors by line number; the listCommits fix above inserted ~40 lines earlier in protocol.ts and shifted one anchored elevation-read site. Mechanical re-anchor via `node scripts/check-system-context-census.mjs --fix` — no behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
check:engine-double-contract requires every findOne/update/delete fake engine double in a test file to be registered in the pinned ledger. Registers the read-only findOne double the new #14038 pin test uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…stcommits-createdat-iso
Merging origin/main shifted lines in identity-write-guard.ts, auth-plugin.ts and share-link-service.ts (unrelated incoming commits); mechanical re-anchor via `node scripts/check-system-context-census.mjs --fix` — no behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…stcommits-createdat-iso
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cc6feca948c8953a619d351ad3db3f30713bfd41 && git checkout cc6feca948c8953a619d351ad3db3f30713bfd41
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 23c72be3cfe7a282da3a07784ad9922f1b860cc8 8c9c060f2944f46171402e46a50b0b453754d82b && git checkout -B drift-repro 23c72be3cfe7a282da3a07784ad9922f1b860cc8 && git merge --no-ff 8c9c060f2944f46171402e46a50b0b453754d82b
node scripts/docs-audit/affected-docs.mjs --json 23c72be3cfe7a282da3a07784ad9922f1b860cc8 |
Independent review — PASS; marked ready and armed for the merge queueAn independent reviewer (not the author) re-derived this PR from the card, the ruling and the code rather than from the PR body. Verdict PASS, no blocking defects. What was re-proved rather than accepted: The helper decision is not an implementer's invention — it executes a ruling that predates #14037. Triage's ruling on #14038 (comment 5487875301) rules option 1 (canonicalise at the mapping) and names this route explicitly: 「与 #13997 走的、以及 #14037 将走的是同一条路」. So the local-helper pattern was pre-authorised before #14037 existed, and #14939 (which closed #14037) then landed the byte-identical The three premises behind refusing The reviewer found stronger evidence for the choice than the PR argues for itself. Tracing what would happen if the Ruling compliance on the one instruction that constrains the prose. The ruling also says ⛔ 「PR说明里不要把这次修复写成「行为变更」」 — the fix honours a declaration rather than changing behaviour. Verified: no "behaviour/behavior change" framing appears in this PR's own text (the two hits in Changeset Clause-② = no. Symbol fence, resolved on this head rather than from quoted numbers. Two ablations, both directions, restore proven by state. Restoring the raw assignment reddens exactly §A ( Consumers searched independently. Merge fallout resolved the right way. The two Gates: 72 commands derived off the merge base with no path arguments — 68 exit 0, 4 genuine One non-blocking fast-follow, recorded not filed
Generated by Claude Code |
Fixes #14038
The defect
listCommits's declared return type sayscreatedAt?: string(
packages/metadata-protocol/src/protocol.ts:19049), but the mappingassigned the raw driver value straight through
(
protocol.ts:19107before this change:...(r.created_at ? { createdAt: r.created_at } : {})).created_atonsys_metadata_commitis an engine-injected audit column — not indatetimeFields— andSqlDriver#formatOutputrepairs it only inside itsif (this.isSqlite)arm (packages/drivers/driver-sql/src/sql-driver.ts:16056,audit-column repair at
:16123, thedatetimeFieldsfold at:16137—both inside the same arm). So on Postgres and MySQL,
listCommitshandedevery in-process consumer a
Datein a field the type promised was astring. The REST door (GET /packages/:id/commits) was never affected —JSON.stringifyalready renders aDateas canonical ISO-Z text.The fix
A narrow per-site conversion at the producer
(
protocol.ts:1726,isoFromValidDate), following #14037'sprecedent (landed as #14939) rather than the shared
canonicalIsoInstantspelling: a valid
Dateis canonicalised to ISO-8601, and every othershape — an already-canonical SQLite string, an absent column, and
an Invalid
Date— passes through unchanged. #14037 deliberately didnot adopt
canonicalIsoInstanthere because #14078 measured an InvalidDatereachable on both live dialects (a MySQL zero datetime; a Postgresyear in 275760..294276) where that spelling raises
RangeError, and #13973is blocked on that ruling. This PR imports neither answer to #14078 — it
just doesn't regress the field it touches. The Invalid-
Dateneutrality ispinned in §D of the new test file so it reds if the contested spelling is
ever swapped in here.
Evidence (Zone 2, file:line)
packages/metadata-protocol/src/protocol.ts:19049;emitted (pre-fix)
protocol.ts:19107(r.created_at, unconverted).canonicalIsoInstant; seethe doc comment on
isoFromValidDate(protocol.ts:1726) and §D of thenew test.
sql-driver.ts'sformatOutputgates both the builtin-audit-column repair and thedatetimeFieldsfold inside oneif (this.isSqlite)arm(
:16056..:16137). No live cell needed — the new test drives ahand-made
Date, matching this package's existing convention (no driverdependency).
rollbackToPackageCommit(
protocol.ts:19742) readslistCommits'createdAtthroughcompareAuditInstants, which already normalises both aDateand anISO string to epoch ms via
canonicalVersionInstant— so canonicalisingthe emitted value to a string does not change its behaviour (no stop
condition; not a regression).
Scope
Out of scope, per the dispatch:
#14938(listDrafts, a sixth site withan inline return type) and the family's standing question of a shared
@objectstack/metadata-coreexport (ruled: after #14078, not before).Tests
New:
packages/metadata-protocol/src/protocol-14038-list-commits-created-at-iso.test.ts(§A Postgres/MySQL
Date→ ISO; §B SQLite text passthrough; §C absentcolumn; §D #14078 neutrality pin).
Union run at final head
8c9c060f2(after mergingorigin/maintwice whilethis branch was in flight, most recently to pick up #14939 itself):
pnpm --filter @objectstack/metadata-protocol exec vitest runover the newfile plus the sibling
#13995and#14037pins in this package — 3 files,13 tests, all passed;
pnpm --filter @objectstack/metadata-protocol typecheckclean;
pnpm check:engine-double-contract,check:system-context-census,check:doc-authoring,check:nul-bytes,check:type-check-coverage, andthe rest of the ~70 commands
node scripts/pm/dispatch-gates.mjs --commandsderives for this diff — all green.
check:dual-build-cjs-loads,check:test-completenessandcheck:type-check-debtread NOT MEASUREDlocally (each needs a full-tree build or a CI test log this environment
doesn't produce) — not evidence of a red, left for CI.
Mechanical fallout from merging
origin/maintwice: twocheck:system-context-censusre-anchors (doc-table line numbers shifted byunrelated commits) and one
check:engine-double-contractpinned-ledgerentry for the new test's read-only fake engine. No behaviour change in
either.
Generated by Claude Code