Class (c) — a lint diagnostic that tells an author app binds somewhere, teaching metadata the runtime silently drops.
Found while landing #17203 (delete the app token from the packages/spec UI prose that claimed the shipping renderer mounts it). That card's file face is spec prose only, and this is a live lint behaviour in another package, so it is filed rather than folded.
The coordinates
packages/lint/src/validate-expressions.ts, read on origin/main 47863f4fb:
export const FIELD_RULE_AMBIENT_ROOTS = ['app'] as const;
and FIELD_RULE_JUDGED_ROOTS = [...SCOPE_ROOTS, ...FIELD_RULE_AMBIENT_ROOTS].
Its docblock states the premise the constant rests on, verbatim:
Roots bound at some evaluation site that SCOPE_ROOTS does not declare (#13935) — the difference between "declared platform-wide" and "bound somewhere", which is the question this rule actually asks.
and names its source of truth:
The in-repo source is packages/spec/src/ui/page.zod — the visibleWhen docblock's "Ambient roots — renderer behaviour, NOT contract-guaranteed" section, which names app, features and os.user as mounted by app-shell's ExpressionProvider, measured at a pinned objectui sha.
Why it is now false
Decision batch #67 (2026-09-07) ruled option B: the engine's SCOPE_ROOTS is the contract and ObjectUI aligns to it. ObjectUI shipped that — buildExpressionScope no longer binds app — and the producer-side option-A card #16420 was closed not_planned in the same ruling.
So app is no longer "bound at some evaluation site". FIELD_RULE_AMBIENT_ROOTS exists to distinguish exactly that from "not bound anywhere", and app has now moved across that line. The membership was correct when #13935 added it; the ruling moved the fact underneath it.
Two consequences, both in the direction of keeping bad metadata:
- A field-level
*When reading app still earns the ambient / renderer-mounted diagnostic, whose content is that the root binds elsewhere and the author is on the wrong surface. The honest diagnostic today is the unbound-root one — the predicate faults wherever it is written.
#17203 deletes the anchor. That docblock section no longer names app, so the sentence quoted above stops describing the file it cites. The constant then has no in-repo source at all.
Deliberately NOT the fix
⛔ Do not widen SCOPE_ROOTS in packages/formula/src/cel-engine.ts. That is option A, ruled not adopted in batch #67, and the docblock here already refuses it in its own words ("The repair deliberately does NOT add app to SCOPE_ROOTS"). That reasoning is still correct and is not what this card questions.
The question this card raises is narrower: with the binding gone, should FIELD_RULE_AMBIENT_ROOTS be empty, and if so, what happens to the tie-break and the message tier that #13935 built on top of it. packages/lint/src/validate-expressions.test.ts pins the current answer directly, including expect([...FIELD_RULE_AMBIENT_ROOTS]).toEqual(['app']), so the change is a deliberate one with a test to move, not a silent edit.
Not measured here
Filed from the domain:spec lane while working #17203, which does not own packages/lint. Refs: #13935, #16420, #17203.
Generated by Claude Code
Class (c) — a lint diagnostic that tells an author
appbinds somewhere, teaching metadata the runtime silently drops.Found while landing #17203 (delete the
apptoken from thepackages/specUI prose that claimed the shipping renderer mounts it). That card's file face is spec prose only, and this is a live lint behaviour in another package, so it is filed rather than folded.The coordinates
packages/lint/src/validate-expressions.ts, read onorigin/main47863f4fb:and
FIELD_RULE_JUDGED_ROOTS = [...SCOPE_ROOTS, ...FIELD_RULE_AMBIENT_ROOTS].Its docblock states the premise the constant rests on, verbatim:
and names its source of truth:
Why it is now false
Decision batch #67 (2026-09-07) ruled option B: the engine's
SCOPE_ROOTSis the contract and ObjectUI aligns to it. ObjectUI shipped that —buildExpressionScopeno longer bindsapp— and the producer-side option-A card #16420 was closednot_plannedin the same ruling.So
appis no longer "bound at some evaluation site".FIELD_RULE_AMBIENT_ROOTSexists to distinguish exactly that from "not bound anywhere", andapphas now moved across that line. The membership was correct when #13935 added it; the ruling moved the fact underneath it.Two consequences, both in the direction of keeping bad metadata:
*Whenreadingappstill earns the ambient / renderer-mounted diagnostic, whose content is that the root binds elsewhere and the author is on the wrong surface. The honest diagnostic today is the unbound-root one — the predicate faults wherever it is written.#17203deletes the anchor. That docblock section no longer namesapp, so the sentence quoted above stops describing the file it cites. The constant then has no in-repo source at all.Deliberately NOT the fix
⛔ Do not widen
SCOPE_ROOTSinpackages/formula/src/cel-engine.ts. That is option A, ruled not adopted in batch #67, and the docblock here already refuses it in its own words ("The repair deliberately does NOT addapptoSCOPE_ROOTS"). That reasoning is still correct and is not what this card questions.The question this card raises is narrower: with the binding gone, should
FIELD_RULE_AMBIENT_ROOTSbe empty, and if so, what happens to the tie-break and the message tier that #13935 built on top of it.packages/lint/src/validate-expressions.test.tspins the current answer directly, includingexpect([...FIELD_RULE_AMBIENT_ROOTS]).toEqual(['app']), so the change is a deliberate one with a test to move, not a silent edit.Not measured here
app.predicate that this diagnostic currently mis-advises. That measurement is the p1 escalation trigger recorded on finding(spec):page.zod.ts'svisibleWhen.describe()still namesappamong the roots the shipping renderer mounts — objectui removed that binding (option B, batch #67) #17203 and it was not run for this card.appentry is questioned.Filed from the
domain:speclane while working #17203, which does not ownpackages/lint. Refs: #13935, #16420, #17203.Generated by Claude Code