|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec): the unknown-authoring-key lint covers every metadata collection, not just objects (#3786) |
| 6 | + |
| 7 | +#4148 introduced the lint for `object` and `field` — the two surfaces #4120 |
| 8 | +caught real drift on. Those two were a sample, not the population: of the |
| 9 | +authorable metadata types, only a handful are `.strict()` (`flow` / `permission` |
| 10 | +/ `position` / `tool` from #4001 Tier-A, joined mid-review by `app` via #4165). |
| 11 | +Every other type strips an |
| 12 | +undeclared key exactly the way `field` did — an author who misspells a key on a |
| 13 | +`page`, an `agent` or a `dashboard` got the same parse-clean-value-gone silence, |
| 14 | +with no lint watching. |
| 15 | + |
| 16 | +`lintUnknownAuthoringKeys` now walks **every metadata collection** — 16 today: |
| 17 | +object, page, dashboard, report, dataset, action, job, agent, skill, hook, |
| 18 | +mapping, datasource, view, email_template, doc, book — and its coverage is |
| 19 | +**derived, not listed**: which collections exist comes from `PLURAL_TO_SINGULAR` |
| 20 | +(the same boundary map the normalizer uses), which schema judges each comes from |
| 21 | +the canonical type→Zod registry, and whether linting is even meaningful is read |
| 22 | +off each schema's own unknown-key posture. A third hand-written "types the lint |
| 23 | +covers" list would have been the #3786 shape all over again, inside the tool |
| 24 | +built to end it. |
| 25 | + |
| 26 | +The posture rules keep the lint from ever disagreeing with the parse: |
| 27 | + |
| 28 | +- **strip** (zod default) → lint: the parse drops unknown keys silently, and |
| 29 | + that silence is what gets reported. |
| 30 | +- **strict** → skip: the parse already rejects loudly with the schema's own |
| 31 | + tombstone guidance; a second, possibly disagreeing voice helps nobody. This |
| 32 | + bucket GROWS as #4001 tiers graduate schemas — `app` graduated (#4165) while |
| 33 | + this change was in review, and the derivation adapted without an edit. |
| 34 | +- **passthrough** → skip: unknown keys survive the parse, nothing is dropped. |
| 35 | +- **unions** (`view`) → the union of member keys; lintable only when a member |
| 36 | + strips and none passes unknowns through. |
| 37 | + |
| 38 | +`defineStack`, `os validate` and `os build` pick the wider coverage up with no |
| 39 | +code change of their own. Verified against the three first-party example apps |
| 40 | +(28 pages, 29 flows, 11 actions and friends in the showcase): all clean, zero |
| 41 | +false positives. Verified by mutation: dropping union handling, inverting the |
| 42 | +strict filter, and skipping a collection each turn the tests red. |
| 43 | + |
| 44 | +New root/kernel exports: `listLintableAuthoringCollections` (+ |
| 45 | +`LintableAuthoringCollection`) — the derived coverage as data, so tooling can |
| 46 | +report what the evidence base for the #4001 strict tiers actually spans. |
| 47 | + |
| 48 | +One import-site change: `lintUnknownAuthoringKeys` moved from the `/data` |
| 49 | +subpath to the package root and `/kernel` (`@objectstack/spec` root import is |
| 50 | +unchanged and remains the canonical site). Covering every type means importing |
| 51 | +every schema, and `/data` is consumed by frontend bundles — the walker moving |
| 52 | +out keeps that chunk from inheriting the whole schema universe. If you imported |
| 53 | +it from `@objectstack/spec/data`, import from `@objectstack/spec` instead. The |
| 54 | +comparator, guidance tables and finding types stay in `/data`, unchanged. |
0 commit comments