← Files AkinatorARCHIVED FILE
docs/rule-evolution.md
3.74 KB · Oct 3, 2026 · 06:33 UTC
# Rule evolution Stage 3 of the v2 pipeline. Rules are not static text: they acquire scars. Run by `skills/everything/scripts/akinator_rules.py`. ## The mechanism this exists for When rule A's **enforcement** produces a new failure B, the system holds both records - the constraint A protects, and the failure A caused. It can then put both in front of whoever writes **A'**, the replacement that satisfies each. ``` rule A --enforced--> failure B --both recorded--> rule A' | | +------------------ superseded_by --------------------+ ``` **The chain is the value.** It is how the third agent understands why a rule is shaped strangely - the strange shape is the scar tissue from B. A rule is never deleted, only superseded, because deleting it deletes the reason, and the reason is the one part a future reader cannot reconstruct from the code. ### The worked shape A single-writer rule protects a quota column from lost updates. Its enforcement routes every write through a row-locked function. Months later an offline backfill goes through the same path, takes a lock per row, and times out after eight hours. - A rule that only fixes the backfill reintroduces the lost update. - A rule that only keeps the lock is what caused the backfill failure. A' has to hold both - typically by scoping the constraint to online writes and giving offline migrations a **documented exception path**. That shape is obvious once both records are on the same page, and invisible from either alone. Putting them on the same page is the whole job. ## Using it ```bash python skills/everything/scripts/akinator_rules.py graph # rules, scopes, supersession python skills/everything/scripts/akinator_rules.py conflicts # overlapping scopes, opposed mandates python skills/everything/scripts/akinator_rules.py caused 07 <failure-fingerprint> python skills/everything/scripts/akinator_rules.py evolve 07 # the brief for a replacement ``` `caused` writes one frontmatter field and leaves the prose byte-for-byte unchanged. Frontmatter is the tool's; the prose a human wrote is not, and a generator that quietly reflows it will be distrusted the first time someone notices. ## Optional frontmatter Absent on every v1 rule and required on none - a rule without it behaves exactly as before: ```yaml id: 11 introduced_by: failure/checker-silent-false-negative supersedes: [04] caused: [failure/backfill-blocked] scope: "src/quota/**" superseded_by: 12 ``` ## Conflicts are reported, never resolved Two rules conflict when their scopes overlap **and** their mandates disagree - same subject, opposite polarity. The tool reports them and stops. Silently picking a winner between two constraints produces a system nobody trusts, and the picking would be invisible in exactly the place it matters most. The resolution is a human decision, recorded as an ADR, producing either a narrowed scope or a synthesized replacement. Scope overlap is judged **conservatively**: an unscoped rule applies everywhere, so it overlaps everything. Being wrong in that direction produces a reported conflict a human dismisses in a second; being wrong the other way hides one forever. ## Related - Docs: `docs/ledger.md` - where failure records come from - Docs: `docs/distil.md` - how a recurrence becomes a rule in the first place - Docs: `docs/akinator-v2-design.md` - stage 3, HARDEN - Rules: `rules/03-rules-need-live-enforcement.md` - Code: `skills/everything/scripts/akinator_rules.py`, `tests/test_rules_evolution.py` ## Review when - A rule is superseded for the first time, to confirm the chain reads well. - The conflict heuristic reports something a human dismisses twice - that means it is too loose and should be narrowed with a test. - Last verified: 2026-08-26.
SHA-256: 84e94272c2f24b5cb3bc00dead35cbd8882e95dc405315ae747b30f4e5f79b1f