Canonical methodology · revision v1.3 · rendered byte-for-byte from the export
Contract-First Multi-Plan Review Methodology
Version: v1.3 — breaker-distilled (§7.3.4 evidence rules, §7.3.5 the verification floor, §7.3.2 L13 anti-negligence rule, §21 Empenho caveat, Appendix A.4, Appendix B rewrite — added 2026-07-14 after the v1.2 Breaker was pointed at v1.2). Prior: v1.2 — lens-distilled (S1′ + S7 + the lens registry §7.3.1-§7.3.3, §5.7, §5.8, §6.1.1, §6.5, §7.7, §20 C18, Appendix A.3 added 2026-07-14 from the Hadúr Jira-bridge cycle: Rounds 16-27, 48 findings — 3 Critical, 34 Major, 9 Minor, 2 Nit). Prior: v1.1 — Hadur dogfood-distilled (§13.10, §24, §20 C16-C17, Appendix A.2 added 2026-04-27 from Hadur 14-round/12-wave cycle).
What v1.2 changed, and the one number that forced it. Of the 37 Criticals + Majors found across the twelve rounds of the Jira-bridge cycle, 10 (27%) came from the first-ever run of a review lens and 12 (32%) came from verifying a previous fix. 59% of the cycle's serious findings came from two mechanisms v1.1 did not mandate: it required neither rotation across kinds of review, nor independent adversarial verification of a fix. v1.2 mandates both. The through-line of every v1.2 amendment is one sentence: v1.1 was good at making you look carefully, and had no mechanism for making you look elsewhere. S1 counted rounds, not kinds of question; §6.1 named a failure mode without detecting it; the verifier-gate scrutinised the fix and never the test; the architectural-stop rule exempted the one artifact everything else depends on. Each was an enumeration one element short — which is, precisely, the bug the reviewed codebase kept shipping (§6.5).
What v1.3 changes, and how it was found: v1.2 added the Breaker, and the Breaker broke v1.2.
§5.8 mandates that a Critical fix, and any landing architectural invariant, must survive a reviewer whose brief is to break it. The v1.2 amendments were an architectural invariant — they rewrote the stopping rule of the entire loop, which is the single most load-bearing piece of the methodology, and the piece everything downstream stops checking once it lands. So §5.8 was applied to §5.8's own revision. It returned four Criticals, and it was right about all four. Stated without softening, because the point of the record is that it is unflattering:
| What v1.2 claimed | What the Breaker constructed |
|---|---|
| S1′ resets every streak when a wave lands | The wave boundary was inferred from the loop's own bookkeeping (findings marked resolved). An operator who transitions nothing produces no wave, no reset, and accrues streaks across code that genuinely changed. The bypass is omission — reachable by doing nothing at all. |
| S7 proves every lens was exercised | S7 read the round's lenses array and believed it. Thirteen strings of JSON bought thirteen exercised lenses. The signal whose entire purpose is to catch the question nobody asked was satisfiable by nobody asking it. |
| Together they make convergence mechanical | Two round files containing findings: [] and lenses: [L1…L13] returned CONVERGED. Not a hypothetical: the test fixture was the demonstration. |
| §6.1.1's reachability audit is mandatory | The Breaker itself had no agent, no brief and no dispatch surface — the role the amendment invented was unreachable. The amendment failed the audit the amendment wrote. |
The root error under the first two is one sentence, and it is §6.5 verbatim: "the code did not change" and "this lens was exercised" were claims the tool accepted from bookkeeping the loop controls. A convergence rule adjudicated by the loop it adjudicates is not a rule; it is a rubber stamp — and a more dangerous one than the rule it replaced, because it reports mechanical confidence where v1.1 at least left a human uneasy.
v1.3 therefore states the evidence rules (§7.3.4): a round's clean verdict counts only against a code identity taken outside the loop, and a lens counts only with provenance that names a distinct reviewer and a hash-pinned brief. It states the verification floor (§7.3.5): what this can and cannot prove, in the words the tool itself prints. It admits the one place the methodology's own logic runs out — a clean L13 streak is worth less than a clean named-lens streak, necessarily, and §7.3.2 now says so and gates it differently. And it applies the same standard backward: §21's worked example, Empenho, would not converge under the rule this document now mandates, and pretending otherwise would be the exact failure the document warns about.
None of this is embarrassing; it is the strongest available evidence for §5.8. A methodology whose adversarial-verification rule cannot find four Criticals in its own amendment on its first outing has not been tested. This one was, immediately, on itself, and it failed — loudly, cheaply, and before anything shipped. That is what the rule is for.
A reusable pattern for building large feature suites (3+ interrelated implementation plans) with AI coding agents — designed to catch drift, signature mismatch, and false-confidence BEFORE any code ships.
This document is the canonical, designed-once description of the Contract-First Multi-Plan Review methodology. It is written for two audiences: a practitioner adopting the methodology on a fresh project, and an agent (human or LLM) authoring or reviewing plans inside a project that already adopted it. The text is normative — it states what the rules ARE, not how they were discovered. Historical context (how each rule emerged from real review cycles) is collected in Appendix A.
Reading guide. §0 (Quick reference — decision tree) is the executive summary; every leaf in the tree corresponds to a numbered §-section that explains the response in detail. Part I (Foundations) defines artifacts, roles, and the loop. Part II (Quality gates) specifies the verifier-gate and how to recognize convergence vs. divergence. Part III enumerates anti-patterns. Part IV defines architectural escalation when narrow corrective waves cannot close drift. Part V provides templates a practitioner can copy. Part VI covers adoption and extension.
Origin. Distilled from the Empenho project (a 9-plan Portuguese time-tracking / billing / fiscal SaaS, ~90k plan-document lines, 11+ review rounds, 318 → 0 Critical findings, R34 convergence after one architectural escalation). Empenho is cited throughout as the worked example, but every rule in normative text is portable: it is the rule, not the project that taught it. Hadur — a Python toolkit at hadur.ai — operationalizes this methodology into a CLI + engine + observability surface so future projects can apply it mechanically rather than re-deriving discipline by hand.
0. Quick reference — decision tree
When a Round of independent review returns findings, walk this tree. The leaf names the response; the body of this document specifies it.
Round N findings:
│
├── Loud-failing executable bugs (TypeError, KeyError, ModuleNotFoundError, ImportError)
│ ├── Narrow per-plan (Type 2) → §7.2 / §14 — per-plan patch wave
│ └── Systemic across plans → §12 — Contract-level signature lock + per-plan adoption
│
├── A check / sanitiser / analysis that enumerates the KNOWN-BAD (denylist, catalog, "is this dangerous?")
│ └── §6.5 — fail-open enumeration: invert the question (allowlist, fail-closed), probe every claim
│
├── A declared capability (CLI flag, config key, exported function, documented verb) with no operator entry point
│ └── §6.1.1 — reachability audit: fail CI on it; a review instruction is an enumeration in a human's head
│
├── A Critical has been fixed and the patch agent reports it closed, gate-green
│ └── §5.8 — independent adversarial verification: a fresh reviewer briefed to BREAK the fix
│
├── A test claims concurrency / locking / atomicity / race / isolation / enforcement
│ └── §5.7 — mutation-prove it: break the invariant, watch the test fail, restore
│
├── Self-Review / Why-paragraph claims contradicting body
│ ├── 1-2 sites → §17 — narrow patch + grep-verify
│ └── 3+ sites in one plan → §17 + §18 — full plan re-walk + grep-class enumeration
│
├── Same-KIND drift in 3+ consecutive deep-audit rounds (the same class of defect recurs)
│ ├── Executable surface canonical → §7.5 / §13 — declaration-by-reference (§44 pattern)
│ └── Executable surface drifting → §7.4 / §12 — Contract architectural pass
│
├── Same-PLACE findings in 3+ consecutive rounds (same file / module / mechanism, findings getting subtler)
│ └── §7.7 — tunnel signal: next round is a free sweep (L13), briefed to NOT review that place
│
├── Stale narrative pins / version drift (catalog C9 + C14, see §20)
│ ├── Single-patch lag → §10.3 — defer to next wave (acceptable narrative lag)
│ └── Multi-minor lag with sig change → §17 — narrow patch (Critical)
│
├── Operational / deployment concerns (Type 3)
│ └── §7.2 — new deliverable outside Contract (e.g. ops-readiness.md)
│
├── Brief-vs-plan model drift on adoption agents
│ └── §9.3 — agent must grep plan for actual model names before implementing
│
├── Round surfaces >10 new Criticals AND >30% systemic
│ └── §7.4 / §12 — divergence signal: pause per-plan waves, do Contract architectural pass
│
└── Round is CLEAN (0C/0M) and you are tempted to stop
├── Some lens in the registry never ran → §7.3 S7 + §7.3.1 — run it. The unrun lens is where the worst finding lives
├── The CODE changed since the streak began → §7.3 S1′ — the streak is VOID; re-run every lens on the current
│ code. Any change counts, wave or not; the trigger is the code, not the record of a wave (§7.3.4)
├── A lens's clean round has no pinned brief / no distinct reviewer / no code identity → §7.3.4 — it is a
│ CLAIM, not evidence: it counts for nothing. Fail closed
├── L13 (free sweep) has not run clean twice → §7.3.2 — the twelve named lenses are themselves an enumeration.
│ And a bare `CLEAN` from L13 is not a clean round: admissible only as `NO FINDING — examined: <surface>`
└── An architectural invariant is your reason to stop → §7.3 architectural-stop preconditions:
it must have SURVIVED an adversarial break attempt (§5.8) and be FAIL-CLOSED (§6.5)
When unsure which response, walk the tree; the correct response is named at the leaf.
Two leaves are easy to confuse, and the confusion is expensive. Same-KIND recurrence (§7.5 — "the same class of defect keeps appearing") escalates to a structural invariant: stop sweeping, enforce it. Same-PLACE recurrence (§7.7 — "the same file keeps appearing") does the opposite: stop drilling, go look somewhere else. Both present identically from inside the loop — as "we keep finding things, round after round" — and in the Jira-bridge cycle the second was mistaken for progress for four consecutive rounds (§7.7).
Part I — Foundations
1. When to use (and when not)
Use when any of the following hold:
- 3+ related implementation plans will be written and implemented by parallel agents.
- Plans share vocabulary: modules, events, types, security invariants, fixtures.
- Review must be rigorous (regulated domain, multi-tenant, money / auth / PII / fiscal).
- Implementation stretches across multiple sessions or weeks — context drift is inevitable.
Don't use for:
- Single-feature work in a single plan.
- Research prototypes where conventions will change daily.
- Small refactors where the entire change fits in one head.
The methodology trades up-front contract-writing cost (1-2 hours for Plan 0, several hours per implementation plan) for an order-of-magnitude reduction in late-cycle integration drift. On a single-plan feature this trade is negative. On a 5+ plan suite written by parallel agents over weeks, it is the difference between shipping and not.
1.1 Scaling Hadúr to project size (added 2026-04-28; SP5 ext §6)
The "don't use for single-plan / single-feature work" bullet above is correct at full tier. Hadúr also ships two reduced tiers — minimal and small — that lower the contract-writing cost so single-plan and 2-3-plan projects can adopt the discipline without paying full price.
Three scale tiers:
| Tier | When | Plan 0 size | Sections | Cross-plan signals |
|---|---|---|---|---|
minimal |
Single-plan project; one well-bounded feature; research prototype that wants [scale-minimal]-tagged drift catalog later |
~50 lines | §1 governance + §18 manifest stub | S5 (cross-plan imports closure) gracefully skips with N/A — single-plan project |
small |
2-3 plan project; medium refactor; cross-plan signature drift IS a risk but the suite is small | ~150 lines | §1 + §3 (drift catalog reference) + §18 + §44 (declaration-by-reference) | All applicable; cross-plan signals run starting at N=2 |
full |
9+-plan SaaS suite; the original Empenho-shape Contract | ~290+ lines | §1-§17 + §19-§44 (full surface) | All applicable |
Bootstrap a project at the chosen tier:
hadur init --size minimal --with-contract # single-plan project
hadur init --size small --with-contract # 2-3 plan project
hadur init --size full --with-contract # full Empenho-shape (default)
The CLI writes project_size: minimal | small | full to .hadur/config.json; the engine reads that field to decide which convergence signals are applicable.
Carve-out tokens. Per the §15 carve-out token system, sections that do not apply at smaller tiers may be tagged with one of:
[scale-minimal]— section is deferred atminimaltier; lift the tag and populate when promoting tosmallorfull.[scale-small]— section is deferred atsmalltier; lift the tag and populate when promoting tofull.
Forward compatible: a minimal Plan 0 grows into a small Plan 0 by adding §3 + §44 bodies and removing the [scale-minimal] tags. A small Plan 0 grows into a full Plan 0 by populating §2, §4-§17, §19-§43.
Promotion path. Hadúr does NOT auto-migrate Plan 0 between tiers. The user runs hadur init --size <new> --force in a separate workspace, copies the new template, and merges by hand — preserving the project-specific content under each §-section.
hadur convergence warns when N exceeds the template's intended scale. A project initialized as minimal that grows to N=3 module plans will see a warning prompting the user to upgrade the Contract template.
The lens registry does NOT scale down with the tier. Contract §-sections scale with the number of plans, because they describe cross-plan surface — that is what makes tiering sound. Lenses do not: a single-plan project still has a security boundary, a lock, a packaging manifest, an error hierarchy, and dead code. The pyyaml finding that crashed every clean install (L12) is entirely tier-independent; so is a credential reaching a wider audience (L6). S7 and the 13-lens registry (§7.3.1) therefore apply in full at minimal, small, and full alike. A tier reduces how much Contract you write; it never reduces the number of questions you ask of the code. Shrinking the registry because the project is small is the §6.5 failure mode applied to the methodology itself — and the smallest projects have the fewest reviewers, which is to say the fewest accidental second opinions.
2. The three artifacts
2.1 Plan 0 — Cross-Plan Contract
A markdown file (canonically docs/superpowers/plans/YYYY-MM-DD-plan-00-contract.md or plan-00-contract.md) containing the §-sections below. Every implementation plan in the suite cites Plan 0 at its header and on every non-obvious decision.
| § | Topic | Load-bearing example |
|---|---|---|
| §1 | Module / directory layout | backend/app/modules/X/{router,models,service,schemas,events,query}.py |
| §2 | Canonical imports + forbidden aliases + CI regex | from app.core.events import bus, queue_event — NOT _default_bus |
| §3 | Multi-tenant isolation (RLS) — if SaaS | Middleware writes request.state.org_id; get_db does SET LOCAL app.current_organization_id |
| §4 | Auth invariants — if SaaS | JWT audiences, magic-link TTLs, rate limits per endpoint |
| §5 | Event bus API + outbox | queue_event(session, topic, payload); events fire on after_commit, not inline |
| §5.2 | dedup_key format |
Stable anchors: {topic}:{entity_id}:{version} — NEVER timestamps |
| §6 | Lock / unlock pattern | Audit FIRST → GUC → status transition (to DRAFT, not intermediate) → event |
| §7 | Money & time types — if applicable | int cents, ROUND_HALF_UP, datetime.now(timezone.utc) — NEVER utcnow() |
| §8 | Cross-module boundaries | No cross-schema SQL joins; consume via query_* Python APIs |
| §9 | Query APIs | Every module other plans import from MUST have query.py with exact signatures |
| §10 | Test conventions | Canonical fixtures (bus_spy, db_session, fresh_session_for_org); behavioral tests mandatory |
| §11 | Commit format | feat(TICKET-XX): description (§spec-section) |
| §12 | Observability | Metrics, tags (organization_id), dashboards |
| §13 | ADRs + runbooks | Bus-factor: every non-obvious decision + runbook before first prod data |
| §14 | File-structure verification | Each file in plan's File Structure has a producing Task (grep enforceable) |
| §15 | Sub-agent dispatch discipline | Phases written by parallel agents: Cross-Phase Contract freezes signatures first |
| §16 | Phase-0 blockers | Decisions that MUST be made before execution starts |
| §18 | Cross-Plan Import Manifest | Each plan lists Exports + Imports as function signatures; CI guard asserts closure |
SaaS-only sections (§3, §4, §7) are explicitly marked N/A in non-SaaS projects rather than dropped — the §-numbering stays stable across projects so methodology citations remain unambiguous. A toolkit project (e.g. Hadur) marks §3/§4/§7 as N/A in its starter-kit mapping (§22).
Versioning. Contract is versioned (v1 → v2 → v2.1.0 → v2.1.1 → ...). Each revision is documented in a changelog at top of file with rationale. Plans cite **Contract revision:** Plan #0 vA.B.C in their own header.
2.2 Implementation plans (1..N)
One file per module (canonically YYYY-MM-DD-plan-NN-<module>.md). Opens with:
**Version:** vX.Y (date) — <what changed>
**Contract revision:** Plan #0 vA.B.C (<citation>)
**Depends on revisions:** Plan #1 v3.7, Plan #5 v3.4, ...
Every concrete decision cites Plan 0 section: per contract §3.5 per-org SET LOCAL loop. The plan ends with a Self-Review containing a Contract §-by-§ compliance table mapping each §-section of the Contract to the Task that implements it (or N/A for this plan with a reason).
2.3 §18 Cross-Plan Import Manifest (in each plan)
Two tables at the bottom of every plan.
Exports — what this plan produces for other plans to consume:
| Symbol | Signature | Owner Task | Consumers |
|---|---|---|---|
user_has_role |
async def user_has_role(session: AsyncSession, *, org_id: UUID, user_id: UUID, role: str) -> bool |
Task 5 | Plan 3, 7 |
Imports — what this plan consumes from others:
| Symbol | Import path | Provider plan + version | Signature |
|---|---|---|---|
query_active_organizations |
app.modules.identity.query |
Plan 1 v3.7 | async def query_active_organizations(session) -> list[Organization] |
CI closure guard. A grep- or AST-based check asserting that every Import row's (symbol, path) has a matching Exports row in the owner plan at the cited version. Plans pinned to stale upstream versions = manifest lies; the guard catches them.
3. The five roles
(Four through v1.1; §3.5 — the breaker — added at v1.2.)
3.1 Plan author
Writes or patches one plan. MUST:
- Open with header citing Contract revision + upstream-plan revisions being consumed.
- Cite
per contract §Xon every non-obvious decision. - End with Self-Review containing a fully-populated Contract §-by-§ compliance table.
- Maintain §18 Exports + Imports tables.
3.2 Independent reviewer
One reviewer per plan per round. The independence invariant is load-bearing:
- Reads ONLY Plan 0 + target plan (optionally nearby-adjacent plans for signature context).
- Does NOT see prior rounds' findings.
- Does NOT see other reviewers' findings from the same round.
- Finds issues blind. Re-discovery is signal: any overlap with prior findings means the finding is genuine.
Every reviewer is dispatched with exactly one lens (§7.3.1). A reviewer has two coordinates, not one: a target (which plan / which code) and a lens (which kind of question it asks — persistence, security, reachability, test quality, …). v1.1 tracked only the first, and the cost of that omission is the headline number of this revision: 10 of 37 Criticals+Majors were found by a lens on its first-ever run. The brief names the lens in a **Lens:** field (§16); the controller persists it on the round's findings; S7 (§7.3) reads it.
The lens does not narrow the reviewer's authority — a reviewer holding the persistence lens that trips over a security defect reports it. The lens says where to point the attention you are given, not what you are permitted to see.
Output format (contract):
### Finding N — <Severity> — <one-line summary>
- Location: <file>:<line>
- Evidence: <quoted code/text + 1-2 sentences>
- Proposed fix: <1-2 sentences>
**Verdict: CLEAN | FINDINGS: X critical, Y major, Z minor.**
Severity scale:
- Critical — runtime fault, security defect, or contract-breaking drift. Blocks commit.
- Major — correctness gap (missing FOR UPDATE, missing try/except on boundary, §18 drift, concurrency race). Blocks freeze.
- Minor — doc inconsistency, stale version label, unused but not harmful.
- Nit — style, cosmetic.
3.3 Patch agent
Applies ONE finding's fix to ONE plan file. MUST:
- Edit the body, not just the changelog header.
- Bump the version trinity (title L1,
**Plan revision:**,**Version:**) in lock-step. - Re-run the §18 manifest if signatures changed.
- Output a concise report describing what was edited, with file path and line ranges.
A patch agent's report is a claim, not a fact (§8.2). The verifier verifies.
3.4 Verifier (grep-pass + walkthrough-pass)
The verifier is a first-class role with two passes (§5). The grep-pass catches editing mistakes; the walkthrough-pass catches behavioral regressions. Both are mandatory before commit for runtime-relevant patches; the grep-pass alone suffices for label sweeps and pure-narrative edits.
The verifier MUST run as a fresh sub-agent (no conversation context). It MUST receive the patched file path + line ranges + the failure mode being closed + the explicit greps to run. It returns one of READY TO COMMIT / PATCH INCOMPLETE — DO NOT COMMIT / BLOCK-COMMIT — runtime-semantics concern: <details>.
Two things the verifier structurally cannot verify, both added at v1.2, both dispatched as separate agents rather than folded into this role:
- The test. Both passes read the diff and reason about it — and a decorative test reads correctly. Only mutation catches it (§5.7).
- A Critical fix. Both passes run inside the wave's own context, reasoning about the diff the wave produced. Breaking a fix requires someone who did not write it and cannot see its reasoning (§5.8). In the Jira-bridge cycle, three independent verifications of a Critical fix found three real bypasses in the fix being verified — each of which had already passed this verifier-gate.
3.5 Breaker (adversarial verifier of a Critical)
A fifth role, added at v1.2 because the four above could not do its job. The breaker is dispatched when a Critical fix is reported closed, or when an architectural invariant (§13) lands or is amended (§5.5). It MUST:
- Run as a fresh sub-agent. It did not write the fix and cannot see the patch agent's reasoning or its report — that report is the account most likely to contain the blind spot, and reading it transplants the blind spot.
- Be told, in its brief, the fact that motivates it: the previous three verifications each found a real bypass; assume this one has one until you have tried hard to find it (§5.8).
- Produce a concrete construction, not an opinion: "write the code that gets past this guard."
- Return
BYPASS FOUND: <the construction>orNO BYPASS FOUND — attempted: <enumeration of what was tried>. A verdict of "looks sound" is inadmissible and the gate is not satisfied by it. The value of a negative verdict lies entirely in the enumeration of failed attacks.
The breaker is not a second verifier and does not replace one. The verifier asks "did the fix land?"; the breaker asks "does the fix hold?" — and in the Jira-bridge cycle the answer to the first was yes, three times in a row, while the answer to the second was no, three times in a row.
4. The Round ↔ Wave loop
4.1 Round (diagnostic)
Parallel dispatch of N reviewers (one per plan + Plan 0 itself). Background execution. Aggregate findings as each returns.
Author/coordinator: "Round K"
→ dispatches N reviewers (backgrounded)
→ reviewers return asynchronously
→ coordinator triages finding list → proposes Wave K scope
4.2 Wave (corrective)
Named Wave K, follows Round K. Executes in this strict order:
- Patches first, in dependency order. Lowest-dependency plans first so downstream patches reference stable signatures.
- Rewrites second, in dependency order. Rewrite agents read already-patched dependencies and copy signatures.
- Contract patches interleaved when findings reveal Contract §-level gaps (regex blind spots, new §-compliance hooks).
- Verifier-gate before each commit (both passes — §5).
Rewrite-first → patches-later is wrong: rewrite agents would guess future signatures, producing more drift (§8.4).
A wave voids every lens's clean streak (§7.3 S1′). This is the single most expensive consequence of v1.2 and it is not negotiable: a clean verdict is a statement about the code that existed when it was made. If the persistence lens came back clean twice and the security lens then finds a Major that produces this wave, those two clean verdicts describe code that no longer exists. The streak resets to zero for every lens, not just the one that found the defect. Plan waves accordingly: the last wave of a cycle is always followed by a full re-run of the registry (§7.3.3).
Read that as a consequence, not as the rule (v1.3). The rule is stated on the code, not on the wave: two rounds count toward one streak only if they reviewed the same tree, and that tree is still on disk (§7.3.4). A wave voids the streaks because it changes the tree — and so does an unrecorded hotfix, a merge, or an edit made while the reviewers were still running. There is nothing to declare and therefore nothing an operator can forget to declare.
4.3 Commit discipline per Wave
- One commit per plan-patch (or per Contract version bump).
- Message:
docs(TICKET-XX): patch plan-NN <module> vX.Y (Wave K Round-M — <finding summary>). - Body explains WHY (failure mode + how the wave fixes it).
- Never
--amendcommitted waves. Always create a new commit. - Never skip hooks (
--no-verify).
Part II — Quality gates
5. The verifier-gate — two mandatory passes, two conditional gates
The verifier-gate is the single most load-bearing quality control in the methodology. It exists because patch agents systematically produce edits that look correct in narrative but fail under grep or runtime. Without the gate, polished-but-broken patches enter main and surface only at the next deep audit (or, worse, in production).
The two passes are the floor, not the ceiling. Both run inside the wave's own context, reading the diff the wave produced and reasoning about it. That vantage point has two blind spots, and v1.2 adds one conditional gate for each:
- §5.7 — mutation. Both passes reason about the diff; a decorative test reads correctly. Only breaking the invariant and watching the test fail proves the test guards anything.
- §5.8 — independent adversarial verification of a Critical. Both passes reason about the fix as explained by the wave that wrote it. Breaking a fix requires an agent that did not write it, cannot see its reasoning, and is told to construct a bypass.
Neither is zeal. Both are here because the gate as specified at v1.1 passed, gate-green and with tests, a credential redaction whose safety oracle was the very catalog the threat was drawn from (§6.5).
5.1 Pass 1 — Grep verifier (fast, mandatory)
After every patch, a fresh agent re-reads the plan file post-edit and runs sanity greps specified by the patch brief:
- Landmark grep — does the narrative contain the promised symbols?
- Version bump grep — is the header bumped?
- Self-review grep — are counter-factual tests named?
- Commit blockers —
[RESERVED]/[TODO]/[PLACEHOLDER]orphans? - Class-specific greps — driven by the failure mode being closed (e.g. for §18 drift: every Imports row's signature matches the Exports row in the upstream plan).
Output: READY TO COMMIT or PATCH INCOMPLETE — DO NOT COMMIT with specific failed greps quoted.
The grep verifier catches editing mistakes: agent forgot to edit the body, narrative changed but executable did not, version bump did not land, only 2 of 3 call sites renamed, polished Why-vN paragraph without body edit. It cannot catch behavioral regressions where the fix compiles, matches its intended greps, but introduces or preserves a runtime bug.
5.2 Pass 2 — Runtime-semantics walkthrough (mandatory for large dispatches)
A second fresh agent dispatches with the patched block + context on the regression + the 11-item checklist (§5.3). It returns PASS / SUSPICIOUS / FAIL per item, with one-sentence justification.
Pass 2 is MANDATORY when any of:
- The dispatch produced > 500 lines of plan-document narrative.
- The dispatch introduces a new migration body (Alembic, Django, raw SQL).
- The dispatch introduces a new subscriber / event handler.
- The patch touches runtime behavior: services, handlers, state machines, concurrency primitives, security checks, cross-plan imports.
Pass 2 is OPTIONAL for pure-narrative edits, label sweeps, version-header bumps, and doc-end-marker fixes. Judgment: if unsure, walkthrough costs ~150k tokens; bug-in-production costs days.
5.3 The 11-item checklist
The walkthrough agent applies this checklist verbatim. For each item it outputs PASS / SUSPICIOUS / FAIL with a one-sentence justification.
- Dependency existence. Every symbol the patch references — functions, enum values, endpoints, columns, config keys — exists at the claimed upstream path. Especially important for cross-plan imports: grep the UPSTREAM plan, not just the current one.
- Error-path behavior. What does each
except/if error/return None/passswallow? Could the swallow mask the exact class of bug the fix is meant to prevent, or a different latent bug? - Inner-failure same-class risk. If the patched code path calls another function that could fail with the same symptom (I/O inside a catch-all that itself was catching I/O failures), is THAT call also protected? Otherwise the fix has the same regression recursion.
- Concurrent callers. Two HTTP retries, two browser tabs, two arq workers, two admins — does DB isolation / unique constraints / SELECT FOR UPDATE / dedup-key hold?
- Redelivery / retry. HTTP retry, arq redelivery, outbox drain retry, mobile PWA flaky network — is state idempotent or does it diverge?
- Partial commit / rollback. Transaction commits partway; external effects (S3 put, Redis write) already happened — is there a reconciliation path?
- Empty-state / first-time. Row missing, GUC unset, counter zero, cache empty — does the fix still hold?
- External-dep failure. Redis / S3 / LLM provider / DB / external API flaky-or-down — fail-closed (safe) or fail-open (unsafe)?
- Cross-session / cross-tenant / cross-tab. Shared browser profile, shared DB conn, multiple admins, multiple tabs on same user — does isolation hold?
- TZ edges. For date / time code, UTC midnight boundaries in extreme TZs (Azores UTC-1, Lisbon WET / WEST). A cron firing at 00:05 UTC on Jan 1 sees Dec 31 in Azores.
- Test truth. Does the regression test exercise the REAL code path or a re-implemented harness? Run the counter-factual: if you revert the fix, does the test fail loudly? If not, the test is decorative; redo it.
5.4 The counter-factual question
For each patch, the walkthrough agent (and the patch agent's own self-check) asks:
If I reverted this change, would any existing test fail?
- If yes for a specific named test with a specific assertion → the fix is load-bearing AND the test is load-bearing.
- If no → either the fix is a no-op, or the test is not actually exercising the fix. Both demand investigation before commit.
This single question is the sharpest tool in the gate. It catches narrative-only "fixes" (the patch added a docstring or comment but no executable behavior), tests with ... ellipsis bodies, tests that exercise a different contract than self-review claims, and tests that pass because the assertion is trivially true.
It is asked of the fix. §5.7 asks it of the test. The question above is reasoned about — the walkthrough agent reads the diff and predicts whether a revert would break something. That prediction is reliable for a fix and unreliable for a test, because a decorative test reads exactly like a load-bearing one. Where the invariant is load-bearing, do not predict the answer: run the mutation (§5.7). Two tests in the Jira-bridge cycle survived this question as a thought experiment and died instantly under an actual mutation.
5.5 When each pass is required
| Dispatch class | Pass 1 (grep) | Pass 2 (walkthrough) | Conditional gate |
|---|---|---|---|
| Label sweep / version-header bump / pure-narrative edit | Required | Optional | — |
| Per-plan patch < 500 lines, no new migration/subscriber | Required | Recommended | — |
| Per-plan patch ≥ 500 lines | Required | MANDATORY | — |
| Any patch introducing a new migration body | Required | MANDATORY | — |
| Any patch introducing a new subscriber / event handler | Required | MANDATORY | — |
| Any patch touching runtime behavior (state machine, concurrency, security check, cross-plan import) | Required | MANDATORY | — |
| Any test claiming concurrency / locking / atomicity / race / isolation / enforcement | Required | MANDATORY | §5.7 mutation-proof |
| Any test written by the patch agent to prove its own fix | Required | per rows above | §5.7 mutation-proof |
| Any claim in an inverted guard's "proven safe" registry (§6.5) | Required | MANDATORY | §5.7 mutation-proof (executable probe) |
| Any fix to a Critical | Required | MANDATORY | §5.8 independent breaker |
| Landing or amending an architectural invariant (§13) | Required | MANDATORY | §5.8 breaker + §6.5 fail-closed check |
Both passes are a hard gate; either FAIL or BLOCK-COMMIT prevents the commit. The conditional gates are hard gates too, on the rows that mandate them: a Critical whose fix has not survived §5.8 is not closed, whatever the patch agent's report says (§8.2, §10.1).
If running via the Hadúr plugin (added Plan 6): the
hadur-controllerpersona (plugin/agents/hadur-controller.md) orchestrates Pass 1 + Pass 2 dispatch automatically per the §5.5 table. Each persona is dispatched as a fresh subagent —hadur-verifierfor Pass 1,hadur-walkthroughfor Pass 2 when mandated. The controller has Task tool access; leaf personas do not (controller dispatches, leaves do not re-dispatch).
5.6 Always re-walk after fixes
When Pass 2 surfaces findings and a narrow follow-up patch closes them, always dispatch a second walkthrough on the post-fix state before committing. Do not rely solely on the patch agent's self-report that the residuals are closed.
Patch agents can polish their report-back without fully landing the edits, or introduce new drift while closing old drift. The second walkthrough is cheap; a partial fix that ships is expensive.
Specifically:
- After first walkthrough returns findings + patch agent closes them → dispatch a second walkthrough focused on (a) each original finding and (b) anything adjacent that might have been touched.
- If the fix touched line numbers, re-verify any narrative or self-review claims about line positions.
- If the fix introduced new code (tests, decorators, imports), re-walk includes THOSE too — not just the original scope.
- If the post-fix walkthrough surfaces more findings, loop until zero-findings OR explicitly classify remaining items as accepted-risk with the user.
The discipline applies to every narrow follow-up patch, regardless of how small. No exceptions because "this one is obvious."
5.7 Mutation is the verifier of the verifier
§5.4's counter-factual question — "if I reverted this change, would an existing test fail loudly?" — is asked of the fix. Until v1.2 it was never asked of the test. That gap shipped two tests in the Jira-bridge cycle that passed while testing nothing:
- a concurrency test whose two threads wrote to different files — no shared resource, no contention, and deleting the very lock it claimed to exercise broke nothing;
- a starvation test that mocked out the function under test, so the lock acquisitions inside it were never executed.
Neither the grep pass (§5.1) nor the walkthrough pass (§5.2) can catch this, and the reason is structural, not a lapse: both passes read the diff and reason about it, and a decorative test reads correctly. Its name is apt, its assertions are plausible, its docstring describes the invariant faithfully. It is simply not connected to the thing it names. Only mutation exposes it.
The rule.
A test that guards a load-bearing invariant must be mutation-proven at least once: deliberately break the invariant, run the test, watch it fail, restore. If it does not fail, it does not guard anything — whatever its name, its docstring, or its author says.
Mandatory for (see the §5.5 table):
- Any test whose name or docstring claims concurrency, locking, atomicity, race, isolation, or enforcement. These are exactly the properties whose absence is invisible in a passing run.
- Any test written by a patch agent to prove its own fix. That agent has every incentive for the test to pass, and none for it to be sharp. This is §8.2 (self-review assertions are claims, not facts) applied to the one artifact the wave offers as its proof.
- Every claim in an inverted guard's "proven safe" registry (§6.5). A label is not a proof; the probe is the proof.
Beware the mock. A test that patches out the function under test exercises the mock. When the fix lives inside a function, the test must drive the real function and fake only the boundary beyond it. The starvation test above is the canonical instance: it mocked precisely the function whose locking it claimed to prove, and passed for that reason.
Record the mutation. A mutation performed and then forgotten is not a gate — it is a memory. Pin the mutation alongside the test (a comment naming what was broken and what failed, or a mutation case in CI), so the next reviewer inherits the proof rather than re-deriving the doubt.
5.7.1 Mutate in isolation — never in the shared tree
A mutation is a deliberate, temporary edit to the source; §7.3.4 anchors a round's clean verdict to the identity of the code on disk. These two rules collide the instant a reviewer breaks an invariant in the very tree the round is being certified against. For the whole window between break and restore the tree is dirty, and while it is dirty:
- any concurrent reviewer's
hadur round closefails — a dirty tree voids eligibility (§7.3.4). The mutation voids the round it is part of, even when it is faithfully restored a second later. - any repo-copy taken for a parallel review captures the mutant — a disabled lock, a broken guard, a nulled check — and can file a false Critical against a defect the reviewer introduced and is about to undo.
Both happened live in Round 28. An L8 test-quality reviewer, doing exactly what §5.7 prescribes, mutated hadur_core/code_identity.py in the shared tree; hadur round close 28 went from exit 0 to exit 3. A concurrent L12 reviewer copied the tree mid-mutation, captured the disabled FileLock, and nearly filed a data-loss Critical (20 concurrent submitters → 1 finding persisted) against a lock test that was in fact correctly catching the mutation. Nothing in §5.7, in the L8 brief, or anywhere else said a word about it.
The rule.
A mutation runs in an isolated worktree or scratch copy — never the shared tree the round is certified against.
hadur worktree createexists for exactly this: break the invariant there, run the test, watch it fail, throw the worktree away. The shared tree never goes dirty, so the window in which a concurrentround closefails or a repo-copy captures the mutant does not exist — isolation removes it entirely, rather than shrinking it and hoping no reviewer reads at the wrong moment.
Why this is in the document and not left to the reviewer to rediscover. A rule whose correct execution breaks another rule is a latent process defect, not a reviewer error. §5.7 obeyed faithfully voided a live round, and a rule that punishes obedience is a rule that will be quietly disobeyed. The methodology must resolve the §5.7-vs-§7.3.4 conflict explicitly, here, rather than leave every reviewer to rediscover it under a moving tree. §7.3.4's "a dirty tree voids eligibility" is the same fact seen from the other side; Round 28 is the evidence that the two sides had never been introduced.
5.8 Independent adversarial verification of a Critical
Empirical result from the Jira-bridge cycle: three independent verifications of a security fix, three real bypasses found in the fix being verified.
| Verification of | What the breaker found | Severity |
|---|---|---|
| The redaction fix (Wave 26) | The guard was a denylist built from the same catalog the threat was drawn from | Critical |
| The redaction fix (Wave 27) | AxeMatch.target — the sibling field from the same DOM node, unredacted |
Critical |
| The bridge invariant (Round 23) | The closure was intra-module; a delegate in another module walked past it | Major |
Every one of these was reported as complete, gate-green, with tests, by a competent patch agent that had genuinely done good work. The §5 verifier-gate did not catch them and structurally cannot: both passes run inside the wave's own context, reasoning about the diff the wave produced and the account the wave gives of it. A fix's own explanation is the worst possible instrument for finding the case its author did not think of.
The rule.
A Critical fix does not close on the verifier-gate. It requires a separate reviewer whose brief is to break it — one that did not write it, cannot see the patch agent's reasoning, and is told explicitly: the previous three verifications each found a real bypass; assume this one has one until you have tried hard to find it.
The brief must demand concrete construction, not opinion. "Write the code that gets past this guard." A reviewer who returns "looks sound" has not verified anything and its verdict is not admissible — the gate is not satisfied by an absence of suspicion, only by a genuine and failed attempt at a bypass. Verdicts are BYPASS FOUND: <the construction> or NO BYPASS FOUND — attempted: <enumerate what you tried>. The second form is only worth reading because of its second half.
This is not zeal, and the counter-factual is concrete. Without this gate the Jira-bridge cycle would have shipped a credential leak into a ticket system with a wider audience than the repository and no purge path — a fix that was, at the moment it was declared closed, green on every gate v1.1 specified.
The fourth datapoint is this document (added v1.3). The v1.2 amendments — which added the rule you are reading — were themselves an architectural invariant: they rewrote the loop's stopping rule. Under §5.5's last row, that mandates a breaker. It was dispatched at them, and it returned four Criticals, of which two were exploitable by omission (write nothing down and the convergence signals pass) and one was demonstrated live: two round files containing no findings and a list of thirteen lens names returned CONVERGED (Appendix A.4). The score of every adversarial verification ever run under this methodology is now: four dispatches, four sets of real defects, zero clean bills of health. Nothing in the record yet supports the belief that a competent fix is sound because a competent author says it is — including, evidently, when the author is this document.
It applies with the most force to the artifact that feels least in need of it: an architectural invariant (§13) is code, it has bugs, and it is the single most dangerous code in the repository, because everything downstream stops looking once it lands (§7.3, architectural-stop preconditions).
6. The five failure modes
The methodology targets five recurring failure classes (four through v1.1; §6.5 added at v1.2). Reviewer briefs probe for them; the verifier-gate closes them; convergence requires they not recur.
6.1 Components without connections
Infrastructure declared but never wired end-to-end.
Examples. Event handlers enqueued but never register_handler'd. RLS declared but TENANT_TABLES registry empty. Exception handler defined but not registered in create_app. Outbox drain function defined but never called. Middleware class declared but never inserted into the middleware stack. Counter-factual: if you reverted the declaration, nothing breaks — because nothing was actually wired.
Detection. §18 closure guard + "every declared-as-wired API has a producer site" grep + the walkthrough's item-1 (dependency existence) + item-11 (test truth — if the test passes whether or not the wiring exists, the wiring is decorative). And, since v1.2, the mechanical audit of §6.1.1 — because the above was already in the document and this mode hit the Jira-bridge cycle eight times anyway.
6.1.1 Reachability audit (mechanical)
§6.1 has been in this document since Round 1 of Empenho. It nevertheless produced 8 of the 37 Criticals+Majors in the Jira-bridge cycle — including a Major-severity function (pull_finding_statuses) that had never been called since the plan that introduced it, and which an earlier wave of that very loop had hardened with a lock, schema validation and atomic writes. We hardened dead code, carefully, and the review that hardened it was correct on every point it examined.
The lesson is the one §6.5 states in general: naming a failure mode does not detect it. §6.1 tells the reviewer what to look for and then leaves them to look — which makes the guarantee exactly as good as one human's enumeration, on one round, of a surface that grows every wave.
The mandate.
Every declared capability must be traced to an operator entry point, or be explicitly marked deferred — in code, with a ticket. Declared capability means: an exported function, a config key, a CLI flag, a documented command, a hook predicate's verb — anything the project asserts it can do.
Ship it as a test, not as a review instruction. An AST walk enumerates the declared surface and the entry points, computes reachability, and fails CI on anything in neither set. A review instruction is an enumeration in a human's head (§6.5); a test is an enumeration the machine re-runs every wave, on a surface that has changed since the human last looked.
Corollary, learned the hard way: a capability's documentation is part of its surface. A runbook that tells the operator to run --retry-failed to drain a backlog, when the flag prints a count and returns, is the same defect wearing prose — the capability is declared (to the only audience that matters, the operator), and it does not reach anything. The doc-consistency test belongs in CI beside the reachability test. This is also why L7 (operator experience) and L10 (reachability) are separate lenses in §7.3.1: the first asks whether the tool tells the operator the truth, the second whether the thing it names exists.
6.2 Signature drift across split agents
Phase / plan A defines Result(invocation_id, output); phase / plan B calls .id; phase C passes a kwarg A does not accept.
Examples. user_has_role(organization_id=...) vs canonical org_id=. write_audit_event row in §18 with fabricated kwargs (reason=, metadata=) that do not match Plan 1's real signature. A handler signature in Plan 5 §18 of (session, event, queue_event) drifting against Plan 1 §18 declaration of (session, event).
Detection. §18 Imports table pins signatures explicitly; CI signature-parity check; the walkthrough's item-1 (dependency existence) on the upstream plan's actual export.
6.3 Behavioral requirements skipped for structural
Plans accurately model data but skip state-machines, concurrency, idempotency, half-day math, cross-year math, etc.
Examples. Status column but no SELECT FOR UPDATE. Payroll finalize mutates status but never emits payroll.finalized event (producer gap). Unlock flow: COUNT then UPDATE without locking rows → TOCTOU. State machine declared but no test for illegal transitions.
Detection. Behavioral tests mandatory in §10 of the Contract. Every transition needs a state-machine test + a concurrency test + an idempotency test. The walkthrough's items 4-6 (concurrent callers, redelivery, partial commit) probe specifically for this class.
6.4 Security enforcement declared but not enforced
CI regex with || true (always passes). Role fields stored but never checked. Rate limits defined but never called. RLS policies PERMISSIVE (OR'd) instead of RESTRICTIVE (AND'd) → lock bypass. Auth endpoints in RATE_LIMIT_EXEMPT list without a per-token counter.
Detection. Penetration-test style review at round N+1 — a reviewer whose brief explicitly asks "given an attacker with X, can they bypass Y?" This is the L6 lens (§7.3.1), and S7 now requires it to be run at least once per cycle rather than left to the coordinator's instinct: in the Jira-bridge cycle the security lens was first applied in round 26 of a 27-round cycle, and it produced the cycle's first Critical immediately. The walkthrough's items 8-9 (external-dep failure, cross-session isolation) probe for fail-open paths; §6.5 gives the shape those paths take.
6.5 Fail-open enumeration
Seven Criticals+Majors of the Jira-bridge cycle — including two of its three Criticals — share one shape, and that shape appeared in neither §6.1-§6.4 nor the §20 drift catalog:
A guard defined as a denylist of the known-bad, so anything it does not recognise passes. It runs. It passes. It is structurally incapable of seeing the thing.
Every instance, in order:
| # | The guard | What walked past it |
|---|---|---|
| R16-P02-M2 | ADF parser caught ADFParseError |
TypeError / AttributeError from a malformed node |
| R23-P01-M1 | Bridge invariant, intra-module closure | A delegate in another module |
| R24-P01-M1 | Bridge invariant, proved reachability | An ordinary bound method (self.push()) |
| R25-P01-M1 | The indexer | async def — dropped before the fail-closed rule could fire |
| R25-P01-M2 | "Single HTTP boundary", a 4-name denylist | urllib3 — already installed |
| R26-P02-M1 | Comment authorship, substring on a public marker | Anyone who can type the marker |
| R27-P02-C1 | Secret redaction, guarded by the credential catalog | Any credential shape not in the catalog |
R27-P02-C1 is the purest case and is worth stating plainly: the redaction's safety oracle was the very catalog the threat was drawn from. That is circular. Its false answer is a silent leak, and there is no run in which it announces itself.
Detection heuristic (cheap, mechanical). Two questions, in order:
- If this guard encounters a construct it does not understand, what happens? If the answer is "it passes", the guard is fail-open.
- The sharper one: is the guard's safety oracle drawn from the same catalog as the threat? If yes, it is circular and provides no guarantee at all — it can only ever confirm what it already knew.
Prescribed fix — invert the question. Do not disprove the unsafe property; prove the safe one.
- Not "is this field dangerous?" (false answer = leak) but "has this field been proven safe?" (false answer = red build).
- Not "can I show this call reaches the wire?" but "can I show it cannot?"
- An allowlist. A fail-closed analysis. A construct the guard does not understand must be rejected, not waved through.
A label is not a proof. If the inverted guard is a registry of "proven safe" claims, each claim must ship an executable probe — and the probe's poison must include something the layer beneath it provably cannot catch, so that a claim cannot be certified by leaning on that layer. (Concretely: the credential net cannot match an email address, so a field claimed structurally-reduced had to kill an email to earn its entry.) This is the part a human reviewer reliably misses — reviewers check that the docstring says the right thing, not that the code does it. Each probe is mutation-mandatory under §5.7.
Where this mode is most dangerous. In the guard, the invariant, the CI gate — the enforcement mechanisms themselves (L9 in §7.3.1, which found four distinct defects in a single architectural invariant). A fail-open invariant is not structural enforcement; it is a bigger denylist with a more confident docstring, and it fails silently in exactly the direction nobody is looking, because everything downstream has stopped looking (§7.3, architectural-stop preconditions).
The methodology is not exempt from this mode, and has now committed it twice.
- A convergence rule satisfied by a fixed list of named lenses is itself a fail-open enumeration. That is why the lens registry closes with L13 (§7.3.2) — and why L13 may not be removed.
- A convergence rule that asks the loop whether the loop is done is the same bug in a second place. v1.2's S1′ decided "did the code change?" by reading finding transitions the loop writes, and S7 decided "was this lens run?" by reading a lens label the loop writes. Both fail open on omission: write nothing, and nothing contradicts you. Both were satisfied, in a live test, by two files containing no findings and a list of lens names. A guard whose input is supplied by the thing it guards has no safety oracle at all — it is the circular case of the second detection question above, and it is worth noticing that the methodology walked into it while writing the section that forbids it. The fix is the same fix: invert. Do not ask the loop to prove the code is unchanged; anchor the verdict to the code (§7.3.4).
The general lesson, applied to any tool that reports on a process: the evidence must come from outside the process. Bookkeeping is a description of what happened, and a description can be silent. The code cannot.
6.5.1 The surface is unbounded; a true choke point is a language construction — and some invariants have none
Inverting the question (§6.5) is necessary but not sufficient, and Round 28 of the Hadúr self-review proved it across thirteen consecutive bypasses. Three Criticals were re-fixed by inverting each guard into a fail-closed AST test that walked the whole call surface and rejected anything unproven. All three inversions were bypassed a second time, each by an executed construction:
| Guard (inverted, fail-closed) | The construction that walked past it |
|---|---|
Every FileLock site must carry timeout= |
timeout=-1 — filelock's block-forever sentinel — satisfies "a timeout= token is present"; the check never read the value. in_helper matched the bare name hold_lock in any module. |
| Every writer scrubs before persisting | The scrub sat in save_round_findings, one schema; waves.py's notes and annotations.py's body_md persist through other schemas, unseen. |
| No caller reaches the wire while disabled | A decorator returning functools.partial(dispatcher, fn) rebinds what the caller invokes; the static graph roots at the literal def. |
The shape is one sentence: a guard that enforces a property by enumerating the
surface it must hold over is a fail-open enumeration, because the surface is
unbounded and the guard's model of it is a denylist of the constructs it was
written to recognise. Inverting the question does not escape this if the
domain of the proof is still "every site, every caller, every schema" — that
domain grows every wave. The timeout=-1 bypass is the purest form: a
fail-closed guard whose safety oracle was nonetheless a syntactic token,
defeated by a value carrying the token and meaning its opposite.
The escape is to relocate the invariant to the choke point — the single primitive every path is forced through by construction — where there is no surface left to enumerate. But "choke point" is not a free pass, because most things that look like one are surface bets in disguise. The three "choke-point" re-fixes each fell a third time, and the reason is diagnostic:
- Lock: "restrict the
filelockimport to two files" — but a third module didfrom hadur_core._persistence import FileLock; the name had leaked past the import. The allowlist was still a surface walk (of import spellings). - Redaction: "scrub in the one write primitive" — but
write_jsonwas not the only writer;hadur handoff writepersisted viaPath.write_text. - Bridge: "gate at the wire" — but the gate saw only the env operand of a two-operand disable condition; a config-disabled repo still fired.
A TRUE choke point is a language-level construction with no residual enumeration — an un-nameable symbol, a required parameter, a type — enforced by the interpreter or the type checker, not by a guard that walks outward. The test is a single question that subsumes the two in §6.5: does the guarantee depend on the guard having enumerated every place the property must hold? If yes, the next construction lives in the place it did not enumerate, and there is always a next place. The Round 28 terminus, with an adversarial Breaker (§5.8) run against each final fix, sorted the three invariants into two categories that every future fix must locate itself within:
-
Closure exists and holds. The bridge wire was made to require the resolved config as a no-default parameter; a caller physically cannot invoke it without threading the state, and
mypy --strictrefuses the omission. A Breaker attacked it along eight vectors — default args, fabricated-enabled configs, second HTTP clients, type-confusion — and found no bypass. Some invariants can be closed absolutely, and the mark of one that is closed is that the enforcement is the language's, not the guard's. -
No closure exists, because the language provides an unblockable escape hatch. Locks and redaction have none:
importlib.import_module("filelock")and arbitraryopen(path, "w")cannot be forbidden in Python by any construction. For these, the honest terminus is threefold and must be stated in that order: (a) close the accidental path by construction (the re-export made un-nameable; the common writers routed through the scrub) — this kills the bypass that actually happens; (b) guard the deliberate path with a maintained denylist that says in its own docstring it is a denylist, will go one-short, and is the accepted residual for an invariant with no language closure — never "by construction"; (c) name the residual as a residual, because the distinction between the accidental bypass (an ordinary refactor writes it without intent) and the deliberate one (an author typesimportlib.import_moduleon purpose, a diff a human signs) is the guarantee, and overstating it is itself the §6.5 error — a claim the code does not keep.
The caution that keeps this from becoming its own dogma. Not every invariant has a choke point, and a fail-closed surface-walk is the correct and only tool when none exists — reachability (L9) genuinely must walk callers. The claim is narrower: when a choke point does exist and the fix guards the surface instead, the fix is one bypass from failing; and when no choke point exists, the fix must say so rather than dress a denylist in a by-construction docstring. The corollary for the Breaker: when a fix is a static walk of callers, sites, or schemas, the first move is not to test its coverage of known constructs — it is to ask what single point does every path pass through, and is the invariant enforced there by the language, reconstructed by walking outward from it, or absent because the language cannot enforce it? Three consecutive re-fixes in one cycle earned that presumption.
7. Convergence, divergence, and asymptotic drift
7.1 Four trajectories
A multi-round review cycle exhibits one of four trajectories (the fourth added at v1.2):
- Converging. Critical + Major counts approach zero monotonically across rounds. Continue the loop until §7.3 stopping rules trip.
- Diverging. Each rotated-brief round surfaces MORE Criticals than the prior, AND a substantial fraction is systemic (Contract-scope addressable). Halt per-plan waves; do Contract architectural pass (§7.4 / §12).
- Asymptotic narrative drift. Executable surface is canonical; counts drop but never reach zero because narrative replication across N+ sync points keeps producing same-class drift each deep audit. Escalate to declaration-by-reference invariant (§7.5 / §13).
- Tunnelling. Counts fall and findings grow more sophisticated — while localising, round after round, to the same file, module or mechanism. This reads exactly like converging from inside the loop, and is not: it is depth being mistaken for coverage while ordinary defects sit untouched a few files away. Break out with a free sweep (§7.7).
The last two are the pair that gets confused. Asymptotic drift is "the same KIND keeps appearing" → escalate to a structural invariant. Tunnelling is "the same PLACE keeps appearing" → go look somewhere else. Opposite treatments; identical felt experience.
7.2 Type 1 / 2 / 3 finding classification
For each finding the coordinator (or a triage reviewer) classifies it into one of three types; aggregates at round-end determine the response:
- Type 1 — Contract-scope. The Contract should mandate / fix this. Aggregates signal Contract rework. Examples: i18n policy, retention category enum, event durability architecture, secret-rotation dual-key discipline,
correlation_idthreading, idempotency-key middleware, migration reversibility mandate, retention-column shape. - Type 2 — implementation bug. Plan declared one thing, coded another. Aggregates signal per-plan patch waves. Examples: phantom imports, missing migration bodies, skeletal
...test bodies, enum reference drift, FK schema mismatches. - Type 3 — operational / deployment. Out-of-Contract-scope. Examples: SLO numbers, Redis topology, load / stress thresholds. Aggregates signal a new deliverable outside the Contract (e.g.,
docs/ops-readiness.md).
Response by dominance (>40% of total Critical+Major):
| Dominant type | Response |
|---|---|
| Type 1 | Contract-level rework (architectural pass — §12) |
| Type 2 | Per-plan patch wave |
| Type 3 | New deliverable outside Contract |
| Mixed (no >40% dominant) | Multi-phase response: Contract pass first if Type 1 share > 30%, then per-plan patches, then Type 3 deliverable |
7.3 Convergence signals
Stop the round-wave loop when all of these are true:
- S1′ — Two consecutive 0C/0M rounds in EACH lens, across ALL lenses, on the current code. ANY change to the code voids EVERY lens's streak. (Amended at v1.2; its evidence rule fixed at v1.3 — "on the current code" is settled by the code itself, never by the loop's record of whether a wave ran. §7.3.4.) The v1.1 signal — "two consecutive rounds with zero Criticals and zero Majors" — is lens-blind, and that is the most dangerous gap this revision closes. See below.
- S2 — Every §-section of the Contract has been explicitly re-verified. Reviewer briefs cover every § across the cycle, not just the §s that recurred in past rounds. Operationally: a §-section × round coverage matrix shows no blank rows.
- S3 — CI forbidden-alias regex passes clean against the executable surface (e.g.
backend/app/+backend/tests/) on the canonical branch. - S4 — Every plan's §18 Exports has a producing Task (grep-closure).
- S5 — Every plan's §18 Imports cites its upstream at current version (no stale revision pins).
- S6 — Self-review "Contract §-by-§ compliance" tables in every plan are fully populated (no N/A-for-unclear, only N/A-for-known-reasons).
- S7 — Lens coverage. Every lens in the project's lens registry (§7.3.1) has been certifiably exercised at least once in the cycle. A lens × round matrix, kept exactly as the §-section × round matrix of S2 is kept, shows no blank rows — and a row is filled by evidence that the lens ran, never by a label claiming it did (§7.3.4). (New at v1.2; the evidence rule added at v1.3, after a two-file fixture with thirteen lens names in it satisfied the v1.2 version and reported CONVERGED.)
Why S1 had to be amended, in one example. S2 tracks coverage of Contract §-sections — module layout, imports, persistence, error hierarchy, CLI conventions. Those are structural axes: they say where the reviewer looked. They say nothing about the orthogonal axis — what kind of question the reviewer asked.
In the Jira-bridge cycle, Round 20 came back 0C/0M. Its lens was re-verification of the persistence/locking work. Had Round 21 used the same lens — a completely legitimate reading of S1 and S2, since the §-matrix had no blank rows — it would also have come back 0C/0M, S1 would have tripped, and the suite would have been declared converged. What would have shipped:
- a
hadur audit securitythat copied the credential it found into a Jira ticket; - audit findings filed under the wrong Plan-Epic;
- a status-mirror function that had never been called since the plan that introduced it;
- a tool that could not see its own dogfood (
hadur convergencereporting round 1 with 26 round files on disk).
Round 21 rotated the lens to end-to-end behaviour and found five Majors immediately. Round 26 applied a security lens for the first time in eleven rounds and found the cycle's first Critical. The lens that had never been run found the worst thing. That is not luck; it is structural — and across the cycle, 10 of 37 Criticals+Majors came from a lens's first-ever run.
Why ANY wave resets EVERY lens. A clean verdict is a statement about the code that existed when it was made, not about the repository forever. If lens A came back clean twice, and lens B then finds a Major that produces a wave, lens A's clean verdicts were about code that no longer exists. The wave changed the thing A was certifying. A streak that survives a wave is not evidence; it is a stale receipt. So the streak resets for every lens in the registry, including lenses that found nothing and lenses whose files the wave did not touch — because "the wave did not touch my files" is itself a claim, and it is exactly the claim a reachability defect or a shared-lock regression falsifies.
And "a wave landed" is not the trigger — a code change is (v1.3). Stating the rule in terms of waves invites the loop to be asked whether a wave happened, and the loop can be silent (§6.5, second instance). The operative rule is the one that has no bookkeeping in it at all:
Two rounds count toward the same streak only if they reviewed the same code, and that code is still the code on disk.
A wave voids every streak because a wave changes the code, which is a consequence, not a definition. So does an unrecorded hotfix, a git pull, a "tiny" uncommitted edit made while the reviewers were running, and a refactor nobody thought to call a wave. All of them void the streak, none of them needs to be declared, and that is the entire point: the reset cannot be skipped by failing to write something down. §7.3.4 gives the mechanism.
Architectural-stop rule. When an architectural invariant lands (§13 declaration-by-reference) AND the next narrow round is fully clean AND the executable surface has been canonical for ≥ 3 prior rounds, the cycle is converged regardless of whether 2-consecutive-clean is mathematically achieved on the post-architectural rounds. The architectural mechanism replaces methodology-chasing with structural enforcement.
The rule as stated above is dangerous, and v1.2 adds two preconditions to it. Its hidden premise is that the architectural invariant is correct because it is architectural. It is not. An invariant is code. It has bugs. It is, in fact, the most dangerous code in the repository, precisely because everything downstream stops looking once it lands.
The Jira-bridge cycle landed a §13 architectural invariant and it was defective in four different ways across three consecutive rounds: it was intra-module (R23); it fail-opened on ordinary bound methods (R24); its indexer dropped
async defentirely before the fail-closed rule could fire (R25); and its "single HTTP boundary" was a four-name denylist that the already-installedurllib3walked straight past (R25). Had the architectural-stop rule been applied after Wave 22a, convergence would have been declared on the strength of a guarantee that did not hold.An architectural invariant may trigger the stop rule only if BOTH preconditions hold:
- It has survived an adversarial review whose brief was to break it — a reviewer told to construct, concretely, a case that gets past it (§5.8). It is the single highest-leverage piece of code in the cycle; it earns the highest scrutiny, not an exemption from it.
- It is fail-closed (§6.5). A fail-open invariant is not structural enforcement; it is a bigger denylist with a more confident docstring, and it will fail silently in exactly the direction nobody is looking.
And a standing note, because the overclaim was written three times: the invariant's documentation must state precisely what it does and does not guarantee. Every hole found in ours was, at the time it was found, contradicted by prose in the runbook claiming the property held. A security claim in a living operator document belongs in CI (§6.1.1 corollary).
Partial signals (good but not sufficient):
- Rounds with only Minor findings (label sweeps, doc drift).
- Reviewers returning
CLEANverdict — but only if their brief explicitly covered the §s at risk and the round's lens is named (§7.3.1) and that lens claim carries provenance (§7.3.4). ACLEANfrom an unnamed lens cannot be credited to S7 and cannot advance any streak under S1′: nobody can say afterwards what question was asked. ACLEANfrom a named lens with no distinct reviewer and no hash-pinned brief is no better — the name is a label, and a label is what an operator types when no reviewer ran at all. And aCLEANfrom L13 is inadmissible in that bare form entirely (§7.3.2).
7.3.1 The lens registry
A lens is a question, not an area. Two reviewers can read the same file, under the same Contract §-section, and one asks "does this match §9's declared signature?" while the other asks "can hostile input from one side of this boundary corrupt the other?" — and they will return disjoint findings. S2 governs the first coordinate; S7 and S1′ govern the second.
The default registry is 13 lenses. A project may extend it; a project may never shrink it. The evidence column is not decoration — it is the argument, drawn from Rounds 16-27:
| # | Lens | The question it asks | Found here in the Jira-bridge cycle |
|---|---|---|---|
| L1 | Contract conformance | Does the code match Plan 0's §-sections? | the existing S2 axis |
| L2 | Persistence & concurrency | Does the lock span the whole read-modify-write? Are writes atomic? Crash consistency? Lost updates? Lock lifetime and timeout? | 6 findings |
| L3 | Error hierarchy & failure surface | Typed errors? Does the exit-code contract hold? Does failure degrade or crash? Is the failure recorded? | 5 findings |
| L4 | Behaviour / state machine / idempotency | Trace full user journeys. Is a re-run genuinely a no-op? Does partial failure leave recoverable state? Are illegal transitions rejected? | 5 Majors |
| L5 | Data correctness / semantics | Is what we write actually right — right parent, right key, right attribution — not merely written without crashing? | 2 Majors |
| L6 | Security, secrets & trust boundary | Can a credential or PII reach a wider audience? Can hostile input from one side corrupt the other? Authorship spoofing? | 3 Criticals |
| L7 | Operator experience | Under stress, does the tool tell the truth? Is the documented recovery procedure real? Does status reflect reality? | 2 Majors |
| L8 | Test quality | Would these tests fail if the invariant broke? Decorative? Mock-deep? Timing-lottery? (§5.7) | 2 tests that tested nothing |
| L9 | Enforcement-mechanism integrity | The guards, invariants and CI gates themselves: are they fail-closed? Can you construct a bypass? (§6.5, §5.8) | 4 defects in one invariant |
| L10 | Reachability / dead surface | Does every declared capability — flag, config key, exported function, documented verb — reach an operator entry point, or is it explicitly deferred? (§6.1.1) | 8 findings |
| L11 | Performance & resource over time | What happens at scale, and over time? Unbounded growth? Lock window? O(n²) on a hot path? | append-only journals |
| L12 | Packaging & environment | Does a clean install work? Are the declared dependencies complete? Does it run off the dev machine? | pyyaml — clean installs crashed at startup |
| L13 | Free sweep (anti-tunnel) | No assigned lens. Fresh eyes, no focus. Find the question that is not in this table. (§7.3.2) | 2 Majors that 4 consecutive rounds walked past |
The four lenses that produced all ten of the cycle's "new lens" findings — L4, L5, L6, L7 — were named nowhere in v1.1 of this document. That is the whole of the case for the registry.
Operationally. The brief names the lens in a **Lens:** field (§16, §19). The controller persists it on the round's findings ("lens": "L6") beside focus_priorities, together with the provenance that makes it evidence — a reviewer id distinct within the round and the sha256 of the brief on disk (§7.3.4). S7 builds the lens × round matrix from the certified lenses; S1′ attributes a 0C/0M verdict to a lens's streak only if that lens is certified and the round's recorded code identity is still the tree on disk. A round whose findings carry no lens is uncountable for both signals — not "assumed L1"; and a round whose lens carries no provenance is uncountable too, which is the harder rule and the one that matters (a missing label is an accident, a bare label is what a hurried operator writes).
7.3.2 L13 is load-bearing — why the registry must not close
The lens registry is itself an enumeration. Twelve named questions, offered as sufficient. Read §6.5 and then read that sentence again.
A convergence criterion satisfied by the twelve named lenses alone would be exactly the fail-open guard that §6.5 forbids: a check that asks "is this one of the failure kinds I know about?", whose false answer is a silent pass, and whose safety oracle is drawn from the same catalog as the threat — the list of things twelve rounds happened to teach us to look for. It would be the methodology committing, in its own convergence rule, the precise bug it spends §6.5 teaching you to avoid. Two of the three Criticals in the cycle that produced this document had that shape.
L13 is the fail-closed complement. It exists because the list is incomplete — not as a catch-all, not as a courtesy row, not as slack in the schedule. Its brief is the only one in the methodology that does not supply a question:
We are not giving you a lens. We are not telling you what to look for. Every other reviewer this round has been pointed at something; you have not, deliberately, because the list of things we know to point at is the list of mistakes we have already made. Read the code with fresh eyes and no focus. Find the question that is not in the registry. If you find it, it becomes L14.
Its evidence is the sharpest in the table: the free sweep of Round 25 found two Majors that four consecutive, competent, increasingly sophisticated rounds had walked straight past (§7.7) — a function that had never been called, and findings filed under the wrong Epic. No named lens was going to find those, because the reason they survived was not that the wrong question was asked. It was that attention was elsewhere, and every named lens is an instruction about where to put attention.
Three consequences, stated so that a future editor cannot tidy them away:
- L13 counts for S1′ and S7 like any other lens. Convergence requires two consecutive 0C/0M free sweeps on the current code. "Vague" is not grounds for exempting it; vagueness is its function.
- L13 may never be removed from the registry, and it may not be "sharpened" into a specific question. A specific question is a fourteenth named lens (which is welcome — add it, and keep L13). The moment L13 acquires a focus, the registry has closed and become fail-open again.
- When a free sweep finds something no lens named, promote it. The registry grows by exactly this mechanism (§23). L13 is the port through which the methodology learns what it does not yet know to ask.
If a future revision of this document proposes deleting L13 as redundant with the other twelve, the answer is in §6.5, and the counter-example is Round 25.
The livelock in L13, and what a clean free sweep is actually worth (v1.3)
Read consequences 1 and 3 together and they bite:
- L13 succeeds by finding the thing nobody thought to name — and when it does, the thing becomes L14.
- S1′ is satisfied only by two consecutive 0C/0M rounds in every lens, L13 included.
Therefore a working L13 cannot satisfy its own stopping condition while it is working. A free sweep that is doing its job posts findings, and a lens that posts findings has no streak. The only L13 that satisfies S1′ is one that found nothing, twice running — which is precisely the shallow-sweep outcome L13 exists to defend against, and which is, from the outside, indistinguishable from a sweep that did not look. §7.3.3 claims the cycle stops having proven "every question, including the one that refuses to be a question, was asked twice… and found nothing." That claim is weaker for L13 than for any other lens, and the document must not pretend otherwise.
This is not an argument for removing L13 or for exempting it — see consequences 1-3 above; an exemption is the fail-open enumeration wearing a hall pass. It is an argument for being precise about what a clean L13 streak buys, and for gating it on something a negligent sweep cannot produce.
Decision 1 — L13 keeps the two-clean-rounds requirement, and its clean streak carries less evidentiary weight than any named lens's. Both halves are load-bearing.
The asymmetry is structural, and follows from the methodology's own logic rather than from any suspicion of the reviewer:
- A clean verdict from L6 means "the question 'can a credential reach a wider audience?' was asked of this code and answered no." That claim is falsifiable and reproducible: the question is pinned in the brief, and any third party can re-ask exactly it and contradict the verdict.
- A clean verdict from L13 means "someone looked, without a question, and nothing struck them." There is no question to re-ask, so there is nothing to reproduce and nothing to contradict. It is a statement about the sweep, not about the code.
That is the irreducible price of having a fail-closed complement to an enumeration: a guard against the unknown cannot report coverage of the unknown. L13's two clean rounds are therefore not proof that no unnamed question remains — nothing can prove that — they are proof that two independent unfocused passes over this code produced no new question. That is genuinely worth having (Round 25 is what one focused-away pass is worth) and it is genuinely worth less than L6's clean streak. Anyone reading a converged verdict is entitled to know which of the two they are holding, so the report says so.
Decision 2 — the anti-negligence rule: a bare CLEAN from L13 is inadmissible.
The methodology already has this rule; it just had not applied it here. §5.8 forbids the Breaker from returning "looks sound" and admits only NO BYPASS FOUND — attempted: <enumeration of what was tried>, because the value of a negative verdict lies entirely in the enumeration. A clean free sweep is the same species of claim, and it earns its place on the streak the same way:
An L13 verdict of no-findings is admissible only in the form
NO FINDING — examined: <enumeration of the surface actually traversed>— the files opened, the entry points traced, the journeys walked, the areas deliberately not reviewed (§7.7). A bareCLEANfrom a free sweep advances no streak and fills no S7 row. It is not a verdict; it is a mood.
This is the cheapest honest mechanism available: it costs the sweep one paragraph, and it converts "I found nothing" into a claim with a truth-value — an enumeration can be checked against the repository, and a sweep whose examined-surface is trivially small, or which lists only files three other lenses covered this round, is a negligent sweep on its face and does not count.
And the two clean free sweeps must be run by different reviewers. A reviewer cannot ratify their own blind spot: a second unfocused pass by the same attention re-runs the first pass, and its agreement is a tautology dressed as corroboration. For a named lens, a repeat reviewer is at least re-asking a fixed question; for L13 the reviewer is the question, so two sweeps by one reviewer are one sweep counted twice. (Hadúr already requires each lens in a round to carry a distinct reviewer id and a hash-pinned brief — §7.3.4 — which stops one reviewer posing as several within a round. The across-round distinctness that L13's streak needs is stated here as discipline and is not yet mechanised; Appendix B lists it as forward work. Do not read the tool's silence as permission.)
Does this rule fail open on the negligent sweep? (§6.5, applied to itself.) Under a bare CLEAN, yes, completely: the sweep that examined nothing and the sweep that examined everything emit the same token, and it passes. Under the enumeration rule, a sweep that looked at nothing must either say so — and be rejected, which is fail-closed — or fabricate an enumeration, which is a different act: no longer negligence but invention, and it is the one thing no mechanism in this document can catch (§7.3.5). That is the correct place for the failure to land. The rule does not abolish the residue; it moves the residue from silence (which is free, and which people reach for when tired) to fabrication (which is deliberate, and which nobody does by accident).
7.3.3 The cost of S1′, stated plainly
S1′ is expensive and the expense is the point. It should be stated honestly rather than discovered late:
After the final wave of a cycle, you run the entire lens registry twice, all clean. Roughly 2N reviewer dispatches for a registry of N lenses — 26 at the default registry — and no wave may land in between, or the count restarts. Within a round the dispatches are independent and parallelisable (§3.2), so the wall-clock cost is two rounds, not 26 sequential reviews; the token cost is real and is the price of the guarantee.
What you are buying. A cycle that stops on the v1.1 signal has proven: two rounds asked some questions and found nothing. A cycle that stops on S1′ has proven: every question in the registry was asked twice of the code that actually ships — the same code, still on disk, by reviewers who each left a pinned brief naming their question — and found nothing. The first is what the Jira-bridge cycle would have concluded at Round 21 — while shipping a credential leak into a ticket system with a wider audience than the repository and no purge path.
Two caveats on that sentence, both mandatory, because an overstated guarantee is the failure this document exists to prevent.
- The L13 row of the claim is weaker than the rest of it (§7.3.2). Twelve of the thirteen streaks certify that a named question was asked and answered no. L13's certifies that two unfocused passes produced no new question — which no re-run can reproduce or contradict, because there was no question. Read a converged verdict as: twelve questions asked twice and answered; one absence of a question, twice, evidenced by what it examined. Do not round that up.
- The whole claim sits on the verification floor of §7.3.5. What is mechanically established is that the artifacts of asking exist, are distinct, are pinned, and are anchored to the shipped code. That a reviewer read anything is not, and cannot be, established by any tool.
Do not economise by pruning the registry. The lens you drop to save a dispatch is, on this cycle's record, the lens that finds the Critical: the security lens ran for the first time in round 26 of 27 and found the worst defect in the suite. If the budget is genuinely not there, the honest response is to declare the cycle unconverged and say which lenses were not run — not to shrink the registry until the surviving lenses come back clean.
7.3.4 The evidence rules — what makes a round countable (added v1.3)
S1′ and S7 are claims about the past ("this lens ran"; "the code has not changed since"). v1.2 answered both by consulting the loop's own records, and both were bypassable by writing nothing down: an operator who never marked a finding resolved produced no wave, hence no reset, hence streaks accruing across code that had genuinely changed; and an operator who typed thirteen lens ids into a file produced thirteen "exercised" lenses. Two files with no findings and a list of names returned CONVERGED. The lesson is §6.5 turned on this document: a guard whose input is supplied by the thing it guards has no safety oracle.
The evidence must come from outside the loop. Two rules; both fail closed.
Rule 1 — a round is anchored to the code it reviewed, by an identity the loop does not mint.
Every round records the identity of the code it reviewed: a digest over the VCS tree at review time, excluding the loop's own record directory. Two rounds count toward the same streak only if their digests match each other AND still match the tree on disk.
Consequences, each of which is the rule doing its job rather than a rough edge:
- A change to the code voids every streak, whether or not anyone called it a wave — because every recorded digest now disagrees with the tree. No declaration is required and none can be omitted (§7.3, "and 'a wave landed' is not the trigger").
- The key is the tree, not the commit id. The commit moves every time a round record is committed; the reviewed code does not. Keying on the commit would void every streak on the act of recording it, and no streak could ever reach two.
- The record directory is excluded, and that exclusion is forced, not chosen. Including it makes the identity self-referential: writing round N would change the identity round N attests to. The residue is worth naming: code smuggled into the record directory is outside the identity by construction. That directory holds bookkeeping and nothing imports from it; if that ever stops being true, the exclusion becomes a hole.
- The exclusion set is not configurable. A per-project exclusion list is bookkeeping the loop controls —
exclude: ["src/"]and the streak survives anything. One forced exclusion, or none. - No VCS, no streak. Not a fallback to a filesystem walk: without the project's own declaration of what its code is, every exclusion heuristic we could invent (
__pycache__,node_modules, …) is a fail-open enumeration, and the file that escapes the digest is exactly the one that changed. - A dirty tree voids eligibility. A digest of a dirty tree describes the last commit, not what the reviewer read, and uncommitted code cannot be re-fetched by anyone else. A streak is a claim a third party must be able to re-check. The cost to the operator is: commit before you review. That is the intended cost. This is also why a §5.7 mutation may not run in the shared tree (§5.7.1): a break-and-restore dirties the tree for the window between the two, and in Round 28 that window voided a round the moment a reviewer obeyed §5.7 inside it. Mutate in an isolated worktree instead.
- A round that records no identity counts for nothing — never "assume it's current".
Rule 2 — a lens claim is certified by provenance, not by its label.
A lens counts toward S1′ or S7 only where the round carries, for that lens: a reviewer identity used by no other lens in that round, and a brief on disk, pinned by content hash, that names the lens it claims. Anything else — a lens id with no provenance, one reviewer on two lenses, one brief hash on two lenses, a brief that has been edited since — is uncertified and counts for neither signal.
Why each clause exists:
- Distinct reviewer per lens. §7.3.1 dispatches one reviewer per lens; one identity holding two lenses is one pass of attention claiming to be two questions. Both claims are rejected, not merely the second — nobody can say which, if either, was real.
- A brief, on disk, hashed. The hash makes the question tamper-evident: an operator cannot rewrite the question after seeing the answer. Without it, "the lens I ran" is decided after the fact by whoever writes the record.
- The brief names its lens. Otherwise any file can be pointed at as a brief.
- The brief lives outside the reviewed surface (in the record directory). A brief committed into the code would change the tree digest between every round and no streak could ever reach two. This is Rule 1 constraining Rule 2, and both had to move to make either work.
- There is no weaker-but-accepted tier. An earlier draft admitted "hash recorded, brief not retrievable" as partial credit. An unretrievable artifact is a claim about an artifact, and accepting claims is the entire bug.
§16 forbids the absent lens label and is structurally silent on the falsely-present one — which is the only case that matters, because it is the one a hurried operator reaches for.
7.3.5 The verification floor — what none of this proves (added v1.3)
Every mechanised gate in §7.3 stops at the same line, and the line must be stated in the document rather than discovered by whoever trusts the green verdict:
The tooling verifies artifacts, not attention. It can establish that a brief exists, is distinct from the round's other briefs, is retrievable, hashes to what the round claims, names the lens, and is anchored to a code state the loop did not choose. It cannot establish that a reviewer read the code, or brought the question to it, or thought about the answer for longer than it took to type
CLEAN.
The floor is real, and it was proved to be real by construction: the agent who built these gates then attacked them, and produced a passing forgery — twenty-six brief files, twenty-six reviewer ids, one honest digest — that converges. Every mechanical check passes, because every mechanical check is satisfied. Nothing was reviewed.
Three things follow, and the third is the one that matters:
- This residue is irreducible, not a to-do. "Did a mind engage with this code?" is not a property of the repository. No hash, no matrix, no CI gate can see it, and a future revision promising to close this gap is promising something no tool can deliver.
- The report must say so, every time it says
met. A verdict that reports mechanical confidence past the point where it has any is committing §6.5 in the convergence rule itself — which this methodology has now done twice in two revisions, and will do a third time the moment the floor goes unstated. Hadúr prints the floor on everymetverdict, in the signal's own detail line, for exactly this reason. - Everything above the floor is worth having precisely because the floor is stated. The gates do not make forgery impossible; they make it deliberate. Before them, a converged verdict could be produced by an operator who was merely tired — silence and omission were free. After them, it takes an operator who sits down and manufactures evidence. That is the whole of the improvement, it is a large improvement, and it is not the same as certainty. The last line of defence in this methodology is an honest reviewer. It always was; v1.2's mistake was to imply otherwise.
7.4 Divergence signal
If Round N with rotated brief focus surfaces >10 new Criticals AND >30% of them are systemic (addressable at Contract / §-level), do NOT dispatch Wave N+1 as per-plan corrective patches. Triage:
- Column A (systemic). Addressable by adding / rewriting a Contract §-section. Examples: i18n policy, retention category enum, event durability architecture, idempotency-key middleware.
- Column B (per-plan unique). Addressable by patching one plan file. Examples: a specific service method bug, a specific migration column fix.
If Column A share > 30% AND absolute count > 10, escalate to Contract-level work (§12). Signals:
- Findings cluster by theme rather than by plan.
- The same class of bug appears in 3+ plans (e.g., "hardcoded pt-PT", "no idempotency key", "no
downgrade()body"). - Fixing each plan individually would produce N private reinventions of the same idiom.
7.5 Asymptotic narrative drift
Distinct from §7.4 divergence. Asymptotic drift is the case where:
- Three or more consecutive deep-audit rounds each surface fresh same-class drift.
- Each "comprehensive" sweep's brief gets longer with new sub-classes (in-body docstrings, adjacent bullets, header parity, etc.).
- Findings shift from load-bearing to documentation-cosmetic — executable surface is canonical; only narrative replication remains.
- Convergence trajectory is downward but with NEW sub-classes per round.
- Sweep agents claim "HIGH confidence" + "X/X verifier-gate PASS" yet next deep audit finds new drift.
When 2+ of those signs are present, escalate to a Contract-level declaration-by-reference invariant (§13). Continued per-round sweeping is asymptotic; drift count drops but never reaches zero because replicated narrative across 30+ sync points (Contract body + §18 manifest + per-plan §18 rows + helper docstrings + Why-paragraphs + Self-Review rows + counter-factual narratives + test fixtures) is itself the drift surface. Each comprehensive sweep catches ~80% of sites; ~20% surfaces in next deep audit.
7.6 Stopping rules per trajectory
All seven signals S1′-S7 (§7.3) are required on every trajectory. The trajectory-specific rows below state what a trajectory demands in addition; none of them substitutes for S1′, and none of them is satisfied by a single clean round. (At v1.1 the Diverging and Asymptotic rows read "+ 1 clean re-Round" and "+ 1 fully-clean Round" — that is now void: a lone clean round after an architectural landing is the exact configuration in which the Jira-bridge cycle's invariant was defective for three consecutive rounds, §7.3.)
| Trajectory | Stop when |
|---|---|
| Converging (§7.1) | All seven S1′-S7 signals trip simultaneously — including two consecutive 0C/0M rounds in every lens, on the post-final-wave code |
| Diverging (§7.4) | Contract architectural pass + clean per-plan adoption wave + S1′-S7 re-established on the post-wave code |
| Asymptotic (§7.5) | §13 declaration-by-reference invariant lands + grandfather clause active + the invariant passes the §7.3 architectural-stop preconditions (adversarially broken, fail-closed) + S1′-S7 |
| Tunnelling (§7.7) | A free sweep (L13) under an explicitly different lens comes back clean — and then S1′-S7. A tunnelled loop may never stop on the strength of the rounds that tunnelled |
Do not stop on intuition ("looks converged"). Do not stop on a single clean round. Do not stop on two clean rounds under the same lens — that is the cheapest possible way to satisfy the old S1 and it proves close to nothing (§7.3). Do not stop on convergence rules alone if the architectural mechanism has not landed where one of the four trajectories required it. And do not stop on an architectural invariant that nobody has tried to break (§5.8).
7.7 The tunnel signal
§7.5 (asymptotic drift) covers the loop that keeps finding the same class of thing. It has no counterpart for the opposite pathology, and the Jira-bridge cycle walked straight into it.
Rounds 21 → 24 tunnelled. Each round drilled deeper into one mechanism (the bridge-disable invariant). Each found a real but narrower defect in it. Each felt like progress — and the findings were getting more sophisticated, which reads as convergence and is not. Meanwhile ordinary defects sat untouched a few files away: a function that had never been called since the plan that introduced it, and audit findings filed under the wrong Epic. Round 25's broad sweep was run only because the narrowing felt wrong, and it found both immediately — things four consecutive competent rounds had walked past.
Detection. If the findings of N consecutive rounds (N ≥ 3) all localise to the same file, module, or mechanism, the loop has tunnelled. The tell that is not a tell: increasing sophistication of findings is evidence of depth, not of coverage, and from inside the loop the two are indistinguishable. Rising subtlety is precisely what a tunnelling loop produces, and it is the feeling most easily mistaken for "we are nearly done."
Prescription. The next round MUST be a free sweep (L13, §7.3.2) — a comprehensive sweep under an explicitly different lens — and its brief must say so, in these terms:
The last N rounds focused on X. That narrowing is itself a risk. Deliberately do NOT review X. You have no assigned lens; find what nobody has been looking at.
Note the interaction with §7.5, and get it right. Asymptotic drift says "the same class keeps appearing → escalate to a structural invariant." The tunnel signal says "the same place keeps appearing → go look somewhere else." These are different diseases with opposite treatments, and mistaking one for the other is easy — both present, from inside, as "we keep finding things in round after round." The discriminator is mechanical: ask whether the recurring axis is the kind of defect (→ §7.5 / §13) or the location of the defect (→ §7.7). A loop can have both at once; treat them separately.
Relation to S1′. A tunnelled loop is the standing counter-example to the v1.1 stopping rule. Its rounds are honest, its findings are real, its counts decline — and the clean round that ends it certifies only that the tunnel is clean. This is why S1′ requires the streak in every lens rather than in the round as a whole.
Part III — Anti-patterns
Anti-patterns are real failure modes observed across review cycles. Each is named so that a brief can enumerate it ("do not do X") and a reviewer can cite it as a finding ("violates §8.1 — narrative-only classification without verification").
8. Process anti-patterns
8.1 Narrative-only classification without verification
A finding that looks like narrative drift ("§18 row has wrong signature, but nothing executable calls this") can mask matching executable drift. Always grep the executable for the same pattern before dismissing as narrative.
Example: Round 10 of a deep audit flagged a write_audit_event §18 row signature drift as narrative-only. Round 11 discovered the corresponding Task also built AuditEvent(reason=, metadata_json=) with non-existent columns — the broken manifest mirrored broken code.
8.2 Self-review assertions are CLAIMS, not facts
Plans often state in self-review "zero inline bus.emit", "all emits via queue_event", "concurrency handled". Every claim must be independently verifiable by grep.
For each "we fixed X" in a plan's Self-Review, construct a grep that would detect the anti-pattern, run it, and pin the result alongside the claim. A claim that cannot be grep-verified is not a claim — it is a wish.
8.3 Header / body version drift
Body is patched; header changelog is not bumped. Auditors cannot tell which wave landed. Fix. The verifier MUST confirm the **Version:** vX.Y header line matches what the patch claims to implement, and that all three / four header sites (title L1, **Plan revision:**, **Version:**, optional **Status:**) agree.
8.4 Rewrite before patches that depend on it
Rewrite agents cannot read patches that have not landed. If a rewrite is dispatched before its dependency patches, the rewrite agent guesses the post-patch signatures — and is reliably wrong. Patch-first, dependency-order discipline (§4.2).
8.5 Parallel phase-agents on the same plan
Different agents writing different phases of the SAME plan drift at integration seams. Either:
- Serialize phases, OR
- Write a Cross-Phase Contract (signatures, types, event topics) and include it in every agent's prompt before dispatch.
When parallel phase-agents on the same plan are unavoidable, dispatch a cleanup-sweep agent post-merge to catch orphan placeholders, section-number gaps, broken cross-refs (§14 / §16.2).
8.6 Diminishing-returns intuition wrong
After N rounds closing big counts, do not stop at "looks converged." Reviewer surface shifts each round — later rounds may probe areas earlier rounds underserved (auth boundary, test coverage, self-review consistency). Continue until §7.3 stopping rules trip, not until the cycle "feels" done.
Two specific ways the intuition lies (both added at v1.2, both observed in the Jira-bridge cycle):
- A clean round under a stale lens feels like convergence and is worth nothing. Round 20 was 0C/0M; the next round rotated the lens and found five Majors immediately (§7.3). "The count went to zero" is a fact about the question that was asked, not about the code.
- Rising sophistication of findings feels like convergence and is often tunnelling (§7.7). Subtler findings mean the loop is drilling, not that the surface is clean.
The intuition to distrust most is the one that arrives after a long, competent, successful stretch of work — which is precisely when it arrives.
8.7 CI regex compounding errors
Every regex iteration closes one class and risks opening another. Document evolution visibly (Contract status line). Test every revision against a curated list of both should-match and should-not-match examples. A regex with || true always passes — that is not a CI guard, it is a placebo (§6.4).
9. Adoption-wave anti-patterns
When a Contract architectural pass lands new invariants, the next phase is per-plan adoption: each plan implements the new primitives. Adoption waves have their own recurring anti-patterns.
9.1 Narrative-only wiring gap
Symptom. Agent declares new primitives (middlewares, helpers, classes) but never wires them into live call sites (create_app, service INSERT paths). Grep-verifier approves — the primitives exist by name. Counter-factual: "if I reverted the declaration, does anything break?" Answer: no, because nothing was actually wired.
Detection. Pass 2 walkthrough — specifically dependency existence (item 1) and test truth (item 11). Grep alone cannot catch this.
Remediation. Name the anti-pattern explicitly in every adoption-wave brief. Once named, agents consistently avoid it.
9.2 Docstring drift from body-correct code
Symptom. Agent copies a helper with a wrong docstring; actual function body remains correct. Grep-verifier approves (code is syntactically fine + runtime semantically correct). Only semantic cross-reference against Contract catches the discrepancy.
Detection. Cross-plan docstring grep + Contract source comparison. Any time N plans co-locate a helper, grep the docstrings and compare against the canonical Contract definition.
Remediation. Two-fold: (a) propagate the correctly-scoped docstring to every subsequent plan's brief with explicit anti-drift note; (b) at wave-end, hoist the helper to a single canonical location (§14.4 co-located helper hoist).
9.3 Brief-vs-plan model drift
Symptom. Brief names models using historical or projected names ("LeaveBalance"); actual plan uses different names ("LeaveEntitlement"). Agent must grep the actual plan file to confirm model names before implementing.
Root cause. Briefs are written from handoff docs that may be weeks old; plan files have iterated since. The handoff doc's projected name was a wish; the plan author chose a different name for semantic clarity.
Remediation. Agent brief MUST instruct: "GREP the plan file for the models before editing. The brief names a superset of expected models; any missing from the plan should be flagged as agent notes model X not present; mapped deliverable to Y instead." Agents that do this consistently produce correct adoptions; agents that skip it consistently miss.
10. Late-cycle anti-patterns
10.1 Trust-but-verify agent claims
Sweep agents and patch agents routinely report "HIGH confidence" + "X/X verifier-gate PASS" + "all sites enumerated" — claims that do not survive independent deep audit. Discipline:
- Agent reports are claims, not facts.
- Always run an independent grep-verifier OR dispatch a separate reviewer before commit.
- Do not let agent confidence calibrate your trust — calibrate against what grep shows.
10.2 Verifier-gate trap — long old_string matches Prior-narrative blocks
When a patch's old_string contains text that recurs in the plan's Prior narrative blocks (changelog history), Edit may match at a historical site rather than the active line. The patch reports success; the active site is unchanged.
Mitigation. Anchor the old_string on unique trailing text (closing parenthesis + period + adjacent unique sentence). After every narrow Edit on a plan with Prior-narrative blocks, run grep -c "<new_phrase>" <file> immediately to confirm the new content actually landed at the intended line.
10.3 Post-bump freshness paradox
A wave that closes pin freshness (§18 Imports rows that point at the upstream's old version) cannot also bump the upstream's version in the same atomic patch — the bump invalidates the just-closed freshness one patch deeper.
Mitigation options.
- Two-pass wave — close existing freshness → commit → bump heads → re-close freshness → commit.
- Accept eventual consistency — declare pin freshness is asymptotic; fix in next wave. Acceptable for single-patch lag.
- Architectural fix — §13 declaration-by-reference removes most pin-chasing because plans cite Contract §-anchors, not Plan-N versions.
In practice, option 3 addresses the root cause; options 1+2 are workarounds suitable for narrow cases.
10.4 Version-header trinity drift
Plan files often have THREE or FOUR header sites that must stay in sync:
- L1 title —
# Plan #N v3.X.Y — Description - Body
**Plan revision:** v3.X.Y(sometimes) - Body
**Version:** v3.X.Y (Wave description...) - Body
**Status:** v3.X.Y(some patterns)
Each wave bump MUST update ALL three (or four). A wave that bumps the title but misses the body **Version:** line ships a plan whose header lies.
Discipline. Any wave that bumps a plan version must grep all 3-4 header sites in that plan + verify they match the new version. The check belongs in Pass 1 (grep verifier).
10.5 Chasing narrative drift past the architectural escalation point
If a deep audit Round N surfaces same-class drift after Round N-1's "comprehensive sweep" claim, AND the executable surface is canonical, AND the drift class is replication-of-canonical-text, escalate to architectural invariant within 1 round (§13). Do NOT run Round N+1 as another narrow sweep.
A real cycle observation: 18 narrow corrective waves were spent closing 80+ findings before a single architectural patch (declaration-by-reference §44) achieved convergence. Roughly 17 of the 18 waves were unnecessary in retrospect — the architectural rework should have been triggered after Round N-3 (3 consecutive deep-audit rounds with same-class drift). Future projects should escalate faster.
Part IV — Architectural escalation
When a narrow corrective wave is the wrong response — because findings cluster systemically, or because narrative drift is asymptotic — the cycle escalates to architectural rework. This part defines when and how.
11. When narrow waves are not enough — triage
After each Round's findings arrive, classify each finding into Column A (systemic) or Column B (per-plan unique). The triage matrix:
| Column A share | Absolute Critical+Major count | Response |
|---|---|---|
| < 30% | < 10 | Per-plan corrective wave (normal §4.2 flow) |
| < 30% | ≥ 10 | Per-plan corrective wave, sequenced over 2-3 sub-waves |
| ≥ 30% | < 10 | Per-plan wave + Contract minor amendment |
| ≥ 30% | ≥ 10 | Contract architectural pass first (§12) |
When in the bottom-right cell, do not dispatch per-plan patches. Plan 0 is the bottleneck; dispatch Contract patches first.
12. Contract-level architectural rework
12.1 Shape
- 1-3 parallel agents, scoped to DIFFERENT §-sections of the Contract (to avoid file-level collisions).
- Minor version bump at the Contract (not a patch — it is an architectural change).
- Cleanup-sweep agent dispatches post-merge to catch orphan placeholders, section-number gaps, broken cross-refs (§12.2).
- Commit Contract vN-new BEFORE dispatching per-plan adoption.
- Per-plan adoption waves then become "bring Plan X to Contract vN-new compliance" rather than "fix N bugs in Plan X."
12.2 Cleanup-sweep after parallel agents on the same file
When N>1 agents edit scoped sections of the SAME file in parallel, ALWAYS dispatch a cleanup-sweep agent before the commit. Grep for [RESERVED] / [TODO] markers nobody claimed, section-number gaps, broken cross-refs.
A historical observation: a Contract architectural pass merged with a §22 orphan because one of three parallel agents inserted a [RESERVED] placeholder nobody requested in the brief. The merge was committed under "intentional placeholder" rationalization; a follow-up Round caught it; the next Contract revision removed it. The cleanup-sweep agent (had it been dispatched) would have caught the orphan pre-merge.
Truncation signals = state of the file, not self-report. When an agent returns "out of usage" / "interrupted" / timeout, read the file COMPLETELY before declaring the dispatch done. Reconcile against the brief's deliverable list. Do NOT rationalize unexpected artefacts as intentional — if the brief did not request [X], [X] should not exist.
13. Declaration-by-reference (§44 pattern)
When asymptotic narrative drift is diagnosed (§7.5), declare a Contract-level invariant with the structure below. The invariant pattern (canonically named §44 in projects that have adopted it) replaces narrow corrective sweeps with structural enforcement.
13.1 Mandate
Every cross-plan signature contract has exactly ONE canonical site (the Contract itself, or a Contract-cited canonical helper module). Consumer plans cite (per Contract §X.Y) instead of replicating the declaration text.
13.2 Forbidden replications
The invariant forbids:
- Multi-line type alias copies across plans.
- Kwarg restatements (a Plan that re-prints the upstream signature instead of citing it).
- Semantic-invariant restatements (a Plan that re-states an invariant the Contract owns).
13.3 Allowed plan-specific content
Each plan may still describe its own scope, usage, data-model, and per-plan invariants — anything that does NOT restate a signature the Contract canonically owns.
13.4 Symbol catalog
The invariant section enumerates governed symbols + canonical site per symbol:
| Symbol | Canonical site |
|---|---|
DSARFanOutHook |
Contract §36.1a |
query_user_dsar_export |
Contract §36.4 |
RetentionCategory |
Contract §21.1 |
The catalog is the CI test target — every catalog row gets enforced.
13.5 Migration mandate (load-bearing simplification)
GRANDFATHER pre-existing replication. Without this clause, the architectural rework would trigger massive rewrites across N plans (hundreds of replicated lines). The grandfather clause:
- Declares the FORWARD invariant only.
- Existing replication stays as-is.
- Future content is CI-bounded.
- Migration cost: ~1 hour (write the §-section + per-plan annotations) instead of multi-day.
Without the grandfather clause, convergence cost > value, and the architectural rework is likely abandoned mid-flight. With it, the drift surface stops growing without paying full migration cost. Natural editing progressively reduces replication over time.
13.6 CI enforcement
A test (canonically named test_canonical_declaration_single_site.py) AST-walks plan files and asserts each catalog symbol is declared at exactly one site. The test starts as a stub (§13.8) and gets a body in the implementation phase.
13.7 Rationale
Cite the empirical R-round trajectory that justified the invariant. The Contract section explicitly references the rounds where same-class drift recurred, the waves that tried narrow correction, and the round at which escalation was triggered. This citation prevents future revisitation: a future agent reading the Contract sees WHY the invariant exists.
13.8 Stub-as-mandate
Add the CI test stub to the foundation plan's §27.1 manifest with [IMPLEMENTATION-PHASE TODO] body. Stub-in-§27.1-manifest = concrete implementation target even when body deferred — anything in §27.1 gets a test body before merge. Stub-as-mandate beats narrative-as-mandate because the manifest is the canonical CI-test invariant list.
13.9 Per-plan annotation
Each consumer plan gets a 1-3 line cross-reference annotation (no replication). Stack on existing §18 manifest. Do not delete grandfathered content.
13.10 Granularity scaling — section-level / line-level / table-cell-level
The §44 invariant pattern scales to three granularities depending on the drift surface:
-
§-section level (Empenho exemplar, Hadur Wave 8a §19/§19.5). When the drift class spans multiple plans + tasks, declare the invariant at top-level §-section. Grandfather pre-existing replication. CI test stub in foundation plan §27.1 manifest.
-
Line-level (Hadur Wave 10a — §1/§11/§18 self-narratives). When the drift is a recurring stale-version literal in §-section prose (each Plan-0 amendment makes the example stale), rephrase the offending lines to drop version literals entirely (
as of v1.2.0→as of the current Contract revision). No new §-section needed; declaration-by-reference applied at sentence level. Cheaper than full §-section escalation when the drift surface is narrow. -
Table-cell level (Hadur Wave 11a — §15 manifest summary). When the drift is in a §-section's data table (e.g., suite manifest with version pins), replace explicit version literals with
currentpointer convention + reword §-prose to declare the table descriptive (not authoritative); authoritative source-of-truth lives elsewhere (header trinity of each owner plan). Add explicit carve-out clause to upstream §-section's amendment-mandate (e.g., §18 Mandate item 4): such §-amendments do NOT trigger §18 atomic re-pin obligation on downstream plans.
Choose the smallest granularity that closes the drift class. Section-level is correct when ≥3 distinct drift loci exist; line-level when 1-3 prose sites; table-cell-level when the drift is concentrated in a single §-section's tabular data.
14. Adoption-wave design patterns
These are PATTERNS, not anti-patterns. They emerge during per-plan adoption and are the correct design when the underlying conditions hold.
14.1 Cascade on parent → child state transitions
A parent audit / approval row inherits the retention category (or analogous classification) of its downstream child when a state transition fires. Implemented via event-handler-style cascade handlers.
Invariant preserved. Retention scope is MINIMAL at insert time and only UPGRADED when the downstream relevance materializes. This keeps GDPR-minimisation tight while never under-retaining.
When to apply. If a plan has downstream events that feed records under stricter retention (fiscal, payroll, audit), the cascade pattern is likely the correct §21-style solution — not flat-category-per-table.
14.2 Stricter-module-policy coexists with Contract default
When a module already ships its own stricter policy for a Contract-scope invariant (e.g. AI-specific 90-day redaction + 365-day deletion + right-to-erasure) AND the Contract default differs (e.g. USER_REQUESTED never auto-expire), the apparent conflict resolves as:
- Contract provides the COLUMN-SCHEMA INVARIANT (the columns exist, the categories are well-defined).
- Module ships its own CRON / cleanup as the AI-specific FLOOR (non-configurable minimum).
retention_categorycolumn is the per-org OVERRIDE KNOB — an EU-enterprise org can set EPHEMERAL (strictly more aggressive than the floor); a relaxed org keeps USER_REQUESTED (floor still applies on top).
The column system never RELAXES the module floor — only potentially tightens it.
14.3 Contract forward-looking claims need verification
When the Contract makes a claim about a plan's state ("Plan 9 migration 0011 has enum ADD VALUE ops"), the agent adopting that contract claim MUST grep the actual plan to verify before implementing. Contract text can drift from reality just like plan text can.
Discipline. Every adoption brief that cites specific Contract claims includes: "grep the actual plan file to verify the claim before implementing; if the claim is forward-looking or outdated, flag it + adjust the Self-Review row rather than fabricate conformance."
14.4 Co-located helper hoist
During adoption, each plan may co-locate a small helper (e.g. a 4-line retention-expires computation). After N plans adopt, there are N copies. Drift in any one copy (docstring, behavior) does not propagate. Cleanup pressure accumulates.
Pre-wave-start prophylactic. If the adoption brief asks each plan to declare a helper, ask instead: "should this helper live in the foundation plan from the start?" If the helper is pure-function and stateless, hoist it upfront to avoid N copies.
Post-wave cleanup. A single consolidation commit removes local copies + switches imports. Bump each plan's patch version (e.g. v3.10.1 → v3.10.2) to record the cleanup. Net negative line-count is the success criterion.
15. The grandfather + stub-as-mandate pair
Multiple architectural fixes rely on grandfather-clauses to keep migration cost bounded:
- Declaration-by-reference §44 — pre-existing narrative replication grandfathered.
- Manifest §28-style additions — pre-existing manifest rows grandfathered until next plan revision.
- Operational §27-style additions — pre-existing TenantBase classes grandfathered until next adoption wave.
The pattern. Forward invariants + grandfathered existing content = high architectural value, low migration cost. CI test enforces forward; existing content gets cleaned up via natural editing.
The companion is stub-as-mandate: the CI test goes into the foundation plan's §27.1 manifest with [IMPLEMENTATION-PHASE TODO] body. The stub-in-the-manifest mandate means the implementation-phase agent has a concrete target — anything in the manifest MUST get a test body before merge. Examples:
test_canonical_declaration_single_site.py(declaration-by-reference §44 enforcement).test_alembic_downgrade_reverses_upgrade(migration reversibility).- Cross-plan invariant tests (period agreement, TZ consistency, advisory lock, clock skew).
Part V — Practical templates
This part contains copy-pastable templates for the common dispatches: reviewer brief, patch agent + verifier brief, walkthrough brief, comprehensive sweep brief. The 18-class drift catalog (§20) is the operational floor for any narrative-drift sweep. Every reviewer and sweep brief carries a **Lens:** field (§7.3.1) — the kind of question it is dispatched to ask.
16. Reviewer-brief template
Self-contained prompt for dispatch to independent reviewers:
You are an independent Round-K reviewer for the <project> multi-plan suite.
**Target:** <path to plan file> (vX.Y, <last-round-touched> — <what-changed>).
**Lens:** <L-N — the lens name + its question, verbatim from §7.3.1>.
This is the KIND of question you are here to ask. It is not a restriction on what you
may report — a finding outside your lens is still a finding — but it is where your
attention goes first, and you are the only reviewer this round asking it.
[For L13 free sweep, replace the above with the §7.3.2 no-lens brief verbatim.]
**Contract:** <path to Plan 0> vA.B.C. Read §2 canonical imports, §<relevant>, §18.
**Cross-plan state (do not review, just reference):** Plan 1 vX.Y, Plan 2 vX.Y, ...
**Your job:** Find NEW findings that prior K-1 rounds missed. Expect 0-3 at this maturity.
Note: at this maturity, a lens on its FIRST run in the cycle has historically found the
worst defects in the suite. If this is your lens's first run, do not calibrate your
expectations to the maturity of the project — calibrate them to the newness of the question.
**Priorities:**
1. <Specific check 1 relevant to this plan>
2. <Specific check 2>
...
**For each finding:** severity, location, evidence (quoted), proposed fix.
**Output format:** Numbered findings + verdict: `CLEAN` or `FINDINGS: N critical, M major, K minor`. Under <word limit>.
Key invariants. Every reviewer dispatched in the same round gets its OWN brief. Do NOT share briefs across plans (leakage of context that defeats independence). Do NOT include prior rounds' findings in the brief — re-discovery is signal that a finding is real. Every brief names exactly one lens (§7.3.1); a round whose briefs name no lens cannot advance S1′ or S7, because nobody can say afterwards what question was asked.
Rotating the lens is the coordinator's job, and it is not optional. Two rounds under the same lens are the cheapest possible way to satisfy the old S1 and prove close to nothing (§7.3). When choosing the next round's lenses, prefer (a) any lens that has never run — its first run is where 10 of the Jira-bridge cycle's 37 Criticals+Majors came from; then (b) L13, if the last N rounds have localised to one place (§7.7); then (c) any lens whose findings the last wave touched. Do not read (c) as "the lenses the wave voided" — the wave voided all of them (§7.3.4); there is no surviving subset to prefer, and the closing rounds of a cycle re-run the whole registry regardless (§7.3.3).
If running via the Hadúr plugin (
plugin/agents/hadur-reviewer.md, added Plan 6): dispatch thehadur-reviewersubagent viaTask("hadur-reviewer", brief)instead of pasting this brief inline. The persona file enforces the role discipline (no Write tool — review-only) and cites this section by §-anchor per §44 declaration-by-reference. Same content, plugin-discoverable surfacing.
Persisted focus_priorities are structured §-section IDs (added at methodology v1.1 — Hadur dogfood §16.5 rotation gap). When the controller persists round findings to .hadur/findings/round-NN.json, the focus_priorities field MUST begin with the §-section IDs the brief enumerates as in-scope, in the form ["§N", "§M", ...] (or bare "N" — the parser strips the § prefix). Free-text commentary may follow the structured IDs. The S2 section-coverage signal reads this field literally; structured IDs let it match the canonical Contract §-keys without falling back to fuzzy text matching. Briefs that rotate focus per §16.5 must enumerate the §-IDs they cover; the controller verbatim-copies that list into the persisted JSON before adding any free-text commentary.
The persisted round record carries its lens (added at methodology v1.2). Beside focus_priorities (the S2 axis — where the reviewer looked), each round's findings record a lens field (the S1′ / S7 axis — what kind of question was asked), as a registry ID from §7.3.1: "lens": "L6". Where a round dispatches several reviewers under different lenses, the field is a list: "lens": ["L6", "L13"]. S7 uses it to build the lens × round matrix and assert no blank rows; S1′ uses it to attribute a 0C/0M verdict to a specific lens's streak — in both cases only after the claim is certified against the round's lens_provenance and code_identity (§7.3.4). The field alone establishes nothing.
A round with no lens field is uncountable, not L1. It advances no streak and fills no row in the lens matrix. This is deliberate and is §6.5 applied to the methodology's own bookkeeping: defaulting an unlabelled round to "presumably contract conformance" would let a cycle converge on rounds nobody can characterise afterwards. Rounds recorded before v1.2 ratification are grandfathered as uncountable — they do not block convergence retroactively, but they cannot be credited toward S7 either; a cycle in flight at the v1.2 boundary re-runs the registry.
A lens field on its own is a claim, and a claim is not evidence (added v1.3). The record therefore carries two more fields, and S1′/S7 read those — the lens list alone buys nothing (§7.3.4):
code_identity— the identity of the code the round reviewed: the VCS commit, a digest over the tree at review time excluding the record directory, and adirtyflag. Rounds with no identity, a stale identity, ordirty: trueadvance no streak.lens_provenance— one row per claimed lens:{lens, reviewer, brief_path, brief_sha256}. The brief must live under the record directory (outside the reviewed surface, or writing it would void the very streak it feeds), must hash to the recorded value, and must name its lens. Thereviewerand thebrief_sha256must each be unique within the round.
An operator who writes "lenses": ["L1", …, "L13"] and nothing else has written thirteen strings of JSON. Before these fields existed, that bought thirteen exercised lenses and a CONVERGED verdict on two empty files — which is not a hypothetical, it is how the v1.2 amendment was broken (§7.3.4, Appendix A.4).
The L13 verdict has a stricter admissible form (§7.3.2). For every named lens, CLEAN is a complete verdict: the question is pinned in the brief, so "no" is an answer to something. For L13 there is no question, so a bare CLEAN says nothing that a reviewer who opened no files could not also say. A free sweep reports:
NO FINDING — examined: <the surface actually traversed: files opened, entry points
traced, journeys walked; and the area deliberately NOT reviewed, if breaking a tunnel>
A bare CLEAN from L13 advances no streak and fills no S7 row — the same rule, and the same reason, as §5.8's refusal of "looks sound" from a breaker. The two clean free sweeps in an L13 streak must additionally come from two different reviewers; one reviewer's second unfocused pass re-runs the first one's attention and corroborates nothing.
Convention example:
{
"round": 11,
"lens": ["L6", "L13"],
"lens_provenance": [
{"lens": "L6", "reviewer": "reviewer-a", "brief_path": ".hadur/briefs/r11-L6.md",
"brief_sha256": "<sha256 of that file>"},
{"lens": "L13", "reviewer": "reviewer-b", "brief_path": ".hadur/briefs/r11-L13.md",
"brief_sha256": "<sha256 of that file>"}
],
"code_identity": {
"vcs": "git",
"commit": "<40-hex HEAD at review time>",
"digest": "sha256:<tree digest, .hadur/ excluded>",
"dirty": false
},
"focus_priorities": [
"§2", "§4", "§8", "§9", "§10", "§11", "§12", "§15", "§16", "§18", "§19",
"Plan 1 + Plan 2 BOTH CLEAN at R11 — §19 architectural invariant holding firm",
"Trajectory C+M: R7=3 → R8=10 → R9=2 → R10=4 → R11=1 — declining"
],
"findings": [...]
}
Note what the example demonstrates: two lenses, two reviewers, two briefs, two hashes. One reviewer id on both rows would reject both claims — one pass of attention cannot be two questions, and nobody can say afterwards which (if either) was real (§7.3.4).
tools/test_focus_priorities_structured.py enforces this convention at CI time: each round JSON's focus_priorities must contain at least one entry matching ^§?\d+(?:\.\d+)?$. Rounds before methodology v1.1 ratification are grandfathered (the test exempts rounds with generated_at before 2026-04-26 if the field is absent). The companion check asserts lens is present and is a known registry ID (or list of them) for rounds recorded after v1.2 ratification — an unknown ID is a hard failure, not a warning, because the registry may be extended only deliberately (§23) and a typo'd lens is a blank row that looks filled. From v1.3 the schema also pins lens_provenance and code_identity — but note that CI presence checks are the weaker half: a present-but-unbacked field is exactly the failure mode here, so the signals themselves re-derive the hashes and re-check the digest at read time rather than trusting the record (§7.3.4). A schema is a shape; a hash is a fact.
17. Patch agent + verifier brief
A patch agent's brief is brief by design — one finding, one fix, one file. The verifier-gate is what makes it safe.
You are a patch agent for the <project> multi-plan suite.
**Target:** <path to plan file> at line <range>.
**Finding:** <severity> — <one-line summary> (from Round-K reviewer).
**Evidence:** <quoted code/text>.
**Proposed fix:** <description>.
**Apply the fix to the file. Bump the version trinity in lock-step:**
- L1 title: `# Plan #N v<X.Y.Z+1> — <description>`
- Body `**Plan revision:** v<X.Y.Z+1>`
- Body `**Version:** v<X.Y.Z+1> (Wave K — <finding summary>)`
- (If used) Body `**Status:** v<X.Y.Z+1>`
**Constraints:**
- Edit the body, not just the changelog.
- If signatures change, update the §18 Exports / Imports tables in this plan AND every consumer plan's Imports row.
- Anchor `old_string` arguments on unique trailing text to avoid matching Prior narrative blocks.
- After each Edit, run `grep -c "<new_phrase>" <file>` to confirm the new content landed.
**Output:** report describing what was edited (file, line ranges, before/after), and the post-edit greps you ran.
The verifier-gate (Pass 1) brief:
You are the verifier for the <project> multi-plan suite. Patch agent claims to have applied <finding> to <file>.
**Read the file post-edit.** Run these greps:
1. <Landmark grep — does narrative contain the promised symbols?>
2. <Version bump grep — is the header bumped?>
3. <Self-review grep — are counter-factual tests named?>
4. <Class-specific greps for this finding's failure mode>
5. <Commit-blocker greps: `[RESERVED]`, `[TODO]`, `[PLACEHOLDER]`>
**For each grep:** state expected count + actual count. PASS / FAIL.
**Output:** `READY TO COMMIT` or `PATCH INCOMPLETE — DO NOT COMMIT` with specific failed greps quoted.
If running via the Hadúr plugin (added Plan 6): dispatch
Task("hadur-patch-agent", brief)for the patch andTask("hadur-verifier", brief)for the grep verifier. Both persona files (plugin/agents/hadur-patch-agent.md,plugin/agents/hadur-verifier.md) cite this section by §-anchor per §44. The persona files enforce role discipline (verifier has no Write tool; patch-agent has no Task tool — it can't dispatch others, the controller does that).
18. Runtime-semantics walkthrough sub-agent brief
The walkthrough is the second pass of the verifier-gate (§5.2). Dispatch a fresh agent — no conversation context — with this brief verbatim.
You are a runtime-semantics walkthrough agent. Adversarial mode.
**Context:**
- File: <path>
- Block range: <lines>
- The regression being fixed: <one paragraph describing the prior bug>
- What the patch does: <one paragraph>
- Why the fix should close the regression: <one paragraph>
**Apply the 11-item checklist below. For each item, output PASS / SUSPICIOUS / FAIL with one-sentence justification. Be adversarial — imagine the worst reasonable scenario.**
1. Dependency existence — every symbol the patch references exists at the claimed path.
2. Error-path behavior — what does each except / if error / return None / pass swallow?
3. Inner-failure same-class risk — patched path calls another function that could fail with the same symptom?
4. Concurrent callers — DB isolation / unique constraints / SELECT FOR UPDATE / dedup-key holds?
5. Redelivery / retry — state idempotent or does it diverge?
6. Partial commit / rollback — external effects already happened; reconciliation path?
7. Empty-state / first-time — fix still holds when row missing / GUC unset / counter zero / cache empty?
8. External-dep failure — fail-closed (safe) or fail-open (unsafe)?
9. Cross-session / cross-tenant / cross-tab — isolation holds?
10. TZ edges — UTC midnight boundaries in extreme TZs?
11. Test truth — counter-factual: if you revert the fix, does the regression test fail loudly?
**Counter-factual sharpening:** for each claimed regression test, reason about whether reverting the fix makes it fail loudly. If not, flag it as decorative — the test is not exercising the fix.
**Output:** per-item verdicts + ONE summary verdict (ALL-PASS / MINOR-CONCERNS / BLOCK-COMMIT). Cite line numbers. Under 1500 words.
Reading the output. Any FAIL → block commit, spawn follow-up patch, re-walk after fix lands (§5.6). Any SUSPICIOUS → file for next-Minor-wave, proceed with commit + document the concern. ALL-PASS → commit with confidence. Read the walkthrough's REASONING, not just its verdict — a grep-based SUSPICIOUS with weak justification is rescindable; a reasoned BLOCK-COMMIT is not.
If running via the Hadúr plugin (added Plan 6): dispatch
Task("hadur-walkthrough", brief)instead of pasting this brief inline. The persona file (plugin/agents/hadur-walkthrough.md) enforces adversarial-mode role discipline — no Bash, no Write, read-only reasoning — and cites the 11-item checklist (§5.3) + this section by §-anchor per §44.
19. Comprehensive sweep brief template
When dispatching a "comprehensive sweep" agent BEFORE escalating to architectural rework, mandate the seven-phase structure below. Without phase 2.5 (class-wide enumeration), the sweep agent fixes cited sites + sites it independently noticed and stops there — leaving the next deep audit to find the missed remainder.
19.0 The sweep's lens (added v1.2)
Every sweep brief opens with a **Lens:** field, exactly as a reviewer brief does (§16). A sweep is not lens-free by default — it is lens-free by mandate, and the difference matters:
**Lens:** L13 — free sweep (anti-tunnel). §7.3.2 brief, verbatim:
We are not giving you a lens. We are not telling you what to look for. Every other
reviewer this round has been pointed at something; you have not, deliberately, because
the list of things we know to point at is the list of mistakes we have already made.
Read the code with fresh eyes and no focus. Find the question that is not in the
registry. If you find it, it becomes L14.
[When the sweep is dispatched to break a tunnel (§7.7), append:]
The last N rounds focused on X. That narrowing is itself a risk. Deliberately do NOT
review X.
A sweep dispatched under a named lens (say, L2 persistence across all plans) is legitimate and is recorded under that lens — but it is not an L13 free sweep and cannot fill L13's row in the S7 matrix, nor break a tunnel under §7.7. The whole value of L13 is the absence of the instruction; supplying one, however broad, forfeits it.
Close the brief with the admissible-verdict clause (added v1.3, §7.3.2). Because L13 supplies no question, its null verdict is the one place in the methodology where "found nothing" and "looked at nothing" emit the same token unless the brief forbids it:
**Verdict.** `FINDINGS: N critical, M major, K minor` — or, if you find nothing:
NO FINDING — examined: <enumerate the surface you actually traversed: the files you
opened, the entry points you traced, the journeys you walked; and the area you were
told not to review, if this sweep is breaking a tunnel>
A bare `CLEAN` is not an admissible verdict from a free sweep and will not be counted
(§7.3.2). We cannot re-ask your question — you were not given one — so the enumeration
of what you examined is the only evidence this sweep happened. It is the same rule §5.8
applies to a breaker who returns "looks sound".
19.1 Phase structure
Phase 1: Initial reconnaissance (greps per finding class)
Phase 2: Classify each match (EXECUTABLE / NARRATIVE / HISTORICAL / SCOPE-EXEMPT)
Phase 2.5: Class-wide enumeration ← THE LOAD-BEARING STEP
Phase 3: Compile edit list
Phase 4: Apply edits (grep-verify each)
Phase 5: Verifier-gate sweep
Phase 6: Header bumps
Phase 7: Final report (HIGH / MEDIUM / LOW confidence)
19.2 Phase 2.5 mandate (verbatim language)
For EACH finding class identified, before making ANY edits, run a class-wide
grep ACROSS ALL N PLANS to enumerate every hit. Then sort hits into:
- Hits with canonical state (correct → leave alone)
- Hits with HISTORICAL annotation (cite Wave-X-supersession explicitly → leave alone)
- Hits requiring fix (any other state → add to edit list)
Output the full enumerated list per class to your report — even classes with 0
actionable hits should report "Class X: N hits enumerated, M added to edit list,
K HISTORICAL/SCOPE-EXEMPT."
19.3 Constraints for sweep agents (include verbatim in brief)
- No deferrals. If a site cannot be fixed this round, document why explicitly. No "deferred" or "TBD."
- Verifier-gate every edit. After each
Edit, grep-confirm at the expected line. - Use unique trailing-text anchors for
old_stringwhen long-text recurs in Prior narrative blocks (§10.2). - Token budget. Use
Readwith explicit line ranges; never full-read 25K-line plans. - Confidence assessment in final report: HIGH / MEDIUM / LOW with explicit rationale. HIGH means "if next deep audit runs, ZERO new findings of any class C1-C18 (§20)." A sweep agent's HIGH is a claim, not a fact (§10.1) — it did not survive contact with the next deep audit in either the Empenho or the Hadúr cycle.
19.4 When the brief is still insufficient → escalate
If after Phase 2.5 + 18-class enumeration the next deep audit STILL surfaces fresh same-class drift, the cycle has hit its asymptotic-convergence limit. Escalate to the §13 declaration-by-reference invariant.
Check first that the recurrence is same-KIND, not same-PLACE. If the sweeps keep returning to one file or mechanism rather than one class of defect, the diagnosis is a tunnel (§7.7), not asymptotic drift, and the treatment is the opposite one: a free sweep away from that place, not a structural invariant over it.
Decision rule. One narrow round to validate Phase 2.5; if still drifting, ARCHITECTURAL ROUND.
20. The 18-class drift catalog
These 18 classes are the operational floor for any narrative-drift sweep. C1-C10 are the original taxonomy; C11-C15 are sub-classes that strict Phase 2.5 enumeration alone may miss; C16-C17 came from the Hadur dogfood cycle; C18 (fail-open enumeration) came from the Jira-bridge cycle and is the only class in this catalog whose instances are not narrative drift at all — they are live security and correctness defects (§6.5). Extend per project as new sub-classes are discovered.
| Class | Description | Detection grep |
|---|---|---|
| C1 | Wrong-owner attribution in narrative | Plan [N != canonical].*<symbol> |
| C2 | Short-form slug in code blocks | "plan-0[0-9]"[^-] (excluding scope-exempt unit tests) |
| C3 | Helper signature drift (1-arg / 3-arg / kw-only / dict-return) | ^async def export_*\(session,, -> dict\b |
| C4 | Title L1 vs body Version drift | head -1 + head -7 \| grep -E "Status:\|Version:" |
| C5 | 3-arg Callable in code blocks | Callable\[\[.*UUID, UUID\], Awaitable (negative match) |
| C6 | Legacy register kwargs | register_*\([^)]*export_hook=\|legacy_kwarg= |
| C7 | Wrong module path | grep canonical vs found path per plan |
| C8 | Kw-only marker before UUIDs | ^\s+\*,$ near function signatures |
| C9 | §18 stale Plan-N pin freshness | Plan #N v3.X.Y vs current head; also re-run after own bumps |
| C10 | Legacy names in narrative | catalog of retired symbol names; flag outside HISTORICAL annotations |
| C11 | In-body docstrings inside async def / def blocks (ship to production) |
grep -A 30 "^async def" and scan body docstrings |
| C12 | Adjacent-bullet enumeration ±50 lines of fixed line | for each line N you fix, scan N-50 to N+50 for sibling drift |
| C13 | Post-bump freshness re-sweep | re-run C9 AFTER own version bumps land in same wave |
| C14 | Version-header trinity parity | title L1 ↔ **Plan revision:** ↔ **Version:** (and **Status:** if used) all match |
| C15 | Per-plan Why-narrative legacy sweep | scan ALL plans, not just plans the brief explicitly named |
| C16 | Mandate ↔ Detection enforcement gap | for each Mandate clause, run its Detection grep + verify it actually matches what the prose says (e.g., datetime\.now\( regex matching datetime\.now(UTC)); too-tight regex misses real cases, too-broad regex false-positives on legitimate code |
| C17 | Detection comment ↔ Detection script narrative drift | when a Detection script's regex is generalized (e.g., one token → multiple tokens), re-read the surrounding comment block + echo message + ensure they describe the new generalized behavior, not the original narrow case |
| C18 | Fail-open enumeration (§6.5) — a guard, parser, sanitiser, index or CI gate defined as a denylist of the known-bad, so an unrecognised construct passes | Enumerate every guard, then ask the two §6.5 questions of each: (1) what happens to a construct it does not understand? — if "it passes", it is fail-open; (2) is its safety oracle drawn from the same catalog as the threat? — if yes, it is circular. Greps that surface candidates: except <SpecificError> with no bare fallback; literal name lists (in {"requests", "httpx", ...}); if <known_bad>: reject with no else: reject; any indexer that enumerates node/def types (does it drop async def?). The catalog itself is a fail-open enumeration — C18's grep list is a starting point, not a closure |
Verifier-gate sweep (Phase 5) per-class check. After edits, re-run each class's grep + classify any remaining hits:
# Example for C5 (must be 0 in code blocks)
grep -cP '^\s+Callable\[\[AsyncSession, UUID, UUID\], Awaitable' docs/superpowers/plans/*-plan-*.md
# Expected: 0
# Example for C9 (post-bump freshness)
sed -n '10p' plan-X.md | grep -oE "Plan #N v3\.X\.Y"
# Compare to head -3 plan-N.md to confirm pin chases head
When C11-C17 keep recurring AFTER strict Phase 2.5 enumeration, the cycle has hit asymptotic narrative drift (§7.5) — escalate to §13. C18 is not subject to this rule: a fail-open guard is never closed by sweeping for more instances of it, because the instances are precisely what it cannot enumerate. C18 is closed by inversion (§6.5) — prove the safe property — and the inverted guard is then adversarially broken (§5.8) before it is trusted.
Part VI — Adopting and extending
21. Worked example — Empenho
The Empenho project (Portuguese time-tracking / billing / fiscal / AI SaaS) is the canonical worked example. Stats:
- Scope. 9 implementation plans + Plan 0 Contract.
- Volume. ~90k lines of plan markdown.
- Duration. 11+ review rounds + 19+ corrective waves.
- Initial state (Round 1). 318 findings, 73 Critical.
- Round 10. All findings narrow / minor — looked converged.
- Round 11. 6 new Criticals surfaced (outbox drain regression, inline
bus.emit × 4, rawAuditEventwith fabricated columns, §18 signature lies, missingbus_spycoverage on entire plan, SW auth-response caching). - Rounds 11→13. Trajectory 18 → 71 → 81 C+M findings — divergence signal (§7.4) tripped at Round 12. Wave 15a Contract v2.2.0 / v2.3.0 addressed 7 systemic gaps in one cycle.
- Rounds 14→28. Mixed Type 1 / Type 2 / Type 3 mix; multi-phase response with Wave 15c Contract §27 operational-readiness, Wave 16 per-plan C+M patches with mandatory walkthroughs,
docs/ops-readiness.mdas new deliverable. - Rounds 29→33. Asymptotic narrative drift on §36 — 2C/3M, 0C/4M, 3C/2m, 1C/2M/2m, 3C/5M/1m. Each "comprehensive sweep" missed a different sub-class (in-body docstrings, adjacent bullets, post-bump freshness, header parity, per-plan Why-narrative legacy).
- Round 34. Wave 19u Contract v2.8.0 §44 declaration-by-reference invariant landed. R34 returned 0C/0M/0m — first fully-clean round since R28.
Key inflections.
- Rounds 10→11 demolished the diminishing-returns intuition (§8.6). Without that probe, the suite would have shipped with ~6 Criticals hidden.
- Rounds 12→13 demonstrated divergence (§7.4): rotating brief focus surfaced more, not fewer, Criticals. Wave 15a → 15c Contract architectural pass closed the systemic findings in 3 commits instead of 37 per-plan patches.
- Rounds 29→33 demonstrated asymptotic narrative drift (§7.5). 18 narrow waves were spent before §44 architectural escalation; 17 of the 18 were unnecessary in retrospect.
Empenho's plan artifacts are preserved at ~/Nextcloud/Projetos/Empenho/docs/superpowers/plans/. The Contract at version v2.8.0 carries §44 declaration-by-reference and the cross-cycle history is documented in its changelog.
21.1 What Empenho's convergence does NOT certify (added v1.3)
Empenho's R34 convergence would not qualify as convergence under the rules this document now mandates, and the worked example must say so.
Look at the shape of it against §7.3. An architectural invariant (Contract §44, declaration-by-reference) landed in Wave 19u; the next round came back 0C/0M; the loop stopped. That is, precisely, the configuration §7.3's architectural-stop preconditions were added to forbid — and they were added because the Jira-bridge cycle's §13 invariant, in exactly this configuration, was defective in four different ways across three consecutive rounds. Specifically, and stated as absences rather than failures:
- §44 was never adversarially broken. No reviewer was briefed to construct a case that got past it. There was no §5.8 at the time; the role did not exist. We therefore do not know whether it had a bypass, and nobody has looked since.
- §44 was never audited fail-closed (§6.5). Its enforcement was a CI grep. Whether that grep's safety oracle was drawn from the same catalog as the threat — the question §6.5 exists to ask — was not asked of it.
- The cycle stopped on one clean round, under one lens, with no registry. S1′, S7 and the lens registry post-date it by two revisions. Which kinds of question Rounds 29-34 asked is not recoverable from the record; what is recoverable (§7.5, Appendix A) shows them asking one kind — narrative-drift recurrence — round after round. Under §7.7 that reads as a tunnel.
The honest summary: R34 is evidence that the escalation to §44 stopped the asymptotic-drift bleeding, and it is not evidence that the suite was clean. No adversarial verification was performed because nothing in the methodology at that date required one; and this document will not retroactively assert a check that nobody ran. Inventing provenance for its own worked example is the exact failure mode §8.2 names (self-review assertions are CLAIMS, not facts) — committed by the document that names it, in the section a reader trusts most.
Why the example is kept. Empenho is cited throughout for the mechanisms it demonstrates under load — contract-first, the Round↔Wave dialectic, the divergence signal at R12, the asymptotic-drift escalation at R33, the diminishing-returns demolition at R11. Every one of those is portable and every one is evidenced. What Empenho is not is a certificate that the stopping rule was sufficient — and the reason we know it was not sufficient is that a later cycle, run on the same rules, was two rounds and one lens away from shipping a credential leak (§7.3). The worked example earned this document; it does not exempt itself from it.
The general rule, for any project holding an old convergence: a cycle that converged under an earlier revision converged under that revision's rules. It is not retroactively unconverged, and it is not retroactively certified. If it is still live and still shipping, the honest move is the one §22 prescribes for any adoption — run the registry against the current code — not to re-read the old verdict more charitably.
22. Adoption checklist (starter kit)
When adopting this methodology on a fresh project:
- [ ] Create
docs/superpowers/plans/YYYY-MM-DD-plan-00-contract.md(scaffold from §2.1's §-table). - [ ] Define canonical import paths in §2 + forbidden-alias CI regex.
- [ ] Define §18 manifest template (Exports + Imports tables).
- [ ] Define test conventions in §10 (canonical fixtures, behavioral tests).
- [ ] Mark SaaS-only sections (§3, §4, §7)
N/Afor non-SaaS projects — keep numbering stable. - [ ] Write Plan 1 first (foundation — for SaaS: auth, RLS, events, audit; for toolkits: engine, persistence, schemas).
- [ ] Before dispatching any implementation work, run Round 1 review on all drafts.
- [ ] Adopt
feat(TICKET-XX): <desc> (§spec-section)commit format. - [ ] Install pre-commit hook running the §2 forbidden-alias regex.
- [ ] Stub
test_canonical_declaration_single_site.pyin §27.1 manifest with[IMPLEMENTATION-PHASE TODO]body — present from day 1, even if §13 declaration-by-reference is forward work. - [ ] Stub a
docs/ops-readiness.mdempty file with[FORWARD-WORK]header — Type 3 findings have a home. - [ ] Define the version trinity discipline (§10.4) and configure the verifier-gate to enforce it.
- [ ] Adopt the lens registry (§7.3.1) — all 13 lenses, including L13. Extend it for the project's domain if needed; never shrink it. Record it where the round briefs are generated, not in someone's head.
- [ ] Add the
lensfield to the round-findings record (§16) and the lens × round matrix to the convergence check (S7). - [ ] Add the evidence fields beside it —
code_identityandlens_provenance(§7.3.4) — and make the convergence check read those, not thelenslist. Without them S1′ and S7 are satisfied by typing lens names into a file; with them, a streak is anchored to a tree digest and every lens claim carries a distinct reviewer and a hash-pinned brief. Commit before you review — a dirty tree voids every streak by design. - [ ] Print the verification floor (§7.3.5) on every
metverdict. "Verifies artifacts, not attention." A convergence report that omits it overclaims, and overclaiming in the stopping rule is how the last two revisions of this document went wrong. - [ ] Adopt L13's admissible-verdict form (§7.3.2):
NO FINDING — examined: <surface traversed>; a bareCLEANfrom a free sweep counts for nothing, and the two clean sweeps of an L13 streak come from two different reviewers. - [ ] Ship the reachability test (§6.1.1) in CI from day 1 — AST walk: declared surface → operator entry points → fail on anything in neither set. Plus the doc-consistency test beside it: a documented capability is a declared capability.
- [ ] Ship one mutation-proof (§5.7) for each load-bearing invariant test, and record the mutation next to the test so the proof is inherited rather than re-derived.
- [ ] Write the §5.8 breaker brief before the first Critical lands, not after. Its opening line is a fact, not a motivational device: the previous three verifications each found a real bypass.
23. Extending the methodology
If the methodology evolves on a project:
- Changes to the loop (§4) are rare — the Round ↔ Wave dialectic is load-bearing.
- Changes to artifacts (§2) happen per project — some projects need fewer §-sections; few need more.
- Changes to roles (§3) should be conservative — the reviewer-independence invariant is what makes it work.
- The anti-patterns list (Part III) is the highest-ROI section to extend. Every real project surfaces 1-2 new ones.
- The drift catalog (§20) is the operational floor — extend per project as new sub-classes are discovered. Document additions back to the project's memory so subsequent projects benefit.
- The lens registry (§7.3.1) may be EXTENDED, never SHRUNK — and L13 may never be removed. A project that discovers a question the registry does not ask (a domain lens: fiscal correctness, accessibility, migration reversibility) adds it as L14, L15, … and pays for it in S7. A project that finds a lens "not applicable" marks it
N/Awith a reason, exactly as §2.1 does for SaaS-only §-sections — it does not delete the row, because a deleted row is a question nobody will notice was never asked. Shrinking the registry to make convergence cheaper is the fail-open enumeration of §6.5, performed on the methodology itself.
The registry grows through L13. That is the designed mechanism: the free sweep finds a defect no named lens would have found, the coordinator asks "what question would have found this?", and the answer becomes a new lens. L2-L12 were themselves distilled retrospectively, from the findings of a cycle that had no registry — which is the same mechanism paid for at full price, twelve rounds late. L13 makes it prospective. If a project runs a full cycle and L13 surfaces no question worth naming, that is evidence the registry fits the domain — not evidence that L13 was wasted.
The methodology is not static — each project teaches it new failure modes. The discipline is to:
- Start with the catalog from this document.
- Add new sub-classes as they emerge from real R-rounds.
- Keep the catalog visible in sweep briefs (§19).
The §-section numbering in this document is itself stable — additions go at the end of each Part, not mid-renumber. Plan citations in real projects (per methodology §7.4) remain valid across revisions.
24. Carve-out tokens for §18 Mandate item 4
When a Contract amendment introduces no contractual surface that downstream plans must implement, the strict §18 Mandate item 4 atomic-within-wave re-pin obligation produces noise without value. Carve-out tokens make the exemption explicit + machine-checkable.
24.1 Mandate
§18 Mandate item 4 prose may declare named carve-out tokens that exempt specific amendment classes from the atomic-within-wave obligation. Each carve-out token is documented with: (a) name in [bracketed-form], (b) ratification revision, (c) what kind of amendment it exempts, (d) the §-section that authorizes the exemption (typically the §-section being amended).
24.2 Token convention
- Tokens are bracketed lowercase + hyphenated:
[§15-only],[detection-only]. - Tokens appear at the start of the changelog row description column, immediately after the date pipe:
| vX.Y.Z | YYYY-MM-DD | [token] <description>. - Multiple tokens on one row are NOT supported in v1.1 — if an amendment fits multiple carve-outs simultaneously, choose the most specific OR drop carve-out and bump downstream.
- Token names are stable: a ratified token may not be renamed (Detection scripts grep for the exact token).
24.3 Detection mechanism
§18 Detection clause 4 reads the latest Plan 0 changelog row, matches the description column against \| \[(token1|token2|...)\] (anchored at start of column to avoid false-positive on prose mentioning the token literally), and skips the stale-pin check when matched. The matched token is interpolated into the operator-visible echo via grep -oE so the audit trail names the specific carve-out.
24.4 Recognized tokens (extend per project)
| Token | Ratified | Exempts | Authorized by |
|---|---|---|---|
[§15-only] |
Hadur Plan 0 v1.3.0 | Amendments touching only §15 (descriptive-not-authoritative suite manifest summary) | §15 Mandate (Hadur) |
[detection-only] |
Hadur Plan 0 v1.3.1 | Detection-script enforcement tightening of an existing Mandate, no normative change for downstream consumers | §18 Mandate item 4 (Hadur) |
24.5 Adding a new carve-out token
To ratify a new token:
- Identify the §-section being amended + verify its content is descriptive, not normative for downstream plans.
- Author §-prose in that section authorizing the exemption.
- Append the token name + entry to §18 Mandate item 4 carve-out clause.
- Update §18 Detection clause 4 regex to include the new token.
- Self-mark the introducing amendment with the new token in its own changelog row (the amendment that introduces the carve-out is itself a candidate for the carve-out it introduces).
24.6 Rationale
Without carve-out tokens, every Plan-0 amendment cascades downstream re-pin even when the amendment is purely descriptive (e.g., a §15 manifest table refresh, or a Detection-script regex tightening). The cascade produces atomic-within-wave obligations that buy no integrity but cost N additional commits per wave. Carve-out tokens make the "no contractual surface" exemption explicit, machine-enforceable, and auditable.
24.7 Anti-patterns
-
Token-pollution. Adding tokens for any inconvenient amendment dilutes the mechanism. A token must correspond to a §-section whose prose authorizes the exemption — not "this amendment was small."
-
Retroactive un-marking. Once a row carries a token, do not silently strip it. If a token classification was wrong, document the correction in a follow-up changelog row.
-
Multi-token rows. v1.1 explicitly does not support compound carve-outs. If an amendment fits multiple, dispatch is by the most specific token only; the others' exemption is implicit from §-section ownership.
Appendix A — Empenho cycle history (this document's evolution)
This Appendix maps the methodology's §-sections to the Empenho rounds and waves where they were forced by real findings. It is descriptive, not normative — the rules are stated normatively above.
| Section | Forced by | Wave or Round |
|---|---|---|
| §1, §2, §3, §4 (artifacts, roles, loop) | Initial design (pre-Round 1) | Plan 0 v1 |
| §6 (the first four failure modes) | Round 1 baseline triage | Triage memo |
| §7.3 (convergence signals S1-S6, pre-v1.2) | Rounds 9-11 — diminishing-returns intuition vs reality | Round-author memo |
| §7.4 (divergence — 30%/10 systemic rule) | Rounds 11-13 trajectory 18→71→81 C+M | Pre-Wave 15a memo |
| §7.5 (asymptotic narrative drift) | Rounds 29-33 §36 same-class recurrence | Pre-Wave 19u memo |
| §5.2 / §5.3 (mandatory Pass 2 walkthrough + 11-item checklist) | Wave 13 → 5 Criticals shipped grep-approved | Wave 14a |
| §5.5 (mandatory walkthrough for >500-line dispatches) | Wave 15b-B/C/D → ~30 bugs grep-approved | Round 14 retrospective |
| §5.6 (always re-walk after fixes) | Wave 16c Plan 2 v3.10.7 Patch A sequence | Round 16 retrospective |
| §8.1 (narrative-only without verification) | Round 11 — write_audit_event mirror |
Wave 11a |
| §8.2 (self-review claims, not facts) | Plan 5 changelog "9 sites rewritten"; T9 had 4 inline emits | Wave 11b |
| §8.3 / §10.4 (version-header trinity) | Wave 19q Plan 8 body version not bumped | Wave 19r |
| §8.6 (diminishing-returns intuition wrong) | Round 10 looked converged; Round 11 surfaced 6 new Criticals | Round 11 |
| §10.2 (verifier-gate trap — Prior narrative match) | Wave 19j Patch E matched v3.15.0 Prior block | Wave 19k F1 |
| §10.5 (chasing past escalation point) | 18 narrow waves before §44 architectural | Wave 19u retrospective |
| §12 (Contract architectural pass) | Wave 15a Contract v2.2.0 / v2.3.0 — 7 systemic gaps | Wave 15a |
| §12.2 (cleanup-sweep after parallel agents) | Wave 15a §22 orphan incident | v2.2.1 retrospective |
| §13 (declaration-by-reference §44) | Rounds 29-33 → Wave 19u Contract v2.8.0 §44 | Wave 19u |
| §13.5 (grandfather clause) | Avoided multi-day rewrite cost on §44 | Wave 19u |
| §14.1 (cascade on parent→child) | Wave 15b-B Plan 4 ExtraCandidate retention | Wave 15b-B |
| §14.2 (stricter-module-policy coexists) | Plan 9 v3.8 F1/F2 + USER_REQUESTED | Wave 15b-C |
| §14.3 (Contract forward-looking claims) | Plan 9 v3.8 §22.7 enum-add forward-guard | Wave 15b-D |
| §14.4 (co-located helper hoist) | Wave 15b-B 14 copies of _compute_retention_expires |
Wave 15b-B.6 |
| §15 (grandfather + stub-as-mandate) | Wave 15c §27.1 + Wave 19u §44 | Cross-cycle |
| §19 (comprehensive sweep — Phase 2.5) | Wave 19q "comprehensive" without Phase 2.5 → R32 | Wave 19s |
| §20 (drift catalog C11-C15) | Rounds 30-33 sub-classes that strict Phase 2.5 missed | Wave 19t |
| §10.3 (post-bump freshness paradox) | Wave 19s C9 closure + same-wave bumps | Wave 19s post-mortem |
The §-numbering of this document was assigned once, in this canonical reorganization. The Appendix above is descriptive history; future revisions append rows rather than renumber sections.
A.2 Hadur dogfood cycle (R1-R14, 12 waves, 4 architectural escalations)
The Hadur project (this very methodology's operationalization) ran a complete dogfood loop on its own Plan 0 + Plan 1 + Plan 2 between 2026-04-25 and 2026-04-27. Selected milestones:
| Round | C+M | Notable | Wave response |
|---|---|---|---|
| R1 | 30 | Initial review of refactor-aligned plans | Wave 1 architectural pass (HADUR-113..117) |
| R5 | 3 | Plan 1 Task 8 narrative-vs-code drift | Wave 5b regen (HADUR-132) |
| R7 | 3 | Plan 1 Tasks 7+14 same class as R5 Task 8 | Wave 7b regen — narrow corrective |
| R8 | 10 | 13 Plan-1+2 Tasks same class — §15.1 escalation | Wave 8a §19/§19.5 architectural — closes the class via grandfather |
| R11 | 1 | Plan 0 §15 stale-pin third recurrence — second §15.1 escalation | Wave 11a §44 applied to §15 itself (table-cell-level) |
| R12 | 1 | §18 Detection didn't honor §15 carve-out | Wave 12a [§15-only] token + generalized to [detection-only] |
| R14 | 0 | All three plans 0C/0M | Convergence achieved |
The cycle distilled three v1.1 candidates ratified into that revision: §13.10 (granularity scaling), §24 (carve-out tokens), §20 catalog C16/C17 (Mandate↔Detection enforcement-gap class).
Pattern re-confirmed. §15.1 architectural escalation triggered TWICE in 14 rounds (Wave 8a + Wave 11a), each time closing a recurring drift class that 2-3 narrow corrective waves could not. This re-validates the Empenho Wave-19u precedent at smaller-project scale.
A.3 Hadúr Jira-bridge cycle (R16-R27) — the cycle that forced v1.2
The Jira-bridge convergence loop ran Rounds 16-27 over Hadúr's Jira integration: 48 findings — 3 Critical, 34 Major, 9 Minor, 2 Nit. Every normative claim added at v1.2 is anchored in this record.
The headline number. Of the 37 Criticals + Majors:
| Where it came from | Count | Share |
|---|---|---|
| The first-ever run of a review lens | 10 | 27% |
| Verifying a previous fix | 12 | 32% |
| Everything else | 15 | 41% |
59% of the cycle's serious findings came from two mechanisms v1.1 did not mandate. Neither lens rotation across kinds of review, nor independent adversarial verification of a fix, appeared anywhere in the document. Both happened because the loop felt wrong to its coordinator — not because a §-section required them. A methodology that depends on its operator's unease is not mechanical, and the whole purpose of this document is to be mechanical.
Section-by-section provenance:
| Section | Forced by | Round or Wave |
|---|---|---|
| §7.3 S1′ (streak per lens; any change to the code voids all streaks — v1.2 said "any wave", which is the same rule stated in a bypassable way, see A.4) | R20 came back 0C/0M under a stale lens; R21 rotated the lens and found 5 Majors immediately. Same-lens R21 would have tripped S1 and shipped a credential leak | R20 → R21 |
| §7.3 S7 + §7.3.1 (lens registry) | 10 of 37 C+M found on a lens's first-ever run; L4/L5/L6/L7 produced all ten and were named nowhere in v1.1 | R16-R27 aggregate |
| §7.3.2 (L13 free sweep, fail-closed complement) | R25's unfocused sweep found 2 Majors that R21-R24 walked past — a never-called function and findings filed under the wrong Epic | R25 |
| §7.3.3 (the cost of S1′) | Stated at ratification, not forced — the honest accounting of what S1′ demands | v1.2 drafting |
| §5.7 (mutation is the verifier of the verifier) | 2 tests that passed while testing nothing: a concurrency test whose threads wrote to different files; a starvation test that mocked the function under test | R16-R27 |
| §5.8 (independent adversarial verification of a Critical) | 3 independent verifications of a Critical fix → 3 real bypasses, each in a fix already reported complete + gate-green + tested | Waves 26, 27; R23 |
| §6.1.1 (mechanical reachability audit) | 8 C+M despite §6.1 having named the mode since Empenho R1 — incl. hardening (lock + schema + atomic writes) applied to a function never called since Plan 1 | R25 |
| §6.5 + §20 C18 (fail-open enumeration) | 7 C+M incl. 2 of 3 Criticals. Purest case: secret redaction whose safety oracle was the credential catalog the threat was drawn from | R16-P02-M2 … R27-P02-C1 |
| §7.7 (the tunnel signal) | R21→R24 each drilled deeper into the bridge-disable invariant, each found a real but narrower defect, each felt like progress | R21-R24 |
| §7.3 architectural-stop preconditions | The §13 invariant was defective 4 ways across 3 rounds: intra-module (R23), fail-open on bound methods (R24), dropped async def (R25), 4-name HTTP denylist that urllib3 passed (R25). The stop rule would have declared convergence after Wave 22a |
R23-R25 |
| L12 (packaging & environment) | pyyaml undeclared — clean installs crashed at startup, invisible on every dev machine |
R16-R27 |
What v1.1 got right, and v1.2 does not repudiate. Reviewer independence (§3.2/§3.4) — every finding of substance came from a reviewer who could not see the others' briefs; when two ran in parallel on the same round, they found disjoint things. The counter-factual question (§5.4) — the single highest-yield question in the document. The §13 declaration-by-reference escalation — stopping the fourth narrow sweep and shipping an enforced invariant was right, and it immediately caught a call site nobody had named. The Round↔Wave separation — diagnosing and fixing in separate passes prevented the "fix what you found" tunnel at the wave level, even while the round level tunnelled (§7.7).
The through-line. Five of the seven v1.2 amendments say the same thing in different places: the methodology was good at making you look carefully, and had no mechanism for making you look elsewhere. Each gap was an enumeration one element short — which is exactly the bug (§6.5) the reviewed codebase kept shipping. The methodology was committing, in its own convergence rule, the defect it teaches you to avoid.
A.4 The Breaker turned on the amendments (v1.2 → v1.3)
v1.2 shipped §5.8: a Critical fix, and any landing architectural invariant, must survive a reviewer briefed to break it. The amendments themselves were an architectural invariant — they rewrote the loop's stopping rule — so the rule was applied to itself before the ink dried. The Breaker found four Criticals and was right about all four.
| # | The amendment's claim | The construction that broke it | Now |
|---|---|---|---|
| 1 | S1′: "any wave voids every streak" | The wave boundary was inferred from findings marked resolved. Transition nothing → no wave → no reset → streaks accrue across code that changed. The bypass is doing nothing. |
Round-level code identity (tree digest, record-dir excluded); the inferred boundary is deleted, not demoted (§7.3.4 Rule 1) |
| 2 | S7: "every lens exercised" | S7 read the lenses array and believed it. Thirteen strings of JSON = thirteen exercised lenses. The signal built to catch the unasked question was satisfiable by not asking it. |
Lens provenance: distinct reviewer + hash-pinned brief on disk that names the lens (§7.3.4 Rule 2) |
| 3 | "Convergence is now mechanical" | Two round files, findings: [], lenses: [L1…L13] → CONVERGED. The test fixture was the demonstration. Verdict: "a more sophisticated rubber stamp than the v1.1 rule it replaces — because it now REPORTS mechanical confidence where v1.1 at least left a human uneasy." |
Both rules above, plus the verification floor printed on every met verdict (§7.3.5) |
| 4 | §6.1.1: every declared capability reaches an entry point | The Breaker had no agent, no brief, and no dispatch surface. The **Lens:** field the amendment mandated could not be emitted by the brief template it shipped. The amendment failed the reachability audit the amendment wrote. |
Breaker wired end-to-end (role → brief → dispatch), lens field emitted |
Root cause, identical under #1 and #2: "the code did not change" and "this lens was exercised" were claims the tool accepted from bookkeeping the loop controls — §6.5's circular oracle, committed in the convergence rule, in the same revision that added §6.5. Findings #1 and #2 were reachable by omission, which is the bypass an operator reaches for by accident rather than by malice, and therefore the worst kind.
Two further things the record should keep. Attacking its own fix, the same agent closed one more bypass: git update-index --assume-unchanged makes a VCS report a clean tree while the file on disk says otherwise. And having built the gates, it constructed the forgery that passes them — twenty-six brief files, twenty-six reviewer ids, one honest digest, converging on nothing — which is how §7.3.5's floor is known to be a floor rather than a hedge.
And the cost, paid in full: the repo that ships this methodology has 27 rounds of real review on record, and under §7.3.4 every one of them now counts toward nothing — they carry no code identity and no lens provenance. That is the correct answer, and taking it was the test of whether the rule was meant.
What this episode is evidence for. §5.8, and nothing else. Four Criticals, in a 200-line amendment, written carefully, by people who had just spent a cycle learning exactly these lessons, reviewed and shipped — and broken within a day by one reviewer whose only instruction was break it. Every argument in §5.8 for adversarially verifying an architectural invariant is this table.
Appendix B — Hadur's role
Hadur (hadur_core Python package + hadur CLI binary, distributed at hadur.ai) operationalizes this methodology into mechanical, observable, reproducible tooling. Where the methodology says "convergence signals S1′-S7," Hadur ships hadur_core.convergence.aggregate_verdict returning the signals + one aggregate verdict + per-signal met / unmet detail. Where the methodology says "§-section × round coverage matrix," Hadur ships hadur_core.coverage.build_matrix returning the matrix + ASCII heatmap + CSV / JSON exports — and the lens × round matrix of S7 is the same construction on the round record's certified lenses (§7.3.4).
How S1′'s streak-voiding rule is mechanised, and how it is NOT (corrected at v1.3 — the sentence this paragraph replaces claimed "a wave landing IS the event that resets every lens's streak, and Hadur already records it", which was wrong twice: this repo had 27 rounds and zero wave records, and the reset is not keyed on wave records at all):
- The reset is keyed on the code, not on any record of a wave. Each round persists a
code_identity— the commit, a sha256 digest over the git tree at HEAD excluding.hadur/, and adirtyflag (hadur_core.code_identity). Two rounds count toward one lens streak only if their digests match and still match the tree on disk. A wave voids every streak because a wave changes the tree; so does an unrecorded hotfix, and neither has to be written down (§7.3.4 Rule 1). The v1.2 implementation, which inferred the wave boundary from findings markedresolved, is deleted — not demoted, deleted: it fail-opened on the operator who simply transitions nothing (Appendix A.4). - A lens claim is certified, not read.
hadur_core.lenses.certify_lensescounts a lens only with alens_provenancerow carrying a reviewer id distinct within the round and a brief under.hadur/whose sha256 matches the record and whose text names the lens.findings.schema.jsonpins lens ids to the registry allowlist, so an invented lens is a schema error at write time rather than a phantom row that silently satisfies S7. hadur_core.wavesstill exists and is still useful — FileLock-protected lifecycle, atomicos.replacetransitions, trajectory history forhadur trends. What it is not is an input to any convergence signal. Wave records are for humans; the digest is for the gate. Keeping them out of the gate is the whole lesson of Appendix A.4: bookkeeping the loop controls cannot adjudicate the loop.- The floor is printed, not implied.
hadur_core.lenses.VERIFICATION_FLOORis appended to the detail line of everymetS1′ and S7 verdict: Hadur verifies artifacts, not attention (§7.3.5). A convergence tool that reports mechanical confidence past the point where it has any is the failure this document exists to prevent, committed by the tool that enforces it.
Hadur's relationship to this methodology is strict and one-directional: the methodology is upstream; Hadur is downstream. Hadur does not amend the methodology — when the methodology evolves, Hadur's **Methodology revision:** header bumps, plans drift, and Hadur's suite-manifest summary detects the drift. Hadur exists to make this methodology faster and harder to skip on future projects, not to redefine it.
Forward work that consumes this document's normative text:
- §13 declaration-by-reference invariant + symbol catalog → Hadur Sub-project 1 Plan 4 (CI test stub
test_canonical_declaration_single_site.py). - §20 18-class drift catalog → Hadur Sub-project 3 Integration (
hadur sweepCLI verb that runs the C1-C18 greps mechanically). C18 is the exception: its instances cannot be enumerated by grep by construction (§6.5) — the verb surfaces candidate guards for a human or L9 reviewer to invert, and must say so rather than reporting a clean sweep. - §7.3.1 lens registry + S7 → shipped: the
lens+lens_provenance+code_identityfields on the round record, the lens × round matrix, and ahadur convergenceverdict that reports which lenses have never run, which were declared with no provenance (a different failure, needing a different fix), and why a round counts toward no streak (no identity / dirty tree / code changed since). A convergence tool that cannot say "you have never run the security lens" reproduces, in the tooling, the exact gap v1.2 exists to close — and one that cannot distinguish "never run" from "claimed without evidence" reproduces the gap v1.3 exists to close. - §7.3.2 L13 anti-negligence rule → NOT YET MECHANISED, and the document does not pretend otherwise. Two clauses are today discipline, not code: (a) the admissible-verdict form
NO FINDING — examined: <surface traversed>— a bareCLEANfrom a free sweep should not advance L13's streak, but nothing currently rejects one; (b) the two clean free sweeps must carry two different reviewer ids —certify_lensesenforces reviewer-distinctness within a round, not across the two rounds of a streak. Both are checkable (the first against the brief text, the second againstlens_provenanceacross rounds) and both are wanted. Until they land, an L13 streak is exactly as strong as the operator's honesty — which is, per §7.3.5, where every one of these gates bottoms out anyway; the difference is that these two have a floor that is reachable and have not reached it yet. - §6.1.1 reachability audit → an AST-walk CI test (declared surface → operator entry points → fail on anything in neither set) + the companion doc-consistency test.
- §5.7 mutation-proof + §5.8 breaker brief → prompt templates beside the existing reviewer / patch / verifier / walkthrough briefs.
- §22 starter-kit checklist → Hadur Sub-project 1 Plan 3
hadur initCLI verb (scaffolds Plan 0 + stub manifest from this document's §-section list). - §16 reviewer brief / §17 patch+verifier brief / §18 walkthrough brief / §19 sweep brief → Hadur Sub-project 1 Plan 1
hadur_core.prompts.bundled/Jinja2 templates (each template renders the brief from the project's current state).
The methodology's text is canonical; Hadur's code is one consumer. Future consumers (other tools, other languages, other sub-projects) cite this document at the same review-methodology.md path and inherit the same normative content.