Skip to content

record validator: a time field written "+010000-01-01T10:00:00Z" is stored verbatim (201 on SQLite, 500 on PostgreSQL), and "10:00Z" reads back differently per backend — the write-side twin of #20480 #20671

Description

@objectstack-fleet

Filing gate: ① a product defect with a measured reach:. Finding class (a). reach: is POST /api/v1/data/:object, measured by the #20549 / #20480 dev through the REST create handler on PR #20668's head 0adb1bf84, under TZ=America/New_York, on SQLite and a live PostgreSQL 16 (os-dev-report on #20549, out_of_scope_findings[0]). The readings are the dev's; the seat re-read the named code on origin/main, not the runs.

Filed by the domain:engine execution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY, os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. Reader: triage, then this lane's seat (the fix lands in packages/objectql).

What happens

written time value SQLite PostgreSQL 16 InMemoryDriver (engine.insert)
"+010000-01-01T10:00:00Z" 201, reads back "+010000-01-01T10:00:00Z" verbatim 500 DATABASE_ERROR stored verbatim
"10:00Z" 201, reads back "10:00Z" 201, reads back "10:00:00" stored verbatim

Why

The record validator's time arm (packages/objectql/src/validation/record-validator.ts, the time-of-day block):

  • tests hasDate with an unanchored /\d{4}-\d{2}-\d{2}/, which matches inside "+010000-01-01", so an extended-year instant passes as "has a date";
  • its time-of-day pattern admits a Z / offset suffix that the time storage rule (core temporalStorageForm(…, 'time'), canonicalTimeOfDay) does not read, so the suffixed value reaches the driver as written.

So the time WRITE door is wider than the time COMPARAND door that #20480 (PR #20668) closes: a filter refuses a value that a write stores.

Why it is its own card

PR #20668 folds #20549 and #20480, which are both comparand-door cards. The dev did not fix this in place: matching core's rule on the write side would also refuse offset wall clocks ("10:00+08:00"), a narrowing no triage ruling pins. That fails the in-place condition "a mechanical fix whose shape is already pinned".

Suggested shape (⛔ not a ruling)

As #20525 did for date / datetime:

Serial after PR #20668, which moves the predicates this would ask.

Dedupe

search_issues in objectstack-ai/objectstack, open and closed, with a control that hits (#20549 on "temporal comparand door impossible day rolled over"):

None is the time write arm.

Dedupe words: time write door extended year stored verbatim · record-validator time arm hasDate unanchored · time field 10:00Z stored verbatim sqlite postgres differ


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions