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
`lintLivenessProperties` now reports a liveness ledger it could not read, instead of going silent.
6
+
7
+
`loadWarnMap` returned the same empty map for two different facts: "this metadata type's ledger classifies nothing as warn-worthy" and "there is no ledger". A missing `<type>.json` and a file whose JSON is broken both returned an empty map with no log, no throw and no other signal, so losing or corrupting ONE file under the `liveness/` directory `@objectstack/spec` ships switched every author warning for that type off in silence — indistinguishable from that type simply having no warnings.
8
+
9
+
The contrast that makes it a defect rather than a design sits one frame up: the DIRECTORY-level failure is loud by construction (the rule returns `[]` and everything depending on it goes red). Loud by directory, silent by file.
10
+
11
+
What changes for consumers:
12
+
13
+
- A new rule id, `LIVENESS_LEDGER_UNREADABLE` (`'liveness-ledger-unreadable'`), exported from the package root beside the four verdict ids. It is not a fifth verdict: the other four grade a property the ledger DID classify, this one says the classification never arrived, so a finding carrying it means no other finding about that metadata type can be trusted. Compare `f.rule` against the constant rather than retyping the slug. It cannot be silenced per finding: the CLI has no per-rule suppression, and `suppressWarnings` is a dashboard-widget key (`spec/src/ui/dashboard.zod.ts`) while this finding's subject is a ledger rather than an authored item, so there is nothing to carry it. The remedy is the one the finding's own hint names — repair or reinstall `@objectstack/spec`.
14
+
-`lintLivenessProperties` raises exactly one such finding per unreadable type, per run — never one per authored item — ahead of the walk's own findings, and keeps walking every type whose ledger IS readable. On an intact installation nothing changes: no ledger is missing, so no finding is added.
15
+
- A ledger that parses but is not a ledger (a bare `null`, an array, a scalar, or a document with no `props` record) is the same reported fault. Reading `.props` off a parsed `null` used to be a `TypeError` — a throw from a rule whose contract is that it never throws, through the one input an author cannot influence.
16
+
17
+
`authorWarnedProperties` still answers the empty set for a ledger it cannot read — a decision procedure returning a set has no way to report a failed read — and that is unchanged for a missing file and for broken JSON. One input does move: a ledger document that parses to `null` used to make it THROW, and it now returns the empty set like the other two. `os lint` runs both halves in one pass, so the run states the fault once rather than never.
**BREAKING for runtime metadata writes** — the ADR-0090 D3 vocabulary freeze (`security-role-word`) now runs at the runtime publish gate for all six collections it judges, so `position` and `app` writes are gated for the first time (#19370)
6
+
7
+
Clause-②: no (narrowing)
8
+
9
+
`validateSecurityRoleWord` moves from `surfaces: ['cli']` to
10
+
`['cli', 'runtime-publish']` and declares
11
+
`runtimeTypes: ['object', 'permission', 'book', 'position', 'app']` — the write
12
+
type of every collection it judges. `position` and `app` join
13
+
`TYPE_TO_STACK_KEY` in `runtime-gate.ts` so the gate can build a per-write
14
+
snapshot for them.
15
+
16
+
**The refusal set grows.** A runtime metadata write — Studio's designer, REST
17
+
`/meta`, an MCP/AI author — that carries the reserved word `role` in a
18
+
security-relevant identifier or label is now refused with the 422 lint envelope
19
+
instead of stored. Concretely, these used to succeed at that door and no longer
20
+
do:
21
+
22
+
- an object, field, action or field-group header named or labelled for `role`;
23
+
- a permission set named or labelled for `role` (e.g. `role_manager`);
24
+
- a documentation book named or labelled for `role`;
25
+
- a **position** named or labelled for `role` (e.g. `sales_role`);
26
+
- an **app** named or labelled for `role` (e.g. `role_hub`).
27
+
28
+
The platform vocabulary the rule freezes is unchanged and so is its fix-it text:
29
+
`permission_set` for capability, `position` for distribution, `business_unit`
30
+
for hierarchy. Nothing is renamed, retired or added — this is the same rule,
31
+
with the same rule id and the same findings, now enforced at the fourth door as
32
+
well as by `os validate` / `os build` / `os lint`.
33
+
34
+
**Nothing changes for the three CLI commands.** Both security entries have run
35
+
on all three since the #8310 split, and their union is byte-identical to before.
36
+
37
+
**Stored rows are untouched** (#4463 D4: the gate blocks new writes, never the
38
+
read path), and `OS_ALLOW_UNLINTED_METADATA_WRITES=1` remains the migration-window
39
+
escape hatch for a tenant that authored one of these names before this landed.
40
+
41
+
Why the two types were held back until now, and why the wait ended: `position`
42
+
and `app` are `allowRuntimeCreate: true`, so a position called `sales_role` could
43
+
be minted through the one entrance a tenant has while an object of that name was
44
+
refused. Under #7220 one rule id sits on ONE side of the wall, so the rule was
45
+
split out and held back whole rather than wired for a subset of its collections.
46
+
Mapping the two write types is what lets it cross, also whole.
47
+
48
+
Deliberately NOT done: carrying `positions` / `apps` as `RuntimeStackContext`
49
+
collections. A collection joins that context because some rule resolves
50
+
references into it; this rule resolves nothing — it judges each identifier and
51
+
label on its own — so a sibling position tells it nothing about the written one
52
+
and its finding cancels in the gate's differential either way. Carrying them
53
+
would cost the publish door one indexed `sys_metadata` read per write for no
54
+
verdict change.
55
+
56
+
<!-- adr-0087: not-required (no-migration-prescription) nothing is retired, renamed or added: no authorable key changes, no stored shape is rewritten, and `objectstack migrate meta` has nothing to reach. The rule, its rule id, its vocabulary and its fix-it text are unchanged since ADR-0090 D3; only the surface it runs on widens. An affected tenant renames its own metadata, which is tenant data rather than a spec migration. -->
0 commit comments