Skip to content

defineStack parses at definition time, so the authoring registry's normalized (pre-parse) tier may never actually be pre-parse for a TS config — the tier's whole reason for existing #6073

Description

@hotlong

Found while implementing #5693. Filed unassigned, observation-class (finding, no pm:queue): the first half is measured, the second half is a consequence I did not verify — see "What still needs measuring". Nothing is known to be broken for a user today.

The tier's contract

packages/lint/src/authoring-rules.ts declares two input tiers, and says why the pre-parse one exists:

/**
 * - `normalized` — the `normalizeStackInput` output, BEFORE the Zod parse. The
 *   rules that need it check keys the parse strips (a flat list view in
 *   `views: []`, `userFilters` on an object list view, a `visibleOn` alias): by
 *   the time `result.data` exists the evidence is gone.
 */
export type AuthoringRuleInputTier = 'normalized' | 'parsed';

Six rules are wired input: 'normalized' today, and their own headers repeat it — e.g. validate-list-view-mode.ts:15 "Runs PRE-parse (on the normalizeStackInput output, before the …)", same in validate-view-containers.ts:14.

What is measured

defineStack — the documented and universal way a TS config declares a stack — parses, strictly by default (packages/spec/src/stack.zod.ts:1245):

const result = ObjectStackDefinitionSchema.safeParse(normalized, { error: objectStackErrorMap });
...
const data = result.data;   // returned to the caller

So export default defineStack({...}) evaluates to result.data — defaults filled, unknown keys stripped — before the CLI ever loads the module. Whatever the commands then do with that value, the parse has already happened.

Directly observed on examples/app-todo (a defineStack config), during #5693:

  • os lint calls runAuthoringRules('lint', { normalized: config, … }) with no parsed key (packages/cli/src/commands/lint.ts:428), and the runner falls back — run.parsed ?? run.normalized (authoring-rules.ts:1035) — precisely so a parsed-tier rule still gets something. The comment there is explicit that os lint "does not Zod-parse".
  • Yet with the flow's runAs line deleted, os lint printed the flow-runas-unscoped message arm that is only reachable when flow.runAs is a string. It could only be a string because FlowSchema's .default('user') had already been applied — i.e. the stack os lint had in hand was post-parse.

That is the measurement: for a defineStack config, os lint's "normalized" input is a parsed stack.

Why it may matter beyond #5693

For flow-runas-unscoped this was harmless-and-then-fixed (#5693 removed the branch that could see the difference). But the tier exists for rules whose evidence is destroyed by the parse. If the value handed to normalizeStackInput in lint.ts / validate.ts / compile.ts is already result.data, then normalizing it again cannot resurrect a key the parse stripped — the alias, the flat list view, the userFilters are gone before any command runs, and the normalized-tier rules would be structurally silent on exactly the configs that make up the repo's own examples.

If that holds, the tier is only genuinely pre-parse for stacks that never went through strict defineStack: raw JSON/YAML, defineStack(x, { strict: false }), and direct API callers. Same family as #4984 / #5693 — a check whose input shape in production differs from the input shape its tests feed it.

What still needs measuring (do this before treating it as a defect)

I did not verify the consequence, only the mechanism. Concretely:

  1. Take one normalized-tier rule with a real stripped-key fixture — validate-list-view-mode (flat list view in views: []) or validate-view-containers — and author that shape in a defineStack TS config in an example app.
  2. Run os lint and os validate on it. Does the rule report? If it reports, the parse is not stripping what we assume (ObjectStackDefinitionSchema may be permissive at that site) and there is nothing here.
  3. If it does not report, compare against the same stack fed as raw JSON — that difference is the defect, and its size is "how many of the six normalized rules are affected".

Worth noting one non-obvious possibility in the other direction: defineStack also runs warnUnknownAuthoringKeys(normalized) on the pre-parse value and warns there, so some of this ground may already be covered at definition time by a different channel — which would change the remedy (delete the tier's claim, or route those rules through the same seam) rather than the finding.

Related


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions