/** * vigiles — Deterministic proof system for self-evolving specifications. * * Six algorithms that verify spec mutations without any LLM dependency: * * 1. MonotonicityLattice — rules can only strengthen over time * 2. ncd() — information-theoretic duplicate detection * 3. BloomFilter — fast approximate set membership * 4. fixedPoint() — compilation convergence detection * 5. MerkleHistory — tamper-evident spec evolution audit trail * 6. propertyTest() — random mutation + invariant checking */ import { ncd } from "./ncd.js"; import type { Rule, ClaudeSpec } from "./spec.js"; export interface MonotonicityViolation { ruleId: string; from: Rule["_kind"]; to: Rule["_kind"]; fromStrength: number; toStrength: number; } export interface MonotonicityResult { valid: boolean; violations: MonotonicityViolation[]; added: string[]; removed: string[]; strengthened: string[]; unchanged: string[]; } /** * Check that a spec mutation is monotonic: existing rules only strengthen. * * - Adding rules: always allowed * - Removing rules: flagged (informational, not a violation by default) * - Strengthening (guidance → check → enforce): allowed * - Weakening (enforce → guidance): violation unless allowWeaken includes the rule ID */ export declare function checkMonotonicity(before: Record, after: Record, options?: { allowWeaken?: Set; }): MonotonicityResult; /** * Compute the join (least upper bound) of two rule kinds in the lattice. * join(guidance, enforce) = enforce */ export declare function latticeJoin(a: Rule["_kind"], b: Rule["_kind"]): Rule["_kind"]; /** * Compute the meet (greatest lower bound) of two rule kinds in the lattice. * meet(guidance, enforce) = guidance */ export declare function latticeMeet(a: Rule["_kind"], b: Rule["_kind"]): Rule["_kind"]; /** Get the numeric strength of a rule kind. */ export declare function ruleStrength(kind: Rule["_kind"]): number; export { ncd }; export interface NCDPair { idA: string; idB: string; distance: number; } /** * Find all rule pairs with NCD below a similarity threshold. * Returns pairs sorted by distance (most similar first). */ export declare function findSimilarRules(rules: Record, threshold?: number): NCDPair[]; /** * Bloom filter — space-efficient probabilistic set membership. * * Insert elements, then query "is X possibly in the set?" * False positives possible. False negatives impossible. * * Optimal parameters: * m = -(n × ln(p)) / (ln2)² (bit array size) * k = (m / n) × ln2 (hash function count) * * Reference: Bloom (1970) "Space/Time Trade-offs in Hash Coding" */ export declare class BloomFilter { private readonly bits; private readonly numHashes; readonly size: number; private _count; /** * Create a Bloom filter. * @param expectedItems Expected number of items to insert * @param falsePositiveRate Desired false positive rate (0-1) */ constructor(expectedItems: number, falsePositiveRate?: number); /** Number of items inserted. */ get count(): number; /** Theoretical false positive rate given current fill. */ get estimatedFPR(): number; /** Insert an element. */ add(item: string): void; /** Query membership. Returns true if the item MIGHT be in the set. */ has(item: string): boolean; /** * Estimate the Jaccard similarity between two Bloom filters. * Uses bit-level comparison — no need to know the original elements. */ static jaccardSimilarity(a: BloomFilter, b: BloomFilter): number; } /** * Build a Bloom filter from a rule's tokens. * Tokenizes rule text into words and n-grams for fuzzy matching. */ export declare function ruleToBloomFilter(rule: Rule, expectedTokens?: number): BloomFilter; export interface FixedPointResult { converged: boolean; iterations: number; /** Hash at each iteration. */ hashes: string[]; /** If not converged, the cycle length (0 = no cycle detected within maxIterations). */ cycleLength: number; } /** * Detect whether a compile function reaches a fixed point. * * A fixed point means: applying the function again produces the same output. * compile(compile(spec)) === compile(spec) * * If the function doesn't converge, detects cycles using Floyd's * tortoise-and-hare algorithm adapted for hash sequences. * * @param compileFn A function that takes content and returns new content * @param initialContent Starting content * @param maxIterations Maximum iterations before declaring divergence */ export declare function fixedPoint(compileFn: (content: string) => string, initialContent: string, maxIterations?: number): FixedPointResult; export interface Mutation { type: "add" | "remove" | "strengthen" | "weaken" | "merge" | "reword"; ruleIds: string[]; description: string; } export interface ProofReceipt { name: string; passed: boolean; detail?: string; } export interface HistoryNode { /** SHA-256 hash of this node (covers all fields except `hash` itself). */ hash: string; /** Hash of the parent node ("genesis" for the first node). */ parentHash: string; /** Hash of the compiled spec content at this version. */ specHash: string; /** What mutation was applied. */ mutation: Mutation; /** Proof receipts — which proofs ran and their results. */ proofs: ProofReceipt[]; /** Unix timestamp (ms). */ timestamp: number; } /** * Merkle history — append-only, tamper-evident spec evolution log. * * Each node's hash covers its parent hash, creating a chain where * tampering with any node invalidates all descendants. */ /** * Read-only view of a MerkleHistory — exposes inspection methods but no * append/mutation surface. Callers that only need to read the audit trail * should depend on this instead of MerkleHistory to prevent bypassing the * engine's validation gates. */ export interface ReadonlyMerkleHistory { readonly length: number; head(): HistoryNode | null; getNodes(): readonly HistoryNode[]; verify(): { valid: boolean; invalidAt: number; }; toJSON(): string; } export declare class MerkleHistory implements ReadonlyMerkleHistory { private nodes; /** Number of versions in the history. */ get length(): number; /** * Get all nodes as a deep defensive copy. Callers cannot mutate node * fields (specHash, mutation, proofs) through this API — that would * bypass append/proof gates and weaken the tamper-evident guarantees. */ getNodes(): readonly HistoryNode[]; /** * Get the latest node, or null if empty. Returned as a deep defensive * copy — see getNodes() for rationale. */ head(): HistoryNode | null; /** * Append a new version to the history. * Returns the hash of the new node. */ append(specHash: string, mutation: Mutation, proofs: ProofReceipt[]): string; /** * Verify the entire chain — every node's hash must be correct, * and every parent pointer must match the previous node. * * Returns the index of the first invalid node, or -1 if valid. */ verify(): { valid: boolean; invalidAt: number; }; /** * Serialize the history to JSON for persistence. */ toJSON(): string; /** * Deserialize a history from JSON. Rejects non-array payloads and * validates that each entry has the required HistoryNode shape so * malformed persisted data can't produce a "valid" history that * later crashes on append/verify. */ static fromJSON(json: string): MerkleHistory; } export type MutationGenerator = (value: T, seed: number) => T; export type Invariant = (value: T) => boolean; export interface PropertyTestResult { passed: boolean; iterations: number; /** If failed, the mutation sequence that caused the failure. */ failingSequence?: T[]; /** If failed, the minimal shrunk counterexample. */ shrunk?: T; /** Name of the invariant that failed. */ failedInvariant?: string; } /** * Property-based testing: generate random mutations, check invariants hold. * * Inspired by QuickCheck/fast-check. Uses a deterministic PRNG for * reproducible failures. * * @param initial Starting value * @param mutate Function that produces a random mutation * @param invariants Named invariant functions that must all return true * @param options Test parameters */ export declare function propertyTest(initial: T, mutate: MutationGenerator, invariants: Record>, options?: { iterations?: number; seed?: number; sequenceLength?: number; }): PropertyTestResult; export interface FitnessResult { score: number; coverage: number; redundancy: number; budgetPressure: number; } /** * Compute the fitness of a spec. Higher is better. * * fitness = coverage × (1 - redundancy) × (1 - budgetPressure) * * - coverage: fraction of rules with enforcement (check or enforce) * - redundancy: fraction of rule pairs that are near-duplicates (NCD < threshold) * - budgetPressure: tokens used / max tokens */ export declare function fitness(spec: ClaudeSpec, options?: { maxTokens?: number; ncdThreshold?: number; }): FitnessResult; //# sourceMappingURL=proofs.d.ts.map