You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(lint): read the registry's own type for an injected column so a field-typed rule can judge created_at / updated_at (#16340)
The object graph recorded registry-injected system columns by NAME only, so a
path resolving to one came back `{ kind: 'ok', injected: true }` with no `meta`
and every rule asking a second question about the leaf had to stay silent.
Measured before the change on one dashboard widget over one object declaring
`close_date: date`: `close_date: 'last_30_days'` was refused while
`created_at: 'last_30_days'`, `created_at: { $eq: … }` and
`updated_at: { $in: [ … ] }` all passed lint and the runtime publish gate — and
the engine then refused each of them with INVALID_FILTER / 400 on first render.
`GraphObject.injected` becomes a `ReadonlyMap<string, GraphField>` carrying each
column's definition, DERIVED from `injectedSystemColumnDefs` (the same spec
tables `applySystemFields` spreads at registration) rather than transcribed, so
lint cannot drift from the registry that provisions the column. The
`filter-preset-comparand` oracle drops its `verdict.injected` bail: the marker
says who wrote the column, the ruling turns on what it is. `id` keeps an empty
slice — the driver provisions the primary key and no definition describes it.
Pins the AUTHOR-FACING message verbatim, not only the graph shape, and pins the
derivation against the spec table so a hand-copied type cannot pass.
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
fix(lint): a field-typed rule reads the registry's own type for an injected column, so `created_at` / `updated_at` stop escaping the preset-comparand refusal (#16340)
6
+
7
+
`@objectstack/lint`'s object graph recorded the registry-injected system columns by NAME only. A path resolving to one came back `{ kind: 'ok', injected: true }` with no `meta`, so every rule asking a SECOND question about the leaf — "is it temporal?" — had to treat it as unanswerable and stay silent. That silence landed on the two most-filtered columns in the platform.
8
+
9
+
Measured on `origin/main``d57611dfd3`, one dashboard widget over one object declaring `close_date: date` and authoring no `created_at`:
|`stage: 'this_quarter'` (a `select` column) | silent | silent |
19
+
20
+
The engine already refused all three of those at query time (`INVALID_FILTER` / 400, the registry's field map in hand), so the gap was purely author-time: `objectstack lint` and the runtime publish gate passed a filter the runtime then refused with a 400 on first render — and an AI author's correction loop only sees what fails the build.
21
+
22
+
## What changed
23
+
24
+
`GraphObject.injected` is now a `ReadonlyMap<string, GraphField>` rather than a `ReadonlySet<string>`: each injected column carries the registry's own definition. Both halves are DERIVED from one plan — membership from `resolveInjectedSystemColumns`, the slice from `injectedSystemColumnDefs` (`@objectstack/spec/data`, the same tables `applySystemFields` spreads at registration) — so lint never hand-copies "`created_at` is a datetime" and cannot drift from the runtime that provisions it. `resolveFieldPath` populates `meta` for an injected leaf accordingly, and `filter-preset-comparand`'s field-type oracle lost its `verdict.injected` bail: the marker says WHO wrote the column, and the ruling turns on what the column IS.
25
+
26
+
`id` is the one addressable column with no definition behind it — the DRIVER provisions the primary key — so its slice is empty and a second question about it is still unanswered, truthfully and only there. The `select`-column reading arm 2 exists to protect is untouched: no injected column is a picklist.
27
+
28
+
**Behaviour change for authors**: a stack that filtered an injected `date` / `datetime` column against one of the thirteen dashboard date-range preset names in an equality or membership position now fails `objectstack lint` and the runtime publish gate where it previously passed. Every such filter was already refused by the engine at query time; the error simply moves to where the filter is written. Write the `{date-macro}` window the message names, or an ISO date.
29
+
30
+
**Type change for direct consumers of the seam**: `GraphObject.injected` changed from `ReadonlySet<string>` to `ReadonlyMap<string, GraphField>`. `.has(name)` answers exactly as before; code that iterated the set or spread it into one needs `.keys()`. Shipped as `minor` under the repo's launch-window convention.
31
+
32
+
A relationship HOP through an injected column stays a skip (`unknowable` / `injected-hop`), deliberately: the slice now carries `reference`, and traversing it would newly judge every path through a platform anchor wherever `sys_user` is compiled into the stack — a widening with its own findings to measure.
0 commit comments