Skip to content

lint: FIELD_RULE_AMBIENT_ROOTS still lists app as "bound at some evaluation site" — batch #67 removed the binding, and #17203 deletes the spec docblock it cites as its source #17330

Description

@os-bill

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:

  1. 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.
  2. #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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions