/** * Rule taxonomy — the single source of truth for tier classification. * * Every governance rule is either: * - **Tier A (contract vocabulary)** — measures drift from a vocabulary the * team *declared* (the two-choice contract: canonical components + token * source files). Headline-eligible; on by default but *inert until a * contract is authored* (each Tier-A rule self-gates on its contract facts). * - **Tier B (generic hygiene)** — generic best-practice opinions (raw values, * a11y, theme literals, Tailwind). Never the headline; off by default for * customers, opt-in per rule. * * This map is consumed by the core customer-default preset (`./presets.ts`), the * cloud policy composer, and the UI rules editor. There is exactly one copy — * importers derive their defaults from it rather than re-listing rules (DRY). * * Identifiers are stable: this introduces no new rule ids, it only attaches a * tier label to existing ones. */ export type RuleTier = "contract" | "hygiene"; /** * Tier of every rule in `RULES`. Tier A is exactly the two-choice-contract * rules; everything else is hygiene. A coverage test asserts this map and * `RULES` stay in lock-step (every rule id classified, no strays). */ export const RULE_TIER: Readonly> = { // ---- Tier A — contract vocabulary ------------------------------------- "components/prefer-library": "contract", // canonical-component bypass (FUI1001/1004) "tokens/css-vars-must-be-defined": "contract", // token drift (FUI2015) // ---- Tier B — generic hygiene ----------------------------------------- "components/unknown-prop": "hygiene", "components/forbidden-prop-value": "hygiene", "props/invalid-value": "hygiene", "imports/preferred-path": "hygiene", "components/preferred-component": "hygiene", "styles/no-raw-color": "hygiene", "styles/no-raw-dimensions": "hygiene", "styles/no-raw-spacing": "hygiene", "styles/no-raw-typography": "hygiene", "tailwind/arbitrary-color": "hygiene", "tailwind/arbitrary-spacing": "hygiene", "tailwind/forbidden-palette": "hygiene", "tailwind/off-scale-spacing-token": "hygiene", "tailwind/raw-color-via-token": "hygiene", "tailwind/unknown-class": "hygiene", "tokens/require-dual-fallback": "hygiene", "theme/no-theme-coupled-literal": "hygiene", "a11y/required-accessible-name": "hygiene", // Composition constraints are authored contracts, but not part of the // two-choice headline contract; they self-activate when patterns are declared // (the compiler injects their configs), so off-by-default here is harmless. "composition/cardinality": "hygiene", "composition/co-occurrence": "hygiene", }; /** * Rules removed from the customer default preset *entirely* — Fragments-internal * conventions that are meaningless on a customer repo. They remain available * under the full `fragments` preset for Fragments' own dogfooding; they are * never even an opt-in toggle in the customer default. * * `tokens/require-dual-fallback` — the `var(--x, $x)` SCSS micro-convention — * was ~51% of the dogfood flood and is the canonical example. */ export const FRAGMENTS_INTERNAL_RULE_IDS: ReadonlySet = new Set([ "tokens/require-dual-fallback", ]); /** Tier of a rule id. Unknown ids fail safe to `hygiene` (off by default). */ export function tierFor(ruleId: string): RuleTier { return RULE_TIER[ruleId] ?? "hygiene"; } /** True when the rule measures declared-contract drift (headline-eligible). */ export function isContractTierRule(ruleId: string): boolean { return tierFor(ruleId) === "contract"; } /** All Tier-A (contract vocabulary) rule ids, in declaration order. */ export function contractTierRuleIds(): string[] { return Object.keys(RULE_TIER).filter(isContractTierRule); } /** All Tier-B (generic hygiene) rule ids, in declaration order. */ export function hygieneTierRuleIds(): string[] { return Object.keys(RULE_TIER).filter((id) => !isContractTierRule(id)); } /** * Vocabulary-first headline order: canonical-component bypass ranks above token * drift, both above any hygiene. This is the SSOT the dashboard headline (brief * 05) consumes so "vocabulary-first" is defined once, not re-encoded per surface. */ const VOCABULARY_ORDER: readonly string[] = [ "components/prefer-library", "tokens/css-vars-must-be-defined", ]; /** Headline rank of a rule id (lower = higher in the vocabulary-first list). */ export function vocabularyRank(ruleId: string): number { const index = VOCABULARY_ORDER.indexOf(ruleId); return index >= 0 ? index : VOCABULARY_ORDER.length; } /** Comparator ordering rule ids vocabulary-first (Tier A before hygiene). */ export function compareByVocabularyRank(a: string, b: string): number { return vocabularyRank(a) - vocabularyRank(b); }