import type { CompiledRule } from '../compiler-schema.js'; /** * The Prop 310 compiled homes, as a runtime-readable list. A rule carrying ANY * of them came from the record path; a rule carrying NONE is legacy. * * DERIVED, never mirrored (Tenet 20): the discriminator is the presence of the * § Design 12 homes themselves, not a separate `dialect:` marker field — a marker * would be a second, drift-capable statement of what the fields already say, and * § Design 4's key space is closed against inventing one. * * The derivation is SAFE because nothing but the record path can write these * fields: they were added to `CompiledRuleSchema` by this slice, and the only * other writer (the legacy lesson-compile actuator) is under the standing * `rule-compilation` freeze and does not regenerate. The byte-identity guard * asserts the empirical half — zero of the shipped 485 rules carries any of them. */ export declare const RECORD_COMPILED_HOME_KEYS: readonly ["excludeGlobs", "requires", "examples", "language", "verificationShadow", "recoveryHint", "curation"]; /** * True when a compiled rule came from the Prop 310 record path — i.e. when the * § Design 7 dialect and its two-array scope rule apply to it. False for every * legacy/mined rule, which keeps the shipped matcher unchanged. * * The test is `examples !== undefined` ALONE, deliberately narrower than "any * § Design 12 home" (falsification round, 2026-08-21). `examples` is the EXACT * discriminator, not merely a sufficient one: * - § Design 5 makes it non-omittable and min-1, so EVERY record carries it; * - `compileRuleRecord` emits it unconditionally, so every lowered rule has it; * - it is the one home no other construct can be present without. * * The wider seven-key disjunction was demonstrated to be UNSAFE in the other * direction: a legacy-shaped rule that ever gained just a `recoveryHint` — a * plausible future addition to the mined path, and a purely cosmetic one — would * silently flip from the shipped tree-wide matcher to the record dialect's * root-only reading of the same `*.ts` glob, narrowing its scope with no signal. * Keying on the field that MEANS "this is a record" removes that coupling * between a diagnostic field and a matcher semantic. * * `RECORD_COMPILED_HOME_KEYS` remains the full home set for the corpus guard * (which asserts the legacy manifest carries NONE of them — a stronger claim * than this predicate needs, and the right one for a freeze check). */ export declare function isRecordPathRule(rule: Pick): boolean; /** * The exemplar lines a rule's Stage-4 / doctor readers treat as "the authored bad * example", single-homed (Tenet 20). * * Prop 310 slice 3 discharges the slice-2 pin: a record rule's authored preimages * live in `examples[i].bad` — the § Design 10 EDITABLE home — and are NOT mirrored * onto the legacy `badExample` field, so every reader of that field would otherwise * read a record rule as having no bad example at all. Both shipped readers now ask * this one function instead: * * - Stage 4 (`lineMatchesBadExample`) — a matched line equal to any of these * classifies `in-scope-bad-example` rather than `candidate-debt`; * - `totem doctor`'s `no-badExample` grandfathering reason — an empty result is * exactly the old `!badExample || badExample.trim() === ''` test. * * BLANK LINES ARE DROPPED, which is what makes both of those byte-identical for * legacy rules: Stage 4 already refuses to match an empty trimmed line, and a * whitespace-only `badExample` must still count as absent for doctor. */ export declare function ruleBadExampleLines(rule: Pick): string[]; /** * The POSTIMAGE twin of `ruleBadExampleLines`, on the identical contract: record * path → every `examples[i].good`, legacy → `goodExample`, blank lines dropped. * * `totem doctor`'s `no-goodExample` grandfathering reason reads it. Without the * twin, a record rule cleared the `no-badExample` reason and then tripped * `no-goodExample` on the very same absence — the record's exemplars live in * `examples`, and § Design 10 makes that the editable home for BOTH sides of the * pair, so a reader that single-homes one side and not the other reports half a * truth. An empty result is exactly the old * `!goodExample || goodExample.trim() === ''` test. */ export declare function ruleGoodExampleLines(rule: Pick): string[]; /** * Exactly the fields the scope predicates READ. Structural rather than the whole * `CompiledRule` so callers holding a projection — Stage 4's `classifyFile` * takes one — pass it directly instead of casting a narrower object back to the * full type. A cast there would have been a lie the compiler stopped checking. */ export type RuleScopeFields = Pick; /** * § Design 12 — fail loud on a TORN record rule: one carrying a Prop 310 * compiled home but no `examples`. * * Such a rule cannot exist downstream of the lowering (`examples` is min-1 at * parse and emitted unconditionally) nor in the frozen legacy corpus (which * carries no home at all), so this only ever catches a hand-edited manifest or * a bypassed producer. Left unchecked, `isRecordPathRule` would class it LEGACY * and its `excludeGlobs` and `requires` would be dropped — a scope silently * widened and a requirement silently unevaluated. The drop direction is toward * flagging, so it is not fail-open, but § Design 12's ban on accepting a * construct and dropping it is a ban on the SILENCE, not just on the unsafe * direction. Fail loud instead. * * Called at dispatcher altitude, once per invocation — the same altitude as the * tree-sitter `requires` backstop, and for the same reason: a per-file check * would re-report the same defect on every file. */ export declare function assertNoTornRecordRules(rules: readonly CompiledRule[]): void; /** * § Design 7 — the two-array scope rule for a RECORD-path rule: * `positiveMatch && !excludeMatch`, both arrays evaluated under the normative * dialect (`matchesRecordGlob`). * * `fileGlobs` is min-1 at parse, so the empty-positive case cannot arise from a * well-formed record; a hand-edited manifest that produced one would match * NOTHING here rather than everything. That direction is deliberate — the * shipped `fileMatchesGlobs` treats "no positive globs" as include-everything, * which for a record-path rule would silently WIDEN a scope the grammar requires * to be declared. A rule that fires nowhere is visible; one that fires everywhere * is the fail-open class § Design 2 exists to kill. */ export declare function recordScopeMatchesFile(filePath: string, fileGlobs: readonly string[] | undefined, excludeGlobs: readonly string[] | undefined): boolean; /** * The ONE scope predicate the rule engine asks: does this rule apply to this * file? Record-path rules get § Design 7's dialect + two-array rule; every other * rule gets the shipped `fileMatchesGlobs` predicate verbatim, including its * "unscoped rule applies to everything" behaviour. * * Single-homed on purpose: the engine had four separate copies of the * `rule.fileGlobs && rule.fileGlobs.length > 0 ? fileMatchesGlobs(...) : true` * expression, and four copies of a dialect fork is how a rule silently gets one * scope at dispatch and a different one at the fail-loud guard. */ export declare function ruleAppliesToFile(rule: RuleScopeFields, filePath: string): boolean; /** * The text the § Design 8 scopes resolve to at a target-match locus L. * * `file` is a LAZY resolver returning `null` when the file cannot be read: the * caller owns the IO, a `line`-scope requirement never triggers a read, and the * unreadable case gets an explicit, documented direction below rather than an * exception thrown from inside a matcher. */ export interface RequiresScopeText { /** L's line — the text of the line the target matched on. */ line: string; /** The whole file containing L, or `null` when unreadable. */ file: () => string | null; } /** * Path-containment check for the § Design 8 whole-file readers. * * The paths handed to these readers come out of a DIFF, which is * attacker-shaped input on any path where a lint runs over untrusted contributed * changes — a `../` prefix would otherwise read outside the repo. `path.relative` * rather than `startsWith` on purpose: the string form admits the * sibling-directory bypass (`/app-secrets` passing a `/app` prefix test), which * is the same reasoning the shipped ast-path containment checks use. * * An out-of-root path is reported as UNREADABLE, not as an error: the requirement * is then unmet and the rule FIRES, preserving the fail-toward-flagging direction * every other read failure on this path takes. */ export declare function isInsideRoot(root: string, filePath: string): boolean; /** * § Design 8 — gate every `requires.pattern` in the loaded manifest through the * SAME safe-regex2 check the compile path applies, before any of them is * evaluated. * * The lowering already gates this, so a pattern reaching here unsafe means the * manifest was hand-edited or written by a bypassed producer. Left ungated it is * a live ReDoS surface, and a worse one than the target pattern's: at * `scope: file` the requirement runs against WHOLE-FILE text, unbounded, on the * pre-commit path. `validateRegex` is the same single-homed check the compile * gate uses, so the two can never drift. * * Dispatcher altitude, once per invocation — the same reason as * `assertNoTornRecordRules`: a per-match check would re-report one defect on * every line of every file. */ export declare function assertRequiresPatternsSafe(rules: readonly CompiledRule[]): void; /** * § Design 8 — fail loud on `ast-grep` + `requires.scope: line`. * * The lowering REJECTS this combination (an ast-grep match is a span, and * reading it as its start line makes the verdict depend on source formatting), * but a hand-edited manifest can still carry it, and the ast dispatcher would * silently evaluate `match.lineText` — shipping exactly the formatting-dependent * semantic the ruling refused. Mirrors the tree-sitter `requires` backstop: * unreachable from any compiled record, unreachable from the legacy corpus, * live only against a torn manifest. */ export declare function assertNoAstGrepLineScope(rules: readonly CompiledRule[]): void; /** * § Design 8 — pass two. Returns TRUE when the required context is PRESENT * within the declared scope containing L, i.e. when the rule must stay SILENT. * The rule fires iff the target matched AND this returns false. * * `requires.pattern` is a regex evaluated TEXTUALLY, independent of the target * engine — "the requirement is a context check, not a second matcher" — so an * ast-grep rule's requirement is still a plain regex over the scope text. * * UNREADABLE FILE (`scope: file` only) ⇒ context ABSENT ⇒ the rule FIRES. The * safe direction for a lint gate is toward flagging: a false positive is visible * and disputable, a suppressed real violation is silent. This mirrors the shipped * Rust test-span exemption, whose read failure likewise yields no exemption * (`rule-engine.ts`: "the exemption fails toward flagging, never toward * suppression"). * * A `requires.pattern` that will not compile means the compile-stage gate was * bypassed or the manifest was hand-edited. Fail LOUD — the same treatment * `applyRulesToAdditions` gives an uncompilable `rule.pattern`; silently treating * it as "context absent" would turn a broken rule into a firing one. */ export declare function requiresContextPresent(rule: CompiledRule, requires: NonNullable, text: RequiresScopeText): boolean; /** * § Design 8 applied to one target match: TRUE when the rule must stay silent at * this locus because its required context is present. Always false for a rule * carrying no `requires` block — which is every legacy rule, so the shipped * evaluation path is untouched by construction. */ export declare function requiresSuppressesMatch(rule: CompiledRule, text: RequiresScopeText): boolean; //# sourceMappingURL=record-runtime.d.ts.map