Repository navigation
fix(core): readsAsWallClock answers from the spec's ClockTimeValueSchema - #20885
Conversation
The private wall-clock regex in temporal-comparand.ts was a second spelling of the time-of-day set the spec's ClockTimeValueSchema defines. It now asks that schema, so one rule answers the comparand door, the record validator's time arm and the spec's time default gate. Pins: 23:59:59.999 read, 24:00 and 10:00Z refused, through isUninterpretableTemporalComparand. Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 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 — 25 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 5d77bff40756516b55dc8ff5454103bf2e3a607d && git checkout 5d77bff40756516b55dc8ff5454103bf2e3a607d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9905e61ca2fddb43d266c23cdb12ced5019c1a74 5d3ff119cf858471f62903b70fccbb90809d6e52 && git checkout -B drift-repro 9905e61ca2fddb43d266c23cdb12ced5019c1a74 && git merge --no-ff 5d3ff119cf858471f62903b70fccbb90809d6e52
node scripts/docs-audit/affected-docs.mjs --json 9905e61ca2fddb43d266c23cdb12ced5019c1a74 |
Contract reviewServed-tier: PR #20885 on card #20771, reviewed at tier from the card (its body and comments 5905061766, 5913205120 and 5914112124), the PR body and file list, the net diff against ① Derived judgmentsNet diff: 3 files, +41 / -4 —
② Semver level
③ Boundary flags
Check-runs on this head, read once immediately before this record was posted at 2026-09-30T15:23Z, never waited on: 32 runs. Success, 22: Auto Label, Build Core, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate and its three shards, Dogfood Verify CLI, Flag docs affected by code changes, Governed Surface Queue Guard, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Temporal Conformance (live PG + MySQL), Test Core (4/6), The card this PR closes must claim this branch, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, filter. Skipped, 3: Build Docs, Console Pin Gate, Packed-tarball smoke. In progress with no conclusion yet, 7: Lint & Repo Gates, Test Core (1/6), (2/6), (3/6), (5/6) and (6/6), Type Check · workspace. Of the required contexts, Build Core, Dogfood Regression Gate, Temporal Conformance and Governed Surface Queue Guard have concluded success; Lint & Repo Gates, Test Core and the type-check lane are still running. No check-run on this head has failed. This verdict is on the contract; landing waits on the required set concluding green, which is the PM's convergence and not this record's. Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #20771
Clause-②: no
readsAsWallClock, the private wall-clock half of core'stimerule inpackages/core/src/utils/temporal-comparand.ts, now answersClockTimeValueSchema.safeParse(s).success. That schema is the spec'stimestored form from@objectstack/spec/data. The private regex is gone, and no second regex was written. Core already imported@objectstack/spec/datain this file, so no dependency edge was added.Shape taken: delegation, not the fallback.
packages/specis untouched, and no spec export was added, soClause-②: noholds. The parse path was measured before choosing (below): it costs about 30 ns more per accepted string and about 1.2 µs more per refused one. At realistic counts that is microseconds per request, so the fallback (a spec-exported regex) was not needed.Measurements (scratch scripts, not committed)
:or.separators, minutes in 1- and 2-digit spellings, 12 seconds/fraction variants, 14 suffixes (Z,z,+00:00,+0000,-00:00,+08:00,-0530,+2359,+24:00, a space, a newline,T, a letter, none), with and without a leading space. The old private regex (at base6c96b37be8) andClockTimeValueSchema.safeParseagreed on all of them: 8,640 accepted by both, 8,646,720 refused by both, 0 by one side only. The schema is a barez.string().regex(...), with no transform, refinement or brand.isUninterpretableTemporalComparand('time', ...)is asked per comparand by the engine's comparand door and the analytics check, and also per written value by the record validator'stimearm and per cell by the import'stimecoercion. So it is not only a per-request cost. At the mixed rate, 10,000 rows with 5timefields cost about 33 ms more (39 ms in total). At the accepted rate, which is what a valid import mostly hits, they cost about 1.5 ms more.5d3ff119cf,check:lean-entry-closureis green:@objectstack/objectql/corekeeps its 15 admitted packages (import 201 modules, require 199).check:dts-closureis green: 71/71 declaration files. The import names a specifier core already loads.Pins
In
packages/core/src/utils/temporal-comparand.test.ts, block[#20771]:23:59:59.999is read;24:00and10:00Zare refused.They are asserted through
isUninterpretableTemporalComparand('time', ...), becausereadsAsWallClockis private. None of the three strings is an ISO instant, so the instant half cannot rescue one, and the verdict is the wall-clock half's alone. ExportingreadsAsWallClockwould widen core's public barrel (export *from this module) and turn this intoClause-②: yes. The existing temporal-comparand pins are unchanged and green.Ablation
The implementation was committed first (
45ba267683). Every leg went throughscripts/ablation-replace.mjsin wrap mode.readsAsWallClockwidened to a bareHH:MMprefix test: 1 red, the24:00/10:00Zpin.readsAsWallClockmade to answer false: 4 red. They are the23:59:59.999pin and three existing wall-clock pins ([#20264],[#20549],[#20480]).git diff HEADis empty.ClockTimeValueSchemawas made to accept a trailingZagain, and spec was rebuilt.scripts/ablation-dist-preflight.mjsfound the marker in 28 built files.10:00Zpin went red (1 failed, 25 passed).Verification
At head
5d3ff119cf, which includes a merge oforigin/main2d5fe76f43:pnpm --filter @objectstack/core test: 61 files, 1795 passed.pnpm --filter @objectstack/core typecheck: exit 0.engine-time-write-zone-lessandengine-temporal-comparand-door: 19 passed, against core dist rebuilt at this head.dispatch-gates --commands: 61 families.--ran: 59 run with exit 0, 2 NOT MEASURED, 0 unrun.check:dual-build-cjs-loadsandcheck:type-check-debt, because their prerequisite is every workspace package built, which is beyond the foreground cap. CI builds that closure..tsfiles witheslint --no-inline-config --format json: 2 files, 0 errors, 0 warnings.eslint.config.mjs: its**/*.{ts,...}andpackages/**blocks.--print-configresolves both files, so neither is ignored.parserOptionsis onlyecmaVersionandsourceType, so this diff cannot move a verdict on an untouched file.Acceptance notes
packages/core/src/utils/temporal-storage-form.tscanonicalTimeOfDaycarries its own wall-clock pattern with capture groups. It needs the groups to writeHH:MM:SS, sosafeParsecannot replace it. That is the storage rulereadsAsWallClockmirrors. Today it admits the same set. It is outside this card's file surface, so it is recorded here and not touched. Carrier: none.isZonedTimeOfDay(record validator) spells a zone-suffix pattern, but it judges the wall-clock half throughisUninterpretableTemporalComparand('time', ...), so it now reaches the spec rule too.Generated by Claude Code