Skip to content

test(dogfood): field-type capability-matrix round-trip (#2004 + AI-authoring) - #2022

Merged
os-zhuang merged 1 commit into
mainfrom
feat/dogfood-field-zoo
Jun 18, 2026
Merged

test(dogfood): field-type capability-matrix round-trip (#2004 + AI-authoring)#2022
os-zhuang merged 1 commit into
mainfrom
feat/dogfood-field-zoo

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Why

This is a development platform where third parties have an AI author arbitrary metadata. The real runtime risk isn't "a platform change broke the CRM example" — it's "the AI used a valid primitive the examples don't exercise, and it silently breaks at runtime." So the dogfood gate (#2020) has to grow from pinning one curated app into a capability matrix: every authorable primitive gets a runtime round-trip proof.

showcase_field_zoo carries one field of (almost) every protocol FieldType, but until now it was only static-checked — the metadata bundle registers it; nothing ever wrote a record and read it back.

What

First capability-matrix block: write one record covering many field types over the real REST data API, read it back, assert each round-trips with type fidelity.

  • Headliner coverage = the fix: array field types persist + Field.time accepts time-of-day #2004 regressions — array-typed fields (multiselect/checkboxes/tags) and Field.time (time-of-day). All proven to round-trip end-to-end on the in-memory (WASM) driver path that objectstack dev uses for :memory:.
  • Broad matrix across text / numbers / temporal / scalar-select / JSON / color, plus behavioral checks for computed (formula = 31.5) and system (autonumber) fields.

The gate earned its keep on day one

On its first run it surfaced three real type-fidelity gaps: rating/slider/toggle persist but read back as strings ('4'/'25'/'1') while number/currency/percent/boolean round-trip correctly with the same harness. The value isn't lost — the type leaks (schema→column mapping / read coercion doesn't cover these types).

Per dogfood philosophy these are not hidden by coercion — they're quarantined with it.fails, which passes while broken and turns RED the day the gap is fixed, forcing the quarantine to be lifted rather than rotting. Filed as a separate task to fix the mapping/coercion and lift the quarantine.

construnner=c.xfail ? it.fails : it;

Notes

  • Private package — no changeset. 21 passed | 3 expected fail.
  • Driver nuance: the harness uses SqliteWasmDriver(':memory:') (what dev uses for in-memory), so this guards the shared validator + WASM driver array path and complements fix: array field types persist + Field.time accepts time-of-day #2004's driver-sql unit test by covering the integration + a second driver.
  • This is the first slice; full 45-type coverage + flow-node / form-widget / RLS-pattern matrices + a generative pass are roadmap, scoped by a forthcoming ADR (binding policy: every authorable+live primitive must carry a runtime proof).

🤖 Generated with Claude Code

…thoring)
Extends the dogfood gate from "pin one example app" toward the platform's real
risk: third parties have an AI author arbitrary metadata, so every authorable
primitive needs a runtime proof — not a static shape check.
`showcase_field_zoo` carries one field of (almost) every protocol FieldType but
was only static-checked (the bundle registers it). This adds the first
capability-matrix block: write one record covering many field types over the
real REST data API, read it back, assert each round-trips with type fidelity.
- Headliner coverage = the #2004 regressions: array-typed fields
(multiselect/checkboxes/tags) and Field.time (time-of-day) — all proven to
round-trip end-to-end on the in-memory (WASM) driver path `objectstack dev`
uses for :memory:.
- On first run it surfaced three real type-fidelity gaps — rating/slider/toggle
persist but read back as strings ('4'/'25'/'1') while number/currency/percent/
boolean round-trip correctly. They are quarantined with `it.fails` (passes
while broken, turns RED when fixed) rather than hidden by coercion, and filed
as a separate task.
Private package — no changeset. 21 passed + 3 expected-fail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercelBot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 18, 2026 6:04am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests size/m labels Jun 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 1 changed package(s). ✅

@os-zhuang
os-zhuang merged commit f943f3b into mainJun 18, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the feat/dogfood-field-zoo branch June 18, 2026 06:13
os-zhuang added a commit that referenced this pull request Jun 18, 2026
…2024)
* docs(adr): ADR-0054 prove-it-runs gate for the authorable surface
Extends ADR-0049 (enforce-or-remove) with a third leg. The liveness ledger
(#1919) classifies every authorable property live/experimental/dead, but "live"
means only a static file:line consumer pointer — proof that something reads the
property, not that authoring it produces correct runtime behavior. #2018 (tz
bucketing: live at every layer, broken in integration) and the field-type
fidelity gaps (#2022: rating/slider/toggle read back wrong-typed) fell through
that gap — call it "unproven liveness".
For a platform whose authors are AI emitting metadata across a combinatorial
space the examples never cover, unproven liveness ships silently into
third-party apps. ADR-0054 upgrades a `live` classification to optionally carry a
`proof` — a @objectstack/dogfood test that authors the property against the real
in-process stack and asserts the runtime outcome. Required as a ratchet (not a
retrofit) for a high-risk authorable class on change, and for any property
implicated in a shipped regression (the fix carries its proof). Generative
testing is explicitly deferred (Phase 3, evidence-gated).
Proposed — for architect review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(adr): accept ADR-0054 (prove-it-runs gate)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…lock text on every dialect (#3994) (#4020)
* feat(spec,ci): temporal hooks onto the IDataDriver contract; conformance job with live non-UTC servers
Closes the two debts the datetime storage work (#3912/#3942/#3954) left
open in ADR-0053.
D-A2 — contract promotion. temporalFilterValue and temporalFilterColumnSql
were duck-typed: analytics probed `typeof driver.x === 'function'` against
a locally-invented interface, and nothing at the type level said a driver
must implement both or neither. #3912's lesson is precisely that coercing
the comparand without normalising the column reintroduces half the bug, so
a driver implementing one hook alone would silently regress. Both are now
optional members of IDataDriver, documented as a PAIR with
"absent = identity" semantics for drivers whose storage form is the wire
form. SqlDriver `implements IDataDriver`, so its signatures are
compile-checked from here on; analytics Picks the contract instead of
inventing a local shape, keeping the runtime typeof guards as the correct
way to consume an optional member. coerceFilterValueForSql already sits
behind the hook as the last-resort boolean/number recovery — the demotion
D-A2 asked for — and stays in exactly that role. ADR-0053 records the
resolution.
D-A3 — the conformance backstop. The live-server suites from #3912/#3942
are opt-in (OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL) and skip without a
server, so nothing ran them in CI and the seam could regress silently.
New `temporal-conformance` job: postgres:16 and mysql:8.0 service
containers, servers pointed at +08:00 post-start (services cannot
override the image command), the Node process at America/New_York, and
assertions in UTC — the three-way skew that caught every bug in this
family, with both suites asserting a non-UTC server so a mis-provisioned
service fails loudly rather than passing vacuously. The WHOLE driver-sql
suite runs under the skewed zone, so a TZ-sensitive assumption anywhere
in the driver's tests fails here before it ships. mysql:8.0 also covers
the other half of the #3942 compatibility claim — the hands-on
verification ran on MariaDB 10.11, the stricter dialect.
Verified locally with the exact CI command against live PG 16
(Asia/Shanghai) and MariaDB (+08:00): 47 files, 480/480, zero skips.
Full pnpm build + test green (132 tasks); spec doc gates green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPxNwPnjcn599ujXpU3ibJ
* fix(driver-sql): Field.time gets a canonical storage form — HH:MM:SS[.fff] wall-clock text on every dialect (#3994)
Field.time repeated the pre-#3912 Field.datetime pattern: writes were never
normalised, only reads were repaired, so one SQLite column accumulated bare
time-of-day TEXT, full-timestamp TEXT and INTEGER epoch ms side by side.
find() looked right; everything comparing the STORED form was wrong —
measured: a business-hours window filter silently dropped 4 of 7 rows, ORDER
BY sorted 14:30 before 08:00, a full-ISO write failed the statement on both
Postgres and MySQL, a bound Date stored a process-timezone wall clock on pg,
MySQL's bare TIME rounded fractional seconds, and NOW() defaults read three
different clocks across the three dialects.
The #3912#3942#3954 construction, transplanted (ADR-0053 D-C1..D-C3):
- One canonicalTimeOfDay — HH:MM:SS, .fff only when non-zero; Date/epoch/
full-timestamp fold to the UTC time-of-day — applied on write (formatInput),
to filter comparands (coerceFilterValue → temporalFilterValue) and on read
(toTimeOnly).
- SQLite: backfillCanonicalTimes converges legacy columns at schema sync
(IS NOT-guarded UPDATE, log-and-swallow); until then filters wrap the
column in sqliteCanonicalTimeSql — correct, just unindexed. os migrate
plan lists the work as normalize_time_storage with a row count.
- MySQL: new time columns are TIME(3); legacy TIME(0) widens at schema sync
(migrateMysqlTimeColumns, plan kind widen_time_columns) since
zero-precision TIME rounds fractional writes.
- NOW() defaults read the UTC clock on every dialect; MySQL 8.0 rejects a
plain CURRENT_TIMESTAMP default on TIME entirely, so the expression
default is also a compatibility fix.
- distinct()/aggregate() present time columns exactly as find() does
(ReadPresentationKind gains 'time').
HH:MM:SS writes round-trip byte-identically (field-zoo f_time, #2022); a
minutes-only HH:MM completes to HH:MM:00; uninterpretable values pass
through untouched.
Verified on live servers: SQLite, PG 16 at Asia/Shanghai, MariaDB 10.11 at
+08:00, Node at America/New_York — 505/505 driver tests with zero skips.
Closes#3994
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPxNwPnjcn599ujXpU3ibJ
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mtests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang