Decisions

ADR-168: Auto-diagnosis library shape — YAML rules, regression fixtures, curation policy

The auto-diagnosis library is a directory of versioned YAML rules under `gyrum-knowledge-base/auto-diagnosis/<shape>.yaml`. Each rule has six required fields (`name`, `trigger`, `diagnosis`, `proposed_fix`,…

#168

ADR-168: Auto-diagnosis library shape — YAML rules, regression fixtures, curation policy

Status: Proposed Date: 2026-05-07

Related: warp#1782 (parent EPIC: self-healing fleet), warp#1783 (amendment: self-healing rides the existing agent-executor queue), ADR-167 (sister: structured failure event schema — defines the events this library matches), ADR-169 (sister: auto-swarm dispatch + auto-merge gating — defines what happens after this library files a ticket), ADR-164 / warp#1735 (ticket category axis — the categories library entries declare), ADR-117 (precedent: module guidelines — same versioned-YAML-with-regression-fixture shape), ADR-115 (precedent: principle-aware reviewers — quote the library entry when reviewing auto-filed PRs), ADR-107 / ADR-110 (precedent: gates as playbook primitives + observe-and-file dominant pattern), warp#1779 (today's atomic-deploy regex bug — the first library entry's motivating shape)

TL;DR

The auto-diagnosis library is a directory of versioned YAML rules under gyrum-knowledge-base/auto-diagnosis/<shape>.yaml. Each rule has six required fields (name, trigger, diagnosis, proposed_fix, approval_mode, version), a sister <shape>.fixture.json regression fixture, and a curation policy that gates promotion from approval-neededauto. The library is read by the detector at startup, hot-reloaded on file change, and validated by gyrum-validate-auto-diagnosis at PR-author time. This ADR locks the rule shape so the first ten library entries (Phase 3 of warp#1782) can ship in parallel without fighting over the YAML format.

Context

ADR-167 locks the wire format every gyrum-* tool emits when it fails. The detector reads that stream and matches against pattern primitives. But primitives alone don't decide what to do — knowing "fire #3 of (deploy-project, fetch-ghcr-versions, regex-no-match:ghcr-tag-list) in the last 10 minutes" is not the same as knowing "this is the atomic-deploy regex bug, propose this PR template, this is safe to auto-merge after fire #5."

The mapping from "matched pattern" to "diagnosis + proposed fix + approval gate" is what the auto-diagnosis library carries. Today's session generated the empirical case: ten distinct break-and-fail events, each diagnosable in ~1 minute by an operator who has seen it before. That diagnosability is institutional knowledge that lives in operator memory and chat scrollback. The library is the substrate that promotes it to durable, queryable, regression-tested artefacts.

Three structural problems force the library to exist as a first-class artefact rather than as inline code in the detector:

  1. Curation cadence ≠ detector release cadence. New failure shapes appear continuously (today: ten in one session). New detector code releases once per few weeks. If the diagnosis-to-action mapping lives in detector source, every new shape forces a detector release; the library's hot-reloadable YAML decouples curation from infrastructure.

  2. Regression fixtures need a stable home. Per warp#1782's pitfalls list: "DON'T let the library become a dumping ground — every entry has a regression test; CI rejects PRs that add entries without tests." A regression fixture pins the trigger pattern + asserts the detector matches it; the fixture lives next to the rule in the same PR. This is the same shape as ADR-117's module guidelines (versioned YAML + regression suite), reused.

  3. Approval-mode is the trust accumulator. Per warp#1782's pitfalls list: "DON'T auto-fire on first occurrence — every new failure shape requires N≥3 operator-handled cycles to validate the pattern + diagnosis BEFORE library entry. Trust must accumulate." The approval mode (requiredrequired-after-Nauto) is per-entry, evolves over time, and needs an explicit migration path in the library shape — not a hand-edit in detector source.

warp#1783 (the amendment) clarified that the dispatch is handled by the existing agent-executor queue: the library's job is to file the right shape of ticket, then step out. ADR-169 covers the queue-pickup contract; this ADR covers what the library entry contains.

This ADR locks the YAML rule format, the regression-fixture contract, the curation policy, and the validator + reviewer surface that gates additions to the library.

Decision

The auto-diagnosis library is a directory of versioned YAML rules at gyrum-knowledge-base/auto-diagnosis/<shape>.yaml. Each rule has the six required fields below, a sister <shape>.fixture.json regression fixture pinned in the same PR, and a curation policy gating promotion from approval-needed to auto mode. The detector reads the directory at startup, hot-reloads on file change, and rejects malformed entries (failing closed). The validator gyrum-validate-auto-diagnosis runs at PR-author time and as part of gyrum-review-pr's gate set on PRs that touch auto-diagnosis/**.

The shape has six parts: the YAML rule format, the regression-fixture contract, the library location + hot-reload semantics, the curation policy (who adds, who reviews, how entries get promoted), the validator surface, and the migration path for an entry's lifecycle.

1. YAML rule format

# gyrum-knowledge-base/auto-diagnosis/atomic-deploy-regex-404.yaml
name: atomic-deploy-regex-404
version: 1

trigger:
  primitive: n-consecutive-same-signature
  match:
    tool: deploy-project
    step: fetch-ghcr-versions
    error_signature: regex-no-match:ghcr-tag-list
  threshold:
    n: 3
    window_minutes: 10

diagnosis: |
  The atomic-deploy step extracts GHCR image versions via a regex over the
  GitHub container registry response. When GHCR's API response shape drifts
  (e.g. a field rename, an extra wrapper element, a pagination change) the
  regex stops matching and every subsequent deploy fails at the same step.

  The signal that this is THIS shape rather than a one-off transient is the
  N-consecutive same-signature pattern: the regex is deterministic, so a
  flake would not produce identical signatures on consecutive fires.

proposed_fix:
  category: approval-needed
  ticket_template: |
    # atomic-deploy regex bug — fetch-ghcr-versions
    Detector observed {{ n_fires }} consecutive failures of
    `deploy-project` step `fetch-ghcr-versions` with signature
    `regex-no-match:ghcr-tag-list` between {{ window_start }} and
    {{ window_end }} on context `{{ context }}`.

    ## Diagnosis
    {{ diagnosis }}

    ## Proposed fix
    Update the GHCR-tag-list regex in
    `ansible/roles/atomic-deploy/tasks/fetch.yaml` to match the current
    response shape. Sample failing payload captured at
    `~/.gyrum/failure-fixtures/{{ event_id }}.json`.

    ## Verification
    - `ansible-playbook deploy-project.yaml` against `{{ context }}` succeeds
    - The failing fixture parses with the new regex
    - Coverage on the regex matcher does not regress

  swarm_brief: |
    You are fixing the atomic-deploy GHCR regex.
    The failure signature is `regex-no-match:ghcr-tag-list`.
    The fixture at `~/.gyrum/failure-fixtures/{{ event_id }}.json` shows
    the current GHCR response shape. Update the regex in
    `ansible/roles/atomic-deploy/tasks/fetch.yaml` minimally — match
    the new shape; do not refactor unrelated code.
    Add a regression test pinning the new payload.
    Reference warp#{{ ticket_number }} in the PR title and commit message.

  verification_spec:
    expected_diff_paths:
      - ansible/roles/atomic-deploy/tasks/fetch.yaml
      - ansible/roles/atomic-deploy/tests/test_fetch.yaml
    expected_diff_max_lines: 50
    must_pass:
      - ansible-lint ansible/roles/atomic-deploy/
      - cd ansible && pytest roles/atomic-deploy/tests/

approval_mode:
  current: required
  promotion_path:
    next: required-after-3
    after_n_successful_fires: 3
    requires_operator_signoff: true

debounce:
  window_hours: 1
  max_fires_per_day: 3

provenance:
  added_by: jon-laptop
  added_at: 2026-05-07T13:00:00Z
  motivating_ticket: warp#1779
  motivating_events: 10  # today's evidence count
  reviewer_signoff: pending  # operator must approve first promotion

Required fields:

  • name — globally unique within the library; matches the filename without .yaml. Used as the entry's stable identifier across versions, in audit trails, and in auto-filed ticket bodies. Closed naming convention: kebab-case, ≤60 chars, descriptive of the shape (not the symptom).
  • version — integer starting at 1. Bumped on any semantic change to trigger, diagnosis, proposed_fix, or approval_mode. Bumping requires a PR with the regression fixture re-run; the fixture's expected output is keyed on the version. Same shape as ADR-117's module-guideline versioning.
  • trigger — declarative match against the event stream defined in ADR-167. Fields:
    • primitive{n-consecutive-same-signature, sudden-new-signature, systemic-across-context, stale-success, drift} — closed enum mapped to the detector's pattern primitives in ADR-167 §4.
    • match — selectors over the event fields (tool, step, error_signature, severity, repo, context). Fields not specified are wildcarded.
    • threshold — primitive-specific: n + window_minutes for n-consecutive; lookback_days for sudden-new; etc. Defaults are primitive-specific and validated against the closed schema.
  • diagnosis — multi-paragraph human-readable string. Embedded in the auto-filed ticket body; consumed by the swarm-dispatched agent's brief. Required to name three things: (a) what condition produces this shape, (b) why this signature distinguishes it from transients / flakes, (c) what category of fix is canonical. The gyrum-validate-auto-diagnosis validator enforces minimum length (200 chars) and rejects placeholder text.
  • proposed_fix — the actionable half of the entry. Sub-fields:
    • category ∈ ADR-164's {warning, approval-needed, incident} — locks the kanban + dashboard surface the auto-filed ticket lands in.
    • ticket_template — Mustache-style templated markdown rendered into the ticket body. Variables ({{ n_fires }}, {{ context }}, {{ event_id }}, etc.) are populated by the detector from the matched events.
    • swarm_brief — the agent's brief; consumed by the staff agent that picks the auto-filed ticket via the existing queue (per warp#1783). The brief MUST name the file paths to touch, the test to add, and the ticket to reference.
    • verification_spec — what the detector inspects after the fix-PR lands to confirm the fix worked. Sub-fields:
      • expected_diff_paths — file paths the fix should touch. ADR-169 uses this for auto-merge gating.
      • expected_diff_max_lines — defence against scope creep; ADR-169 fails-closed if exceeded.
      • must_pass — commands that must exit 0 against the merged fix.
  • approval_mode — the per-entry trust state. Sub-fields:
    • current{required, required-after-N, auto} — what the auto-filed ticket's category is on a fresh fire.
    • promotion_path — declarative description of the next promotion step (e.g. "after 3 successful fires with operator signoff, move to required-after-3"). The library reader does not auto-promote; promotion requires an explicit PR bumping current and version. The path field exists so the curator and reviewers can see where this entry is heading.

Required sibling fields (separate top-level blocks):

  • debounce — sub-fields window_hours (sliding window per (name, match) group; default 1) and max_fires_per_day (default 3). Per warp#1782's pitfalls list: "DON'T let detector drift cause notification storm — debounce + max-fires-per-day per shape." The detector reads these fields and applies them BEFORE filing the ticket; an entry without explicit debounce inherits the closed defaults.
  • provenance — sub-fields added_by, added_at, motivating_ticket, motivating_events, reviewer_signoff. The audit trail half of the entry. The curation policy (§4 below) gates entries on having reviewer_signoff: <user> for any approval_mode.current=auto.

2. Regression-fixture contract

Every YAML rule has a sister fixture at gyrum-knowledge-base/auto-diagnosis/<name>.fixture.json:

{
  "name": "atomic-deploy-regex-404",
  "version": 1,
  "fixture_events": [
    {"tool":"deploy-project","context":"warp","step":"fetch-ghcr-versions","error_signature":"regex-no-match:ghcr-tag-list","input_hash":"sha256:7d8c…","ts":"2026-05-07T12:46:29.728749Z"},
    {"tool":"deploy-project","context":"warp","step":"fetch-ghcr-versions","error_signature":"regex-no-match:ghcr-tag-list","input_hash":"sha256:7d8c…","ts":"2026-05-07T12:48:30.000000Z"},
    {"tool":"deploy-project","context":"warp","step":"fetch-ghcr-versions","error_signature":"regex-no-match:ghcr-tag-list","input_hash":"sha256:7d8c…","ts":"2026-05-07T12:50:31.000000Z"}
  ],
  "expected_match": true,
  "expected_ticket_category": "approval-needed",
  "expected_swarm_brief_contains": [
    "atomic-deploy GHCR regex",
    "ansible/roles/atomic-deploy/tasks/fetch.yaml"
  ],
  "negative_fixtures": [
    {"description":"two fires (under threshold) does not match","events":[{},{}],"expected_match":false},
    {"description":"different signature does not match","events":[{"error_signature":"http-403:insufficient-scope"}],"expected_match":false}
  ]
}

Required fields:

  • name + version — must match the rule file's name + version. CI fails closed on mismatch.
  • fixture_events — sequence of event JSON objects (per ADR-167's wire format) that, when fed to the detector with this rule loaded, produce a match.
  • expected_match — boolean. true for a positive fixture; mostly true since the negative-fixtures sub-array carries the negatives.
  • expected_ticket_category — must equal the rule's proposed_fix.category. Cross-check.
  • expected_swarm_brief_contains — list of substrings the rendered swarm brief MUST contain. Catches templating regressions where a Mustache variable goes empty.
  • negative_fixtures — array of sub-fixtures asserting expected_match: false. Required to have ≥1 negative; the validator rejects rules whose fixture lacks negatives.

The CI gate gyrum-validate-auto-diagnosis runs the detector engine against each rule's fixture in dry-run mode and asserts:

  1. Positive fixture matches and produces the expected ticket category + brief.
  2. Each negative fixture does NOT match (catches over-broad triggers).
  3. Rule version matches fixture version.
  4. The fixture's fixture_events round-trip the wire-format validator (ADR-167's wire-edge validator).

Without the fixture, the rule is "claimed" — somebody believed it would match a class of failures, but no test pins the claim. Per ADR-117's precedent, claimed-without-evidence is the failure mode regression fixtures exist to retire.

3. Library location + hot-reload semantics

Location: gyrum-knowledge-base/auto-diagnosis/. Reuses the existing knowledge-base repo per ADR-116 (RAG layer); the directory is one of several first-class data sources the broker indexes.

Read path: the detector reads the directory at startup. It walks each .yaml file, parses + validates each entry, and registers the rule in an in-memory matcher. On file change (detected via inotify; on systems without inotify, a 30s poll fallback), the detector re-reads the changed file and atomically swaps the in-memory rule. A rule that fails validation on reload is rejected with a warning; the previous version remains active. Failing closed on reload means a malformed YAML cannot take down rules that were healthy; the curator's typo doesn't break the fleet.

Versioning: the library's git history is the authoritative version log. Every PR that adds, modifies, or removes a rule is the audit trail. ADR-115's principle-aware reviewers quote the rule's current version when reviewing auto-filed PRs, so the question "which library version produced this auto-fix?" is structurally answerable.

Out-of-band kill switch: each rule's filename can be renamed to <name>.disabled.yaml to disable the entry without deleting it. The detector skips .disabled.yaml files. This is the "operator can disable a rule mid-fire" surface ADR-169 §6 requires; rename-and-push is faster than draft-PR + merge for emergency disables, and the audit trail (git log) still records the disable + re-enable.

4. Curation policy

Who adds entries: any agent or operator with knowledge-base write access. The PR adding the entry follows the standard gyrum-start-workgyrum-complete-pr flow.

Who reviews entries: the standard 3-persona panel via gyrum-review-pr. A new principle-aware reviewer (per ADR-115) is recommended — Sasha (substrate-shape reviewer) per warp#1392 — for auto-diagnosis-touching PRs specifically; the existing panel covers the general case until Sasha's expertise is wired in.

Promotion path — the entry's approval_mode.current field starts at required for every new entry. Promotion to required-after-N or auto requires:

  1. Empirical evidence. The entry must have N≥3 successful auto-filed-ticket fires, each with the proposed fix shipping cleanly. The detector tracks this via provenance.successful_fires (incremented per fix-PR-merge that closes the auto-filed ticket).
  2. Operator signoff. The promotion PR must be approved by the operator (not by a persona); provenance.reviewer_signoff records the operator handle and the date.
  3. No regressions. The most recent 30 days of fires must have ≥95% diagnosis accuracy (operator-marked "right diagnosis" / "wrong diagnosis" via the auto-filed ticket's resolution comments). The detector tracks this via provenance.accuracy_30d; a promotion PR that inverts a worsening accuracy trend gets rejected by the validator.

Demotion path — an entry can be demoted (e.g. autorequired) on:

  • 1 false-positive in the most recent 7 days, OR
  • 3 false-positives in the most recent 30 days, OR
  • operator-issued kill switch (the .disabled.yaml rename).

Demotions ride a one-line PR; per warp#1782 the demotion PR's review gate is intentionally light (the trust direction is unsafe → safer; over-demotion is recoverable).

Curation cron — Phase 5 of warp#1782 (deferred, soak period): a weekly cron reads ADR-167's GET /api/v1/events/uncovered endpoint (signatures with no library match in the last 7 days) and files an approval-needed ticket per signature, prompting the operator to consider whether a new library entry is warranted. This ADR locks the curation policy; the cron is a Phase 5 implementation detail.

5. Validator surface

gyrum-validate-auto-diagnosis is the wire-edge gate. Runs at three points:

  1. PR-author time, via gyrum-review-pr on PRs touching gyrum-knowledge-base/auto-diagnosis/**. Validates schema, runs fixtures, cross-checks version + name.
  2. Detector startup time, against the in-memory parsed library. Same validator code; rejected entries log a warning and are not registered.
  3. Detector reload time, on file-change events. Same validator; rejected entries leave the previous version active.

The validator's checks:

  • YAML parses cleanly + matches the locked schema (six required fields + two required sibling blocks).
  • name matches filename (modulo .yaml).
  • version is a positive integer.
  • trigger.primitive is in the closed enum.
  • trigger.match keys are subset of ADR-167's wire-format fields.
  • proposed_fix.category is in ADR-164's closed enum.
  • proposed_fix.swarm_brief references the ticket number variable (catches "agent will ship without back-link" regressions).
  • proposed_fix.verification_spec.expected_diff_max_lines ≤ 500 (caps the auto-merge blast radius per ADR-169 §3).
  • approval_mode.current is in the closed enum.
  • debounce.window_hours ≥ 1 AND debounce.max_fires_per_day ≤ 10 (defence against notification storm).
  • provenance.reviewer_signoff is set when approval_mode.current is auto.
  • The fixture file exists at the expected path.
  • The fixture has ≥1 negative case.
  • Running the rule's matcher against each fixture event produces the expected verdict.

6. Migration path for an entry's lifecycle

[no entry]
   ↓ (operator observes new shape; files manual ticket; ships fix)
   ↓ (operator authors library PR with fixture + swarm_brief)
   ↓
[approval_mode: required, version: 1]
   ↓ (3 successful fires, operator signoff PR)
   ↓
[approval_mode: required-after-3, version: 2]
   ↓ (3 more successful fires, operator signoff PR)
   ↓
[approval_mode: auto, version: 3]
   ↓ (false-positive observed)
   ↓
[approval_mode: required, version: 4]   (DEMOTION; trust resets)
   ↓ ...

Every transition is a PR; every PR rides gyrum-review-pr; every change bumps version. The lifecycle is the audit trail.

Consequences

Wins

  • Decoupled curation cadence. New library entries land at curator pace; the detector picks them up at hot-reload pace (sub-minute). No detector release required for a new shape.
  • Regression fixtures pin every claim. Every entry has a positive + negative fixture; CI rejects entries without them. The library cannot drift into "we said this matches but actually it doesn't" without the fixture failing.
  • Trust accumulates per shape. The approval-mode promotion path is structural: requiredrequired-after-Nauto requires empirical evidence + operator signoff. The library cannot silently auto-merge new shapes the operator hasn't seen succeed first.
  • Operator kill switch is one rename. <name>.disabled.yaml disables an entry mid-fire without code changes. Recovery latency is git push, not a detector restart.
  • Validator catches cap on auto-merge blast radius. The expected_diff_max_lines ≤ 500 enforcement at validation time means an entry whose fix-shape can produce sprawling diffs will be flagged at PR-author time rather than at auto-merge time.
  • Composes with existing primitives. The shape reuses ADR-117's module-guideline pattern (versioned YAML + fixture), ADR-164's category axis, ADR-167's wire format. No new substrate.

Costs

  • Every entry needs a fixture. That's labour per entry; the offset is "every entry has a fixture" — the failure mode of "we wrote a rule that doesn't match what we thought" can't happen.
  • Knowledge-base repo gets a new top-level directory. The gyrum-knowledge-base/auto-diagnosis/ directory is additive; no existing path semantics change. The directory will accumulate ~50-100 entries within 6 months of soak per warp#1782's 10-shape starter list × 5-10× growth from the curation cron.
  • The validator is non-trivial — it parses YAML, runs the matcher engine in dry-run mode, cross-references fixtures. The engine is shared with the detector (one matcher implementation, two callers), so the marginal cost is the validator harness, not a separate engine. Estimated 200-300 LOC for the validator wrapper + ~100 LOC for fixture-runner shim.
  • Hot-reload introduces a new failure mode — a malformed YAML on disk could in principle take down the matcher if the reload code were buggy. Mitigated by the "fail-closed on reload, keep previous version active" contract; the worst case is one rule stays at the old version until the curator pushes a fix.

Risks

  • Library becomes a dumping ground. The pitfall called out in warp#1782: entries pile up, fixtures get skipped, false positives storm. Mitigated by the validator's hard-fail on missing fixtures, missing negatives, and over-broad debounce; entries that don't survive review never land. A weekly curation cron (Phase 5) reads the entries' accuracy-30d field and auto-files demotion tickets when an entry's accuracy drops below threshold.
  • Promotion-path gaming. A hostile or careless curator could land a rule directly at auto by skipping the promotion path. Mitigated by the validator's provenance.reviewer_signoff requirement when current=auto AND by the persona reviewers (per ADR-115) being instructed to refuse auto PRs without an explicit successful_fires ≥ N evidence chain in the PR body.
  • Trigger-collision across rules. Two rules whose trigger.match fields overlap could both fire on the same event, producing two auto-filed tickets. Mitigated by the validator's collision check at PR-author time: gyrum-validate-auto-diagnosis --check-collisions runs the new entry against every existing entry's fixture events and fails if any non-negative fixture matches the new entry's trigger.
  • Fixture-staleness after detector primitive changes. When the detector's primitive set evolves (e.g. a new primitive: foo lands), existing fixtures still pass but the entry might want to use the new primitive. Mitigated by the version-bump-on-semantic-change contract: a primitive switch is a semantic change, requires version bump, requires fixture re-run, surfaces in the PR diff.
  • Forgotten kill-switch unlock. An operator disables an entry via .disabled.yaml rename, fixes the underlying issue, forgets to re-enable. Mitigated by the curation cron: weekly summary lists .disabled.yaml entries with disable-age > 7 days, files an approval-needed ticket per orphan asking "still relevant?".

Mitigations

  • Validator at three points (PR-author, detector startup, detector reload) with the same code path. A schema regression cannot bypass any one point.
  • Cap on expected_diff_max_lines at validation time defends against scope creep at auto-merge time. ADR-169 enforces the same cap at merge time as defence-in-depth.
  • Closed enums everywhere (primitive, category, approval_mode.current) make typos surface at validation time, not runtime.
  • Persona reviewer runs the regression fixture in CI on every library PR. Same shape as ADR-117's guideline-regression suite.
  • Provenance fields record who added, who signed off, what motivated the entry. Auditable from git log + the rule's own provenance block.

Alternatives considered

Inline rules in detector source (compile-time list)

Bake the rules into the detector's source code as a compiled-in []Rule slice; ship via detector releases. Rejected: couples curation cadence to release cadence (every new shape is a deploy), prevents non-developer curators from contributing entries, breaks the operator kill-switch shape (would require code-edit + rebuild + redeploy per emergency disable). The YAML + hot-reload contract was the structural fix for these failure modes in ADR-117's module guidelines; reusing it here is the consistent move.

Database-backed rules (rules in Postgres)

Store rules as rows in a auto_diagnosis_rules table; admin UI for CRUD; detector reads via SQL. Rejected: loses the git audit trail (who-added-what-when becomes a row history reconstruction job), loses the standard PR review gate (no easy way to put admin-UI edits through gyrum-review-pr), loses ADR-115's principle-aware reviewer surface (no PR for the reviewer to read). The "CRUD UI" framing is the wrong shape for an artefact whose primary value is being reviewable + reproducible.

One YAML file with all rules

Single auto-diagnosis.yaml with a top-level array of rules. Rejected: blast radius of a typo is "every rule down" not "one rule down"; PR diff readability degrades with each new entry; concurrent PRs adding entries hit merge conflicts on every push. The per-shape file pattern is the same shape as ADR-117's per-module-type guideline files; reuse the proven shape.

Rules in detector code; fixtures in knowledge-base

Hybrid: rules compile into the detector binary; fixtures live in knowledge-base for testability. Rejected: the rule and the fixture have to ship together (a rule version-bump that doesn't update the fixture is a regression); separating them across two repos doubles the PR shape for every change. The colocation contract (<name>.yaml next to <name>.fixture.json) is what makes the regression-fixture pattern survive contact with curation pressure.

No approval-mode field; everything starts at required and curators graduate manually

Drop approval_mode from the schema; have curators just hand-edit category from approval-needed to auto when they think it's safe. Rejected: loses the structural promotion-path enforcement (no validator check for "≥N successful fires"); makes the promotion decision implicit in a PR diff that touches one field; the "auto-promotion gaming" risk above becomes the default. The explicit approval_mode block with current + promotion_path is what makes "this entry was promoted with evidence" a PR-reviewable claim rather than a category-rename.

Cross-references

  • Operator framing 2026-05-07: "Things break and fail, how can we catch the not deploying or working issue structurally so you can raise a fix automatically, dogfooding ultrathink."
  • ADR-167 — structured failure event schema. The events this library matches against; the wire-format selectors used in trigger.match.
  • ADR-169 — auto-swarm dispatch + auto-merge gating. Consumes the proposed_fix.swarm_brief and proposed_fix.verification_spec from this library; defines what happens after the auto-filed ticket lands.
  • ADR-164 / warp#1735 — ticket category axis. The proposed_fix.category field is locked to this enum.
  • ADR-117 — module guidelines. Same versioned-YAML-with-regression-fixture shape; reused intentionally.
  • ADR-115 — principle-aware reviewers. Reviewers quote the library entry's version when reviewing auto-filed PRs.
  • ADR-116 — RAG layer. The gyrum-knowledge-base/auto-diagnosis/ directory is a first-class indexed data source.
  • ADR-107 — gates as playbook primitives. Same intuition: lift implicit knowledge to first-class queryable artefact.
  • warp#1782 — parent EPIC; Phase 0 sub-ticket S0.2 is this ADR. Phase 3 is the first 5 library entries (S3.1 through S3.5).
  • warp#1783 — amendment; the auto-filed ticket rides the existing agent-executor queue.
  • warp#1779 — the canonical motivating shape; the example YAML in §1 above is exactly this entry, scheduled to ship as Phase 3 sub-ticket S3.1.

Supersedes: none Superseded by: leave blank until a later ADR reverses this one