/** * Governance integrity — a pure verdict over a FULLY-resolved governance policy * that answers "is this governance actually armed, or merely declared?". * * The load-bearing distinction is **enabled ≠ armed**. A rule can be `enabled` * in the policy yet inert because it has no vocabulary to measure against: * `components/prefer-library` with no effective canonical source enforces * nothing; `tokens/css-vars-must-be-defined` with an empty token vocabulary * enforces nothing. This module derives, per rule *family*, whether the family * is armed (has both an enabled rule AND the vocabulary/signal it needs), then * rolls those up into a single status. * * Browser-safe and filesystem-free: it never reads the repo. All file-derived * signals (token vocabulary size, whether the css-vars rule is active) are * supplied by the caller, which is also responsible for fully resolving the * policy (merging presets / localCanonical / designSystem / enrichment) BEFORE * calling in. */ import type { CanonicalSource, GovernanceConfig, GovernanceSeverity } from "./governance.js"; import { compileGlobalGovernanceFacts } from "./facts/index.js"; import { FRAGMENTS_INTERNAL_RULE_IDS, RULE_TIER } from "./rules/tiers.js"; import { BLOCKING_RULE_ALLOWLIST } from "./rules/emit-gate.js"; export type GovernanceIntegrityStatus = "healthy" | "degraded" | "inert"; export type GovernanceIntegrityFamilyId = "policy" | "components" | "tokens" | "hygiene" | "blocking-deny"; export interface GovernanceIntegrityFamily { id: GovernanceIntegrityFamilyId; armed: boolean; rules: string[]; reason?: string; remediation?: string; } export interface GovernanceIntegrityInput { /** * FULLY resolved policy — the caller merges presets / localCanonical / * designSystem / enrichment first. `undefined` means no policy at all. */ policy: GovernanceConfig | undefined; policySource: "config" | "preset" | "fallback" | "none"; /** Any governance intent signal present (caller computes). */ declared: boolean; /** Size of the contract token vocabulary; core cannot read files. */ tokenVocabularySize?: number; /** Whether `tokens/css-vars-must-be-defined` is activated (caller supplies). */ cssVarsActive?: boolean; mode?: "scan" | "ci" | "hook" | "doctor" | "setup"; } export interface GovernanceIntegrityVerdict { status: GovernanceIntegrityStatus; declared: boolean; fatalForCi: boolean; blockingCapable: boolean; families: GovernanceIntegrityFamily[]; armed: GovernanceIntegrityFamilyId[]; summary: string; remediations: string[]; } interface EffectiveRuleConfig { enabled: boolean; severity?: GovernanceSeverity; options?: Record; } /** * Whether a canonical source contributes an effective component vocabulary. * * Mirrors `sourceIncludesComponent` in `./rules/components-prefer-library.ts` * (a follow-up refactor will make that rule import this): a `directory` source * with no `include` is a wildcard (armed); an explicit `include` must be * non-empty; a package-like `npm`/`registry` source with no `include` is NOT an * effective vocabulary, because it has no export inventory to enumerate. */ export function isEffectiveCanonicalSource(source: CanonicalSource): boolean { return source.include !== undefined ? source.include.length > 0 : source.kind === "directory"; } /** * Whether `components/prefer-library`'s effective options carry a real component * vocabulary: at least one canonical mapping, or at least one effective * canonical source. Accepts the raw fact `options` bag (values are `unknown`). */ export function hasEffectiveComponentVocabulary( options: Record | undefined ): boolean { const mappings = options?.["canonicalMappings"]; if (Array.isArray(mappings) && mappings.length > 0) return true; const sources = options?.["canonicalSources"]; return ( Array.isArray(sources) && sources.some((source) => isEffectiveCanonicalSource(source as CanonicalSource)) ); } /** * Compile the policy to effective per-rule configs, keyed by rule id. Reuses the * fact compiler so the effective enabled/severity/options match exactly what the * scan engine sees (including the canonicalSources → prefer-library injection). */ function effectiveRuleConfigs( policy: GovernanceConfig | undefined ): Map { const configs = new Map(); for (const fact of compileGlobalGovernanceFacts(policy)) { if (fact.kind !== "governance_rule_config") continue; configs.set(fact.ruleId, { enabled: fact.enabled, severity: fact.severity, options: fact.options, }); } return configs; } /** Whether the policy declares the css-vars-must-be-defined style rule. */ function policyDeclaresCssVars(policy: GovernanceConfig | undefined): boolean { return Boolean(policy?.styles?.some((style) => style.kind === "style.cssVars.mustBeDefined")); } function dedupe(values: string[]): string[] { return [...new Set(values)]; } function summarize( status: GovernanceIntegrityStatus, flags: { componentsArmed: boolean; tokensArmed: boolean; blockingArmed: boolean } ): string { if (status === "inert") { return "Governance inert — no enforceable rule family is armed"; } if (status === "degraded") { return "Governance degraded — hygiene rules armed but no contract vocabulary is declared"; } const contract: string[] = []; if (flags.componentsArmed) contract.push("components"); if (flags.tokensArmed) contract.push("tokens"); const suffix = flags.blockingArmed ? ", blocking-capable" : ""; return `Governance healthy — contract vocabulary armed (${contract.join(" + ")})${suffix}`; } export function evaluateGovernanceIntegrity( input: GovernanceIntegrityInput ): GovernanceIntegrityVerdict { const configs = effectiveRuleConfigs(input.policy); // --- policy family -------------------------------------------------------- const policyArmed = input.policy !== undefined && input.policySource !== "none"; const policyFamily: GovernanceIntegrityFamily = { id: "policy", armed: policyArmed, rules: [], }; if (!policyArmed) { policyFamily.reason = "no governance policy resolved"; } // --- components family ---------------------------------------------------- const componentsConfig = configs.get("components/prefer-library"); const componentsArmed = componentsConfig?.enabled === true && hasEffectiveComponentVocabulary(componentsConfig.options); const componentsFamily: GovernanceIntegrityFamily = { id: "components", armed: componentsArmed, rules: ["components/prefer-library"], }; if (!componentsArmed) { if (componentsConfig?.enabled === true) { componentsFamily.reason = "components/prefer-library enabled but no effective canonical source"; componentsFamily.remediation = "add govern.canonicalSources (a directory source, or npm/registry with a non-empty include) or designSystem.path/packageName"; } else { componentsFamily.reason = "components/prefer-library not enabled"; } } // --- tokens family -------------------------------------------------------- const cssVarsActive = input.cssVarsActive ?? policyDeclaresCssVars(input.policy); const tokenVocabularySize = input.tokenVocabularySize ?? 0; const tokensArmed = cssVarsActive === true && tokenVocabularySize > 0; const tokensFamily: GovernanceIntegrityFamily = { id: "tokens", armed: tokensArmed, rules: ["tokens/css-vars-must-be-defined"], }; if (!tokensArmed) { if (!cssVarsActive) { tokensFamily.reason = "tokens/css-vars-must-be-defined not active"; tokensFamily.remediation = "activate the css-vars rule (govern.styles: style.cssVars.mustBeDefined)"; } else { tokensFamily.reason = "no token vocabulary"; tokensFamily.remediation = "declare token source files so the contract token vocabulary is non-empty"; } } // --- hygiene family ------------------------------------------------------- const hygieneRules: string[] = []; for (const [ruleId, config] of configs) { if (config.enabled !== true) continue; if (RULE_TIER[ruleId] !== "hygiene") continue; if (FRAGMENTS_INTERNAL_RULE_IDS.has(ruleId)) continue; hygieneRules.push(ruleId); } hygieneRules.sort(); const hygieneArmed = hygieneRules.length > 0; const hygieneFamily: GovernanceIntegrityFamily = { id: "hygiene", armed: hygieneArmed, rules: hygieneRules, }; if (!hygieneArmed) { hygieneFamily.reason = "no hygiene-tier rule enabled"; } // --- blocking-deny family ------------------------------------------------- const failOnWarnings = input.policy?.ci?.failOnWarnings === true; const blockingRules: string[] = []; for (const ruleId of BLOCKING_RULE_ALLOWLIST) { const config = configs.get(ruleId); if (config?.enabled !== true) continue; const familyArmed = ruleId === "components/prefer-library" ? componentsArmed : hygieneArmed; if (!familyArmed) continue; if (config.severity !== "error" && !failOnWarnings) continue; blockingRules.push(ruleId); } blockingRules.sort(); const blockingArmed = blockingRules.length > 0; const blockingFamily: GovernanceIntegrityFamily = { id: "blocking-deny", armed: blockingArmed, rules: blockingRules, }; if (!blockingArmed) { blockingFamily.reason = "no blocking-capable rule (armed rules are warn-severity only)"; blockingFamily.remediation = 'raise a blocking-eligible rule to severity "error", or set ci.failOnWarnings: true'; } // --- roll-up -------------------------------------------------------------- const contractArmed = componentsArmed || tokensArmed; const enforceableArmed = componentsArmed || tokensArmed || hygieneArmed; const status: GovernanceIntegrityStatus = !enforceableArmed ? "inert" : contractArmed ? "healthy" : "degraded"; const fatalForCi = input.declared && status === "inert"; const families: GovernanceIntegrityFamily[] = [ policyFamily, componentsFamily, tokensFamily, hygieneFamily, blockingFamily, ]; const armed = families.filter((family) => family.armed).map((family) => family.id); const remediations = dedupe( families .map((family) => family.remediation) .filter((remediation): remediation is string => remediation !== undefined) ); const summary = summarize(status, { componentsArmed, tokensArmed, blockingArmed }); return { status, declared: input.declared, fatalForCi, blockingCapable: blockingArmed, families, armed, summary, remediations, }; }