/** * Unified finding-enforcement policy (change: add-finding-enforcement-policy). * * OpenLore emits governance findings from several deterministic sources — the * pre-flight blast-radius guard, the change-impact certificate, and (this change) * the stale-decision-reference check. Each had grown its OWN config for "should * this block a commit or merely inform?" (`blastRadius.block`, * `impactCertificate.block`), so an operator had to learn N enforcement stories. * * This module is the single source of truth. It decouples two things that should * be separate: * - a finding's *intrinsic severity* — a property of the finding, owned by the * source that computes it (never altered here); * - its *enforcement class* — whether THIS repository wants it to block, owned * by `.openlore/config.json` `enforcement.policy` (a `code → class` map). * * Resolution is a pure, order-independent function with a fixed precedence: * explicit `off` > explicit `blocking` > explicit `advisory` > source default. * `advisory` is the source default for every code, so a repository that declares * no policy behaves exactly as it does today. Deterministic, no LLM (north star * `c6d1ad07`). */ import type { EnforcementClass, EnforcementConfig, BlastRadiusConfig, ImpactCertificateConfig } from '../../../types/index.js'; /** A repository's declared policy after normalization: a clean `code → class` map. */ export type EnforcementPolicy = Record; /** * A governance finding in the shape the policy can govern: a stable `code`, an * intrinsic `severity` (owned by the source — informational here, never used to * decide the class), and enough context to render it. Every finding source maps * its native finding onto this shape before the gate classifies it. */ export interface GovernanceFinding { /** Stable, documented code — the key a declared policy names. */ code: string; /** The emitting source's intrinsic severity. Never altered by the policy. */ severity: string; /** Which source produced it (for attribution in gate output). */ source: string; /** The artifact/surface/symbol the finding concerns. */ subject: string; /** Human-readable conclusion. */ message: string; } /** A finding paired with the enforcement class the policy resolved for it. */ export interface ClassifiedFinding extends GovernanceFinding { enforcementClass: EnforcementClass; } /** * The catalogue of stable governance finding codes the policy can name. Every * code a source emits MUST be registered here with its source-declared default * class, so (a) a declared policy that names an unknown code can be flagged, and * (b) the catalogue is the documented contract for what an operator may govern. * * `defaultClass` is `advisory` for every code: blocking is always opt-in, per * `add-preflight-blast-radius-guard`/AdvisoryByDefault. The field exists so a * future source CAN declare a stricter default without changing the resolver. */ export interface FindingCodeSpec { defaultClass: EnforcementClass; source: string; description: string; } export declare const FINDING_CODE_REGISTRY: Record; /** Whether a code is registered (so a declared policy entry is recognized). */ export declare function isKnownFindingCode(code: string): boolean; /** The source-declared default class for a code (`advisory` if unregistered). */ export declare function sourceDefaultClass(code: string): EnforcementClass; /** * The pure precedence core. Given the policy's explicit class for a code (if any) * and the source-declared default, pick the effective class: * explicit `off` > explicit `blocking` > explicit `advisory` > source default. * Order-independent and total. Exposed separately so the precedence is unit-tested * directly, including a source default of `blocking` (which no current code uses). */ export declare function applyPolicyPrecedence(explicit: EnforcementClass | undefined, sourceDefault: EnforcementClass): EnforcementClass; /** * Resolve the enforcement class for a finding. Pure function of the finding's * `code`, the declared `policy`, and its intrinsic `severity`. The severity is * NOT used to decide the class (the policy owns enforcement, the source owns * severity) — it is part of the signature so the contract is explicit and a * future severity-aware default is expressible without a signature change. * Identical inputs produce identical output regardless of policy declaration order. */ export declare function resolveEnforcementClass(code: string, policy: EnforcementPolicy | undefined, _severity?: string): EnforcementClass; /** * Normalize a raw `enforcement` config block into a clean policy map. Tolerant by * design (config is untrusted): a non-object block, non-object `policy`, or any * entry whose value is not a valid class is dropped. Never throws — a malformed * policy degrades to "no policy declared," preserving current behavior. Unknown * codes are RETAINED (a policy may name a code before its source ships); use * {@link unknownPolicyCodes} to surface them as non-failing findings. */ export declare function normalizeEnforcementPolicy(raw: EnforcementConfig | undefined): EnforcementPolicy; /** Codes named by a declared policy that no installed source emits (sorted, stable). */ export declare function unknownPolicyCodes(policy: EnforcementPolicy): string[]; /** * Lower the legacy per-surface `block: [...]` configs onto unified policy entries, * so a `blastRadius.block` / `impactCertificate.block` declaration resolves * identically to the equivalent `enforcement.policy`. The legacy sugar is a thin * equivalent of, and is superseded by, the unified policy. Returns only the * lowered entries; callers merge them UNDER an explicit `enforcement.policy` so a * direct policy entry always wins over inherited legacy sugar. */ export declare function lowerLegacyBlockConfig(config: { blastRadius?: BlastRadiusConfig; impactCertificate?: ImpactCertificateConfig; } | null | undefined): EnforcementPolicy; /** * Build the effective policy a gate consults: the lowered legacy `block` sugar * with an explicit `enforcement.policy` layered ON TOP (a direct policy entry * always wins). Both inputs are normalized/tolerant — a malformed config yields an * empty policy, never a throw. */ export declare function effectivePolicy(config: { enforcement?: EnforcementConfig; blastRadius?: BlastRadiusConfig; impactCertificate?: ImpactCertificateConfig; } | null | undefined): EnforcementPolicy; export interface GateResult { /** Every finding with its resolved class, sorted by a stable key. */ classified: ClassifiedFinding[]; blocking: ClassifiedFinding[]; advisory: ClassifiedFinding[]; /** Deliberately silenced findings — listed as informational, never failing. */ off: ClassifiedFinding[]; /** True iff at least one finding resolved to `blocking`. */ gated: boolean; } /** * Classify every finding through the policy and partition by class. The gate fails * (`gated`) only when at least one finding resolves to `blocking`. Findings are * sorted by a stable key so identical inputs produce identical output. Pure — no * I/O, no LLM. */ export declare function classifyFindings(findings: readonly GovernanceFinding[], policy: EnforcementPolicy | undefined): GateResult; //# sourceMappingURL=enforcement-policy.d.ts.map