Commit a7163ea
feat(lint): the ADR-0078 completeness gate — Zod-valid but runtime-dead now fails at author time (#4547)
* feat(lint): the ADR-0078 completeness gate — Zod-valid but runtime-dead now fails at author time (#4544)
An instance can be Zod-valid (gate 1 green), use only live properties (gate 2
green), and have a correctly-authored sibling proven to run (gate 3 green) — and
still be dead, because it omits a config its consumer needs and the consumer
silently no-ops. The founding case (cloud#687): an AI authored `{ type:
'summary' }` with no `summaryOperations`; the engine's index builder skips it,
the field reads 0 forever, the dependent occupancy-rate formula is stuck at 0 —
and the agent reported the work done, because every gate it could see was green.
This is worse than the unknown-key hole #4001 just closed. There the author
wrote a key we don't know, and the parse now rejects it with a prescription.
Here every key is one we know, the schema is satisfied, nothing warns, and the
author gets a success — false completion without anyone mistyping anything. The
review step that catches a human's bare summary (seeing the field render 0) is
exactly the step AI authoring removes.
One shared predicate, every surface. Instance-completeness checks previously
existed ONLY in cloud's AI-build graph-lint, so a stack authored with `os` + a
coding assistant, an MCP agent, `os validate` in CI, or by hand got none of them
(`formula_without_expression` existed nowhere in the framework). The judgement
now lives in `@objectstack/spec/kernel`'s `checkFieldCompleteness` /
`checkViewCompleteness` — sibling of `isIncoherentAggregate`, the ADR-0019
pattern — consumed by the new `@objectstack/lint` validator and registered as an
author-time rule (28 -> 29), so `os build` / `os validate` / `os lint` / MCP /
hand authoring are all covered. Cloud graph-lint can re-home its duplicates onto
the same predicate rather than drifting from it.
Every rule cites the runtime line that makes it true, because the completeness
audit's scariest candidate (a "sharing rule fails open") collapsed on a
three-file read, and #4001's last batches shipped four confidently wrong
prescriptions before learning the same thing:
field/summary-without-operations engine.ts `if (!d.summaryOperations) continue` error
field/formula-without-expression engine.ts plans only fields that HAVE one error
field/relationship-without-reference $expand `if (!referenceObject) continue` error
field/choice-without-options record-validator.ts: empty list disables the error
(select, radio) server-side value check entirely
field/choice-without-options same branch, shared with free-form warning
(checkboxes)
view/layout-without-binding renderer falls back to literal default names warning
(kanban, calendar, gantt)
The deliberate NON-rules are pinned as hard as the rules. `multiselect` without
options is NOT flagged: record-validator.ts says verbatim `// free-form (tags
without options)`. The runtime blesses it as a mode, making it ADR-0078 case (3)
"genuinely optional" — flagging it would be another false prescription, and the
test is where that attempt fails first. `timeline` / `tree` views are out of v1
for the same reason: config schemas exist, renderer behaviour has not had its
verification pass. Verify, then enforce — one shape at a time.
It found a real one on its first run against a real app.
`showcase_field_zoo.f_summary` was a bare `Field.summary({ label: 'Roll-up
Summary' })` — one line below an `f_formula` that IS complete, in the object
whose entire job is to show what each field type looks like. The canonical
example of a roll-up in this repo computed nothing. It could not be fixed by
adding `summaryOperations`: a roll-up aggregates a child into its parent, and the
zoo is a leaf (`f_master_detail` makes it a child of `showcase_project`, and
nothing is a child of the zoo). Removed, with the working examples named —
`showcase_invoice.total` for the plain sum, `showcase_expense_report`'s
`total_amount` / `approved_amount` for the `summaryOperations.filter` variant.
The rule it broke was the file's own: "relationship types point at the other
showcase objects so they have REAL targets".
Verification: 19 predicate tests + 7 walk tests (both spellings of every
collection — a gate that walks half the stack is this campaign's recurring
"instrument reporting coverage it doesn't have"); CRM and Todo produce zero
findings; showcase clean after the fix with its 60 coverage tests passing; full
monorepo suite 132/132.
Phase 1 of #4544. Phase 2 (the cloud authoring-path config-drop fix) is in the
cloud repo; Phase 3 lands the Tier-B shapes one verification pass at a time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
* chore(spec): regenerate the API surface snapshot for the ADR-0078 kernel exports
The completeness gate adds nine kernel exports — `checkFieldCompleteness`,
`checkViewCompleteness`, `CompletenessFinding`, the five rule-id constants and
`FUNCTIONAL_COMPLETENESS_RULES`. `check:api-surface` reported them as
`0 breaking (removed/narrowed), 9 added`: purely additive, which is what a new
shared predicate should be. Snapshot regenerated so the gate agrees.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
* chore: lower the showcase i18n ratchet 452 -> 451 after removing the inert roll-up
Removing `showcase_field_zoo.f_summary` took its `label: 'Roll-up Summary'`
with it, so app-showcase declares one fewer untranslated string. The ratchet is
bidirectional by design — an improvement that leaves the baseline stale would
let a later regression back in under it — so the baseline moves down with the
count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 95fe777 commit a7163ea
11 files changed
Lines changed: 713 additions & 2 deletions
File tree
- .changeset
- examples/app-showcase/src/data/objects
- packages
- cli/src/lint
- lint/src
- spec
- src/kernel
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
Lines changed: 20 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
134 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
135 | 154 | | |
136 | 155 | | |
137 | 156 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
| 78 | + | |
78 | 79 | | |
79 | 80 | | |
80 | 81 | | |
| |||
229 | 230 | | |
230 | 231 | | |
231 | 232 | | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
232 | 251 | | |
233 | 252 | | |
234 | 253 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
31 | 41 | | |
32 | 42 | | |
33 | 43 | | |
| |||
Lines changed: 114 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1492 | 1492 | | |
1493 | 1493 | | |
1494 | 1494 | | |
| 1495 | + | |
1495 | 1496 | | |
1496 | 1497 | | |
1497 | 1498 | | |
| |||
1569 | 1570 | | |
1570 | 1571 | | |
1571 | 1572 | | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
1572 | 1578 | | |
1573 | 1579 | | |
1574 | 1580 | | |
| |||
1880 | 1886 | | |
1881 | 1887 | | |
1882 | 1888 | | |
| 1889 | + | |
1883 | 1890 | | |
1884 | 1891 | | |
1885 | 1892 | | |
| |||
1889 | 1896 | | |
1890 | 1897 | | |
1891 | 1898 | | |
| 1899 | + | |
| 1900 | + | |
1892 | 1901 | | |
1893 | 1902 | | |
1894 | 1903 | | |
| |||
0 commit comments