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(example-showcase): guard the authored action predicates against the sparse face (#8990) (#9280)
* fix(example-showcase): guard the authored action predicates against the sparse face (#8990)
Every record-scoped `visible` / `disabled` predicate in app-showcase now carries
the `has()` guard the sparse action face requires. A row action's predicate binds
a LIST ROW carrying only the view's `$select` projection, and CEL aborts with
`No such key` on a column that row never projected -- fail-closed, so the button
silently is not offered, which is indistinguishable from the gate saying no.
Measured against the running app's own payloads (showcase booted, real seeded
records, real view projections): 40 of the 53 predicates in
predicate-matrix.action.ts aborted on a default-list row before this change and 0
do after, while every verdict on a record-detail binding is unchanged -- so the
Full-vs-Minimal contrast the fixture exists to demonstrate is preserved exactly.
The guard is minimal per predicate rather than blanket: has() alone where the read
is only compared by == / != , the full has(x) && x != null conjunction only where
an operand can fault (traversal, method call, ordering, arithmetic, `in`, bare !).
Census correction: the remainder was 57, not the 12 carried forward from PR #9166.
`zooTypeGate(name, label, visible)` passes its predicate POSITIONALLY, so the
`visible:`-key grep behind both the original 34 and the 12 saw 8 of these and
missed 45. The new test reads predicates off the exported actions instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
* docs(objectql): carry #8990's three sparse-face measurements to the canonical rule
The showcase migration (PR #9280) measured three shapes the two-level nested
table at :79 reads as already covered and does not reach. Recorded additively at
the definition site, because a rule stated only in a reader is how #8975 came to
exist:
1. A three-level path needs has() at EVERY segment. Guarding the leaf subsumes
the parent's `!= null` but NOT the parent's has(): `has(record.r) &&
has(record.r.p.s)` still faults `No such key: p` on `{r: null}` / `{r: {}}`.
2. A nested leaf used for ORDERING still needs its own `!= null` -- the leaf
has() proves presence, not value, and the equality exception does not extend
to `< <= > >=`.
3. Indexing faults on an EMPTY list, not only a null one: `record.b[0]` on
`{b: []}` gives `No such key: index out of bounds`, so `!= null` is not the
guard for a subscript; a `.size()` test is.
Every cell of all three tables was evaluated against the canonical
@objectstack/formula engine before being written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
* chore(gates): narrow the showcase source-resolution registries after the formula alias
Both shrink-only registries recorded @objectstack/example-showcase as still
reaching @objectstack/formula through dist/. It no longer does -- this PR added
the vitest alias and the tsconfig `paths` rule so the new sparse-face test
evaluates and typechecks against engine SOURCE. Dropping the entry is a SHRINK,
the permitted direction; the gates printed the exact replacement lists and both
were applied verbatim.
Deliberately NOT fixed by removing the alias to make the registries true again:
that would trade a green gate for the hazard the gates exist to name -- a test
that runs green against a stale engine build, and a typecheck that PASSES over a
contract that has since moved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
---------
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments