Repository navigation
A CEL defaultValue stores a raw Date into datetime/date fields — the stored-value contract says ISO-8601 string, and the NOW() token normalizes but the CEL branch does not #7373
Description
Activity
Triage:
pm:queue+domain:engine-core.- Landing:
packages/objectql/src/engine.ts—applyFieldDefaults(verified at :2490 onorigin/main@60f0dd8) assigns the expression-envelope result verbatim, while theNOW()token routes throughresolveNowDefault(:2443, used at :2568). The CEL branch has no per-type normalization counterpart — matches the report. - Dedup:
FieldSchema.defaultValuehas the samez.unknown()acceptance hole as the action param one, but it is NARROWER — a literal must first be told apart from the runtime-token / CEL vocabulary before it can be checked #7127 (acceptance-side ofdefaultValue, dispatched) and [finding] schema.mdx ships twodefaultValuesamples that are wrong today — a bare CEL source string stored verbatim (and emitted as a column DEFAULT), and a currency object literal the engine stores but the SQL DDL silently drops #7244 (docs samples, dispatched) are adjacent but distinct, as the body itself argues; no open issue/PR covers store-time normalization of the CEL result. - Release grading: no
target:v17— knex coerces aDateat the wire so SQL-backed storage is unaffected; the violating stored shape is reachable on the memory driver and via the platform's own value-shape scan, which makes this a real defect but not a ship-blocker under the four-class test.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Landing:
Claim: PM loop wave 8 (seat #6019)
Session:session_01MwoubC3jL271FYt9rGXwxb
Branch:claude/issue-7373-cel-default-date-normalization
Worktree:objectstack-7373
Domain:domain:engine-coreRace re-read: body + triage re-read; no competing session ID.
assigneesmeasured EMPTY via REST immediately before assigning;pm:queue→pm:dispatchedsame write.engine.tsfreed by PR #7452's merge (edbf8733c) — this is the last card in the serial queue for that file; #7413 is assessed after this lands.
Generated by Claude Code
ACCEPT — PR #7469. Ready + queued.
Verified by reading the body and diff:
- ✅ The census and the driver table decided the remedy, not taste. In-tree
dialect: 'cel'corpus: exactly twodefaultValuesites (one temporal). Per-driverDatebehaviour read at each write seam: driver-sql and driver-mongodb already canonicalize to exactly what the engine now produces (byte-identical post-change); only driver-memory stored the raw object — which is where the defect was visible. One engine-side answer serves every driver, the same closure data: theNOW()defaultValue token is unresolved on non-SQL datasources — the engine stamps the literal string, and the record validator then rejects the insert #4597/data: the SQL driver emits thecurrent_userframework token as a literal column DEFAULT, so the database itself writes a non-id into alookup('sys_user')#4560 gave theNOW()token. - ✅ The refusal option was rejected with evidence, and it corrects my brief:
validateRecordaccepts aDateondate/datetimefrom any caller, and temporal types are not in ADR-0104's strict block — so there is no strict user-write path the defaults path was bypassing (my dispatch speculated there might be; the measurement says no). Refusal would also have broken the documented envelope precisely on the backends that store it correctly today. - ✅ One table, both branches: the expression branch routes through the same per-type normalization the
NOW()token uses — not a second copy of the contract. - ✅ The ADR-0053 day-shift fork is closed, not filed: UTC-midnight
Date+toISOString()(UTC getters throughout) cannot shift the day; pinned directly with the America/Los_Angeles case where UTC day ≠ reference-tz day. - ✅ The pins guard against the exact illusion that hid the bug: stored values asserted against
valueSchemaForitself (not a hand-copied shape) and on type as well as text —JSON.stringifyrenders aDateas its ISO string, which is what made the defect read as correct. Controls:NOW()byte-identical, literals untouched, non-date CEL results andInvalid Datepass through. - ✅ Changeset present; ⛔ no
packages/spectouch — no declaration needed.
CI fully green. Auto-merge armed; issue closes on merge. The known
engine.tsqueue is now empty pending the #7413 assessment.
Generated by Claude Code
- ✅ The census and the driver table decided the remedy, not taste. In-tree
- added a commit that references this issue
on Aug 17, 2026
Found while measuring the replacement samples for #7244 (docs-only card; this is the code-side divergence that measurement turned up). Filed unassigned for triage. Measured on
origin/main@f188ed60e.What was measured
applyFieldDefaults(packages/objectql/src/engine.ts:2490) has three interesting branches for adefaultValue:{ dialect, source }— evaluated byExpressionEngine, and the result assigned verbatim:out[f.name] = result.value;NOW()token — routed throughresolveNowDefault(f.type, now), which produces the value in the storage shape the declared type calls for;The CEL branch has no counterpart to
resolveNowDefault.daysFromNow(n)/today()/now()return a JSDate(ADR-0053 D1: the reference-tz calendar day as a UTC-midnightDate), so theDateis what lands in storage — whilevalueSchemaForsays an instant is an ISO-8601 string.Probe, memory driver, engine insert with
due_datetime: { type: 'datetime', defaultValue: { dialect: 'cel', source: 'daysFromNow(7)' } }:The same declaration on a
datefield is the same story, one shape further off:The
NOW()token on the identical field is the control, and it is clean:(
JSON.stringifyrenders aDateas its ISO string, which is why the first two lines read correct —typeof object/ctor Dateis the actual shape.)Why it matters
Date), so nothing refuses the write — the divergence is silent.os migrate value-shapeswalks stored values against this veryvalueSchemaFor, so rows defaulted this way are a violation by the platform's own scan.Dateat the wire, the memory driver stores the object as-is. Same declaration, different stored shape per datasource — the exact split data: theNOW()defaultValue token is unresolved on non-SQL datasources — the engine stamps the literal string, and the record validator then rejects the insert #4597 / data: the SQL driver emits thecurrent_userframework token as a literal column DEFAULT, so the database itself writes a non-id into alookup('sys_user')#4560 closed for theNOW()token, reappearing on the CEL branch.defaultValuesamples that are wrong today — a bare CEL source string stored verbatim (and emitted as a column DEFAULT), and a currency object literal the engine stores but the SQL DDL silently drops #7244 lands.Not the same defect as #7127 (
Blocked-by:nothing). #7127 is about whether the declaration shape is accepted at author time; this is about what the evaluated value is normalized to at store time. A fix for #7127 would still leave aDatein the column.Suggested shape (not a ruling)
Route the CEL branch's result through the same
resolveNowDefault-style per-type normalization theNOW()token uses, so a temporal expression default stores what the declared type's contract names. Whether that normalizer is shared or type-dispatched is a design call for whoever picks this up.Refs: #7244 (the docs card this came out of), #7127, #4597 / #4560 (the
NOW()-token precedent), ADR-0053, ADR-0104.Generated by Claude Code