Skip to content

Method 3.2: write a rule in the strongest kind of detector that can express it - #14

Open
LTSCommerce wants to merge 129 commits into
nextfrom
editor/semantic-detectors
Open

LTSCommerce wants to merge 129 commits into
nextfrom
editor/semantic-detectors

Conversation

@LTSCommerce

@LTSCommerce LTSCommerce commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

What this change does

Method specification, clause 3.2: a rule is written in the strongest kind of detector that can express it, semantic over syntactic over textual, with two grounds for a weaker kind (no detector of the stronger kind can express the rule, or none the project can use is available for the language) and the grounds recorded with the rule. Section 1 defines the three kinds by what a rule can ask of the code; section 2, the table in section 3, section 4's sign-off proviso, section 7 and Appendix A carry the consequences.

Detector specification: clause 8.2, a verdict on a detector names its kind in each configuration it grades; section 9's sentence on that obligation; section 2's table row for the three kinds.

Version headers are 1.2.0-dev, unpublished (method) and 1.1.0-dev, unpublished (detector), with package.json at 1.2.0-dev.

Scope

The change was reduced to the text above after runs 19 to 30, which were made on an earlier, wider text under the acceptance rules this repository had then and failed on passages that text had not touched; those passages are listed in acceptance/ROUTED.md for changes of their own.

Acceptance

Under the process of pull request 22 (merged into this branch):

  • Method specification: run 70 passed.
  • Detector specification: runs 71 and 74 failed on clause 8.2's wording; run 79 passed.

Each run's record is under acceptance/runs/, and its answer sheets are posted below.

This branch carries pull request 23 (the detector route rule); its text is graded there.

Open for the owner

  1. A rule already in place in a weaker kind of detector than the language offers: clause 3.2 asks for the grounds to be written down at the next commit that touches it, which is a cost to existing projects the scope tests should weigh.
  2. Clause 3.2 does not count a detector or configuration available only through a hosted service or a licence tier; detector clause 5.4 forbids findings reportable only there. Whether the two should be one rule.
  3. The scope tests' observed case: the wider text was prompted by rules written textually where a semantic detector was to hand; clause 8.2 has no observed case of its own and rests on clause 3.2.

🤖 Generated with Claude Code

LTSCommerce and others added 25 commits October 5, 2026 09:45
Reader isolation and the packet, a marking section (question 2, traceable
misses, unanswered questions, the same passage, delegated marking),
thresholds for any cohort size, row precedence, the record, the reviewer and
agent-surface rebuild in CONTRIBUTING.md, and how the process itself changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One rule for a miss traceable to the text: the reasoning quotes the
document, or the key says the miss counts; the key can add, never take
away, and a quoting miss is traceable even where it misreads the fixture.
Quoting is defined, question 2 answers must assert nothing the key marks
wrong, unanswered keyed items count one miss each, and "the same passage"
is settled for fragments, whole paragraphs and passages named unquoted.
A delegated marker receives the question set and fixture too, and the
editor supplies the record's facts. Every pass-criteria row must hold;
the last covers only what the others do not. Answer sheets, the reader
command and a delegated marker's instruction are kept with the record.
The process files are named; review findings that change the document
mean a fresh run, the step 5 header does not. The three record lists
agree, lines are rewrapped, and the changelog names the case from the
repository.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SPEC.md defines lexical, syntactic and semantic detectors in section 1.
Clause 3.2 says the practitioner SHOULD host a rule in a semantic
detector where the language has one that accepts bespoke rules, a
syntactic one before a lexical one otherwise, and MUST record why with
the remediation docs where a rule sits lower. The section 3 map and
Appendix A carry it, and the agent-facing prompt is rebuilt.

DETECTOR-SPEC.md section 8: a verdict on a detector MUST say which of
the three it is; that is not a level of conformance. Section 2's gloss
carries the terms. TOOLING-SPEC.md needs nothing: its 4.1 summary counts
the MUSTs of sections 4 to 7, which are unchanged.

Unreleased changelog lines carry a run NNN placeholder; the cohort is
next. Headers stay on the published versions until the record commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The method question set gains load-bearing statement 2d, on which kind
of detector should host a rule and what is recorded when another is
used; ACCEPTANCE.md lists it with the other three. The method key adds
2d, a restatement bullet and a wrong-if for "only a semantic detector",
and keeps 3.2 at Yes for the ledger fixture, which does not say what its
analyser reads. The detector key accepts a restatement bullet on the
verdict naming the kind and says the Lintomatic verdict is not marked on
it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Process changes are recorded in acceptance/CHANGES.md; the fourth
  section leaves CHANGELOG.md, which is again for the three
  specifications only, so tools/versions.py no longer reads a phantom
  toolchain Unreleased entry.
- The isolation rule and the kept answer sheets apply from run 019;
  runs 16 to 18 stand as recorded.
- A delegated marker's verdict is final unless the editor shows a rule
  was misapplied, then re-marked with the reason recorded; any open
  judgement takes the stricter reading.
- The claude -p example carries the flags that isolate a reader:
  --tools "", --setting-sources "", --strict-mcp-config,
  --no-session-persistence, from an empty directory, packet inline.
- A cohort is an odd number of readers, at least five; the editor may
  choose a larger one for a more load-bearing change. Fewer than five
  counting readers voids the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s lexical

The owner decided: a rule the practitioner writes MUST be written in the
strongest kind of detector that can express it, semantic, else syntactic, else
textual. A weaker kind is allowed only where the stronger cannot express the rule
or the language has no detector of that kind that accepts bespoke rules, and the
reason is recorded on the rule's remediation docs and in the description of the
commit that adds or moves the rule. The practitioner judges it; a project MAY
require the owner's sign-off, which the section 4 line on choosing a detector
now notes. Lexical becomes textual, each kind defined by what it can see.

Rust examples checked against current sources: Clippy's adding-lints guide is
upstream contribution only, so it is semantic but takes no bespoke rules; Dylint
runs lints from user-specified dynamic libraries written as LateLintPass, which
runs after type checking, so it is the Rust semantic host.

Detector section 8: the verdict names the kind by the most a rule in it can see
in any configuration it documents. Appendix A and the project prompt rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Method 2d quotes the two new MUST sentences of clause 3.2; an answer that calls
the semantic kind only a preference, or makes the owner's approval always
needed, is wrong. The ledger fixture's 3.2 verdict stays Yes, since the fixture
does not say what its analyser can see. Detector key: lexical becomes textual.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t in the repository

The owner chose to keep the repository from growing by five or more sheet files a run. The
record links one pull request comment holding every sheet unedited and the instruction a
delegated marker was given. A process change carries no run placeholder: it is recorded in
acceptance/CHANGES.md and is not tested by a cohort, as ACCEPTANCE.md already says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Defines a wording change once, keyed items, quoting, the same passage
and cold readers; makes the reader packet, isolation command and set-aside
and void-run rules exact; has the marker write the record; posts the
exact prompt and the marker's instruction with the sheets; restores the
paraphrase rule for traceable misses; removes the keys' limits on which
misses count against the document; adds the enforcing checks to the
process files and says how a process change moves through the steps.
acceptance/CHANGES.md loses its Unreleased heading and names the pull
request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kind of detector is that of the configuration a rule runs in;
enabling or configuring an existing rule is not writing one; a
semantic host a toolchain may not route a defence through under
toolchain clause 4.1 does not count. The remediation docs always name
the detector and its kind, and each step down from semantic records
its reason with the stronger detectors considered. The owner's
sign-off is a recorded project requirement, not a calibration; section
4's dividing line, the section 3 map and section 7's gloss carry it.
Syntactic and semantic are defined by whether a detector sees the
types of values; Semgrep leaves the examples. The context paragraph
is a marked worked example without the alias claim. Appendix A names
the kind, the move and the sign-off; agent surfaces rebuilt.

Detector section 8 names the kind per graded configuration and credits
none reachable only through a hosted service or licence tier; section
9 names that as its one obligation on whoever gives a verdict.
README, PRIMER and the mypy and semgrep register pages defer to 3.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Question 2d reworded before any run, as ruling 17 decides, with its
ACCEPTANCE.md line. Key 2d quotes the revised 3.2 sentences verbatim,
adds the kind on the remediation docs as supporting, asks for the
semantic kind first and keeps the either-place leniency (C12). The
restatement bullet carries the kind on the remediation docs. The ledger
fixture's 3.2 row is now Partial or No, since its wiki page names
neither detector nor kind, and section 7's verdicts name 3.2 with 3.6.
The detector key's restatement and Lintomatic quotation follow section
8's per-configuration wording and section 9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fourth row's block lifts only when no reader of a fresh cohort names
the same passage, as on origin/next, and the keys say "names". Quoting has
no length floor and excuses only case, quotation-mark style and markup. A
question 2 answer states each thing the key's answer states unless the key
says it may be left out. A reader is set aside only for empty answer text or
shared context, and any set-aside voids the run unless it already fails. A
failed text is never rerun unchanged; only version numbers and dates set for
a release are excepted from a wording change. A process change names the run
it applies from and is followed whole. The marker receives this pull
request's earlier runs and the readers set aside; keys and marking name the
commit the readers read. The record lists set-asides, the attestation,
re-marks, managed settings, early adoption and void runs; posted sheets are
whole outputs, never edited, hidden or deleted. Question 1 misses count only
where the key says; half right is a miss; detector 7.1's conditional is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clause 3.2 drops the toolchain 4.1 exclusion, so a partly conforming semantic detector still
counts; a stronger configuration of a detector the project runs counts, and one only a hosted
service or licence tier provides does not. The duty to name the detector and kind is on a rule
the practitioner writes, and the reason may go in the commit that first records it, for rules
that predate the clause. Section 1's Semantic is now what a rule can ask: the type of any
expression and what any name refers to, across the whole program; partial or single-file
inference is Syntactic. Detector 8 names the kind the detector has without any hosted service
or licence tier. Appendix A carries the detectors considered and the test for "has one", and the
project prompt is rebuilt. index.md and PRIMER.md follow 3.2. The changelog lines follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The method key quotes the revised 3.2 sentences verbatim, in 2d and the application row. 2d drops
the toolchain 4.1 exclusion and says which three elements a correct answer gives; the
restatement key accepts a bullet that puts rules in a semantic detector; the application notes
no longer count every 3.2 Yes against the document, leaving that to marking. The detector key
quotes no changed sentence and is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A question 1 bullet that quotes the document makes its miss traceable;
a reasoning that names the clause a case fails or meets states what the
document requires; a question 2 quotation must itself state part of the
key's answer. The fourth row matches a fresh reader's passage against
the earlier passage's paragraph as it reads after the change, the record
quotes confusing passages verbatim, and a delegated marker gets the
earlier runs' documents and the three facts it was missing. An edited
comment voids only a run not already failed, before merge. A process
change applies to runs after it merges, or to earlier runs made under it
at a named commit. A set-aside reader's traceable miss fails the run.
The keys count passages named, not only quoted; every keyed answer of
question 3 is a keyed item; "the same passage" covers the sentences an
occurrence of the whole quotation overlaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clause 3.2 defines when a detector accepts bespoke rules (a documented
extension point, whether or not it meets the rest of the detector
specification) and says a rule its extension points cannot reach is one
it cannot express. The owner's sign-off, where a project requires it, is
required by a record beside the calibrations and is itself recorded with
the reason. Clause 3.6's three things defer to what 3.2 adds. Appendix A
and the project prompt carry the licence exclusion for a configuration
the project runs, and the recording of the sign-off. The mypy register
note no longer steers away from a mypy plugin as the semantic host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Method key, question 1: a bullet naming the semantic kind without the
fallback is correct, one saying no weaker kind is ever allowed is wrong.
Statement 2d (iii): naming one of the two places is enough; presenting
them as alternatives is wrong. Application 3.2: a Yes is marked wrong,
including one that leaves the wiki failure to 3.6 alone, and counts
against the document where the reasoning quotes it. No quoted sentence
changed, and no question, threshold or fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Question 2: a correct answer also asserts nothing the document does
  not state, so a wrong claim the key does not list is no longer correct.
- A reader is set aside when its process reports an error, when the
  command or working directory recorded for it is not one The cohort
  requires, or when its reported model version is not the cohort's
  model; the existing void rule applies. The unanswered-question bullet
  agrees.
- CONTRIBUTING.md step 5 sets the status paragraph and citation to the
  -dev version with the header, so the version exception covers them.
- The sentence after the pass-criteria table agrees with the fourth row.
- A delegated marker of a run made under an unmerged process change
  receives ACCEPTANCE.md and acceptance/runs/README.md at the named
  commit.
- acceptance/CHANGES.md line updated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Section 2's toolchain-gap exit and clause 3.2's bespoke-detector and
  gap paragraphs now turn on no extensible detector of a kind that can
  express the rule within clause 3.1's bounds, so a class only a kind the
  language lacks could hold is routed to a bespoke detector or a gap.
- Clause 3.2 states one reading of writing a rule: enabling an existing
  rule or tightening its thresholds is not; giving an existing rule a
  project pattern, or changing the pattern of a project rule, is. The
  reference now names the off-the-shelf paragraph.
- Section 1 names mypy as semantic only with check_untyped_defs (mypy's
  command-line documentation: by default the bodies of functions without
  annotations are not type checked); tools/mypy.md says the same.
- Detector section 9 no longer calls the verdict obligation one on a
  practitioner: section 8 binds whoever gives a verdict on a detector.
- CHANGELOG lines follow, run NNN placeholders kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The detector key's restatement now says how a bullet that the detector,
or a verdict on it, must state its kind is marked: correct, as the
section 8 obligation, though the document puts it on the verdict. No key
quotes a sentence changed in this revision, so no quotation moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ble set-asides by the marker

Two minor points of review 6: a run made under an unmerged process change names its commit before
the readers start, so the choice cannot follow the marks; and a delegated marker sets aside, with
the cause, a reader whose sheet shows one, the editor supplying only causes the sheets cannot show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review 6 found both pages still sent a project's own Python classes straight to Pylint or a
Flake8 plugin, skipping the semantic host and the recorded reason clause 3.2 requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ctors

# Conflicts:
#	acceptance/method/KEY.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 019: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted. Local paths and the owner's name are replaced by placeholders in the instruction and definition below; nothing else is changed.

Command and working directory

ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt, each reader in a fresh empty directory made by mktemp -d outside the repository

Model version each reader reported

reader 1: claude-haiku-5-5
reader 2: claude-haiku-5-5
reader 3: claude-haiku-5-5
reader 4: claude-haiku-5-5
reader 5: claude-haiku-5-5

The delegated marker's task prompt

MODE: mark

Worktree: <worktree> (branch editor/semantic-detectors). You have not seen the drafting or the review.

You are the delegated marker for run 019. Read ACCEPTANCE.md and acceptance/runs/README.md in full: the run is made under the unmerged process change of pull request 13 at commit a695010, whose versions of those two files are the ones in this worktree (check with `git diff a695010 HEAD -- ACCEPTANCE.md acceptance/runs/README.md`: the only difference is this pull request's question-set line adding question 2d for SPEC.md). Read acceptance/method/KEY.md and acceptance/method/QUESTIONS.md and the fixture acceptance/fixtures/remediation-ledger.md, and the document SPEC.md, all at commit 5b6093f (`git show 5b6093f:<path>`). Earlier run records of other pull requests, for the record's style: acceptance/runs/016 to 018. There are no earlier runs of this pull request.

The answer sheets: <run-dir>/reader-1.json to reader-5.json (each reader's whole output; the answer text is the `result` field, also in sheet-1.md to sheet-5.md). The exact prompt every reader received: <run-dir>/prompt.txt.

Facts from the editor, which the record states as supplied:

- Document: SPEC.md. Pull request: 14 (https://github.com/Defence-Before-Fix/defence-before-fix.github.io/pull/14), branch editor/semantic-detectors, commit 5b6093f.
- Cohort: five readers, model alias haiku; the model version each reader reported is in <run-dir>/models.txt.
- Isolation: each reader a separate `claude -p` process started through the `ccy-claude` wrapper (which only reattaches the session's credential and passes arguments through verbatim), in a fresh empty directory made by `mktemp -d` outside any repository, with the packet on standard input. Command: `ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt`. The machine applies no managed (policy) settings (no managed settings file present). The editor attests the rest of the isolation.
- Readers set aside for a cause the sheets cannot show: none.
- Marker: a fresh dbf-spec-editor agent (Claude Opus) dispatched with this instruction and its agent definition, <agent-definition, below>, having seen neither the drafting nor the review.
- Run number 019; run 1 in pull request 14; runs in the pull request so far, counting void runs: 1.
- Made under a process change before it merged: yes, pull request 13 at commit a695010, named before the readers started.

Mark every sheet question by question under ACCEPTANCE.md's Marking section, apply the pass criteria exactly, and take the stricter reading wherever the rules leave a judgement open, stating it in the record. Write the record to <worktree>/acceptance/runs/019-semantic-detectors-run-1.md in the order acceptance/runs/README.md gives; leave "the findings applied" as "To be stated by the editor" and the link to the answer sheets as "LINK-TO-SHEETS". Do not commit. Reply with the verdict, and for a FAIL every finding with the passage, the readers and the rule, in under twenty lines. British spelling, no em dashes. Write nothing else; never use sed.

The agent definition the marker ran under

---
name: dbf-spec-editor
description: Drafts, reviews, key-updates, marks or records a change to the Defence Before Fix specification, one mode per dispatch, following the specification repository's CONTRIBUTING.md, SCOPE.md and ACCEPTANCE.md exactly. Dispatched by the dbf-spec-change skill; it cannot run a cohort itself.
tools: Read, Write, Edit, Grep, Glob, Bash
model: opus
---

You work on one change to the Defence Before Fix specification, in the worktree path you are
given. The process is defined by that repository's `CONTRIBUTING.md`, `SCOPE.md`,
`ACCEPTANCE.md` and `acceptance/README.md`; read the ones your mode needs, in full, before you
act. You invent no steps. You never merge, push to `next` or `main`, release, tag or publish,
and you never change a question, threshold or fixture. Your prompt names one mode.

Write as the specification is written: British spelling, no em dashes, defined terms linked as
`spec-qa.py` requires, every clause or section number linked to its heading. Commit on the
worktree's branch.

## MODE: draft

1. Read `SCOPE.md`, `DECLINED.md`, and every clause the change touches, with the terminology
   entries they use.
2. Decide the kind. A change to the sentences carrying MUST, SHOULD or MAY in a numbered section
   or appendix is an obligation; anything else is a clarity edit.
3. For an obligation, answer the five SCOPE tests in a sentence or two each, and name the
   section the obligation extends. If a test fails, or an existing clause already covers it, say
   so and stop without editing.
4. Make the smallest edit that does the job, in the clause that owns the subject.
5. Add one line under `Unreleased` in `CHANGELOG.md`, saying what changed and why, with a
   placeholder `run NNN`. Change no version.
6. If Appendix A changed, run `python3 build-agent-surfaces.py` and commit its outputs.
7. Run `python3 spec-qa.py` and `python3 -m unittest discover -s tools -p 'test_*.py'`; both
   clean.
8. Reply with: kind, SCOPE answers, files changed, commit, QA result, and a filled
   `.github/PULL_REQUEST_TEMPLATE.md` body. Under forty lines.

## MODE: review

You have not seen the drafting. Read the whole changed document and the diff against
`origin/next`. Report, numbered, with clause numbers: contradictions with other clauses, terms
used outside their definition, a sentence a reader could take two ways, an obligation an owner
could not verify from the record. Do not edit.

## MODE: keys

Read `acceptance/README.md` and the `KEY.md` for each changed document. Update every quotation of
a changed sentence to the new wording, verbatim, in a new commit; run `python3 spec-qa.py` until
`key-quote-stale` is gone. If the change adds a load-bearing obligation no keyed question
covers, propose the question and its key in your reply; add it only if your prompt says the owner
has decided to.

## MODE: mark

You have not seen the drafting or the review. Read `ACCEPTANCE.md` in full, the `KEY.md` for the
document, the document at the commit named, and the sheets under `untracked/scratch/cohort/NNN/`.

- Mark each sheet against the key, question by question, as runs 16 to 18 in
  `acceptance/runs/` do.
- For every miss, say whether the reader's reasoning quotes the document; such a miss is a
  finding whatever the count.
- For question 4, record every passage quoted, with its clause. Ignore any suggestion a reader
  volunteers.
- Apply the pass criteria exactly. Where `ACCEPTANCE.md` leaves a judgement open, make it and
  say in the record which judgement and why.
- Write `acceptance/runs/NNN-<slug>-run-<k>.md` in the shape of runs 16 to 18, stating how the
  readers were isolated (separate `claude -p` processes, no tools, no settings, no project
  context). Do not commit it. Reply with the verdict and the findings only.

## MODE: record

Commit the run record, replace the `run NNN` placeholder in `Unreleased` with a link to it, and
for an obligation set the document header to `<next version>-dev, unpublished` in the same
commit if it still names a published version. Run `python3 spec-qa.py` and
`python3 tools/changes.py --base origin/next`; both clean. Reply with the commit.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 019: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Answer sheets, each reader's whole output as its process returned it

reader-1

{"duration_api_ms":48112,"stop_reason":"end_turn","session_id":"0a8bc357-f5a4-43aa-a1b5-cea4eef2404b","total_cost_usd":0.01211205,"usage":{"input_tokens":2,"cache_creation_input_tokens":32845,"cache_read_input_tokens":535,"output_tokens":11075,"output_tokens_details":{"thinking_tokens":9247},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":32845,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":11075,"cache_read_input_tokens":535,"cache_creation_input_tokens":32845,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":32845},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":11075,"cacheReadInputTokens":535,"cacheCreationInputTokens":32845,"webSearchRequests":0,"costUSD":0.01211205,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":9247,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Treat a defect as evidence of a class: name the pattern and its hazard, bounded so it is neither a single instance nor every use of a language feature (3.1).\n- Search for other instances by at least two independent techniques before running the rule, until the last technique adds nothing new (3.1).\n- Write a blocking rule in the strongest detector kind that can express it, never a test. Record the detector and kind, and justify any weaker kind (3.2).\n- Prove the rule fires in its own commit, keep narrowings only with a written and searched sentence, and send any doubtful exclusion to the owner (3.3).\n- Sweep the whole codebase, record and corroborate the count, then fix every instance by addressing the hazard. Baselines and suppressions belong to the owner (3.4, 4).\n- Make the rule fail the project's real entry point with a terse message and a stable identifier that resolves to documentation versioned with the rule (3.5, 3.6).\n\n## 2. Load-bearing statements\n\n**2a.** The practitioner attributes the defect to a class and builds the defence before fixing anything: the rule is proven, the codebase is swept, and the count is recorded, and only then is every instance fixed, with the specific defect still reproduced by a test.\n> \"The [Practitioner] MUST run the [Rule] across the entire codebase and record the total [Instance] count before fixing anything, and MUST then fix every [Instance] found rather than only the one that was reported.\"\n\n**2b.** Only the owner may adopt a baseline or suppress an instance, and an agent may not do so on its own authority.\n> \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\"\n\nThe owner is defined as \"Always a human.\"\n\n**2c.** A remediation conforms when all six clauses of section 3 were followed for the defect. A reviewer's verdict on it must rest on reproduction, not on the report.\n> \"A verdict on a remediation or a [Defence] MUST rest on reproduction, not on the report: the reviewer reruns the [Defence] red at the commit that introduced it and green at the final commit, through the project's own entry point for accepting changes...\"\n\n**2d.** A rule must be written in the strongest detector kind that can express it, with a weaker kind allowed only where the stronger cannot. For each step down, the practitioner must record which reason holds and why.\n> \"A [Rule] the [Practitioner] writes MUST be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a [Syntactic] one, else a [Textual] one.\"\n\n> \"...the [Practitioner] MUST also record, for each step down from [Semantic], which of those two reasons holds and why, naming the [Detectors] of the stronger kind that were considered...\"\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n| --- | --- | --- |\n| 3.1 | Partial | The class and its hazard (\"silent wrong output\") are recorded, and the wider class is named. But no two independent search techniques are recorded, the five findings came from the rule itself (which cannot count as a technique), and there is no statement on whether a Runner check pins the reported behaviour. |\n| 3.2 | Partial | A rule was written in the project's static analyser, not as a test. But the record does not name the detector or its kind, and the documentation does not say which kind it is. |\n| 3.3 | Partial | The proof is met: commit B holds the rule and fixture with the originating `Ledger::total()` still present, is red, and survives the merge. But the `Report::render()` exclusion is an inline ignore, which is a suppression, with no searched sentence, and it was not referred. |\n| 3.4 | No | A count of five is recorded from the rule's own output and is not corroborated by an independent search. One instance was suppressed at the call site rather than fixed, which the spec names as not a fix. |\n| 3.5 | No | The rule blocks and was run through the project's quality invocation, but the green run depends on an inline ignore that no recorded decision covers. |\n| 3.6 | No | The failure message is terse and carries the identifier. But the documentation is on a company wiki, not versioned with the rule. The spec accepts only URLs the project controls and versions alongside the rule. |\n\n**Remediation conforms under section 7:** No. Clause 3.4 is not met, and clauses 3.5 and 3.6 are not met.\n\n**Defence alone conforms:** No. Section 7 requires 3.1, 3.2, 3.3, 3.5 and 3.6, and 3.5 and 3.6 fail outright.\n\n**Decision not the practitioner's (section 4):** The inline `// ignore proj.caught-then-used` on `Report::render()`. Section 4 lists \"Suppressing an [Instance]\" among decisions that MUST go to the owner. The project has a named owner, so the default-owner route in section 1 does not apply, and escalation goes to that person. The decision not to build the wider rule (\"any caught error that is not rethrown or reported\") is also listed in section 3.1's table as the owner's to make, so it arguably also belonged to the owner.\n\n## 4. Confusion\n\nSection 1, \"Owner\":\n> \"Where the project has recorded nobody, the [Owner] is whoever instructed the [Practitioner], and [Escalation] goes there.\"\n\nSection 3.1:\n> \"Both bounds measure breadth within one level; the level itself is a separate question, answered against the report.\"","ttft_ms":41730,"type":"result","duration_ms":48385,"uuid":"dffb29dd-9073-4d37-8873-f68575e6c5e5","ttft_stream_ms":969,"time_to_request_ms":272,"first_content_frame_ms":969,"queued_turn_count":0,"result_index":0}

reader-2

{"duration_api_ms":71633,"stop_reason":"end_turn","session_id":"494f5e6c-6d57-4250-ab4a-48ff167aab0f","total_cost_usd":0.01513575,"usage":{"input_tokens":2,"cache_creation_input_tokens":32846,"cache_read_input_tokens":535,"output_tokens":17122,"output_tokens_details":{"thinking_tokens":15155},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":32846,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":17122,"cache_read_input_tokens":535,"cache_creation_input_tokens":32846,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":32846},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":17122,"cacheReadInputTokens":535,"cacheCreationInputTokens":32846,"webSearchRequests":0,"costUSD":0.01513575,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":15155,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Don't fix the defect yet. First name the class (the pattern that allowed it) and the hazard it carries, bounded both ways (3.1).\n- Search for other instances by at least two independent techniques, not the rule itself, until a further technique finds nothing new (3.1).\n- Write a blocking rule in the strongest detector kind that can express it (Semantic, else Syntactic, else Textual), never as a test, and make it read code rather than run it (3.2).\n- Prove the rule fires, on the defect or a fixture, in a red commit of its own before the fix. Narrow only with a written sentence explaining why the hazard cannot arise; otherwise the exclusion is a suppression for the owner (3.3).\n- Sweep the whole codebase, record and corroborate the count, and fix every instance. Baselines, suppressions and knowingly unfixed instances belong to the owner, not the practitioner (3.4, 4).\n- Make the rule blocking in the project's entry point, with a terse message and a stable identifier resolving to documentation versioned with the rule (3.5, 3.6). Record any deferred defect where decisions are enumerable, not only in conversation.\n\n## 2. Load-bearing statements\n\n**2a.** Before fixing, attribute the defect to a class and build and prove the defence, committing it while the instance is still present and committing the fix after it.\n> \"When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is fixed.\" (section 2)\n\n**2b.** Only the owner may adopt a baseline or suppress an instance; an agent may not do so on its own authority.\n> \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\" (section 4)\n\nThe owner is \"Always a human.\" (Terminology, Owner)\n\n**2c.** A remediation conforms when all six clauses of section 3 were followed for that defect, and the verdict that they were rests on reproduction, not on the report.\n> \"**A remediation [Conforms]** if all six clauses of section 3 were followed for that [Defect], and the verdict that they were rests on reproduction, as this section closes by requiring, not on the report.\" (section 7)\n\n**2d.** The rule must be written in the strongest detector kind that can express it. A weaker kind is allowed only where the stronger cannot express it, and for each step down the practitioner must record which reason holds, and why, naming the stronger detectors considered.\n> \"A weaker kind is allowed only where the stronger kind cannot express the [Rule], as where the [Rule] reads a file the [Semantic] [Detector] does not parse, or where the language has no [Detector] of the stronger kind that accepts bespoke [Rules].\" (3.2)\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n| --- | --- | --- |\n| 3.1 | No | The class and hazard are well stated, but the record has no two named search techniques, no finding for each, and no statement on whether a Runner pins the reported behaviour. The wider-class rejection rests on \"most are legitimate\", not a hazard sentence confirmed by search. |\n| 3.2 | Partial | The rule is a detector rule, not a test, but neither the documentation nor the commit names the detector or its kind, so the strongest-kind requirement cannot be checked. |\n| 3.3 | Yes | Commit B holds the rule, a fixture, and a red CI run on the originating `Ledger::total()` shape, before the fix in commit C. The merge kept all five commits. No narrowing is claimed. |\n| 3.4 | No | The five findings come from the rule alone, with no independent corroboration. The count includes the fixture, which the sweep excludes, so the real count is four. `Report::render()` was left unfixed under an inline ignore. |\n| 3.5 | No | The rule fails rather than warns, and the local run of the entry point passed. But the green run depends on an inline suppression that no recorded decision covers. |\n| 3.6 | No | The message is one line with the identifier, but the documentation is on a company wiki, not versioned with the rule in the repository. |\n\n**Remediation conforms under section 7?** No. Clauses 3.1, 3.4, 3.5 and 3.6 are not met, and 3.2 cannot be verified.\n\n**Defence alone conforms?** No. Section 7 requires 3.5 and 3.6 of the defence, and both fail: the green run rests on an uncovered suppression, and the documentation is not versioned with the rule. A rule without its documentation is not a defence, and 3.2 cannot be verified.\n\n**Decision not the practitioner's:** The inline `// ignore proj.caught-then-used` on `Report::render()`. Section 4 requires owner decisions for \"Suppressing an [Instance]\" and for \"Accepting a known [Instance] as unfixed for any reason.\" The project records nothing about inline ignores, and the agent did not raise the case with anyone.\n\nA second decision, arguable, is leaving the wider rule unbuilt. Clause 3.1's table routes \"whether the next wider [Rule] is built\" to the owner, but section 4's own bullet covers only the case where the rejection is because no instance exists today.\n\n## 4. Confusion\n\nClause 3.1, on the level of the class:\n> \"Where the [Defect] was reported as a behaviour, ... the [Class] a [Detector] can read usually sits one level below that [Hazard]: the mechanism of this [Instance], not the failure the report opens with.\"\n\nI could not tell from the terms defined which direction \"one level below\" runs.\n\nClause 2, on the attempt to express a rule:\n> \"naming at least two independent techniques tried at expressing the [Rule], in the vocabulary clause 3.1 uses for a search\"\n\nI could not tell what the search vocabulary of clause 3.1 is, or how it applies to expressing a rule.","ttft_ms":65612,"type":"result","duration_ms":71920,"uuid":"b909fb2d-d73b-4155-ab91-d2b7fe6d92ea","ttft_stream_ms":1014,"time_to_request_ms":282,"first_content_frame_ms":1014,"queued_turn_count":0,"result_index":0}

reader-3

{"duration_api_ms":58438,"stop_reason":"end_turn","session_id":"02404b2e-396d-4a86-8f41-2b219b7f08ad","total_cost_usd":0.01362885,"usage":{"input_tokens":2,"cache_creation_input_tokens":32844,"cache_read_input_tokens":535,"output_tokens":14109,"output_tokens_details":{"thinking_tokens":12082},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":32844,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":14109,"cache_read_input_tokens":535,"cache_creation_input_tokens":32844,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":32844},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":14109,"cacheReadInputTokens":535,"cacheCreationInputTokens":32844,"webSearchRequests":0,"costUSD":0.01362885,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":12082,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Don't fix the reported Instance first. Treat it as evidence of a Class, and attempt to express that Class as a Rule before fixing (\"building the [Defence] ... MUST be attempted before the [Defect] is fixed\", section 2).\n- Attribute the Defect to a Class with a Hazard sentence, bounded both ways, after two independent searches run before the Rule (3.1).\n- Write the Rule in the strongest kind of Detector that can express it, never as a test (3.2).\n- Prove the Rule fires, in a red commit of its own, before the fix (3.3).\n- Sweep the whole codebase, record the count, and fix every Instance. Do not baseline, suppress, or leave one unfixed without the Owner (3.4, 4).\n- Make the Rule blocking and permanent through the project's own entry point, with a terse message, a stable Identifier, and versioned docs (3.5, 3.6). Then fix the original Defect with its reproduction test.\n\n## 2. Load-bearing statements\n\n- **2a.** The Defence is built and proven first, then the Defect is fixed. \"The [Defence] comes first, because once the fix has landed the evidence is gone and the opportunity closes with it.\" (section 6)\n- **2b.** Only the Owner, who is always a human, may adopt a Baseline or suppress an Instance, and an agent may not do so on its own authority. \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\" (section 4; the Owner is defined in section 1 as \"Always a human.\")\n- **2c.** A remediation conforms when all six clauses of section 3 were followed for that Defect, and the verdict must rest on reproduction. \"A verdict on a remediation or a [Defence] MUST rest on reproduction, not on the report: the reviewer reruns the [Defence] red at the commit that introduced it and green at the final commit, through the project's own entry point for accepting changes, and reruns the originating symptom against the fix.\" (section 7)\n- **2d.** The Rule must be written in the strongest Detector kind that can express it (Semantic, else Syntactic, else Textual). A weaker kind is allowed only where the stronger one cannot express the Rule, and the practitioner must then record the reasons and the stronger Detectors considered. \"Where that kind is weaker than [Semantic], the [Practitioner] MUST also record, for each step down from [Semantic], which of those two reasons holds and why, naming the [Detectors] of the stronger kind that were considered, on the [Rule]'s [Remediation docs] and in the description of the commit that adds the [Rule]...\" (3.2)\n\n## 3. Application\n\n- **3.1 Attribute the defect to a class: No.** The Class, the Hazard sentence and the next wider Rule are written down, but the record names no two independent search techniques run before the Rule, and says nothing about whether a Runner check pins the reported behaviour. The reason for not building the wider Rule (\"most are legitimate\") is not a Hazard sentence confirmed by search. \"A record missing any of the five has not finished this clause.\"\n- **3.2 Express as a rule: Partial.** The Rule is in a project static analyser, which is a Detector and not a test. But the fixture never names the Detector or its kind, and the wiki page described does not either, so the strongest-kind requirement cannot be verified.\n- **3.3 Prove the net: Yes.** Commit B holds the Rule, the fixture and a red CI run on `Ledger::total()` while that Instance is still present, and the fix comes in commit C. The merge commit keeps all five commits, so the proof survives. The description does not say whether a fresh checkout reproduces the red run, so only the commit structure is shown.\n- **3.4 Sweep, then fix every instance: No.** The count of five includes the fixture, which 3.4 says is never an Instance, so the real count is four. The count was not independently corroborated. `Report::render()` was left unfixed behind an inline ignore, against \"Fix them all.\"\n- **3.5 Enforce permanently and block: No.** The Rule fails rather than warns, and the whole invocation was run locally. But the green run depends on an inline ignore that no recorded decision covers, and \"the project's recorded decisions say nothing about inline ignores.\" Section 3.5 says a green run that depends on such a Suppression does not conform.\n- **3.6 Terse message and real documentation: No.** The one-line message is terse and the Identifier is stable, and the page states the prohibition and the propagating construction. But the docs live on a company wiki rather than in the repository, committed with the Rule. The text requires that they \"MUST be versioned as one,\" and \"A link into a third-party wiki ... does not [conform].\"\n\n**Conformance.** The remediation does **not** conform under section 7, because 3.1, 3.4, 3.5 and 3.6 fail and 3.2 cannot be verified. The defence alone does **not** conform either, because it requires 3.1, 3.2, 3.5 and 3.6, and 3.5 and 3.6 fail.\n\n**Decision not the practitioner's.** The inline `// ignore proj.caught-then-used` on `Report::render()`, and the decision to leave that Instance unfixed, belong to the Owner. Section 4 says the Owner decides \"Suppressing an [Instance]\" and \"Accepting a known [Instance] as unfixed for any reason.\" The project has a named owner, so the default-Owner rule in section 1 does not apply. The agent's own doubt about whether \"rendering must never throw\" removes the Hazard is also an escalation trigger: \"Doubt is [Suppression]\" (3.3).\n\n## 4. Confusion\n\n- Clause 3.2: \"a [Rule] whose construct its extension points cannot reach is one that [Detector] cannot express, and one its kind cannot express where no other [Detector] of that kind reaches it.\"\n- Clause 3.1: \"Both bounds measure breadth within one level; the level itself is a separate question, answered against the report.\"","ttft_ms":52090,"type":"result","duration_ms":58725,"uuid":"c831b2a2-be6b-4db7-ace1-9ab9a0e795d9","ttft_stream_ms":1112,"time_to_request_ms":283,"first_content_frame_ms":1112,"queued_turn_count":0,"result_index":0}

reader-4

{"duration_api_ms":46927,"stop_reason":"end_turn","session_id":"f6b8530b-c2a8-4536-9044-f8227f1feaf8","total_cost_usd":0.011987250000000001,"usage":{"input_tokens":2,"cache_creation_input_tokens":32846,"cache_read_input_tokens":535,"output_tokens":10825,"output_tokens_details":{"thinking_tokens":8933},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":32846,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":10825,"cache_read_input_tokens":535,"cache_creation_input_tokens":32846,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":32846},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":10825,"cacheReadInputTokens":535,"cacheCreationInputTokens":32846,"webSearchRequests":0,"costUSD":0.011987250000000001,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":8933,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- When handed a defect, treat it as evidence of a class: name the pattern and the hazard it carries, and attribute the defect to that class (3.1).\n- Search for other instances by at least two independent techniques, chosen and run before the rule exists, and record what each found (3.1).\n- Write a blocking rule in the strongest detector kind that can express it, not a test, and record the detector and its kind (3.2).\n- Prove the rule fires in a red commit of its own, before the fix (3.3).\n- Sweep the whole codebase, record a corroborated count, and fix every instance. Suppressions, baselines and unfixed instances belong to the owner (3.4, 4).\n- Make the rule permanent and failing through the project's own entry point, with a terse message and an identifier that resolves to documentation versioned with the rule (3.5, 3.6).\n\n## 2. Load-bearing statements\n\n- **2a.** Attribute the defect to a class, then build and prove the defence, then sweep, then fix. The defence comes first: \"When it can, building the Defence is not optional and MUST be attempted before the Defect is fixed.\"\n- **2b.** Only the owner may adopt a baseline or suppress an instance. An agent may not do either on its own authority: \"An Agent MUST NOT Baseline, suppress or knowingly leave an Instance unfixed on its own authority, whatever the Instance count turns out to be.\"\n- **2c.** A remediation conforms when all six clauses of section 3 were followed for that defect. The verdict rests on reproduction: \"A verdict on a remediation or a Defence MUST rest on reproduction, not on the report\".\n- **2d.** A rule must be written in the strongest detector kind that can express it: \"A Rule the Practitioner writes MUST be written in the strongest kind of Detector that can express it: a Semantic one, else a Syntactic one, else a Textual one.\" A weaker kind needs a recorded reason for each step down, naming the stronger detectors considered, on the rule's remediation docs and in the adding commit's description.\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n| --- | --- | --- |\n| 3.1 | **No** | Commit A names the class and the hazard, and records a wider class with a reason. It names no search techniques and records nothing about whether a Runner check pins the reported behaviour. The only \"search\" is the rule's own run in commit B, which section 3.1 excludes. \"A record missing any of the five has not finished this clause.\" |\n| 3.2 | **Partial** | The rule is in an analyser and is a code-reading detector, so it is not a test. Its kind is never named, and the described documentation does not name the detector or kind. Whether a weaker kind was used, and why, is unrecorded. |\n| 3.3 | **Partial** | Part A holds: commit B is a red run with the originating shape present, kept separate from the fix in commit C, and the merge commit keeps all five commits. Part B fails: the `Report::render` exclusion is an inline ignore with a sentence (\"rendering must never throw\") that does not say why the hazard cannot arise. That makes it a Suppression under 3.3, not a Narrowing. |\n| 3.4 | **No** | One instance is left suppressed rather than fixed. The count of five includes the fixture, which 3.4 excludes, so the real count is four. The count is not corroborated by an independent search. Whether the sweep covered the whole codebase is not recorded. |\n| 3.5 | **No** | The rule is set to fail and the local run passed. But the green run depends on a Suppression with no recorded decision, and the project's records say nothing about inline ignores. The spec says a rule \"whose green run depends on a Suppression that no recorded decision covers\" does not conform. |\n| 3.6 | **No** | The message is terse and carries the identifier, file, line and URL. The documentation sits on a company wiki, outside the repository, unversioned with the rule. The spec says \"An external URL Conforms only where the project controls it and versions it alongside the Rule.\" |\n\n**Remediation under section 7:** Does not conform. Clauses 3.1, 3.4, 3.5 and 3.6 fail, and 3.3 is only partly met.\n\n**Defence alone under section 7:** Does not conform. A defence needs 3.1, 3.2, 3.3, 3.5 and 3.6. The defence fails 3.5 (the suppression is unrecorded), fails 3.6 (the documentation is off-repo), and its 3.1 bounds are not evidenced, because no search was recorded. Its 3.2 kind is unrecorded.\n\n**Decision not the practitioner's (section 4):** The inline `// ignore proj.caught-then-used` on `Report::render()`. Section 4 lists \"Suppressing an Instance\" among the decisions that \"MUST go to whoever owns the codebase\". The agent took it alone and did not raise it, although the project has a named owner. A secondary point: the decision to leave the wider class unbuilt was given the reason \"most are legitimate\". Section 3.1 treats that as an open hazard question for the owner, so it should also have been referred rather than settled in the record.\n\n## 4. Confusion\n\nOn my one reading, these passages were confusing:\n\n- **Clause 3.2:** \"a [Rule] whose construct its extension points cannot reach is one that [Detector] cannot express, and one its kind cannot express where no other [Detector] of that kind reaches it.\"\n- **Clause 3.4:** \"and the scale at which that becomes true is tremendous.\"\n- **Section 4:** \"This does not reopen a [Class] whose bounds a [Hazard] sentence has settled, and it reaches no further than the one wider [Rule] already on the record.\"","ttft_ms":40232,"type":"result","duration_ms":47260,"uuid":"4dd40249-13a8-413d-b096-cc420248de7a","ttft_stream_ms":1028,"time_to_request_ms":336,"first_content_frame_ms":1028,"queued_turn_count":0,"result_index":0}

reader-5

{"duration_api_ms":61845,"stop_reason":"end_turn","session_id":"316fbf92-5cd2-4a22-9d38-5818216a6b5d","total_cost_usd":0.013898250000000001,"usage":{"input_tokens":2,"cache_creation_input_tokens":32846,"cache_read_input_tokens":535,"output_tokens":14647,"output_tokens_details":{"thinking_tokens":12603},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":32846,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":14647,"cache_read_input_tokens":535,"cache_creation_input_tokens":32846,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":32846},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":14647,"cacheReadInputTokens":535,"cacheCreationInputTokens":32846,"webSearchRequests":0,"costUSD":0.013898250000000001,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":12603,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Before fixing a defect, name the class it is an instance of, with a one-sentence hazard, after at least two independent searches (3.1).\n- Express the class as a Blocking rule in the strongest detector kind that can express it, never a test, and name the detector and kind in the rule's docs (3.2).\n- Prove the rule fires on the instance or a fixture, in its own commit, before trusting it. Narrow only with a written sentence showing the hazard cannot arise in the excluded code (3.3).\n- Sweep the whole codebase, record the count, and fix every instance. Do not baseline, suppress, or leave an instance unfixed on your own authority (3.4, section 4).\n- Make the rule permanent and failing through the project's own entry point, with a terse message and a stable identifier that resolves to documentation versioned with the rule (3.5, 3.6).\n- Record any deferred defect or \"no pattern exists\" conclusion where the project's decisions are enumerable. Only then fix the defect itself, with a reproducing test (section 2, section 6).\n\n## 2. Load-bearing statements\n\n**2a.** The practitioner attributes the defect to a class, attempts a rule, builds and proves it, and sweeps for every instance before fixing anything, then fixes the defect.\n> \"When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is fixed.\"\n\nClause 3.4 fixes the order of the sweep: \"The [Practitioner] MUST run the [Rule] across the entire codebase and record the total [Instance] count before fixing anything, and MUST then fix every [Instance] found rather than only the one that was reported.\"\n\n**2b.** Only the owner, who is always a human, may adopt a baseline or suppress an instance. An agent may not do so on its own authority.\n> \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\"\n\n**2c.** A remediation conforms when all six clauses of section 3 were followed for that defect. The verdict that they were must rest on reproduction, not on the report.\n> \"**A remediation [Conforms]** if all six clauses of section 3 were followed for that [Defect], and the verdict that they were rests on reproduction, as this section closes by requiring, not on the report.\"\n\n**2d.** The rule must be written in the strongest kind of detector that can express it: Semantic, then Syntactic, then Textual. A weaker kind is allowed only when a stronger one cannot express the rule. The practitioner must then record, for each step down, which of the two reasons holds and why.\n> \"A [Rule] the [Practitioner] writes MUST be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a [Syntactic] one, else a [Textual] one.\"\n\nThe record must go on the rule's remediation docs and in the commit description, and must name the stronger detectors considered.\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n|---|---|---|\n| 3.1 | Partial | The class, its hazard sentence and the next wider class are named, but the record has no two independent search techniques recorded before the rule ran, and no statement on whether a Runner check pins the reported behaviour. The rejection reason \"most are legitimate\" is not the hazard sentence clause 3.1 requires. |\n| 3.2 | Partial | The rule is in a static analyser, not a test, but the fixture never names the detector kind, and the wiki docs do not name the detector or kind. Whether the strongest kind was used cannot be judged. |\n| 3.3 | Partial | The red run is in its own commit (B), with the originating instance still present and the fix in C, so the proof itself is met. But the `Report::render()` exclusion in C is an ignore comment, which is a Suppression, not a Narrowing: its sentence does not address the hazard, and it was not referred to the owner. |\n| 3.4 | No | Four real instances were found; three were fixed and one was suppressed inline rather than fixed, and the count was not corroborated by any independent search. |\n| 3.5 | No | The green run through the local entry point depends on a Suppression that no recorded decision covers, and the project's records say nothing about inline ignores. |\n| 3.6 | No | The message is terse with a stable identifier, but the documentation lives on a company wiki, not in the repository committed with the rule, so it is not versioned with the rule. |\n\n**Remediation conforms under section 7:** No. Clauses 3.1 to 3.6 are not all satisfied.\n\n**Defence alone conforms:** No. Section 7 requires clauses 3.1, 3.2, 3.3, 3.5 and 3.6. Clause 3.5 fails because the suppression is uncovered, and clause 3.6 fails because the docs are not versioned with the rule. Clauses 3.1 and 3.2 are also unresolved.\n\n**Decision not the practitioner's (section 4):** The inline `// ignore proj.caught-then-used` on `Report::render()` in commit C. Section 4 lists \"Suppressing an [Instance]\" among the decisions that \"MUST go to whoever owns the codebase.\" The project has a named owner, so the default-owner provision does not apply, and the fixture says the agent did not raise it.\n\nA secondary candidate is leaving the wider class unbuilt. Under section 4 that is the owner's call where the wider check is hard to build without false positives, and clause 3.1 says uncertainty about whether a case carries the hazard routes upwards. The fixture's reason is a legitimacy judgement, and it does not say the owner decided.\n\n## 4. Confusion\n\n- Section 2, \"Attempt rather than pre-judge\": \"naming at least two independent techniques tried at expressing the [Rule], in the vocabulary clause 3.1 uses for a search\"\n- Clause 3.2: \"a [Rule] whose construct its extension points cannot reach is one that [Detector] cannot express, and one its kind cannot express where no other [Detector] of that kind reaches it.\"\n- Clause 3.4: \"and the scale at which that becomes true is tremendous.\"","ttft_ms":55308,"type":"result","duration_ms":62125,"uuid":"7e041771-f4ce-461f-94d4-19d7abd7196e","ttft_stream_ms":1222,"time_to_request_ms":282,"first_content_frame_ms":1222,"queued_turn_count":0,"result_index":0}

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 019: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

The exact prompt every reader received, part 1 of 2

===== FILE: QUESTIONS.md =====
# Questions for a cold reader of SPEC.md

You have been given `SPEC.md` and the fixture `fixtures/remediation-ledger.md`, and nothing else.
Read the document once, then answer every question below in order. Quote the document where a
question asks for a quotation. Do not consult anything outside what you were given.

## 1. Restatement

In your own words and in at most six bullets, what does this document require a practitioner to
do when they are handed a defect?

## 2. Load-bearing statements

Answer each in one sentence, followed by the sentence of the document you are relying on, quoted.

- 2a. What does a practitioner do before fixing the defect they were handed, and in what order?
- 2b. Who may adopt a baseline or suppress an instance, and may an agent do so on its own
  authority?
- 2c. What does it mean for a remediation to conform, and on what must a reviewer's verdict on
  it rest?
- 2d. Which kind of detector must a rule the practitioner writes be written in, when may a weaker
  kind be used, and what must the practitioner record then?

## 3. Application

Grade the ledger remediation, as described in the fixture, against each of the six clauses of
section [3](../../SPEC.md#3-the-method): [3.1](../../SPEC.md#31-attribute-the-defect-to-a-class), [3.2](../../SPEC.md#32-build-the-net), [3.3](../../SPEC.md#33-prove-the-net-by-making-the-rule-fire), [3.4](../../SPEC.md#34-sweep-the-codebase-then-fix-every-instance), [3.5](../../SPEC.md#35-enforce-permanently-and-block), [3.6](../../SPEC.md#36-make-the-failure-message-terse-and-point-it-at-real-documentation). For each give Yes, Partial or No and one line of
reasoning. Then state whether the remediation conforms under section [7](../../SPEC.md#7-conformance), whether the defence
alone conforms, and which decision in the fixture, if any, section [4](../../SPEC.md#4-authority-which-decisions-belong-to-whom) says was not the
practitioner's to take.

## 4. Confusion

Quote any passage you found confusing on your one reading, with its clause number. Do not say what
would make it clearer and do not propose any change. Write "none" if there was none.

===== FILE: SPEC.md =====
# Defence Before Fix: Method Specification

**Version**: 1.1.0, published 2026-10-02
**Companion to**: [the detector specification](DETECTOR-SPEC.md), version 1.0.0, and [the toolchain specification](TOOLING-SPEC.md), version 0.2.0
**Author**: [Joseph Edmonds](https://ltscommerce.dev), [Edmonds Commerce](https://edmondscommerce.co.uk)
**Coined**: 22 February 2026, in [the original article](https://ltscommerce.dev/articles/defence-before-fix-static-analysis)

> **Defence Before Fix** ([DBF]) is a phase that runs *before* a [Defect] is fixed. Rather than dropping
> straight into remediating the specific [Instance] in front of you, you first treat that [Instance]
> as evidence of a [Class], and you build the automated [Defence] that detects every occurrence of
> that [Class] across the whole codebase. The [Defence] is only trusted once it has been seen to
> fire.

The [Defect] is what makes this possible. It is a real, confirmed, impactful example of a harmful
pattern, which is precisely the raw material a good custom [Rule] needs and which speculative
[Rules] never have. Every [Defect] is therefore an opportunity to extend the codebase's permanent
defensive [Coverage], and that opportunity exists only in the window before the fix.

Defence Before Fix does not replace test-driven development. The specific [Defect] is still
reproduced with a test and proven fixed, exactly as normal. The two operate at different levels:
TDD addresses the [Instance], Defence Before Fix addresses the [Class].

Not to be confused with *Defence in depth*, which is a security term meaning something else
entirely. The [Defence] here comes before the fix in time, not in layers.

US spelling: **Defense Before Fix**. Abbreviated [DBF] throughout.

---

## Status of this document

This is version 1.1.0 of the specification. It is normative: section 3 defines the method,
section 4 states who decides what, and section 7 defines what [Conformance] means and who may claim
it.

**This document is the source of truth for what the method is.** Where anything else describing
Defence Before Fix disagrees with it, including the article in which the term was first published,
this document is correct.

Who coined the term, when it was first published, and what is and is not being claimed are recorded
separately in [provenance](PROVENANCE.md), which is meta information about this specification
rather than part of it. A short introduction to the method is in [the primer](PRIMER.md).

Changes are recorded in the changelog at the end.

## 1. Terminology

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in
RFC 2119.

#### DBF

The acronym for Defence Before Fix, used as shorthand for this method and for anything built under it. A [DBF] [Rule] is a [Rule] as this specification defines it; a [DBF] [Toolchain] is one that [Conforms] to the [Toolchain] specification; a [DBF] project is one that [Conforms] to section 7.

#### Defect

Any observed problem worth acting on: a bug, a code review finding, a performance observation, an incident, an inconsistency. The method does not care which.

#### Class

The pattern, style, idiom or configuration that permitted the [Defect], expressed generally enough that other occurrences of it are also [Defects] or latent [Defects].

#### Instance

One occurrence of a [Class]. The reported [Defect] is one [Instance]; there are usually others.

#### Hazard

The harm a [Class] causes, which is **not necessarily a failure**. It may be a failure, but it may equally be error hiding, or something merely sloppy that makes the code harder to reason about or to change safely.

#### Detector

A tool that reads code without executing it and reports occurrences of a pattern. A [Detector] is not a [Runner]. By what it can see in the configuration it runs in, a [Detector] is [Textual], [Syntactic] or [Semantic], and clause 3.2 says which kind a [Rule] is written in. What a [Detector] must offer so that a [Practitioner] can write, prove, run and resolve a [Rule] in it is specified in the [detector specification](DETECTOR-SPEC.md).

#### Runner

A tool that **executes** code and reports what happened: a test [Runner], a compilation, a benchmark, a smoke check. A [Runner] answers questions about behaviour at one moment, on the paths it happened to exercise; a [Detector] answers questions about the text of the code, everywhere it exists. Detection under this method MUST come from a [Detector], for that reason.

#### Textual

Of a [Detector]: seeing the code only as text, as grep does. It sees how the code is spelt and nothing of its structure, so it cannot tell a call from a comment that names it.

#### Syntactic

Of a [Detector]: seeing the parse tree of the code, and at most some of the types of values, as ast-grep does. It sees the structure, but cannot always tell apart two calls of the same name on values of different types.

#### Semantic

Of a [Detector]: one in which a [Rule] can ask the type of any expression and what any name refers to, across the whole program, as the language's compiler or type checker resolves them, as in PHPStan, mypy with `check_untyped_defs`, Clippy, Dylint and ESLint with typescript-eslint's type information. One that infers types only in part, or only within one file, is [Syntactic]. The same [Detector] can be [Syntactic] in one configuration and [Semantic] in another: ESLint without type information is [Syntactic]. A [Semantic] [Detector] need not accept bespoke [Rules]: Clippy does not, and in Rust a project's own [Semantic] [Rules] are written for Dylint.

#### Rule

One pattern definition within a [Detector].

#### Defence

A **[Blocking]** [Rule] together with the supporting documentation that clarifies it and clearly signposts best practice. A [Rule] without its documentation is not a [Defence], and neither is a [Rule] that only warns.

#### Coverage

The accumulated set of [Defences] a project has built. Every [Defect] handled under this method extends it.

#### Remediation docs

The fuller documentation a failure [Message] points to, explaining what a [Rule] is about, why it exists and how to fix a violation correctly.

#### Blocking

A [Rule] is [Blocking] when its firing prevents a change from being accepted by whatever the project uses to accept changes. A report that does not block is a **[Warning]**, and a [Rule] that only warns is not a [Defence].

#### Warning

A report that does not block. A [Rule] that only warns is not a [Defence].

#### Message

The text a [Rule] prints when it fires. Terse, and carries the [Rule]'s [Identifier].

#### Identifier

The stable string a [Rule] reports with, by which its [Remediation docs] are found. Survives renames of the [Rule].

#### False positive

A report on code that does not carry the [Hazard].

#### Narrowing

Reducing a [Rule]'s scope so it stops reporting code that does not carry the [Hazard]. [Narrowing] that excludes code which does carry the [Hazard] is a [Suppression].

#### Suppression

Any means of preventing a [Rule] from reporting an [Instance] that carries the [Hazard]: an ignore comment, an ignore entry, a [Baseline], or a [Narrowing] that excludes it.

#### Baseline

A recorded set of pre-existing [Instances] a [Rule] is configured not to report. A [Suppression] in bulk.

#### Exception

A recorded [Owner] decision that a specific [Instance] or scope is excluded from a [Defence]. The only legitimate form of [Suppression]. This document never uses the word in its programming-language sense.

#### Sweep

Running a [Rule] across the whole codebase to enumerate every [Instance].

#### Fixture

Code written to carry the [Hazard] deliberately, so a [Rule] can be proven to fire when the codebase has no [Instance] to prove it on.

#### Calibration

A project-level ruling on a threshold or parameter this document leaves open.

#### Practitioner

Whoever is doing the work of this method on a given [Defect], human or [Agent]. Decides how the [Defence] is built. Has no authority over [Exceptions].

#### Agent

A [Practitioner] that is an automated system. Everything said of a [Practitioner] applies to an [Agent]; section 8 exists because of what is additionally true of one.

#### Owner

Whoever holds authority over a project's [Exceptions] and [Calibrations]. Always a human. The project decides who, and clause 8.7 requires the answer to be findable. Where the project has recorded nobody, the [Owner] is whoever instructed the [Practitioner], and [Escalation] goes there. One limit applies to that default. The default [Owner] is usually the person under the same deadline as the [Practitioner], so a decision they take under section 4 to keep an [Instance] unfixed, [Suppress], [Baseline] or remove a [Rule] MUST be recorded in the project record with its justification. That record MUST mark the decision as taken by a default [Owner] and state why nobody better placed was reachable, where the next reader can see who decided and why. Such a decision SHOULD be revisited by someone not under that deadline before the next release. A project that wants the separation section 4 describes names an [Owner]; the default exists so work is not blocked, not to certify that a second judgement was applied.

#### Escalation

Referring a decision to the [Owner], whilst continuing with everything that does not depend on it.

#### Toolchain

Whatever a project assembles to run its checks through: its [Detectors] and [Runners], and the parts around them that route, list, record and resolve, from third-party, first-party and project-level parts in any combination. It is measured at the project level. What a [Toolchain] must offer beyond what each [Detector] in it offers is specified in the [toolchain specification](TOOLING-SPEC.md).

#### Conform

To satisfy every MUST of the relevant section 7 level, or of the companion specification being claimed. Partial satisfaction is not [Conformance].

## 2. When the method applies

Defence Before Fix applies to a [Defect] when that [Defect] can be attributed to a pattern, style,
idiom or configuration that a [Detector] can be made to recognise.

When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is
fixed. This is the substance of the method rather than a preliminary to it.

When it cannot, the [Defect] is outside the method's scope and is fixed conventionally. Not every
[Defect] is an [Instance] of a detectable pattern, and forcing a [Rule] where no pattern exists
produces [Detectors] that fire on innocent code, which is worse than having no [Detector].

**Attempt rather than pre-judge.** The [Practitioner] MUST attempt to express the [Class] as a [Rule]
rather than deciding in advance whether it is expressible. Failing to write a [Rule] that satisfies
clause 3.1's bounds is itself the evidence that the [Defect] is out of scope, and it is cheaper and
more reliable than a judgement made before trying. The conclusion that no pattern exists MUST be
stated in one sentence, naming at least two independent techniques tried at expressing the
[Rule], in the vocabulary clause 3.1 uses for a search, and recorded where the project's
other decisions are enumerable under clause 8.7, not only with the fix. It is the cheapest way
out of the method and the only one that would otherwise leave no trace; enumerating it is how an
[Owner] sees an [Agent] routing around the method, and naming the techniques is what lets
them judge whether the attempt was one.

There are three ways out of the method and they are recorded differently. No pattern exists: the
[Defect] is out of scope under this section, and only the sentence above is recorded. A pattern exists but the
language has no extensible [Detector] of a kind that can express the [Rule] within clause 3.1's
bounds, and a bespoke one is not practical: a [Toolchain] gap
under clause 3.2. A pattern exists, a
[Detector] exists, but the [Toolchain] lacks a mechanism the [toolchain specification](TOOLING-SPEC.md) requires: a
mechanism gap under clause 3.2, and the [Rule] is still built. The attempt that separates the first
from the second is complete when the [Practitioner] has checked the [Detectors] the project already
runs and the language's own [Detector] ecosystem for an extension point, and found none.

**Deferring the fix is not a fourth.** A [Defect] whose fix is postponed has not left the
method; the attempt this section requires is owed when the fix is taken up. A
[Practitioner] who finds a [Defect] and does not fix it now MUST record it where the project's
other decisions are enumerable under clause 8.7, naming the [Class] where one is already
apparent. Whether it stays unfixed is the [Owner]'s decision under section 4; the record is what
puts it in front of them. Reporting it in conversation does not satisfy this: the conversation
ends, and with it the only trace that the [Defect] was ever seen.

**Why**: each of the three ways out costs the [Practitioner] something to take and leaves
something an [Owner] can read: a recorded sentence naming two techniques tried, or a [Toolchain]
gap. Deferral costs nothing, and without this record it would leave nothing, which makes it
cheaper still than the sentence above and unseen from outside. A [Defect] mentioned once and
never written down cannot be told apart from a [Defect] nobody found, so neither the [Class] nor
the [Defence] it implies is ever reached.

## 3. The method

Six clauses, in order. In brief, before the detail:

| Clause | The [Practitioner] MUST                                                                                     | The record shows                                                                                                                                     | Goes to the [Owner] under section 4                                                                                              |
| ------ | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| 3.1    | Name the [Class] the [Defect] belongs to, bounded both ways, after an independent search                    | The [Class], the [Hazard] sentence, the two search techniques, the next wider [Rule] not built, whether a [Runner] check pins the reported behaviour | Whether the next wider [Rule] is built, and whether the [Rule] stays narrower than the search showed                             |
| 3.2    | Express the [Class] as a [Rule] in the strongest kind of [Detector] that can express it, never a test       | The [Rule], its [Detector] and kind, why it is in a weaker kind than [Semantic] where it is, and any [Toolchain] gap that stopped a bespoke one      | Whether a [Class] that could be defended is left undefended, and a weaker kind where the project requires the [Owner]'s sign-off |
| 3.3    | Make the [Rule] fire, on the [Instance] or a [Fixture], in a commit of its own                              | The red run, and the sentence behind every [Narrowing]                                                                                               | Any exclusion whose sentence cannot be written: that is [Suppression]                                                            |
| 3.4    | [Sweep] the whole codebase, record the count, then fix every [Instance]                                     | The count, corroborated, and what was fixed by hand or by pattern                                                                                    | Any [Instance] left unfixed, and any [Baseline]                                                                                  |
| 3.5    | Make the [Rule] permanent and [Blocking] in the project's own checks                                        | The green run through the project's entry point, and the recorded decision behind any [Suppression]                                                  | Removing or disabling the [Rule], and any [Suppression] added to it                                                              |
| 3.6    | Print a terse [Message] with a stable [Identifier] that resolves to documentation versioned with the [Rule] | The [Remediation docs]                                                                                                                               | Nothing                                                                                                                          |

The fourth column is what an [Agent] or other [Practitioner] MUST NOT decide alone, clause by
clause; section 4 states the full list and governs where the two differ.

**Three of them turn on a judgement this specification deliberately does not close**: whether code
carries the [Hazard] (3.1, 3.3), whether a search was comprehensive (3.1, 3.4), and how broadly to
draw the [Class] (3.1). Any threshold given here would be calibrated to one codebase, one team and
one generation of [Toolchain], and would be wrong everywhere else.

**Those judgements are settled at project level, not in this document.** A project fixes its own
thresholds and parameters, in its configuration or in its [Toolchain]'s defaults, and where a case is
genuinely ambiguous it is raised, discussed and written down so the next person inherits the answer
rather than the argument. A project's accumulated decisions become part of its [Coverage] in the same
way its [Rules] do.

**A project MUST make those decisions discoverable**, by the same means as its [Defences], so that a
[Practitioner] arriving cold can find what has already been settled instead of guessing at it or
re-opening it. The burden is the project's, not the reader's: a decision that cannot be found by the
means clause 8.7 requires is, for the [Practitioner]'s purposes, a decision the project has not
recorded, and the paragraph below applies. The search is therefore bounded: enumerate the project's
[Defences] as clause 8.5 requires them to be enumerable, and whatever is reachable from there is the
record. Nothing further is owed before concluding that nothing is recorded.

**Where the project has recorded nothing, the [Practitioner] is not blocked.** Proceed on the
examples below, **state the [Calibration] assumed**, and record it with the [Remediation docs]
for the [Defence] being built, which clause 3.6 already requires to exist, to ship with the project
and to be reachable by a stable [Identifier]. That statement becomes the project's first recorded
decision on the point, and the next [Practitioner] inherits it.

A project MAY of course keep these decisions somewhere else and say so. **The default above exists
so that a [Practitioner] who finds no project convention still has one**, because "the project
decides" and "the project has decided nothing" would otherwise combine into a deadlock. What is
never acceptable is assuming silently, since a [Calibration] nobody knows was chosen cannot be
corrected.

Worked examples of the three, to calibrate against:

| Judgement         | Too little                                                                                         | Too much                                                                                   |
| ----------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **[Hazard]**      | Only counting things that crash. Error hiding and code nobody can safely change are [Hazards] too. | Counting every stylistic preference, so the [Rule] defends taste rather than the codebase. |
| **Comprehensive** | One text search for the exact token from the reported [Defect], then stopping.                     | Auditing the whole system by hand before writing a [Rule] at all.                          |
| **[Class]**       | A [Rule] matching the variable name in the original bug report.                                    | A [Rule] matching every use of the language feature the bug happened to involve.           |

### 3.1 Attribute the defect to a class

The [Practitioner] MUST first establish what [Class] the [Defect] belongs to. The question is not
"what went wrong here" but "what kind of thing is this an [Instance] of".

**In order, this clause is five steps**, each detailed below:

1. Name the [Class] as a pattern, and write the one sentence that says what [Hazard] it carries.
2. Search the codebase for other [Instances] by at least two independent techniques, chosen and
   run **before** the [Rule] is run against the codebase, until the last technique adds nothing.
3. Widen the [Class] if the search found [Instances] the [Rule] would miss; narrow it if the
   [Rule] would match code that does not carry the [Hazard].
4. Where the [Class] stays at the reported [Instance], name the next wider [Rule] and why it
   was not built.
5. Record all of that, the five things listed at the end of this clause, before clause 3.2.

A worked example. The [Defect]: a currency conversion's error was caught and its empty result
used as a total. Step 1 names the [Class] "a caught error whose call's result is then used as if
the call had succeeded", with the [Hazard] "silent wrong output". Step 2 searches twice: a text
search for every catch in the codebase, read for whether the result is used afterwards, and a
reading of every caller of the fallible conversion and payment functions; the second finds two
[Instances] the first missed, so a third technique, asking the team which other calls fail
quietly, is run and finds nothing new. Step 3 keeps the [Class] as named because the [Rule]
would catch all four. Step 4 names the wider [Rule], "any caught error that is not rethrown or
reported", and records that it was not built because most of the codebase's catches are
legitimate and the [Owner] has not decided whether that is a [Hazard]. Step 5 writes the four
things down.

A [Defect] usually belongs to several overlapping [Classes] at different levels of abstraction, and
choosing between them determines the [Rule], the [Instance] count and the scope of the remediation.
Two bounds apply, and both are checkable during the work rather than matters of taste:

- **Lower bound.** If the [Rule] catches only the originating [Instance], the [Class] is probably drawn
  too narrowly and MUST be widened, unless the independent search below has found no other
  [Instance] and the [Practitioner] records that the [Class] is genuinely singular today. A
  [Rule] that matches exactly one thing is an [Instance] [Detector] wearing a [Rule]'s clothes.
  A [Rule] drawn to the exact value or name found in the [Defect] is the same [Narrowing] however
  it is spelt, and where it stays because the search found nothing wider, the record MUST also name the next
  wider [Rule], the one with the [Instance]'s distinguishing detail dropped, and state why it was
  rejected. That rejection is tested as a [Narrowing] is under clause 3.3: a sentence stating why the
  [Hazard] cannot arise in the code the wider [Rule] would add, confirmed by search, with uncertainty
  routed upwards under section 4.
- **Upper bound.** If the [Rule] matches code that does not carry the [Hazard], the [Class] is drawn
  too broadly and MUST be narrowed. [False positives] destroy a [Defence]'s credibility faster than a
  missing [Rule] does. One report on code that does not carry the [Hazard] is enough; there is no
  tolerated rate.

**Both bounds measure breadth within one level; the level itself is a separate question, answered
against the report.**
Where the [Defect] was reported as a behaviour, a check that reported clean having run nothing, a
process that stopped before its work, the [Class] a [Detector] can read usually sits one level
below that [Hazard]: the mechanism of this [Instance], not the failure the report opens with.
That [Class] is the right one to build, because no [Detector] reads "this check still produces
its signal". It is not the whole answer. The record MUST state whether the reported behaviour is also
pinned by a check a [Runner] executes, under section 6, either one added with this remediation or
an existing one named, and where neither is offered, why not. A [Class] drawn at the mechanism with
the behaviour left unpinned defends the [Instance] thoroughly and the report not at all.

**Resolve the lower bound by searching independently, not by judgement.** The [Rule] is not the only
way to find [Instances], and it is the least trustworthy one whilst it is still unproven. Search for
other [Instances] by other means, whether that is a text search, reading the code, or asking someone
who knows the system, and, once the [Rule] exists under clause 3.2, confirm it catches what those
searches found. That confirmation is not one of the techniques.

That search MUST be a comprehensive one, carried out by a person, model or [Agent] competent to
carry it out. No fixed technique is prescribed, because what is comprehensive depends entirely on
the [Class] and the codebase. What is not acceptable is a cursory look that exists to discharge the
requirement, since this search is what the single-[Instance] conclusion and the [Sweep] count both
rest on, and a bad search validates a bad [Rule] silently.

**The stopping criterion is saturation, not effort.** Use at least two independent techniques, and
the search is complete when the last technique added found nothing the earlier ones had missed. A
second technique that turns up new [Instances] means a third is owed; a second that turns up nothing
new means the search is done. That is a test the [Practitioner] can apply and record without a
threshold, and it is the standard clause 3.4 refers back to.

Two techniques are independent when they would miss different things. A text search for the token
and a reading of the code paths that consume the value are independent; two text searches for two
spellings of the same token are one technique; asking someone who knows the system is a third. For a
[Class] defined by how code is spelt, writing the idiom the other ways it is commonly spelt and searching
the codebase for each of them is a technique, because a search for one spelling misses every other;
whether the [Rule] catches those spellings is the confirmation step, not the technique. The
[Rule] itself is never one of the techniques, because the search exists to check the [Rule].
A clean run of the [Rule] is therefore not evidence of saturation either: "the [Rule] found nothing
the reading had missed" is one technique and a [Rule] run, not two techniques, and the stopping
criterion cannot be applied to it.
The record MUST name both techniques, chosen before the [Rule] is run against the codebase, and
MUST state what each found that the other could not have checked. A technique counts only if it does
not invoke the [Detector] under proof in any configuration and would exist unchanged had the
[Rule] never been written; a [Sweep]-shaped run of the [Rule], however comprehensive it looks, is
never one of the two. Where a later technique shows that two earlier checks shared a blind spot, they
were one technique, and a third is owed.

That turns the single-[Instance] case into something checkable rather than a matter of taste. If an
independent search finds [Instances] the [Rule] missed, the [Class] was drawn too narrowly and the [Rule]
MUST be widened until it catches them. Fixing those [Instances] by hand does not discharge the
widening: they are still [Instances] the [Rule] missed, and where the [Practitioner] leaves the
[Rule] unwidened because the wider check is harder to build without [False positives], that is
the [Owner]'s decision under section 4 and is recorded as one. If an independent search finds nothing
the [Rule] did not already have, then one [Instance] is a reasonable conclusion rather than an
assumption, and the [Rule] is correct as written.

**What this clause leaves on the record**, before clause 3.2 begins: the [Class], spelt as a
pattern; the [Hazard] sentence; the two search techniques and what each found; the next wider
[Rule] that was not built, with the reason; and, where the [Defect] was reported as a behaviour,
whether a check a [Runner] executes pins that behaviour, or why not. A record missing any of the
five has not finished this clause.

**Why**: the [Class] is the unit of work. Everything downstream operates on it, so an error here
wastes all the effort that follows.

### 3.2 Build the net

The [Practitioner] MUST express the [Class] as a [Rule] in a [Detector], and the [Detector] MUST read code
rather than execute it.

A test MUST NOT serve as the [Detector]. A test proves that one input produces one wrong output; a
[Rule] finds the pattern wherever it occurs, including in code nobody thought to test.

Where an off-the-shelf [Rule] or a tightening of existing configuration genuinely detects the
[Class], using it [Conforms]. However, a [Toolchain] that supports only its own built-in [Rules] cannot
support the method in general, so **a [Conforming] [Toolchain] MUST allow bespoke custom [Rules]**.
The [Rules] that matter most are tightly coupled to the project and carry project-specific
knowledge, both in the pattern they match and, importantly, in the [Message] they emit.

Any tool that reads code and reports pattern matches qualifies, whatever its category. Static
[Detectors] such as PHPStan, ESLint, mypy and Clippy are the usual instruments; so are custom AST
walkers, architectural fitness checks, and [Detectors] that inspect a change before it lands. These
are examples rather than a permitted list, and a [Detector] is not disqualified for being unfamiliar.

**Qualifying is not the same as being the right host.** A [Rule] the [Practitioner] writes MUST
be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a
[Syntactic] one, else a [Textual] one. The kind is that of the configuration the [Rule] runs in.
Enabling an existing [Rule], or tightening its thresholds, is not writing one, and the
off-the-shelf paragraph above covers it; giving an existing [Rule] a pattern of the project's own,
or changing the pattern of a [Rule] the project wrote, is writing one. A
weaker kind is allowed only where the stronger kind cannot express the [Rule], as where the
[Rule] reads a file the [Semantic] [Detector] does not parse, or where the language has no
[Detector] of the stronger kind that accepts bespoke [Rules]. The language has one when the
project already runs it or the language's own [Detector] ecosystem offers it, the same check
section 2 asks for before a [Toolchain] gap is recorded. A stronger configuration of a [Detector]
the project runs is one; a [Detector] or configuration of the stronger kind available only
through a hosted service or a licence tier is not. A [Detector] accepts bespoke [Rules] when the
project can add a [Rule] of its own to it through a documented extension point, such as a plugin
interface or an API for writing [Rules], whether or not it meets the rest of the [detector specification](DETECTOR-SPEC.md); a [Rule] whose
construct its extension points cannot reach is one that [Detector] cannot express, and one its
kind cannot express where no other [Detector] of that kind reaches it. On the [Remediation docs] of a [Rule] they
write, the [Practitioner] MUST name the [Detector] and its kind. Where that kind is weaker than [Semantic], the [Practitioner] MUST also
record, for each step down from [Semantic], which of those two reasons holds and why, naming the
[Detectors] of the stronger kind that were considered, on the [Rule]'s [Remediation docs] and in
the description of the commit that adds the [Rule], moves it to another [Detector], or first
records the reason. The [Practitioner] judges whether a reason holds, as section 4 gives them the choice of [Detector]; a
project MAY require its [Owner]'s sign-off on a weaker kind as well, by recording that
requirement where it records its [Calibrations]; the sign-off is then recorded with the reason,
and where the project has recorded no such requirement none is needed.

**A worked example.** Take a [Rule] against calling a method named `context` on one type. A
[Detector] that cannot see types reports every call named `context`, whatever type it is called
on, which is a [False positive] clause 3.1 does not tolerate; restricting it by the name of the
variable the call is made on misses the same call on a variable named otherwise, which is an
[Instance] the [Rule] never sees. A [Semantic] [Detector] decides by the type, so the bounds of
clause 3.1 can be held there.

Where the affected language has no extensible [Detector] available of a kind that can express the
[Rule] within clause 3.1's bounds, the [Practitioner] MAY build a
bespoke one: a program of the project's own that reads code and reports matches of the [Class], run
through the project's own entry point like any other [Detector]. This is a last resort rather than
a default, because a [Detector] the ecosystem maintains is cheaper to keep and easier for the next
[Practitioner] to find, but a bespoke [Detector] is far better than none: the [Class] is defended,
and every clause of this method applies to the bespoke [Detector] unchanged, including the proof in
3.3, the enforcement in 3.5 and the [Identifier] and documentation in 3.6.

Only where a bespoke [Detector] is not practical either is the [Class] undefended. That is a
**[Toolchain] gap and MUST be recorded as one**, with the reason a bespoke [Detector] was not
practical, rather than treated as the [Defect] being out of scope. The distinction matters, because
the first is a decision somebody can revisit and the second quietly disappears.

A gap is recorded in the project record where the [Toolchain] defines one, as the
[toolchain specification](TOOLING-SPEC.md) requires it to; only where it does not is the location the project's choice, and
then the only requirement is that somebody deciding what to invest in the [Toolchain] would find
it. A gap recorded where nobody looks has been forgotten with extra steps.

There are two kinds of gap and they lead to different work. The language having no extensible
[Detector] of a kind that can express the [Rule] within clause 3.1's bounds, and no practical
bespoke one, is the gap above, and the [Class] waits for one. The project's [Toolchain]
lacking a mechanism the [toolchain specification](TOOLING-SPEC.md) requires, such as a proving harness or an
[Identifier] resolver, is a mechanism gap, and it does not put the [Class] out of reach: the
[Practitioner] builds the [Rule], uses the substitutes clauses 3.3 and 3.6 already allow, and records
the mechanism gap alongside it. A mechanism gap that is also a breach of the [toolchain specification](TOOLING-SPEC.md)'s
own obligations, such as a [Toolchain] that cannot run its [Defences] on its own source, is recorded
against the [Toolchain]'s claim under section 7 as well, because that is the fact its [Owner] needs.
Where that record lives is the [Toolchain]'s own [Conformance] declaration, under the
[toolchain specification](TOOLING-SPEC.md)'s clause 9.2, or the [detector specification](DETECTOR-SPEC.md)'s clause 8.1 for a [Detector]; a [Practitioner] who cannot write there reports it by the channel
section 4 names, as a blocked decision.

**What happens next is not waiting.** Once the gap is recorded, the [Defect] is fixed conventionally
under section 2, with its reproduction test, and the work moves on. What the record changes is the
[Class], not the [Defect]: the [Class] is known to be undefended, and the next person to see an
[Instance] of it finds a decision to revisit rather than nothing.

**Why**: this clause is what distinguishes the method. Detection happens at the level of the
[Class], using an artefact that can be pointed at the whole codebase, and it happens before
anything is fixed.

### 3.3 Prove the net by making the rule fire

A new [Rule] MUST be proven to fire before it is trusted. It is never the goal to write a [Rule] and
be instantly green.

**This clause has three parts.** Part A: the proof, a red run kept as a commit of its own. Part B:
[Narrowing], which is the [Practitioner]'s to decide only when they can write down why the
[Hazard] cannot arise in what is excluded, and is [Suppression] for the [Owner] otherwise. Part C:
proving a [Rule] when the pattern is absent from the codebase, and what a [Rule]'s own code owes.

**Part A: the proof.**

**Proving and sweeping are two questions, not necessarily two runs.** Proving asks "does this [Rule]
work at all", and the answer is pass or fail. Sweeping, in clause 3.4, asks "how much of this [Class]
is present", and the answer is a count. A single execution of the [Rule] answers both, and no second
run is required. What matters is that the two answers are not confused with one another: a large
count does not make a [Rule] more proven, and a [Rule] that fired does not tell you the [Sweep] is
complete.

**The proof MUST survive as a commit of its own.** The [Defence] is committed with the originating
[Instance] still present, and the fix is committed after it. That first commit is the red run: it is
what a reviewer under section 7 checks out to reproduce the proof, and it is the only record that the
[Rule] fired on real code rather than on a [Fixture] alone. Where the [Defence] and the fix share a
commit, the red run can only be reconstructed by hand-reverting lines the reviewer has to guess at,
and the proof rests on that guess.
Surviving as a commit means a fresh checkout of that commit, followed by the project's own declared
setup, reproduces the proof. Dependency installation and generation driven by files the commit does
carry, a manifest, a lockfile, are that setup. Anything else the proof depends on that version control
does not carry, an empty directory, an ignored file, an artefact placed by hand, is state the commit
does not contain, and a reviewer under section 7
reproduces from a fresh checkout rather than from the [Practitioner]'s working tree, so a
proof that passes only there does not survive.
Both commits MUST remain individually reachable in the history the reviewer inspects. A merge that
flattens them into one destroys the proof, so a project whose merge policy does that MUST keep the
[Defence] commit reachable by another recorded reference, a tag or the retained branch, or MUST NOT
claim the remediation [Conforms]. How the project merges is its own business under section 8;
what must survive the merge is not.

**Part B: [Narrowing].**

**The decision comes first, and it turns on one sentence.** Can the [Practitioner] write down why
the [Hazard] cannot arise in the code being excluded? If they can, the exclusion is a [Narrowing],
it is theirs to make, and they record that sentence. If they cannot, or are not sure, the exclusion
is a [Suppression] and belongs to the [Owner] under section 4. Doubt is [Suppression]. The test is
the [Hazard], never the count. Two exclusions of the same [Rule] show the difference:

| Excluded code                                                             | The sentence                                                                        | Which it is                             | Who decides    |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------- | -------------- |
| A catch in test helpers that asserts on the caught error and returns it   | "The result is the error itself, so it cannot be used as if the call had succeeded" | [Narrowing]                             | [Practitioner] |
| A catch in a nightly export, excluded because the export "hardly matters" | Cannot be written: the caught result is still used, so the [Hazard] is present      | [Suppression], referred under section 4 | [Owner]        |

The sequence is: write the sentence; confirm it by search, the way clause 3.1's search confirms
the [Class], so that the excluded code is looked at and not assumed; record both. A sentence
that cannot be confirmed is a sentence that cannot be written, and the exclusion goes to the
[Owner]. Ask, for every exclusion, which row it sits in:

- If the excluded code **carries the [Hazard]**, the exclusion is [Suppression] and is forbidden to
  the [Practitioner], however many or few [Instances] it removes.
- If the excluded code **does not carry the [Hazard]**, the exclusion is precision and is required
  by clause 3.1's upper bound.
- If the [Practitioner] **is not sure which**, the exclusion is [Suppression], and it goes to the
  [Owner] with the doubt stated.

A [Rule] MUST NOT be narrowed because its [Instance] count is uncomfortably high. A high count is a
finding about the codebase, not a [Defect] in the [Rule]. **Firing on more than the originating
[Defect] is success.** A [Rule] that catches the reported [Instance] and forty-nine others has done
exactly what it was built to do, and the forty-nine are the reason the method exists.

**Then the proof, which runs the other way round, and both ways.** Where the change under proof is
that the [Rule] should stop firing on code that does not carry the [Hazard], three things are shown:
the [Rule] firing on that code before the change, from a commit still reachable in history and to the
same standard as a new [Rule]'s red run; the [Rule] not firing on it afterwards; and a retained
[Fixture] on which it still fires afterwards, the case that motivated the [Rule]. Where the narrower
shape was chosen from the outset and no wider [Rule] was ever built, the before state is a [Fixture]
of the wider pattern the [Rule] does not catch, retained as the record of what was left out.

A [Narrowing] reduces the [Practitioner]'s own work, which is why it is not left to self-report
alone. The sentence is recorded with the [Narrowing] as part of the [Rule]'s [Remediation docs];
the search confirms it. Code a [Narrowing] excludes MUST be searched to the standard of clause
3.1, and an [Instance] that search finds there disproves the sentence and reverses the
[Narrowing]. Every [Narrowing] MUST be enumerable by the
same means as the project's [Exceptions], so the [Owner] sees them in one place, and a
[Narrowing] that excludes more code than the [Rule] still covers MUST be reported to the [Owner]
as if it were a [Suppression]. The sentence remains the test of whether an exclusion is honest;
the search and the listing are what make a fluent dishonest one visible.
Where the [Toolchain]'s own listing shows the [Narrowing] with its sentence, that listing is
the record, and no separate prose is owed for it.

The sentence may turn out to be wrong. The method does not require the [Practitioner] to be
infallible; it requires the reasoning to be written down where the next reader, or the next
[Defect], can test it. A [Narrowing] with a recorded reason is correctable. One without is
indistinguishable from a [Suppression], which is why the sentence is the test and a percentage is
not.

**Part C: proving when the pattern is absent, and the [Rule]'s own code.**

**Where the pattern is present, prove the [Rule] against it.** At minimum the [Rule] MUST detect the
originating [Defect]. Where the [Defect] was found in code review rather than in production, the
pattern exists in the branch under review, so the [Rule] is written and proven there before the fix
lands.

**Where the pattern is genuinely absent, prove the [Rule] another way.** A [Rule] may legitimately be
green against the codebase: the branch that carried the pattern may have been rejected and
orphaned, the [Instance] may already have been fixed, or the [Rule] may be a purely proactive [Defence]
against a pattern that might occur but does not yet. In each of these cases the [Rule] MUST still be
proven, using [Fixture] code that demonstrates the pattern the [Rule] is meant to catch. That [Fixture]
SHOULD be kept as the [Rule]'s own test rather than deleted, so the proof re-runs whenever the [Rule] runs
instead of expiring the moment it succeeds.

**A green run proves nothing unless the [Rule] was loaded.** Before treating any run as evidence,
confirm the [Rule] was active in it, by enumerating the [Defences] that run was configured with or by
seeing it fire on a [Fixture] in the same run. A [Toolchain] analysing its own source with a
configuration that omits its own bundled [Rules] reports clean on every one of them, and both the
[Practitioner] and a reviewer have taken that for a pass.

**A [Rule] about how code is written may match its own source.** A [Rule] that forbids a construct in
[Rule] code will contain that construct, because it has to look for it. That match is not an
[Instance]: the construct there is the [Rule]'s subject, not its use, so the [Hazard] is absent and
clause 3.1's upper bound requires the [Narrowing]. Exclude it as precisely as possible, write the
sentence, and keep a test that proves the exclusion reaches nothing else.

**[Rules] are software, and SHOULD be built test-first like any other software.** How practical
that is varies considerably between [Detectors], some of which offer purpose-built [Rule]-testing harnesses
and some of which offer nothing, so this is a best-effort requirement rather than an absolute one.
Where a [Detector] makes [Fixture]-based testing impractical, an acceptance or smoke check that
demonstrates the [Rule] firing is an acceptable substitute.

[Fixtures] are part of the [Rule] and are versioned with it, under the co-location requirement in
clause 3.6. A [Fixture] is input to the [Rule]'s test, not part of the code the [Defence] enforces:
the [Sweep] excludes it under clause 3.4, and the enforced run does not report it, so there is no
circularity in a [Fixture] that deliberately fails the [Rule] it proves. Writing the [Fixture] is the [Practitioner]'s own decision under section 4, not something
to refer upwards.

Where they sit within the repository is the project's business. **Absent a convention, put them
where the project's existing tests live, and where there are none, alongside the [Rule]** - then say
which was chosen, as with any other [Calibration] under section 3.

**Why**: proving is the [Rule]'s own validation. A [Rule] that does not catch the [Instance] that
prompted it does not detect the [Class] it claims to, and shipping it would add a [Defence] that defends nothing
whilst defending nothing. This is the clause most easily skipped, because a green run feels like
success and looks like a clean codebase.

### 3.4 Sweep the codebase, then fix every instance

The [Practitioner] MUST run the [Rule] across the entire codebase and record the total [Instance]
count before fixing anything, and MUST then fix every [Instance] found rather than only the one
that was reported.

**"Entire codebase" means everywhere the pattern can occur, and nowhere it cannot.** For most
[Classes] that is a single language, because the pattern is a feature of that language, and sweeping
unrelated languages would be theatre. What the clause forbids is stopping at the package,
component or service that happened to report the [Defect] when the pattern plainly reaches further.
The question is where the pattern can exist, never where the bug was found.

Whether vendored dependencies, generated code and test [Fixtures] are in scope is the project's
decision, and it SHOULD be a recorded one rather than an implicit one, because a [Class] that is
excluded silently is indistinguishable from a [Class] that was never swept. **Where a project has
recorded no such decision, [Sweep] all first-party source in every language where the pattern can
occur, and exclude generated code and vendored dependencies**, then record that as the decision.
First-party source is code the project maintains in its own repository; generated code is what a
build step produces from other source; vendored code is a dependency copied in rather than
installed. Modified vendored code is first-party, because the project now maintains it. The two
exclusions are not a judgement that such code carries no [Hazard]. Unmodified vendored code is
somebody else's project, and an [Instance] found in it is reported upstream rather than fixed
in place; generated code carries an [Instance] only because its generator does, and the generator
is the first-party source the [Sweep] covers. An [Instance] the [Practitioner] notices in either
is recorded as a known [Instance] under section 4, not ignored.
A [Rule]'s own [Fixtures] under clause 3.3 are never [Instances]: they carry the pattern
deliberately, as the proof, and the [Sweep] count excludes them.

**The count MUST be corroborated independently**, to the standard set out in clause 3.1: a
comprehensive search by someone competent to make it. A [Rule] that is too narrow produces a small
count and a clean-looking [Sweep], and nothing inside the [Rule] can reveal that.

Where the independent search finds [Instances] the [Rule] did not, **clause 3.1 governs: the [Rule] was
drawn too narrowly and MUST be widened until it catches them.** The two are not permitted to
disagree, and the search is the authority, not the [Rule].

**Each fix MUST address the [Hazard] rather than the [Rule].** Throw on the absent case, or propagate
the absence explicitly so the caller decides: whatever the correct behaviour turns out to be. What is
forbidden is a change that turns the [Rule] green whilst leaving the failure mode intact, and
suppressing the [Rule] at the call site, which is not a fix at all.

**Supplying a default is usually the [Hazard] in another form**, not a fix. A default makes the absent
case look present, and the failure moves downstream to where nobody expects it and nothing names it.
A default is a fix only where absence is a legitimate state whose meaning the code defines, and then
the default states that meaning rather than filling the gap with a placeholder. This is the same
test as clause 3.3's [Narrowing] sentence and carries the same discipline: the [Practitioner]
MUST write down, with the fix, what the absence means and why the default is that meaning, and
where the sentence cannot be written the absence is an error and MUST be treated as one.

**Repetition is not the problem.** Where every [Instance] genuinely has the same correct answer,
applying that answer to all of them is right, and the fact that it looks mechanical is not an
objection. The requirement is that each [Instance] was actually examined and the same answer was
genuinely correct for it, rather than assumed correct because it was correct elsewhere. Where one
change is applied across many [Instances], the [Practitioner] MUST record which were examined
individually and which received the change by pattern, and MUST examine a sample of the latter
large enough, given how much the surrounding code varies, that a wrong answer there would have
been seen. "All examined" with nothing behind it is a claim, not a record.

**Fix them all.** There is no count at which a [Practitioner] stops fixing. A [Practitioner]
stops only when the next [Instance] needs a decision they lack the authority to make, and section 4
says which decisions those are; a hundred [Instances], or a thousand, is work rather than a
decision. A [Baseline] of the existing [Instances], so that the [Rule] blocks only new ones, MAY
be adopted where fixing every [Instance] is genuinely unfeasible, and the scale at which that
becomes true is tremendous. **No threshold is given here deliberately.** Any figure would be
calibrated to the [Toolchain] of the moment rather than to anything durable, and the honest measure is
the size of the change rather than the hours it would take, since the hours depend entirely on what
is doing the work.

**Under AI-assisted development a [Baseline] is almost never the right answer.** The historical case
for baselining was the cost of human hours, and that cost has largely collapsed: an [Agent] can work
through hundreds of [Instances] at a price that made a [Baseline] unavoidable a few years ago. Reaching
for one now is usually a habit rather than a judgement. This specification stops short of
forbidding [Baselines] outright, and only just.

Adopting one is a decision for whoever owns the codebase, taken after fixing has genuinely been
attempted rather than on the strength of the [Instance] count alone. "Attempted" is the
[Owner]'s judgement on the [Practitioner]'s report, not a threshold the [Practitioner] applies:
the [Practitioner] fixes until stopped by a decision they cannot make, and reports what was fixed and
what remains. That report MUST state the count fixed, the count remaining, what stopped the fixing,
and what would have to be tried next, so the [Owner] judges a record and not an adjective. **A
[Practitioner] executing this method does not have that authority** - see section 4. A baselined project SHOULD reduce the
[Baseline] over time and MUST NOT allow it to grow.

**Why**: consistency across a codebase is fundamental, and the [Sweep] is where this method
delivers most of its value. In the published worked example the reported [Defect] was one of
twenty-three; the other twenty-two were bugs waiting to surface in different contexts, reported
by different customers, at different times. A project that knows about twenty-three [Instances]
and fixes one has produced a documented list of [Defects] it has chosen to keep.

### 3.5 Enforce permanently, and block

The [Rule] MUST become a permanent part of the project's quality checks, and it MUST **fail** rather
than warn. Three things must hold for this clause to be met: the [Rule] is in the checks the
project runs to accept changes; it fails rather than warns; and every [Suppression] it carries is
covered by a recorded decision. A [Rule] that reports a violation without failing does not
[Conform], and neither does a [Rule] whose green run depends on a [Suppression] that no recorded
decision covers, since the checks are then passing around the [Instance] rather than enforcing
against it.

**Where those checks run, and what the project does when they fail, is out of scope.** Continuous
integration, git hooks, branch policy and release process are the project's own business; see
section 8. What this clause requires is that the [Defence] is permanent, applies to everyone rather
than to whoever remembers it, and produces a failure rather than a remark.

Removing a [Rule], or adding a [Suppression] for an [Instance], MUST be a recorded decision rather
than a silent edit, and belongs to the [Owner] under section 4, which governs. Where a [Rule]
carries a [Suppression] that no recorded decision covers, this clause is not followed until the
decision is recorded or the [Suppression] removed.

**Enforcement is demonstrated through the project's own entry point.** Running the [Detector]
directly shows that the [Rule] can fire; it does not show that the project's checks will run it.
The [Practitioner] MUST run the invocation the project uses to accept changes, over everything
that invocation covers, and see the [Rule] reported there. A [Toolchain] that wraps its
[Detectors] in its own command is asking for that command to be used, and a [Practitioner]
who bypasses it has proven the [Rule] and not the [Defence].

For a [Bundled defence], the project whose entry point demonstrates it is any project the
[Toolchain] is genuinely installed into, the [Consuming project] included, since that is
where the [Rule] will be enforced. A mechanism gap that stops the [Toolchain] running its own entry
point on its own source moves the demonstration to such a project; it does not weaken it. The
substitutes clause 3.2 allows, for proving under clause 3.3 and for resolution under clause 3.6, do
not extend to this clause: the demonstration here is through an entry point or it is not made. A
project created for the purpose, with the [Toolchain] installed into it from the source under
test, is such a project, so a [Toolchain] with no consumer yet is not excused.

**Why**: the purpose is that the mistakes of the past become structurally impossible to repeat, and
a [Warning] is not structure. A [Warning] is a suggestion, and suggestions decay under deadline
pressure, which is the condition under which the original [Defect] was written.

### 3.6 Make the failure message terse, and point it at real documentation

The failure [Message] MUST be terse, a

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 019: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

The exact prompt every reader received, part 2 of 2

nd it MUST carry a stable [Identifier] that resolves to
[Remediation docs] shipped with the project.

That documentation MUST state three things, besides what clause 3.2 adds for a [Rule] the
[Practitioner] writes: what the [Rule] is about, why it exists, and how to
fix a violation correctly using the project's preferred approach.

A [Practitioner] who finds that an existing [Rule]'s printed [Identifier] does not resolve records
it where clause 3.2's gaps are recorded, naming the [Rule] and the [Identifier], with no [Rule] build of
their own to attach it to. It does not block the remediation. Where the mechanism cannot resolve
[Identifiers] at all, it is a [Toolchain] gap and blocks the [Toolchain]'s claim under section 7
until fixed; where the mechanism resolves others and only this [Rule]'s documentation is missing, it is a
fault in that [Rule]'s [Remediation docs], owned by whoever wrote the [Rule], and bears on
the project's claim rather than the [Toolchain]'s.

**The split between the two is by job, not by length.** The [Message] carries what was detected,
where, and the [Identifier]; it is read under interruption by somebody trying to get on with
something else. The documentation carries the reasoning and the remedy; it is read once, by
somebody who has decided to understand the [Rule], and it is maintained as the project's thinking
develops. Anything that would grow over time belongs in the documentation.

**A [Rule] and its documentation are one artefact and MUST be versioned as one.** They live in the
same repository and are committed together, or in a library the project depends on at a clearly
tracked version. A [Rule] carries knowledge specific to the project it defends, and its
documentation is where most of that knowledge actually sits, so anything that lets the two drift
apart destroys the value of both. The same applies to a [Rule]'s [Fixtures] under clause 3.3.

An external URL [Conforms] only where the project controls it and versions it alongside the [Rule]. A
link into a third-party wiki or a general article does not, because neither can be relied upon to
still describe this [Rule].

**Repository structure is left to the project.** Where these artefacts sit, how they are foldered,
whether [Rules] are vendored or depended upon: none of that is this specification's business. What
is required is that they move together.

The [Identifier] MAY be a [Rule] ID resolved by a command, an anchor in a documentation file, or a
URL. It MUST continue to resolve for as long as any released version can emit it, which is a
stronger requirement than surviving the current release. Where a [Rule] is redesigned such that its
meaning materially changes, a new [Identifier] SHOULD be issued and the old one SHOULD keep
resolving to an explanation of what became of it.

A [Message] that names a pattern without leading anywhere does not [Conform]. "Pattern X detected"
teaches nothing.

**Why**: the [Message] is read at the moment of failure, by someone who wants to get on with their
work, so it has to be short enough to read and specific enough to act on, whilst the reasoning
has to live somewhere it can be maintained and can grow. Splitting it this way keeps the [Message]
terse without losing the teaching, and the documentation becomes the project's accumulated
engineering knowledge rather than a comment nobody revisits. The best custom [Rules] are
opinionated documentation encoded as automation.

## 4. Authority: which decisions belong to whom

The method is frequently executed by somebody who does not own the codebase, including an [Agent]
acting under instruction. This section states which decisions that person takes alone and which
they do not.

**Decisions the [Practitioner] takes alone**, without seeking approval:

- What [Class] the [Defect] belongs to, and where its bounds sit (clause 3.1).
- Which [Detector] to use and how to express the [Rule] in it (clause 3.2), unless the project
  requires the [Owner]'s sign-off on a weaker kind of [Detector], as clause 3.2 allows.
- How to prove the [Rule], and what [Fixture] to prove it against (clause 3.3).
- What the correct behaviour is at each [Instance], and therefore what each fix should be (clause
  3.4).
- The wording of the failure [Message] and the [Remediation docs] (clause 3.6).

**Decisions that MUST go to whoever owns the codebase:**

- Adopting a [Baseline], in whole or in part (clause 3.4).
- Suppressing an [Instance], or removing or disabling an existing [Rule] (clauses 3.4 and 3.5).
- Accepting a known [Instance] as unfixed for any reason.
- Deciding that a [Class] will not be defended at all, where a [Rule] for it is achievable.
- Leaving unbuilt the next wider [Rule] that clause 3.1 required the [Practitioner] to name, where
  it was rejected because no [Instance] of it exists today rather than because the [Hazard] cannot
  arise there. The absence of an [Instance] is the [Practitioner]'s reason not to widen unasked; it
  is not authority to exclude the extension, and recording it as a known gap does not change whose
  decision it is. This does not reopen a [Class] whose bounds a [Hazard] sentence has settled, and
  it reaches no further than the one wider [Rule] already on the record.
- Leaving a [Rule] narrower than an independent search under clause 3.1 showed it should be, where
  the reason is that the wider check is harder to build without [False positives] rather
  than that the [Hazard] is absent from what it would add. The [Instances] the search found are
  fixed regardless; what stays with the [Owner] is the [Class] left partly undefended.

The dividing line is that the [Practitioner] decides **how the [Defence] is built**, save a weaker
kind of [Detector] where the project requires the [Owner]'s sign-off on it, and the [Owner]
decides **what the codebase is permitted to keep**. An [Agent] MUST NOT [Baseline], suppress or
knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to
be.

**The default position is that none of these are permitted at all.** [Suppressions], [Baselines] and
every other means of evading a [Defence] are not techniques with a threshold governing their use; they are
[Exceptions] to the method, and the standing answer is no. An [Exception] exists only where a human has
discussed it, agreed it and documented it for that project. Nothing an [Agent] concludes on its own
creates one.

**Uncertainty is itself an [Escalation] trigger.** Where the [Practitioner] is not confident that code
they are about to exclude is free of the [Hazard], that exclusion is a [Suppression] and belongs to the
[Owner], whatever it is called. This is deliberately asymmetric: [Narrowing] is the [Practitioner]'s
decision only whilst they are sure, and doubt routes it upwards. Without this, the [Hazard] judgement
in clause 3.3 would let a [Practitioner] suppress an [Instance] whilst never touching the clause that
would have escalated it.

**Where the work is entirely [Agent]-driven, do the work.** Fixing [Instances] is cheap now, and an
[Agent] that reaches for an [Exception] has almost always found a shortcut rather than a genuine
obstacle.

**What to do whilst waiting.** A decision awaiting the [Owner] MUST NOT stall the rest of the work.
The [Practitioner] completes everything within their own authority, reports the blocked decision
with the [Instance] count and what it would cost to fix, and leaves the [Rule] unmerged rather than
merged in a weakened form. Where the only thing pending is whether one matched case carries the
[Hazard], the [Rule] MAY merge at full width with that case recorded as a known [Instance]
awaiting the [Owner], enumerable under clause 8.7: the [Rule] is not weakened, the case is not
hidden, and the [Practitioner] is not pushed towards declaring that no pattern exists. Reporting a
[Sweep] of four hundred [Instances] and stopping is a useful outcome; quietly baselining them is not.

Report by whatever channel carries the rest of the [Practitioner]'s work: for an [Agent], its
output to whoever instructed it. An [Instance] in code the [Practitioner] is not permitted to
change is reported the same way, as a blocked decision with its count, and is never a reason to
narrow the [Rule] so that it stops seeing it.

**The [Instance] count alone never triggers [Escalation].** A large [Sweep] is work, not a blocked
decision. Stopping to report is what the [Practitioner] does when an [Owner] decision is genuinely
required, which means an [Exception] is being contemplated, or when they are uncertain whether an
exclusion carries the [Hazard]. It is not what they do because the number is uncomfortable. Five
hundred [Instances] with no [Exception] needed is five hundred [Instances] to fix.

A remediation MAY be delivered across several changes where the volume warrants it, **provided the
[Rule] does not become [Blocking] until every [Instance] is fixed**, since a [Blocking] [Rule] merged over a
codebase that still violates it either fails continuously or has been weakened to avoid doing so.
No [Instance] may be knowingly left unfixed at the end without the [Owner]'s decision.

**Why**: without this, an executing [Agent] either stalls at the first judgement call it cannot
authorise or takes the decision unilaterally, and the second failure is much harder to notice than
the first. Both were observed in cold readings of an earlier draft of this specification.

## 5. Ordering the quality checks

Defence Before Fix presumes quality checks ordered as follows, and the ordering is enforcing rather
than advisory:

1. [Detectors] - type checking, linting, custom [Rules]
2. Automated tests - unit, integration, functional
3. Build verification - services start, dependencies resolve
4. Human acceptance testing - visual review, workflow validation

[Detectors] MUST run first, and a failure at that level MUST stop the levels below it from
being treated as meaningful.

This constrains the **sequence of checks**, not the infrastructure that runs them. How the project
expresses that sequence is out of scope, per section 8. It is a property of the project, not a step
in any one remediation: a [Practitioner] handling a [Defect] is not required to reorder the
project's checks, and a project whose checks run in another order is a project that does not
[Conform], which is a finding to record, not work to do on the way to a fix.

**Why**: a [Detector] is preventive and a test is diagnostic. A [Detector] reads every file, every
time it runs, so it cannot miss a file merely because nobody thought to write a test for it.
Failures at a lower level also produce confusing results at the levels above, so any other order
costs time diagnosing symptoms of a problem the first level would have named directly.

## 6. Relationship to test-driven development

Defence Before Fix neither replaces nor competes with TDD. They operate at different levels, and
a full remediation uses both: a test, executed by a [Runner], pins the [Instance]; a [Rule], evaluated by
a [Detector], catches the [Class].

| Concern            | TDD                                | Defence Before Fix                           |
| ------------------ | ---------------------------------- | -------------------------------------------- |
| Operates on        | The specific [Defect]              | The [Class] the [Defect] belongs to          |
| Artefact           | A test                             | A [Rule] in a [Detector]                     |
| Proves correctness | For one behaviour, by executing it | For a pattern, by reading the code           |
| Answers            | "Is this bug fixed?"               | "Can this kind of bug still exist anywhere?" |

The specific [Defect] is still reproduced with a test and proven fixed, exactly as normal. What
this method adds is the phase before that work begins, in which the codebase's permanent [Defence]
[Coverage] is extended using the evidence the [Defect] has just supplied.

The name records the ordering. The [Defence] comes first, because once the fix has landed the
evidence is gone and the opportunity closes with it.

## 7. Conformance

[Conformance] is claimed at one of four levels. Partial [Conformance] MUST NOT be described as
[Conformance].

**A remediation [Conforms]** if all six clauses of section 3 were followed for that [Defect], and
the verdict that they were rests on reproduction, as this section closes by requiring, not on the
report.

**A [Defence] [Conforms]** if it satisfies clauses 3.1, 3.2, 3.3, 3.5 and 3.6: it is drawn to a
[Class] within both bounds and not to the reported [Instance], it is evaluated by reading code,
in the kind of [Detector] clause 3.2 requires, it
was proven to fire against either the originating [Instance] or [Fixture] code, it fails rather
than warns, and its [Message] is terse and resolves to [Remediation docs] versioned alongside
it. A [Defence] can [Conform] whilst the [Sweep] is incomplete; what that describes is a
[Conforming] [Defence] over a codebase with known [Instances], which section 4 requires to be
recorded, not a [Conforming] remediation.

**A [Toolchain] [Conforms]** if it permits bespoke custom [Rules], supports the ordering in section 5,
and satisfies clauses 8.1 to 8.5 and 8.7: the [Practitioner] can run it, the result reaches them in
the output they are reading, [Identifiers] resolve mechanically, documentation states the correct
construction, and both the [Defences] and the project's recorded decisions can be enumerated. Clause
8.6 is a SHOULD and does not bear on [Conformance], deliberately: it is the one clause that acts
before a mistake rather than after, so no failing run can prove it absent, and [Conformance] is
claimed only over what a run can prove. The [toolchain specification](TOOLING-SPEC.md) names a
[Toolchain] that also satisfies it as [Conforming] with [Agent] support, which is the claim to
make when it is true. Those obligations fall in two documents: what each [Detector] the
[Toolchain] routes a [Defence] through must offer is stated in the
[detector specification](DETECTOR-SPEC.md), and what the assembled [Toolchain] must add is stated
in the [toolchain specification](TOOLING-SPEC.md), which requires the first as its own opening clause,
carries the ordering in section 5 and the correct construction of clause 8.4 as clauses of its own,
and states the listing and the project record that clauses 8.5 and 8.7 require.

**A project [Conforms]** if its [Defences] and [Remediation docs] [Conform], and if its recorded
decisions under section 3 are discoverable. Nothing here constrains how the project runs its checks
or what it does when they fail.

[Toolchain] [Conformance] is stated in full in the [detector specification](DETECTOR-SPEC.md)
and the [toolchain specification](TOOLING-SPEC.md) together, which give each of the obligations above as a
clause with its own reasoning, and add what a [Detector] and a [Toolchain] must provide so
that a project can meet its own. The summary here is normative and sufficient to judge a
[Toolchain] by; the companion documents are where a [Detector] maintainer and a
[Toolchain] author should work from.

A verdict on a remediation or a [Defence] MUST rest on reproduction, not on the report: the reviewer
reruns the [Defence] red at the commit that introduced it and green at the final commit, through the
project's own entry point for accepting changes, and reruns the originating symptom against the fix.
Where the [Rule] was proven against a [Fixture] under clause 3.3, the red run is against the
retained [Fixture]. Where the originating symptom cannot be reproduced through the entry point,
an incident or an observation at production scale, the reviewer says so and verifies the red and green
runs alone. A verdict on a [Toolchain] or a project rests on the reviewer exercising the clauses
named above, not on the claimant's report of having done so. A report that reads as [Conforming]
has not been shown to be.

A [Toolchain] MAY additionally audit its own [Rules] against clause 3.6, failing its own release if any
[Rule] fails something without resolving to [Remediation docs]. This is the strongest
available demonstration that the clause is honoured rather than asserted.

## 8. Operating a defence under AI-assisted development

*This section is normative.*

### The argument these clauses rest on

For a human developer, a failure [Message] that teaches is good practice. They may read it, may
internalise it, may ignore it, and which of those happens depends on their seniority, their
workload and how many times they have seen the [Message] before.

For an [Agent], the failure [Message] is the entire remediation loop. It is consumed as instruction,
in the same turn, every time, with no fatigue and no seniority gradient. A [Message] that resolves
to documentation explaining the correct approach does not simply block the [Agent], it redirects it,
and it does so identically on the thousandth occurrence as on the first.

That reframes the usual complaint about AI-written code. The difficulty was never that [Agents] make
mistakes, since people do too. The difficulty is that nobody built the channel to correct them at
the level of the [Class] rather than the [Instance].

If the value of the method depends on that loop closing, then what closing it requires has to be
stated rather than assumed. The clauses below are what a [Defence] must do to be usable by an [Agent]
at all.

### Scope: this specifies a defence, not a pipeline

**Continuous integration, git hooks, branch policy, review process and release management are out
of scope.** How a project chooses to run its quality checks, and what it does when they fail, is
the project's own business and no part of this specification.

What is in scope is the [Defence] itself: a [Rule] that reads code, the documentation that explains it,
and the requirement that the [Practitioner] can run it and act on the result. A specification that
told projects how to run their checks would be overreaching, and would be ignored for it.

### 8.1 The practitioner MUST be able to run the defence themselves

A [Rule] that only reports through infrastructure the [Practitioner] cannot invoke is not usable by
them, whatever it does for anyone else.

**Why**: an [Agent] that cannot check its own work against a [Defence] cannot iterate against it, so
the loop never closes in the turn where the mistake was made, which is the only moment it is cheap
to fix.

### 8.2 The result MUST reach the practitioner in the output they are already reading

The [Detector]'s own output, at the point of the work. Not exclusively a dashboard, a report artefact or
a summary elsewhere.

**Why**: a [Message] that teaches nobody, because nobody sees it, is the same as no [Message].

### 8.3 The identifier MUST resolve without a human

By a command the [Practitioner] can run, a file they can read, or a URL they can fetch.

**Why**: clause 3.6 requires the [Identifier] to resolve. This requires it to resolve *for the
reader*, which for an [Agent] means mechanically. An explanation that lives in a colleague's head
resolves for nobody at three in the morning either.

### 8.4 Documentation MUST state the correct construction, not only the prohibition

Explaining why the pattern is dangerous is not sufficient. The documentation has to show what to do
instead, specifically enough to act on.

**Why**: this is the difference between [Blocking] an [Agent] and redirecting it. A prohibition alone
leaves it to guess at the replacement, and it will guess.

### 8.5 A project's defences MUST be enumerable

A [Practitioner] MUST be able to list what defends this codebase, and read each [Defence]'s
documentation, without triggering it first.

**Why**: an [Agent] arriving at a codebase has no colleague to ask and no memory of last time.
Without this, a project's standards can only be learned by violating them one at a time.

### 8.6 A project SHOULD publish a summary of its defences suitable for an agent's context

One terse line per [Defence], stating the [Rule] as a standing instruction rather than as a failure
report, each linked to its full documentation. *"Error hiding is forbidden"* is the shape.

**Why**: everything else in this method operates after the mistake. This operates before it. A
summary small enough to sit in an [Agent]'s working context turns the accumulated [Defences] from a
series of ambushes into a description of how this project expects code to be written, and it costs
one table.

### 8.7 Recorded project decisions MUST be discoverable by the same means

The judgements section 3 delegates to project level, once agreed and written down, MUST be
reachable exactly as the [Defences] are.

**Why**: section 3 says a project's accumulated decisions become part of its [Coverage]. [Coverage]
nobody can find is not [Coverage], and every [Practitioner] who arrives after a decision will otherwise
re-open it.

## 9. Citation

> Edmonds, Joseph. *Defence Before Fix*, version 1.1.0. First published 22 February 2026.
> <https://ltscommerce.dev>

## Appendix A: Instructing an agent

Where an [Agent] is expected to follow this method, give it the clauses rather than the article.
This appendix restates sections 2 to 4; where the two differ, the sections govern.

> When you find a [Defect] of any kind, do not fix it yet.
>
> First work out what [Class] it belongs to: the pattern, style, idiom or configuration that allowed
> it. Do not decide in advance whether that is possible, attempt it. If you cannot write a [Rule]
> for it, say so and fix the [Defect] conventionally.
>
> Write a custom [Rule] that detects the [Class], in a tool that reads code rather than running it.
> Put it in a [Semantic] [Detector], one in which a [Rule] can ask the type of any expression and
> what any name refers to, across the whole program; failing that, one that sees syntax; and only
> then one that sees text. Use a weaker one only where the stronger one cannot express the [Rule],
> or the language has none that takes custom [Rules]: none the project runs, in any configuration
> it can use without a hosted service or a licence tier, and none its ecosystem offers outside
> one. Name the [Detector] and its kind in the [Rule]'s documentation. Where you use a weaker one,
> write down which reason holds, and why, naming the stronger [Detectors] you considered, there and
> in the description of the commit that adds the [Rule], moves it to another [Detector], or first
> records the reason, and where the project requires its [Owner]'s sign-off on a weaker one, get
> it and record it with the reason. Draw
> the [Rule] so that it catches more than the single [Instance] you started from, but never so
> broadly that it matches code which does not carry the [Hazard].
>
> Do not trust the [Rule] as your only way of finding [Instances]. Search independently as well, by
> text search and by reading the code, and check the [Rule] catches what you found by hand. Make that
> search a thorough one rather than a gesture, because everything downstream rests on it. If your
> own search turns up [Instances] the [Rule] missed, widen the [Rule] until it catches them; the search
> wins, not the [Rule]. If it turns up nothing new, then one [Instance] is a supported conclusion rather
> than a guess.
>
> The [Hazard] is whatever harm the [Class] does, and it need not be a failure. Error hiding counts.
> So does something merely sloppy that makes the code harder to reason about safely.
>
> Prove the [Rule] fires before you trust it. It must catch the originating [Defect]. If the pattern
> is not present in the codebase, because it was already fixed or because you are defending
> against it pre-emptively, prove the [Rule] against [Fixture] code that demonstrates the pattern and
> keep that [Fixture] as the [Rule]'s test.
>
> Then run the [Rule] everywhere the pattern can occur, which usually means one language but never
> just the component the bug was reported in, and report the [Instance] count. Catching far more
> [Instances] than you expected is success, not evidence the [Rule] is too broad. Only ever narrow a
> [Rule] to exclude code that does not carry the [Hazard]; never to make the number smaller.
>
> Fix every [Instance]. Examine each one; where the same answer is genuinely right for all of them,
> applying it to all of them is correct. Never satisfy the [Rule] whilst leaving the [Hazard] in place,
> and never suppress the [Rule] at the call site.
>
> You do not have the authority to [Baseline], to suppress an [Instance], or to leave a known [Instance]
> unfixed, however large the count turns out to be. Those belong to whoever owns the codebase, and
> the standing answer is no unless a human has already agreed and documented an [Exception] for this
> project. If you hit one, finish everything else, then report the count and what fixing it would
> take, and leave the [Rule] unmerged rather than merging it weakened.
>
> A [Defect] you find and do not fix now is recorded where the project keeps its other decisions,
> naming the [Class] if you can already see it. Mentioning it in your output is not a record.
>
> If you are unsure whether code you want to exclude carries the [Hazard], you are suppressing it, so
> refer it upwards rather than deciding. Narrow only where you are confident.
>
> Otherwise, just do the work. Fixing [Instances] is cheap for you, and reaching for an [Exception] is
> almost always a shortcut rather than a real obstacle.
>
> Make the [Rule] a permanent part of the project's quality checks, failing rather than [Warning]. Write
> its failure [Message] terse, carrying a stable [Identifier] that resolves to documentation shipped
> with the project saying what the [Rule] is about, why it exists and how to fix a violation
> correctly. Check you can run the [Rule] yourself and read its output, because if you cannot, nor can
> the next [Agent].
>
> Only then fix the original [Defect] in the normal way, with a test that reproduces it.

## Changelog

| Version | Date       | Change                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1.0.0   | 2026-09-08 | Initial specification, formalising the method published on 22 February 2026. Revised before publication after three independent cold readers understood the method correctly and still could not execute its judgement calls.                                                                                                                                                                                                                                                                                                                                                        |
| 1.0.1   | 2026-09-08 | Clarity, no obligation changed: section 3 opens with a map of the six clauses; clause 3.1 opens with its five steps and closes with what it leaves on the record; clause 3.3 is in three named parts and its [Narrowing] part opens with the decision. The header and the terminology entries for [Detector], [Toolchain] and [Conform] name the [detector specification](DETECTOR-SPEC.md) 1.0.0 alongside the [toolchain specification](TOOLING-SPEC.md) 0.2.0, and [Conform] extends to the companion specification being claimed. Accepted under [ACCEPTANCE.md](ACCEPTANCE.md). |
| 1.1.0   | 2026-10-02 | New obligation: a [Defect] whose fix is deferred is recorded where the project's other decisions are enumerable under clause 8.7, naming the [Class] where apparent; whether it stays unfixed is the [Owner]'s decision under section 4, and a mention in conversation is not a record. Appendix A carries the sentence for an [Agent]. Section 7's definition of a [Conforming] remediation names reproduction in the same sentence. Accepted under [ACCEPTANCE.md](ACCEPTANCE.md).                                                                                                 |

<!-- Term link definitions -->

[agent]: #agent
[agents]: #agent
[baseline]: #baseline
[baselines]: #baseline
[blocking]: #blocking
[bundled defence]: TOOLING-SPEC.md#bundled-defence
[calibration]: #calibration
[calibrations]: #calibration
[class]: #class
[classes]: #class
[conform]: #conform
[conformance]: #conform
[conforming]: #conform
[conforms]: #conform
[consuming project]: TOOLING-SPEC.md#consuming-project
[coverage]: #coverage
[dbf]: #dbf
[defect]: #defect
[defects]: #defect
[defence]: #defence
[defences]: #defence
[detector]: #detector
[detectors]: #detector
[escalation]: #escalation
[exception]: #exception
[exceptions]: #exception
[false positive]: #false-positive
[false positives]: #false-positive
[fixture]: #fixture
[fixtures]: #fixture
[hazard]: #hazard
[hazards]: #hazard
[identifier]: #identifier
[identifiers]: #identifier
[instance]: #instance
[instances]: #instance
[message]: #message
[narrowing]: #narrowing
[owner]: #owner
[practitioner]: #practitioner
[remediation docs]: #remediation-docs
[rule]: #rule
[rules]: #rule
[runner]: #runner
[runners]: #runner
[semantic]: #semantic
[suppress]: #suppression
[suppression]: #suppression
[suppressions]: #suppression
[sweep]: #sweep
[syntactic]: #syntactic
[textual]: #textual
[toolchain]: #toolchain
[warning]: #warning

===== FILE: fixtures/remediation-ledger.md =====
# Fixture: the ledger remediation

A fictional remediation, described as the pull request and its commits would show it. Grade it
against the document you were given. Where the description does not say, answer from what is
written.

## What the pull request shows

A defect: `Ledger::total()` silently returned zero when a currency conversion failed, because the
conversion call's error was caught and the empty result treated as a valid amount.

The practitioner, a coding agent, did the following, in this order, over five commits.

1. **Commit A.** Wrote a note in the pull request naming the class as "a fallible call whose
   error is caught and whose result is then used as if the call had succeeded", stating that
   the hazard is silent wrong output rather than a crash, and naming the next wider class it
   did not build, "any caught error that is not rethrown or reported", as not attempted because
   the codebase has hundreds of such catches and most are legitimate.
2. **Commit B.** Wrote a rule in the project's static analyser, identifier `proj.caught-then-used`,
   that flags a caught error followed by a use of the call's result in the same function. Added
   the rule to the project's analyser configuration. The commit also contains a fixture file
   reproducing the `Ledger::total()` shape, and the CI run for this commit is red on that rule
   with five findings: the fixture, `Ledger::total()`, and three other functions.
3. **Commit C.** Fixed `Ledger::total()` and two of the three others, so that the failure is
   propagated. For the third, `Report::render()`, the agent added an inline `// ignore proj.caught-then-used` comment with the note "rendering must never throw", and did not raise
   it with anyone. The CI run is green.
4. **Commit D.** Wrote the rule's documentation as a page on the company wiki, at a URL the rule's
   failure message prints in full. The page states what the rule forbids and why, and shows the
   propagating construction to write instead. The failure message is one line: the identifier,
   the file and line, and the wiki URL.
5. **Commit E.** Confirmed in the pull request description that the rule is set to fail the
   build, not warn, and that the whole quality invocation was run locally and passed.

The pull request was merged with a merge commit; all five commits remain in the history.

The project's recorded decisions say nothing about inline ignores. The project has a named owner.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 020: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted. Local paths and the owner's name are replaced by placeholders in the instruction and definition below; nothing else is changed.

Command and working directory

ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt, each reader in a fresh empty directory made by mktemp -d outside the repository

Model version each reader reported

reader 1: claude-haiku-5-5
reader 2: claude-haiku-5-5
reader 3: claude-haiku-5-5
reader 4: claude-haiku-5-5
reader 5: claude-haiku-5-5

The delegated marker's task prompt

MODE: mark

Worktree: <worktree> (branch editor/semantic-detectors). You have not seen the drafting or the review.

You are the delegated marker for run 020. Read ACCEPTANCE.md and acceptance/runs/README.md in full: the run is made under the unmerged process change of pull request 13 at commit a695010, whose versions of those two files are the ones in this worktree (check with `git diff a695010 HEAD -- ACCEPTANCE.md acceptance/runs/README.md`: the only difference is this pull request's question-set line adding question 2d for SPEC.md). Read acceptance/detector/KEY.md and acceptance/detector/QUESTIONS.md and the fixture acceptance/fixtures/lintomatic.md, and the document DETECTOR-SPEC.md, all at commit 5b6093f (`git show 5b6093f:<path>`). Earlier run records of other pull requests, for the record's style: acceptance/runs/016 to 018. Run 019 of this pull request, on SPEC.md, is being marked at the same time by another marker; it does not bear on this document.

The answer sheets: <run-dir>/reader-1.json to reader-5.json (each reader's whole output; the answer text is the `result` field, also in sheet-1.md to sheet-5.md). The exact prompt every reader received: <run-dir>/prompt.txt.

Facts from the editor, which the record states as supplied:

- Document: DETECTOR-SPEC.md. Pull request: 14 (https://github.com/Defence-Before-Fix/defence-before-fix.github.io/pull/14), branch editor/semantic-detectors, commit 5b6093f.
- Cohort: five readers, model alias haiku; the model version each reader reported is in <run-dir>/models.txt.
- Isolation: each reader a separate `claude -p` process started through the `ccy-claude` wrapper (which only reattaches the session's credential and passes arguments through verbatim), in a fresh empty directory made by `mktemp -d` outside any repository, with the packet on standard input. Command: `ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt`. The machine applies no managed (policy) settings (no managed settings file present). The editor attests the rest of the isolation.
- Readers set aside for a cause the sheets cannot show: none.
- Marker: a fresh dbf-spec-editor agent (Claude Opus) dispatched with this instruction and its agent definition, <agent-definition, below>, having seen neither the drafting nor the review.
- Run number 020; run 2 in pull request 14 (run 019 on SPEC.md was made at the same time); runs in the pull request so far, counting void runs: 2.
- Made under a process change before it merged: yes, pull request 13 at commit a695010, named before the readers started.

Mark every sheet question by question under ACCEPTANCE.md's Marking section, apply the pass criteria exactly, and take the stricter reading wherever the rules leave a judgement open, stating it in the record. Write the record to <worktree>/acceptance/runs/020-semantic-detectors-run-2.md in the order acceptance/runs/README.md gives; leave "the findings applied" as "To be stated by the editor" and the link to the answer sheets as "LINK-TO-SHEETS". Do not commit. Reply with the verdict, and for a FAIL every finding with the passage, the readers and the rule, in under twenty lines. British spelling, no em dashes. Write nothing else; never use sed.

The agent definition the marker ran under

---
name: dbf-spec-editor
description: Drafts, reviews, key-updates, marks or records a change to the Defence Before Fix specification, one mode per dispatch, following the specification repository's CONTRIBUTING.md, SCOPE.md and ACCEPTANCE.md exactly. Dispatched by the dbf-spec-change skill; it cannot run a cohort itself.
tools: Read, Write, Edit, Grep, Glob, Bash
model: opus
---

You work on one change to the Defence Before Fix specification, in the worktree path you are
given. The process is defined by that repository's `CONTRIBUTING.md`, `SCOPE.md`,
`ACCEPTANCE.md` and `acceptance/README.md`; read the ones your mode needs, in full, before you
act. You invent no steps. You never merge, push to `next` or `main`, release, tag or publish,
and you never change a question, threshold or fixture. Your prompt names one mode.

Write as the specification is written: British spelling, no em dashes, defined terms linked as
`spec-qa.py` requires, every clause or section number linked to its heading. Commit on the
worktree's branch.

## MODE: draft

1. Read `SCOPE.md`, `DECLINED.md`, and every clause the change touches, with the terminology
   entries they use.
2. Decide the kind. A change to the sentences carrying MUST, SHOULD or MAY in a numbered section
   or appendix is an obligation; anything else is a clarity edit.
3. For an obligation, answer the five SCOPE tests in a sentence or two each, and name the
   section the obligation extends. If a test fails, or an existing clause already covers it, say
   so and stop without editing.
4. Make the smallest edit that does the job, in the clause that owns the subject.
5. Add one line under `Unreleased` in `CHANGELOG.md`, saying what changed and why, with a
   placeholder `run NNN`. Change no version.
6. If Appendix A changed, run `python3 build-agent-surfaces.py` and commit its outputs.
7. Run `python3 spec-qa.py` and `python3 -m unittest discover -s tools -p 'test_*.py'`; both
   clean.
8. Reply with: kind, SCOPE answers, files changed, commit, QA result, and a filled
   `.github/PULL_REQUEST_TEMPLATE.md` body. Under forty lines.

## MODE: review

You have not seen the drafting. Read the whole changed document and the diff against
`origin/next`. Report, numbered, with clause numbers: contradictions with other clauses, terms
used outside their definition, a sentence a reader could take two ways, an obligation an owner
could not verify from the record. Do not edit.

## MODE: keys

Read `acceptance/README.md` and the `KEY.md` for each changed document. Update every quotation of
a changed sentence to the new wording, verbatim, in a new commit; run `python3 spec-qa.py` until
`key-quote-stale` is gone. If the change adds a load-bearing obligation no keyed question
covers, propose the question and its key in your reply; add it only if your prompt says the owner
has decided to.

## MODE: mark

You have not seen the drafting or the review. Read `ACCEPTANCE.md` in full, the `KEY.md` for the
document, the document at the commit named, and the sheets under `untracked/scratch/cohort/NNN/`.

- Mark each sheet against the key, question by question, as runs 16 to 18 in
  `acceptance/runs/` do.
- For every miss, say whether the reader's reasoning quotes the document; such a miss is a
  finding whatever the count.
- For question 4, record every passage quoted, with its clause. Ignore any suggestion a reader
  volunteers.
- Apply the pass criteria exactly. Where `ACCEPTANCE.md` leaves a judgement open, make it and
  say in the record which judgement and why.
- Write `acceptance/runs/NNN-<slug>-run-<k>.md` in the shape of runs 16 to 18, stating how the
  readers were isolated (separate `claude -p` processes, no tools, no settings, no project
  context). Do not commit it. Reply with the verdict and the findings only.

## MODE: record

Commit the run record, replace the `run NNN` placeholder in `Unreleased` with a link to it, and
for an obligation set the document header to `<next version>-dev, unpublished` in the same
commit if it still names a published version. Run `python3 spec-qa.py` and
`python3 tools/changes.py --base origin/next`; both clean. Reply with the commit.

LTSCommerce and others added 4 commits October 12, 2026 14:19
After run 74: clause 8.2 no longer says which configuration's kind is named beyond "each
configuration the verdict grades"; the sentence on a hosted service or a licence tier, which
three readers could not place, is gone, and the changelog line follows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 070: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Command and working directory

ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt, each reader in a fresh empty directory made by mktemp -d outside the repository

Model version each reader reported

reader 1: claude-haiku-5-5
reader 2: claude-haiku-5-5
reader 3: claude-haiku-5-5
reader 4: claude-haiku-5-5
reader 5: claude-haiku-5-5

The delegated marker's task prompt

MODE: mark

Worktree: <worktree> (branch `editor/semantic-detectors`). The readers read commit befe52c. Read, at that commit and in that worktree: ACCEPTANCE.md in full (it is the process as pull request 22 proposes it, merged into this branch; the run is made under that process change before it merges, at commit f3ec69e, named here before the readers started), acceptance/runs/README.md, acceptance/method/QUESTIONS.md, acceptance/method/KEY.md, acceptance/fixtures/remediation-ledger.md and SPEC.md. You may read acceptance/runs/064-defined-terms-method-run-12.md for the shape of a record only; ACCEPTANCE.md says a judgement an earlier run recorded is not a rule. Do not take any judgement from the records of runs 19 to 30, which were made under the earlier rules on an earlier text of this change.

Run: 070. Document: SPEC.md. Change: semantic detectors (pull request 14): a new obligation in clause 3.2 that a rule the practitioner writes is written in the strongest kind of detector that can express it, with two grounds for a weaker kind and the recording of the grounds and of the detector and its kind; the terms Textual, Syntactic and Semantic in section 1; the section 3 map's 3.2 row; section 2's and clause 3.2's "of a kind that can express the Rule within clause 3.1's bounds"; clause 3.6's deferral to what 3.2 adds; section 4's sign-off proviso; section 7's conforming defence; Appendix A. The method question set gains item 2d for the new obligation. Pull request: 14, open on this branch; runs 19, 21, 23, 25, 27 and 29 read this document on an earlier, larger text of the change under the earlier acceptance rules. Run within the pull request: 13 (runs 19 to 30 came before, on both documents). Record file to write: <worktree>/acceptance/runs/070-semantic-detectors-run-13.md. Do not commit it.

Sheets: <run-dir>/sheet-1.md to sheet-5.md, each the `result` of reader-N.json. Prompt the readers received: <run-dir>/prompt.txt; its sha256 is 659a8d793ec9f5e94261703c695c94c17f3045a72f6988899c5734ac7ce59f9c, and it is byte for byte the question set, SPEC.md and the fixture at befe52c, each after one `===== FILE: <name> =====` line, one blank line between files. Model: every reader reported claude-haiku-5-5 (<run-dir>/models.txt); the cohort's model named for the run is claude-haiku-5-5. Every reader's JSON has is_error false and subtype success; no reader is set aside.

Facts the editor supplies for the record: cohort of five readers, none reused from any earlier run; each a separate `claude -p` process started through the `ccy-claude` wrapper (which only reattaches the session's credential) in a fresh empty directory made by `mktemp -d` outside any repository, with the packet on standard input, command `ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt`; no tools, no settings, no project context, no saved session; the machine applies no managed settings; the editor attests the rest of the isolation. Marked by a delegated marker, a fresh `dbf-spec-editor` agent (Claude Opus) given this instruction and its agent definition, having seen neither the drafting nor the review.

Text the change wrote or altered, for "Where a finding lies": the output of `git -C <worktree> diff 9e914fe befe52c -- SPEC.md` (9e914fe is the merge base with `next`). Run it and read it. A finding in a paragraph that diff adds, rewords, moves, deletes a sentence from, or changes a word, term or link in, or that a reader sets against such a paragraph, blocks; any other finding is routed to acceptance/ROUTED.md, and the record says so for each.

Mark every sheet, keyed item by keyed item, by ACCEPTANCE.md's Marking section and the key alone. Write the record stating what acceptance/runs/README.md lists, in its order, with the marks table, each miss with the reader's reasoning in brief, every passage named under question 4 with its words or description, clause and readers, each finding and where it lies, any open point and the direction you marked it, the process-change commit f3ec69e, the verdict, and a placeholder line for the link to the posted sheets. Reply with the verdict and the findings only, under thirty lines.

The agent definition the marker ran under

---
name: dbf-spec-editor
description: Drafts, reviews, key-updates, marks or records a change to the Defence Before Fix specification, one mode per dispatch, following the specification repository's CONTRIBUTING.md, SCOPE.md and ACCEPTANCE.md exactly. Dispatched by the dbf-spec-change skill; it cannot run a cohort itself.
tools: Read, Write, Edit, Grep, Glob, Bash
model: opus
---

You work on one change to the Defence Before Fix specification, in the worktree path you are
given. The process is defined by that repository's `CONTRIBUTING.md`, `SCOPE.md`,
`ACCEPTANCE.md` and `acceptance/README.md`; read the ones your mode needs, in full, before you
act. You invent no steps. You never merge, push to `next` or `main`, release, tag or publish,
and you never change a question, threshold or fixture. Your prompt names one mode.

Write as the specification is written: British spelling, no em dashes, defined terms linked as
`spec-qa.py` requires, every clause or section number linked to its heading. Commit on the
worktree's branch.

## MODE: draft

1. Read `SCOPE.md`, `DECLINED.md`, and every clause the change touches, with the terminology
   entries they use.
2. Decide the kind. A change to the sentences carrying MUST, SHOULD or MAY in a numbered section
   or appendix is an obligation; anything else is a clarity edit.
3. For an obligation, answer the five SCOPE tests in a sentence or two each, and name the
   section the obligation extends. If a test fails, or an existing clause already covers it, say
   so and stop without editing.
4. Make the smallest edit that does the job, in the clause that owns the subject.
5. Add one line under `Unreleased` in `CHANGELOG.md`, saying what changed and why, with a
   placeholder `run NNN`. Change no version.
6. If Appendix A changed, run `python3 build-agent-surfaces.py` and commit its outputs.
7. Run `python3 spec-qa.py` and `python3 -m unittest discover -s tools -p 'test_*.py'`; both
   clean.
8. Reply with: kind, SCOPE answers, files changed, commit, QA result, and a filled
   `.github/PULL_REQUEST_TEMPLATE.md` body. Under forty lines.

## MODE: review

You have not seen the drafting. Read the whole changed document and the diff against
`origin/next`. Report, numbered, with clause numbers: contradictions with other clauses, terms
used outside their definition, a sentence a reader could take two ways, an obligation an owner
could not verify from the record. Do not edit.

## MODE: keys

Read `acceptance/README.md` and the `KEY.md` for each changed document. Update every quotation of
a changed sentence to the new wording, verbatim, in a new commit; run `python3 spec-qa.py` until
`key-quote-stale` is gone. If the change adds a load-bearing obligation no keyed question
covers, propose the question and its key in your reply; add it only if your prompt says the owner
has decided to.

## MODE: mark

You have not seen the drafting or the review. Read `ACCEPTANCE.md` in full, the `KEY.md` for the
document, the document at the commit named, and the sheets under `<run-dir>/`.

- Mark each sheet against the key, keyed item by keyed item, by the rules under "Marking" in
  `ACCEPTANCE.md` and nothing else: the key's required elements, accepted grades, conditions and
  wrong readings. Invent no judgement; where neither the rules nor the key settles a mark, mark
  it in the direction the open-point rule gives and record the point.
- A single miss under questions 1 and 3 counts against the document only where the key names
  that reading; say which.
- For question 4, record every passage named, with its words or description and its clause.
  Group the passages by paragraph, count the readers, and for each finding say whether it lies in
  text the change wrote or altered, read against the diff the prompt gives you, or is routed to
  `acceptance/ROUTED.md`. Ignore any suggestion a reader volunteers.
- Apply the pass criteria exactly.
- Write `acceptance/runs/NNN-<slug>-run-<k>.md` stating what `acceptance/runs/README.md` lists,
  in its order. Do not commit it. Reply with the verdict and the findings only.

## MODE: record

Commit the run record, replace the `run NNN` placeholder in `Unreleased` with a link to it, and
for an obligation set the document header to `<next version>-dev, unpublished` in the same
commit if it still names a published version. Run `python3 spec-qa.py` and
`python3 tools/changes.py --base origin/next`; both clean. Reply with the commit.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 070: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Answer sheets, each reader's whole output as its process returned it

reader-1

{"duration_api_ms":78089,"stop_reason":"end_turn","session_id":"6356daf5-891c-4d3c-bef4-ab7dd04a26c4","total_cost_usd":0.015480130000000002,"usage":{"input_tokens":2,"cache_creation_input_tokens":31352,"cache_read_input_tokens":2153,"output_tokens":18376,"output_tokens_details":{"thinking_tokens":15927},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":31352,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":18376,"cache_read_input_tokens":2153,"cache_creation_input_tokens":31352,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":31352},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":18376,"cacheReadInputTokens":2153,"cacheCreationInputTokens":31352,"webSearchRequests":0,"costUSD":0.015480130000000002,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":15927,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"# Answers to QUESTIONS.md\n\n## 1. Restatement\n\n- Do not fix the reported defect first. Name the class it belongs to as a pattern, with one sentence stating its hazard.\n- Search for other instances using at least two independent techniques, run before the rule is run against the codebase. Widen or narrow the class to match what the search finds.\n- Write a blocking rule in the strongest kind of detector that can express it (semantic, then syntactic, then textual), never a test. Prove it fires in a red commit of its own.\n- Sweep the whole codebase where the pattern can occur, record the count, and fix every instance. Do not baseline, suppress, or leave any instance unfixed on your own authority.\n- Make the rule permanent and failing in the project's own entry point, with a terse message whose stable identifier resolves to documentation versioned with the rule.\n- Only then fix the original defect, with a reproduction test. Record anything deferred or out of scope where the project's decisions are enumerable, and refer exceptions to the owner.\n\n## 2. Load-bearing statements\n\n**2a.** Before fixing the defect, the practitioner attributes it to a class, writes the hazard sentence, runs two or more independent searches, sets the class bounds, and records all of it. The rule is then built, proven, swept over the codebase, and enforced. The original defect is fixed last.\n\n- \"The [Practitioner] MUST first establish what [Class] the [Defect] belongs to.\" (3.1)\n- \"When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is fixed.\" (2)\n- \"Record all of that, the five things listed at the end of this clause, before clause 3.2.\" (3.1, step 5)\n- \"The [Practitioner] MUST run the [Rule] across the entire codebase and record the total [Instance] count before fixing anything\" (3.4)\n- \"Only then fix the original [Defect] in the normal way, with a test that reproduces it.\" (Appendix A)\n\n**2b.** Only a human owner may adopt a baseline or suppress an instance. An agent may not do either on its own authority, even as the practitioner.\n\n- \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\" (4)\n- \"Owner: Whoever holds authority over a project's [Exceptions] and [Calibrations]. Always a human.\" (1)\n- \"An [Exception] exists only where a human has discussed it, agreed it and documented it for that project. Nothing an [Agent] concludes on its own creates one.\" (4)\n\n**2c.** A remediation conforms when all six clauses of section 3 were followed for that defect. The verdict must rest on reproduction, not on the report.\n\n- \"A [Defence] ... A remediation [Conforms] if all six clauses of section 3 were followed for that [Defect], and the verdict that they were rests on reproduction, as this section closes by requiring, not on the report.\" (7)\n- \"A verdict on a remediation or a [Defence] MUST rest on reproduction, not on the report: the reviewer reruns the [Defence] red at the commit that introduced it and green at the final commit, through the project's own entry point for accepting changes\" (7)\n- \"Partial [Conformance] MUST NOT be described as [Conformance].\" (7)\n\n**2d.** The rule must be written in a semantic detector, failing that a syntactic one, and only then a textual one. A weaker kind is allowed on one of two grounds. The grounds, the detector kind, and the stronger detectors considered must be recorded in the rule's docs and in the adding commit's description. Owner sign-off applies only where the project requires it.\n\n- \"A [Rule] the [Practitioner] writes MUST be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a [Syntactic] one, else a [Textual] one.\" (3.2)\n- \"A weaker kind is allowed only where the stronger kind cannot express the [Rule] within clause 3.1's bounds, or where the language has no [Detector] of the stronger kind that accepts bespoke [Rules]; those are the two grounds.\" (3.2)\n- \"the [Practitioner] MUST also record the grounds in two places, both of them: on the [Rule]'s [Remediation docs], and in the description of a commit.\" (3.2)\n\n## 3. Application\n\n| Clause | Grade | One line of reasoning |\n|---|---|---|\n| 3.1 | Partial | The class and hazard sentence are named and a wider class is rejected, but the two search techniques and their findings are not recorded, and the rejection gives no hazard-absence sentence. |\n| 3.2 | Partial | The rule is a custom rule in a static analyser, not a test, but neither the rule's docs nor the commit names its detector kind, and no grounds are recorded. |\n| 3.3 | Yes | Commit B holds the rule, a fixture and a red CI run on the still-present `Ledger::total()`, separate from the fix commit, and the merge kept all five commits. |\n| 3.4 | No | Three of four real instances were fixed, and `Report::render()` was suppressed with an inline ignore, which the spec says is not a fix. |\n| 3.5 | No | The rule fails and the local run passed, but the green run depends on an inline ignore that no recorded decision covers. |\n| 3.6 | No | The message is terse and carries the identifier, but the docs live on a company wiki, not versioned with the rule. |\n\n**Remediation under section 7:** It does not conform. Clauses 3.4, 3.5 and 3.6 are No, and 3.1 and 3.2 are Partial, while section 7 requires all six to have been followed. The final-commit green also cannot be taken as reproduction of a valid pass, because it depends on the suppression.\n\n**Defence alone:** It does not conform. Section 7 requires 3.1, 3.2, 3.3, 3.5 and 3.6 in full. The defence fails 3.5 (the uncovered inline ignore), 3.6 (docs not versioned with the rule), and in part 3.1 and 3.2. Only 3.3 is fully met.\n\n**Decision not the practitioner's:** The inline ignore on `Report::render()` is a suppression, and section 4 reserves suppressing an instance for the owner. The project has a named owner, so the default-owner route does not apply, and the agent did not raise it with anyone. The choice of how `Report::render()` handles the error is the practitioner's to make, but the suppression is not. Section 3's table also reserves \"whether the next wider [Rule] is built\" for the owner, so the agent's decision in commit A not to build the wider rule was also not its to take.\n\n## 4. Confusion\n\n- Clause 3.1: \"**Both bounds measure breadth within one level; the level itself is a separate question, answered against the report.**\" I could not tell which \"level\" is meant, or how \"the report\" answers the question about it.\n- Clause 3.1: \"That rejection is tested as a [Narrowing] is under clause 3.3\" I could not see how clause 3.3's test, which is written about excluding code from a rule, applies to rejecting a wider rule.\n- Section 2: \"in the vocabulary clause 3.1 uses for a search\" Clause 3.1 does not appear to define a vocabulary for searches, so I could not tell what I was meant to name.","ttft_ms":70314,"type":"result","duration_ms":78471,"uuid":"33917134-3166-4eb9-9b00-e434ec793cab","ttft_stream_ms":1179,"time_to_request_ms":381,"first_content_frame_ms":1179,"queued_turn_count":0,"result_index":0}

reader-2

{"duration_api_ms":61960,"stop_reason":"end_turn","session_id":"a7d8dd0f-8c9b-4d89-802a-05b3109bf5b8","total_cost_usd":0.013545129999999999,"usage":{"input_tokens":2,"cache_creation_input_tokens":31352,"cache_read_input_tokens":2153,"output_tokens":14506,"output_tokens_details":{"thinking_tokens":11416},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":31352,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":14506,"cache_read_input_tokens":2153,"cache_creation_input_tokens":31352,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":31352},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":14506,"cacheReadInputTokens":2153,"cacheCreationInputTokens":31352,"webSearchRequests":0,"costUSD":0.013545129999999999,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":11416,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Before fixing a defect, attribute it to a class: the pattern, idiom or configuration that allowed it, with one sentence naming its hazard.\n- Search for other instances by at least two independent techniques, run before the rule exists, and widen or narrow the class until the search saturates. Record the next wider rule that was not built, and why.\n- Express the class as a blocking rule in the strongest kind of detector that can express it (semantic, then syntactic, then textual), never as a test.\n- Commit the rule with the originating instance still present, and show it fires (the red run). The fix comes in a later commit.\n- Sweep the whole codebase, record and corroborate the count, then fix every instance. Baselines, suppressions and leaving instances unfixed belong to the owner, not to the practitioner or an agent.\n- Make the rule permanent and failing through the project's own entry point, with a terse message carrying a stable identifier that resolves to documentation versioned with the rule. A verdict on the result rests on reproduction.\n\n## 2. Load-bearing statements\n\n**2a.** The practitioner attributes the defect to a class (3.1), builds and proves the rule (3.2–3.3), then sweeps and records the count before fixing anything (3.4). Appendix A puts the fix of the original defect last, after enforcement and documentation. The document does not place those steps in one consistent sequence (see §4).\n\n- §2: \"When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is fixed.\"\n- §3.3: \"The [Defence] is committed with the originating [Instance] still present, and the fix is committed after it.\"\n- §3.4: \"The [Practitioner] MUST run the [Rule] across the entire codebase and record the total [Instance] count before fixing anything, and MUST then fix every [Instance] found.\"\n- Appendix A: \"Only then fix the original [Defect] in the normal way, with a test that reproduces it.\"\n\n**2b.** Only the owner may adopt a baseline or suppress an instance, and an agent may not do so on its own authority. The owner is always a human.\n\n- §4: \"**Decisions that MUST go to whoever owns the codebase:** Adopting a [Baseline], in whole or in part (clause 3.4).\" and \"Suppressing an [Instance], or removing or disabling an existing [Rule] (clauses 3.4 and 3.5).\"\n- §4: \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\"\n- Definitions, *Owner*: \"Always a human.\"\n\n**2c.** A remediation conforms when all six clauses of section 3 were followed for that defect. The verdict that they were must rest on reproduction, not on the written report.\n\n- §7: \"A [Remediation] [Conforms] if all six clauses of section 3 were followed for that [Defect], and the verdict that they were rests on reproduction, as this section closes by requiring, not on the report.\"\n- §7: \"A verdict on a remediation or a [Defence] MUST rest on reproduction, not on the report: the reviewer reruns the [Defence] red at the commit that introduced it and green at the final commit, through the project's own entry point for accepting changes, and reruns the originating symptom against the fix.\"\n\n**2d.** The rule must be written in the strongest kind of detector that can express it. The order tried is semantic, then syntactic, then textual. A weaker kind is allowed on two grounds only, and the practitioner must record the grounds in two places.\n\n- §3.2: \"A [Rule] the [Practitioner] writes MUST be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a [Syntactic] one, else a [Textual] one.\"\n- §3.2: \"A weaker kind is allowed only where the stronger kind cannot express the [Rule] within clause 3.1's bounds, or where the language has no [Detector] of the stronger kind that accepts bespoke [Rules]; those are the two grounds.\"\n- §3.2: \"the [Practitioner] MUST also record the grounds in two places, both of them: on the [Rule]'s [Remediation docs], and in the description of a commit.\" The record must also name \"the [Detectors] of the stronger kind that were considered\".\n\n## 3. Application\n\n- **3.1 Attribute the defect to a class: No.** Commit A names the class, the hazard and the next wider class, but records no two independent search techniques, no account of what each found, and no statement on whether a runner check pins the reported behaviour. The clause says a record missing any of those five things has not finished.\n- **3.2 Build the net: Partial.** The rule is in the project's analyser and reads code rather than running a test. But the fixture never names the detector's kind or records grounds for a weaker kind, and the documentation is not shipped with the rule.\n- **3.3 Prove the net: Yes.** Commit B holds the rule, the fixture, the originating shape (`Ledger::total()` appears among the findings) and the red CI run, and commit C fixes afterwards. The merge commit keeps all five commits. The description does not show a fresh checkout reproducing the red run, so that remains unverified.\n- **3.4 Sweep, then fix every instance: No.** The count of five includes the fixture, which the spec excludes, so there are four real instances. No independent search corroborates the count. `Report::render()` was left unfixed under an inline ignore, which contradicts \"fix every [Instance] found\".\n- **3.5 Enforce permanently, and block: No.** The rule is set to fail, and the local run of the whole quality invocation passed (commit E). But the inline ignore on `Report::render()` is a suppression with no recorded decision, and §3.5 says a green run that depends on such a suppression does not conform.\n- **3.6 Terse message, real documentation: No.** The message is one line with a stable identifier, and the content of the wiki page (what is forbidden, why, and the propagating construction) meets §3.6 and §8.4. But the documentation lives on a company wiki, not in the project and versioned alongside the rule, and §3.6 says a URL conforms only where the project controls it and versions it alongside the rule.\n\n**Remediation under §7: does not conform.** Clauses 3.1, 3.4, 3.5 and 3.6 are not followed, and 3.2 is only partly followed. The fixture gives no reproduction-based verdict, only the practitioner's CI and PR descriptions.\n\n**Defence alone under §7: does not conform.** §7 requires 3.1, 3.2, 3.3, 3.5 and 3.6 in full. Only 3.3 is met, and 3.2 is partial. The §3.5 suppression and the wiki-only documentation also defeat the defence on their own terms.\n\n**Decisions §4 says were not the practitioner's to take:**\n1. The inline `// ignore proj.caught-then-used` on `Report::render()` (commit C). §4 reserves \"Suppressing an [Instance]\" and \"Accepting a known [Instance] as unfixed for any reason\" to the owner. The project has a named owner, so the default-owner clause does not apply, and the project's silence on inline ignores creates no exception.\n2. Not building the next wider rule, \"any caught error that is not rethrown or reported\" (commit A). The §3 table places \"Whether the next wider [Rule] is built\" with the [Owner], and the fixture's stated reason (\"most … are legitimate\") turns on a hazard judgement.\n\n## 4. Confusion\n\n- **§4, \"Whilst waiting\":** \"the [Practitioner] … leaves the [Rule] unmerged rather than merged in a weakened form. Where the only thing pending is whether one matched case carries the [Hazard], the [Rule] MAY merge at full width with that case recorded as a known [Instance] awaiting the [Owner].\" This sits alongside \"provided the [Rule] does not become [Blocking] until every [Instance] is fixed\" in §4, and I could not tell how a full-width, blocking rule can merge over a known instance.\n- **§2, \"Attempt rather than pre-judge\":** \"naming at least two independent techniques tried at expressing the [Rule], in the vocabulary clause 3.1 uses for a search\". The two techniques here are for expressing the rule, while §3.1's two are for searching instances, and I could not tell whether the phrase is meant to link them.\n- **§3.1, worked example, step 4 versus §4:** The worked example records that the wider rule \"was not built because most of the codebase's catches are legitimate and the [Owner] has not decided\". §4 reserves that decision to the owner. I could not tell whether the practitioner may leave it unbuilt pending the owner's decision.\n- **Appendix A versus §3.4 and §2:** Appendix A says \"Only then fix the original [Defect]\" after enforcement and documentation. §3.4 says \"fix every [Instance]\" straight after the sweep, and §2 says the defence must be attempted \"before the [Defect] is fixed\". I could not tell where the original defect's fix falls in the sequence.\n- **§3.2, \"at each step down the order\":** \"which of the two grounds holds at each step down the order [Semantic], [Syntactic], [Textual], and why\". I could not tell what \"each step\" requires to be recorded.","ttft_ms":51562,"type":"result","duration_ms":62287,"uuid":"3a79e84d-c64b-4a74-8129-6aec9477be61","ttft_stream_ms":1766,"time_to_request_ms":325,"first_content_frame_ms":1766,"queued_turn_count":0,"result_index":0}

reader-3

{"duration_api_ms":66077,"stop_reason":"end_turn","session_id":"eec59222-bf29-4d73-8c72-d9899881d0d8","total_cost_usd":0.01412813,"usage":{"input_tokens":2,"cache_creation_input_tokens":31352,"cache_read_input_tokens":2153,"output_tokens":15672,"output_tokens_details":{"thinking_tokens":13044},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":31352,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":15672,"cache_read_input_tokens":2153,"cache_creation_input_tokens":31352,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":31352},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":15672,"cacheReadInputTokens":2153,"cacheCreationInputTokens":31352,"webSearchRequests":0,"costUSD":0.01412813,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":13044,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"# QUESTIONS.md answers\n\n## 1. Restatement\n\n- When handed a defect, do not fix it first. Attribute it to a class: a pattern, idiom or configuration that a detector can recognise.\n- Write a rule for that class in the strongest kind of detector that can express it (Semantic, then Syntactic, then Textual). A test is never the detector.\n- Search for other instances by at least two independent techniques before the rule is run, and widen the rule if the search finds misses.\n- Prove the rule fires before trusting it, in a commit of its own. Narrow only with a written sentence, and send doubt to the owner.\n- Run the rule across the whole codebase, record the count, and fix every instance by addressing the hazard. A practitioner, and especially an agent, may not baseline or suppress instances on its own authority.\n- Make the rule blocking in the project's own entry point, with a terse message and an identifier that resolves to documentation versioned with the rule. Then fix the original defect with a reproducing test.\n\n## 2. Load-bearing statements\n\n**2a.** The practitioner names the class, runs at least two independent searches, records the class, hazard, searches and next wider rule, and builds the rule. The rule must be proven to fire, then the codebase is swept and the count recorded, and only then are the instances fixed. The defence is committed before the fix.\n- \"When it can, building the Defence is not optional and MUST be attempted before the Defect is fixed.\" (section 2)\n- \"The Practitioner MUST run the Rule across the entire codebase and record the total Instance count before fixing anything, and MUST then fix every Instance found rather than only the one that was reported.\" (3.4)\n- \"The Defence is committed with the originating Instance still present, and the fix is committed after it.\" (3.3)\n- \"Only then fix the original Defect in the normal way, with a test that reproduces it.\" (Appendix A)\n\n**2b.** Only the owner of the codebase, who is always a human, may adopt a baseline or suppress an instance. An agent may not do either on its own authority.\n- \"Adopting a Baseline, in whole or in part (clause 3.4).\" and \"Suppressing an Instance, or removing or disabling an existing Rule (clauses 3.4 and 3.5).\" (section 4, decisions that MUST go to whoever owns the codebase)\n- \"An Agent MUST NOT Baseline, suppress or knowingly leave an Instance unfixed on its own authority, whatever the Instance count turns out to be.\" (section 4)\n- \"Owner: Whoever holds authority over a project's Exceptions and Calibrations. Always a human.\" (section 1)\n- \"Nothing an Agent concludes on its own creates one.\" (section 4, on Exceptions)\n\n**2c.** A remediation conforms when all six clauses of section 3 were followed for that defect. The verdict that they were followed must rest on reproduction by a reviewer, not on the report.\n- \"A remediation Conforms if all six clauses of section 3 were followed for that Defect, and the verdict that they were rests on reproduction, as this section closes by requiring, not on the report.\" (section 7)\n- \"A verdict on a remediation or a Defence MUST rest on reproduction, not on the report: the reviewer reruns the Defence red at the commit that introduced it and green at the final commit...\" (section 7)\n\n**2d.** The rule must be written in the strongest kind of detector that can express it, tried in the order Semantic, then Syntactic, then Textual. A weaker kind is allowed only on two grounds: the stronger kind cannot express the rule within clause 3.1's bounds, or the language has no stronger detector that accepts bespoke rules. The practitioner must record the grounds in the rule's remediation docs and in the description of the commit that adds or moves the rule, naming the stronger detectors considered.\n- \"A Rule the Practitioner writes MUST be written in the strongest kind of Detector that can express it: a Semantic one, else a Syntactic one, else a Textual one.\" (3.2)\n- \"A weaker kind is allowed only where the stronger kind cannot express the Rule within clause 3.1's bounds, or where the language has no Detector of the stronger kind that accepts bespoke Rules; those are the two grounds.\" (3.2)\n- \"the Practitioner MUST also record the grounds in two places, both of them: on the Rule's Remediation docs, and in the description of a commit.\" (3.2)\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n|---|---|---|\n| 3.1 | No | Commit A records the class, the hazard, and the next wider class with a reason. It names no two search techniques and says nothing about whether a Runner check pins the reported behaviour. The reason given for not building the wider rule (\"most are legitimate\") is not the hazard-cannot-arise sentence the clause requires. \"A record missing any of the five has not finished this clause.\" |\n| 3.2 | Partial | The rule is a static-analyser rule that reads code, so it is not a test. However, neither the wiki docs nor commit B name the detector or its kind, and no grounds are recorded. |\n| 3.3 | Yes | Commit B holds the rule and its fixture, with a red CI run showing five findings. The fix follows in commit C, and the merge kept all five commits. No narrowing was made. |\n| 3.4 | No | The count of five comes from the red run, but no independent search corroborates it. `Report::render()` is left unfixed under an inline ignore. The record does not say which instances were fixed by hand and which by pattern. |\n| 3.5 | No | The rule is set to fail, and the practitioner ran the project's quality invocation locally. But the green run depends on an inline ignore that no recorded decision covers. |\n| 3.6 | No | The message is one line with a stable identifier. The documentation is on a company wiki, not versioned with the rule in the project's repository. \"A Rule and its documentation are one artefact and MUST be versioned as one.\" |\n\n**Section 7, remediation.** It does not conform. Clauses 3.1, 3.4, 3.5 and 3.6 are not satisfied. The fixture also contains no reviewer reproduction, so the verdict requirement is unmet on the record given.\n\n**Section 7, defence alone.** It does not conform. A defence conforms only if clauses 3.1, 3.2, 3.3, 3.5 and 3.6 are met in full. Clause 3.5 fails because the green run depends on an uncovered suppression, and clause 3.6 fails because the docs are not versioned with the rule. Clauses 3.1 and 3.2 are also not met in full, for the reasons above. Clause 3.3 is met.\n\n**Decision not the practitioner's.** Section 4 says suppressing an instance and accepting a known instance as unfixed both belong to the owner. The inline `// ignore proj.caught-then-used` on `Report::render()`, with the note \"rendering must never throw\", was the agent's decision and was not raised with anyone. The fixture says the project has a named owner, so this was not a default-owner case. A second decision also falls in the owner's column: the agent left the wider rule unbuilt because of the number of legitimate catches. That is a precision judgement, not a hazard-absent sentence.\n\n## 4. Confusion\n\n- Clause 3.1: \"A Rule drawn to the exact value or name found in the Defect is the same Narrowing however it is spelt.\" This uses \"Narrowing\" for an over-narrow rule, while the terminology section defines it as reducing scope to stop reporting code without the hazard.\n- Clause 3.3 and Appendix A: \"the fix is committed after it\" and \"Only then fix the original Defect.\" It is unclear whether the originating instance's fix comes before or after the fixes for the rest of the sweep.\n- Clause 3.4: \"the scale at which that becomes true is tremendous.\"\n- Section 4: \"The absence of an Instance is the Practitioner's reason not to widen unasked.\"","ttft_ms":57604,"type":"result","duration_ms":66411,"uuid":"b478ff26-9c93-472b-b72d-c98f8ff1ade1","ttft_stream_ms":1129,"time_to_request_ms":332,"first_content_frame_ms":1129,"queued_turn_count":0,"result_index":0}

reader-4

{"duration_api_ms":69679,"stop_reason":"end_turn","session_id":"a71be2d8-7594-45a8-bb7e-5747d3b1447a","total_cost_usd":0.014640930000000002,"usage":{"input_tokens":2,"cache_creation_input_tokens":31351,"cache_read_input_tokens":2153,"output_tokens":16698,"output_tokens_details":{"thinking_tokens":14226},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":31351,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":16698,"cache_read_input_tokens":2153,"cache_creation_input_tokens":31351,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":31351},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":16698,"cacheReadInputTokens":2153,"cacheCreationInputTokens":31351,"webSearchRequests":0,"costUSD":0.014640930000000002,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":14226,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Don't fix a defect first. Treat it as evidence of a class: the pattern that allowed it.\n- Attribute the defect to a class by naming the pattern and a one-sentence hazard, bounded both ways. Use at least two independent search techniques, run before any rule is run against the codebase.\n- Write a blocking rule for that class in a detector that reads code, in the strongest kind that can express it (semantic, then syntactic, then textual). Never write a test as the detector.\n- Prove the rule fires, with the red run kept as its own commit. Any exclusion needs a written sentence, or it goes to the owner.\n- Sweep the whole codebase, record the count, corroborate it, then fix every instance. Only the owner may baseline, suppress, or leave an instance unfixed.\n- Make the rule permanent and failing through the project's own entry point, with a terse message and an identifier that resolves to docs versioned with the rule. Then fix the original defect with a reproduction test.\n\n## 2. Load-bearing statements\n\n**2a.** Before the fix, the practitioner attributes the class and records it (3.1), writes the rule and proves it fires in a commit of its own (3.2–3.3), and sweeps and records the count (3.4). Then it fixes every instance. The timing of enforcement is governed by section 4.\n- \"When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is fixed.\" (section 2)\n- \"The [Defence] is committed with the originating [Instance] still present, and the fix is committed after it.\" (3.3)\n- \"record the total [Instance] count before fixing anything, and MUST then fix every [Instance] found rather than only the one that was reported.\" (3.4)\n\n**2b.** Only the owner, who is always a human, may adopt a baseline or suppress an instance. An agent may not do either on its own authority, and an inline ignore counts as a suppression.\n- \"Adopting a [Baseline], in whole or in part\" and \"Suppressing an [Instance], or removing or disabling an existing [Rule]\" go to \"whoever owns the codebase.\" (section 4)\n- \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\" (section 4)\n- \"Suppression: Any means of preventing a [Rule] from reporting an [Instance] that carries the [Hazard]: an ignore comment, an ignore entry, a [Baseline], or a [Narrowing] that excludes it.\" (section 1)\n\n**2c.** Conformance means satisfying every MUST of the relevant level, and \"Partial satisfaction is not [Conformance].\" A remediation conforms when all six clauses of section 3 were followed, and the verdict must rest on reproduction.\n- \"A verdict on a remediation or a [Defence] MUST rest on reproduction, not on the report: the reviewer reruns the [Defence] red at the commit that introduced it and green at the final commit, through the project's own entry point for accepting changes, and reruns the originating symptom against the fix.\" (section 7)\n\n**2d.** The rule must be written in the strongest kind that can express it. The kinds are tried in the order semantic, syntactic, textual. A weaker kind is allowed only on two grounds, and the grounds must be recorded in two places.\n- \"A [Rule] the [Practitioner] writes MUST be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a [Syntactic] one, else a [Textual] one.\" (3.2)\n- \"A weaker kind is allowed only where the stronger kind cannot express the [Rule] within clause 3.1's bounds, or where the language has no [Detector] of the stronger kind that accepts bespoke [Rules]; those are the two grounds.\" (3.2)\n- \"the [Practitioner] MUST also record the grounds in two places, both of them: on the [Rule]'s [Remediation docs], and in the description of a commit.\" (3.2)\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n| --- | --- | --- |\n| 3.1 | Partial | Commit A records the class and hazard sentence and names the wider class. But no two independent search techniques are recorded; the only search was the rule's own run, which 3.1 excludes. No record says whether a Runner check pins the reported behaviour. The reason given for not building the wider rule (\"most are legitimate\") is not the hazard-absence sentence 3.1 requires. |\n| 3.2 | Partial | The rule is in a static analyser, so it is not a test. But the Remediation docs (Commit D) do not name the detector or its kind, and no grounds are recorded. The fixture never states the kind, so the strongest-first order cannot be checked. |\n| 3.3 | Partial | Commit B is a red run with the originating instance present, and the merge keeps it reachable, so Part A is met. But the inline ignore on `Report::render` (Commit C) is an exclusion the agent took itself, which Part B routes to the owner. |\n| 3.4 | No | The five-finding count came from the rule's own CI run, so it is not corroborated by an independent search. `Report::render` was left unfixed under an ignore. |\n| 3.5 | No | The rule fails the build, but the green run depends on an ignore that no recorded decision covers. The project records nothing about inline ignores. |\n| 3.6 | No | The message is one line carrying the identifier, so it is terse. But the documentation is a company wiki page not versioned with the rule, and 3.6 says a link into a wiki does not conform. |\n\n**Does the remediation conform under section 7?** No. Clauses 3.4, 3.5 and 3.6 fail outright, and 3.1–3.3 are not fully met. The evidence is the PR description and Commit E's report of a local run, not a reviewer's reproduction, which section 7 requires.\n\n**Does the defence alone conform?** No. Section 7 requires 3.1, 3.2, 3.3, 3.5 and 3.6 in full. Three are partial, and 3.5 and 3.6 fail.\n\n**Which decision was not the practitioner's?** Suppressing the `Report::render` instance with an inline ignore. Section 4 reserves \"Suppressing an [Instance]\" and \"Accepting a known [Instance] as unfixed for any reason\" for the owner. The project has a named owner, so the default-owner provision does not apply. A second decision also goes upward: the agent's reason for not building the wider rule is not a hazard-absence sentence, and 3.1's table places \"Whether the next wider [Rule] is built\" with the owner.\n\n## 4. Confusion\n\n- Appendix A: \"Make the [Rule] a permanent part of the project's quality checks, failing rather than [Warning].\" is followed by \"Only then fix the original [Defect] in the normal way, with a test that reproduces it.\" Section 4 says the rule \"does not become [Blocking] until every [Instance] is fixed\", and the original defect is one of those instances. On my reading, the appendix and section 4 disagree about whether the rule goes blocking before the original defect is fixed.\n- Clause 2: \"naming at least two independent techniques tried at expressing the [Rule], in the vocabulary clause 3.1 uses for a search.\" On my reading, it is unclear whether \"techniques\" here means the search techniques of 3.1 or a separate set of attempts at expressing the rule.","ttft_ms":62265,"type":"result","duration_ms":69990,"uuid":"295ca7ab-e1b5-41d1-9656-9a4c41b56819","ttft_stream_ms":1025,"time_to_request_ms":310,"first_content_frame_ms":1025,"queued_turn_count":0,"result_index":0}

reader-5

{"duration_api_ms":69288,"stop_reason":"end_turn","session_id":"2ce2a35e-f92c-4c41-96b2-62769695fbc0","total_cost_usd":0.01459463,"usage":{"input_tokens":2,"cache_creation_input_tokens":31352,"cache_read_input_tokens":2153,"output_tokens":16605,"output_tokens_details":{"thinking_tokens":14022},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":31352,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":16605,"cache_read_input_tokens":2153,"cache_creation_input_tokens":31352,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":31352},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":16605,"cacheReadInputTokens":2153,"cacheCreationInputTokens":31352,"webSearchRequests":0,"costUSD":0.01459463,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":14022,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Don't fix the defect first. Name the class it is an instance of, with a one-sentence hazard.\n- Find other instances by at least two independent techniques, run before any rule is run against the codebase, and continue until a further technique finds nothing new.\n- Widen or narrow the class to match what the search found, and record the next wider rule you did not build and why.\n- Write a blocking rule in the strongest detector kind that can express it (Semantic, then Syntactic, then Textual), never as a test, and document it under a stable identifier.\n- Prove the rule fires, in its own commit, on the originating instance or a fixture, before the fix lands. Narrow it only with a written sentence showing the hazard cannot arise in what is excluded.\n- Sweep the codebase, record the count and fix every instance. Baselines, suppressions and leaving instances unfixed belong to the human owner. Then make the rule permanent and failing, reachable through the project's entry point, with a terse message and versioned documentation.\n\n## 2. Load-bearing statements\n\n**2a.** The practitioner attributes the defect to a class (3.1), then writes the rule (3.2), proves it fires (3.3), sweeps and records the count, and only then fixes every instance (3.4). The rule must be attempted before the fix: \"When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is fixed.\" The sweep must come before fixing: \"The [Practitioner] MUST run the [Rule] across the entire codebase and record the total [Instance] count before fixing anything, and MUST then fix every [Instance] found rather than only the one that was reported.\" The red commit comes first: \"The [Defence] is committed with the originating [Instance] still present, and the fix is committed after it.\"\n\n**2b.** A human owner decides. An agent may not do so on its own authority. Section 4: \"An [Agent] MUST NOT [Baseline], suppress or knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to be.\" The term Owner is defined as \"Whoever holds authority over a project's [Exceptions] and [Calibrations]. Always a human.\" An Exception is \"A recorded [Owner] decision that a specific [Instance] or scope is excluded from a [Defence]. The only legitimate form of [Suppression].\"\n\n**2c.** A remediation conforms when all six clauses of section 3 were followed for that defect and the verdict rests on reproduction. Section 7: \"A [Remediation] [Conforms] if all six clauses of section 3 were followed for that [Defect], and the verdict that they were rests on reproduction, as this section closes by requiring, not on the report.\" The reproduction requirement: \"the reviewer reruns the [Defence] red at the commit that introduced it and green at the final commit, through the project's own entry point for accepting changes, and reruns the originating symptom against the fix.\"\n\n**2d.** The rule must be written in a Semantic detector if one can express it, then Syntactic, then Textual: \"A [Rule] the [Practitioner] writes MUST be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a [Syntactic] one, else a [Textual] one.\" A weaker kind is allowed on two grounds only: \"A weaker kind is allowed only where the stronger kind cannot express the [Rule] within clause 3.1's bounds, or where the language has no [Detector] of the stronger kind that accepts bespoke [Rules].\" The grounds must be recorded in two places: \"on the [Rule]'s [Remediation docs], and in the description of a commit.\" The record states \"which of the two grounds holds at each step down the order [Semantic], [Syntactic], [Textual], and why, naming the [Detectors] of the stronger kind that were considered.\"\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n|---|---|---|\n| 3.1 | Partial | The class, the hazard sentence and the next wider rule are all recorded. However, no two search techniques are named or shown to have run before the rule, nothing says what each found, and nothing says whether a Runner pins the reported behaviour. The reason given for not building the wider rule, \"most are legitimate\", is a hazard judgement, not a confirmed sentence. |\n| 3.2 | Partial | The rule is in a static analyser, reads code and blocks, so it is not a test. But the detector and its kind are never named in the documentation, and no grounds are recorded for whichever kind it is in. Nothing shows a Semantic detector was considered. |\n| 3.3 | Partial | Commit B holds the rule with the originating instance still present, and CI is red. Commit C fixes it afterwards, and both commits survive the merge. However, the enforced run reports the fixture, which 3.3 says it must not, and the fixture's status after commit C, when CI is green, is never explained. |\n| 3.4 | No | The count of five includes the fixture, so the instance count is four. Nothing corroborates the count with an independent search. `Report::render()` was left unfixed behind an inline ignore, which is a suppression at the call site. |\n| 3.5 | No | The rule is set to fail and the local entry-point run passed. But the green run depends on an inline ignore that no recorded decision covers, and 3.5 says such a green run does not conform. |\n| 3.6 | No | The failure message is terse and carries the identifier. But the documentation is a company wiki page, not versioned with the rule in the same repository, and 3.6 requires the rule and its docs to be versioned together. |\n\n**Remediation under section 7:** No. Clauses 3.1 to 3.3 are only partly met and 3.4 to 3.6 are not met. Section 7 requires all six clauses. A reviewer reproducing the work would hit the same gaps, because the ignore comment and the wiki link are both visible in the code and the pull request.\n\n**Defence alone under section 7:** No. Section 7 requires 3.1, 3.2, 3.3, 3.5 and 3.6 \"in full\". The defence fails 3.5 because the inline ignore is uncovered, and 3.6 because the documentation is not versioned with the rule. It also fails 3.1 and 3.2 for the reasons above. The incomplete sweep would not by itself prevent conformance, but these defects would.\n\n**Decision that was not the practitioner's:** The inline `// ignore proj.caught-then-used` on `Report::render()`, and the decision to leave that instance unfixed, both sit under section 4's list of decisions that \"MUST go to whoever owns the codebase\": \"Suppressing an [Instance]\" and \"Accepting a known [Instance] as unfixed for any reason.\" The agent did not raise it, even though the project has a named owner. A second candidate is the decision not to build the wider rule on the ground that \"most are legitimate\", which is a hazard judgement. Section 4 says uncertainty about the hazard routes upwards, so that call was also not the practitioner's to make alone.\n\n## 4. Confusion\n\n- Section 4: \"**Decisions that MUST go to whoever owns the codebase:**\". Read alongside the Terminology entry for Owner, \"Whoever holds authority over a project's [Exceptions] and [Calibrations]\", I could not tell whether \"whoever owns the codebase\" and \"Owner\" name the same party.\n- Clause 3.1: \"**Both bounds measure breadth within one level; the level itself is a separate question, answered against the report.**\" I could not map \"one level below\" onto the lower and upper bounds.\n- Clause 3.3, Part B: \"a [Narrowing] that excludes more code than the [Rule] still covers MUST be reported to the [Owner] as if it were a [Suppression].\" I could not work out what \"excludes more code than the [Rule] still covers\" means in practice.","ttft_ms":60547,"type":"result","duration_ms":69589,"uuid":"65b9b690-a3e6-46c5-a44d-8e7059c423e1","ttft_stream_ms":1108,"time_to_request_ms":299,"first_content_frame_ms":1108,"queued_turn_count":0,"result_index":0}

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 070: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

The exact prompt every reader received, part 1 of 2

===== FILE: QUESTIONS.md =====
# Questions for a cold reader of SPEC.md

You have been given `SPEC.md` and the fixture `fixtures/remediation-ledger.md`, and nothing else.
Read the document once, then answer every question below in order. Quote the document where a
question asks for a quotation. Do not consult anything outside what you were given.

## 1. Restatement

In your own words and in at most six bullets, what does this document require a practitioner to
do when they are handed a defect?

## 2. Load-bearing statements

Answer each part of each question in a sentence or two, then quote the sentence or sentences of
the document you are relying on.

- 2a. What does a practitioner do before fixing the defect they were handed, and in what order?
- 2b. Who may adopt a baseline or suppress an instance, and may an agent do so on its own
  authority?
- 2c. What does it mean for a remediation to conform, and on what must a reviewer's verdict on
  it rest?
- 2d. In which kind of detector must a rule the practitioner writes be written, and in what order
  are the kinds tried; on what grounds may a weaker kind be used; and what must the practitioner
  record then, and where?

## 3. Application

Grade the ledger remediation, as described in the fixture, against each of the six clauses of
section [3](../../SPEC.md#3-the-method): [3.1](../../SPEC.md#31-attribute-the-defect-to-a-class), [3.2](../../SPEC.md#32-build-the-net), [3.3](../../SPEC.md#33-prove-the-net-by-making-the-rule-fire), [3.4](../../SPEC.md#34-sweep-the-codebase-then-fix-every-instance), [3.5](../../SPEC.md#35-enforce-permanently-and-block), [3.6](../../SPEC.md#36-make-the-failure-message-terse-and-point-it-at-real-documentation). For each give Yes, Partial or No and one line of
reasoning. Then state whether the remediation conforms under section [7](../../SPEC.md#7-conformance) and why, whether the defence
alone conforms and why, and which decision in the fixture, if any, section [4](../../SPEC.md#4-authority-which-decisions-belong-to-whom) says was not the
practitioner's to take.

## 4. Confusion

Quote any passage you found confusing on your one reading, with its clause number. Do not say what
would make it clearer and do not propose any change. Write "none" if there was none.

===== FILE: SPEC.md =====
# Defence Before Fix: Method Specification

**Version**: 1.2.0-dev, unpublished
**Companion to**: [the detector specification](DETECTOR-SPEC.md), version 1.0.0, and [the toolchain specification](TOOLING-SPEC.md), version 0.2.0
**Author**: [Joseph Edmonds](https://ltscommerce.dev), [Edmonds Commerce](https://edmondscommerce.co.uk)
**Coined**: 22 February 2026, in [the original article](https://ltscommerce.dev/articles/defence-before-fix-static-analysis)

> **Defence Before Fix** ([DBF]) is a phase that runs *before* a [Defect] is fixed. Rather than dropping
> straight into remediating the specific [Instance] in front of you, you first treat that [Instance]
> as evidence of a [Class], and you build the automated [Defence] that detects every occurrence of
> that [Class] across the whole codebase. The [Defence] is only trusted once it has been seen to
> fire.

The [Defect] is what makes this possible. It is a real, confirmed, impactful example of a harmful
pattern, which is precisely the raw material a good custom [Rule] needs and which speculative
[Rules] never have. Every [Defect] is therefore an opportunity to extend the codebase's permanent
defensive [Coverage], and that opportunity exists only in the window before the fix.

Defence Before Fix does not replace test-driven development. The specific [Defect] is still
reproduced with a test and proven fixed, exactly as normal. The two operate at different levels:
TDD addresses the [Instance], Defence Before Fix addresses the [Class].

Not to be confused with *Defence in depth*, which is a security term meaning something else
entirely. The [Defence] here comes before the fix in time, not in layers.

US spelling: **Defense Before Fix**. Abbreviated [DBF] throughout.

---

## Status of this document

This is version 1.2.0-dev of the specification. It is normative: section 3 defines the method,
section 4 states who decides what, and section 7 defines what [Conformance] means and who may claim
it.

**This document is the source of truth for what the method is.** Where anything else describing
Defence Before Fix disagrees with it, including the article in which the term was first published,
this document is correct.

Who coined the term, when it was first published, and what is and is not being claimed are recorded
separately in [provenance](PROVENANCE.md), which is meta information about this specification
rather than part of it. A short introduction to the method is in [the primer](PRIMER.md).

Changes are recorded in the changelog at the end.

## 1. Terminology

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in
RFC 2119.

#### DBF

The acronym for Defence Before Fix, used as shorthand for this method and for anything built under it. A [DBF] [Rule] is a [Rule] as this specification defines it; a [DBF] [Toolchain] is one that [Conforms] to the [Toolchain] specification; a [DBF] project is one that [Conforms] to section 7.

#### Defect

Any observed problem worth acting on: a bug, a code review finding, a performance observation, an incident, an inconsistency. The method does not care which.

#### Class

The pattern, style, idiom or configuration that permitted the [Defect], expressed generally enough that other occurrences of it are also [Defects] or latent [Defects].

#### Instance

One occurrence of a [Class]. The reported [Defect] is one [Instance]; there are usually others.

#### Hazard

The harm a [Class] causes, which is **not necessarily a failure**. It may be a failure, but it may equally be error hiding, or something merely sloppy that makes the code harder to reason about or to change safely.

#### Detector

A tool that reads code without executing it and reports occurrences of a pattern. A [Detector] is not a [Runner]. By what it can see in the configuration it runs in, a [Detector] is [Textual], [Syntactic] or [Semantic], and clause 3.2 says which kind a [Rule] is written in. What a [Detector] must offer so that a [Practitioner] can write, prove, run and resolve a [Rule] in it is specified in the [detector specification](DETECTOR-SPEC.md).

#### Runner

A tool that **executes** code and reports what happened: a test [Runner], a compilation, a benchmark, a smoke check. A [Runner] answers questions about behaviour at one moment, on the paths it happened to exercise; a [Detector] answers questions about the text of the code, everywhere it exists. Detection under this method MUST come from a [Detector], for that reason.

#### Textual

Of a [Detector]: one in which a [Rule] can ask only how the code is spelt, as grep does. It sees the text and nothing of its structure, so it cannot tell a call from a comment that names it.

#### Syntactic

Of a [Detector]: one in which a [Rule] can ask about the parse tree of the code, and at most some of the types of values, as ast-grep does. It sees the structure, but cannot always tell apart two calls of the same name on values of different types.

#### Semantic

Of a [Detector]: one in which a [Rule] can ask the type of any expression and what any name refers to, across the whole program, as the language's compiler or type checker resolves them, as in PHPStan, mypy with `check_untyped_defs`, Clippy, Dylint and ESLint with typescript-eslint's type information. One that infers types only in part, or only within one file, is [Syntactic]. The same [Detector] can be [Syntactic] in one configuration and [Semantic] in another: ESLint without type information is [Syntactic]. A [Semantic] [Detector] need not accept bespoke [Rules]: Clippy does not, and in Rust a project's own [Semantic] [Rules] are written for Dylint.

#### Rule

One pattern definition within a [Detector].

#### Defence

A **[Blocking]** [Rule] together with the supporting documentation that clarifies it and clearly signposts best practice. A [Rule] without its documentation is not a [Defence], and neither is a [Rule] that only warns.

#### Coverage

The accumulated set of [Defences] a project has built. Every [Defect] handled under this method extends it.

#### Remediation docs

The fuller documentation a failure [Message] points to, explaining what a [Rule] is about, why it exists and how to fix a violation correctly.

#### Blocking

A [Rule] is [Blocking] when its firing prevents a change from being accepted by whatever the project uses to accept changes. A report that does not block is a **[Warning]**, and a [Rule] that only warns is not a [Defence].

#### Warning

A report that does not block. A [Rule] that only warns is not a [Defence].

#### Message

The text a [Rule] prints when it fires. Terse, and carries the [Rule]'s [Identifier].

#### Identifier

The stable string a [Rule] reports with, by which its [Remediation docs] are found. Survives renames of the [Rule].

#### False positive

A report on code that does not carry the [Hazard].

#### Narrowing

Reducing a [Rule]'s scope so it stops reporting code that does not carry the [Hazard]. [Narrowing] that excludes code which does carry the [Hazard] is a [Suppression].

#### Suppression

Any means of preventing a [Rule] from reporting an [Instance] that carries the [Hazard]: an ignore comment, an ignore entry, a [Baseline], or a [Narrowing] that excludes it.

#### Baseline

A recorded set of pre-existing [Instances] a [Rule] is configured not to report. A [Suppression] in bulk.

#### Exception

A recorded [Owner] decision that a specific [Instance] or scope is excluded from a [Defence]. The only legitimate form of [Suppression]. This document never uses the word in its programming-language sense.

#### Sweep

Running a [Rule] across the whole codebase to enumerate every [Instance].

#### Fixture

Code written to carry the [Hazard] deliberately, so a [Rule] can be proven to fire when the codebase has no [Instance] to prove it on.

#### Calibration

A project-level ruling on a threshold or parameter this document leaves open.

#### Practitioner

Whoever is doing the work of this method on a given [Defect], human or [Agent]. Decides how the [Defence] is built. Has no authority over [Exceptions].

#### Agent

A [Practitioner] that is an automated system. Everything said of a [Practitioner] applies to an [Agent]; section 8 exists because of what is additionally true of one.

#### Owner

Whoever holds authority over a project's [Exceptions] and [Calibrations]. Always a human. The project decides who, and clause 8.7 requires the answer to be findable. Where the project has recorded nobody, the [Owner] is whoever instructed the [Practitioner], and [Escalation] goes there. One limit applies to that default. The default [Owner] is usually the person under the same deadline as the [Practitioner], so a decision they take under section 4 to keep an [Instance] unfixed, [Suppress], [Baseline] or remove a [Rule] MUST be recorded in the project record with its justification. That record MUST mark the decision as taken by a default [Owner] and state why nobody better placed was reachable, where the next reader can see who decided and why. Such a decision SHOULD be revisited by someone not under that deadline before the next release. A project that wants the separation section 4 describes names an [Owner]; the default exists so work is not blocked, not to certify that a second judgement was applied.

#### Escalation

Referring a decision to the [Owner], whilst continuing with everything that does not depend on it.

#### Toolchain

Whatever a project assembles to run its checks through: its [Detectors] and [Runners], and the parts around them that route, list, record and resolve, from third-party, first-party and project-level parts in any combination. It is measured at the project level. What a [Toolchain] must offer beyond what each [Detector] in it offers is specified in the [toolchain specification](TOOLING-SPEC.md).

#### Conform

To satisfy every MUST of the relevant section 7 level, or of the companion specification being claimed. Partial satisfaction is not [Conformance].

## 2. When the method applies

Defence Before Fix applies to a [Defect] when that [Defect] can be attributed to a pattern, style,
idiom or configuration that a [Detector] can be made to recognise.

When it can, building the [Defence] is not optional and MUST be attempted before the [Defect] is
fixed. This is the substance of the method rather than a preliminary to it.

When it cannot, the [Defect] is outside the method's scope and is fixed conventionally. Not every
[Defect] is an [Instance] of a detectable pattern, and forcing a [Rule] where no pattern exists
produces [Detectors] that fire on innocent code, which is worse than having no [Detector].

**Attempt rather than pre-judge.** The [Practitioner] MUST attempt to express the [Class] as a [Rule]
rather than deciding in advance whether it is expressible. Failing to write a [Rule] that satisfies
clause 3.1's bounds is itself the evidence that the [Defect] is out of scope, and it is cheaper and
more reliable than a judgement made before trying. The conclusion that no pattern exists MUST be
stated in one sentence, naming at least two independent techniques tried at expressing the
[Rule], in the vocabulary clause 3.1 uses for a search, and recorded where the project's
other decisions are enumerable under clause 8.7, not only with the fix. It is the cheapest way
out of the method and the only one that would otherwise leave no trace; enumerating it is how an
[Owner] sees an [Agent] routing around the method, and naming the techniques is what lets
them judge whether the attempt was one.

There are three ways out of the method and they are recorded differently. No pattern exists: the
[Defect] is out of scope under this section, and only the sentence above is recorded. A pattern exists but the
language has no extensible [Detector] of a kind that can express the [Rule] within clause 3.1's
bounds, and a bespoke one is not practical: a [Toolchain] gap
under clause 3.2. A pattern exists, a
[Detector] exists, but the [Toolchain] lacks a mechanism the [toolchain specification](TOOLING-SPEC.md) requires: a
mechanism gap under clause 3.2, and the [Rule] is still built. The attempt that separates the first
from the second is complete when the [Practitioner] has checked the [Detectors] the project already
runs and the language's own [Detector] ecosystem for an extension point, and found none.

**Deferring the fix is not a fourth.** A [Defect] whose fix is postponed has not left the
method; the attempt this section requires is owed when the fix is taken up. A
[Practitioner] who finds a [Defect] and does not fix it now MUST record it where the project's
other decisions are enumerable under clause 8.7, naming the [Class] where one is already
apparent. Whether it stays unfixed is the [Owner]'s decision under section 4; the record is what
puts it in front of them. Reporting it in conversation does not satisfy this: the conversation
ends, and with it the only trace that the [Defect] was ever seen.

**Why**: each of the three ways out costs the [Practitioner] something to take and leaves
something an [Owner] can read: a recorded sentence naming two techniques tried, or a [Toolchain]
gap. Deferral costs nothing, and without this record it would leave nothing, which makes it
cheaper still than the sentence above and unseen from outside. A [Defect] mentioned once and
never written down cannot be told apart from a [Defect] nobody found, so neither the [Class] nor
the [Defence] it implies is ever reached.

## 3. The method

Six clauses, in order. In brief, before the detail:

| Clause | The [Practitioner] MUST                                                                                     | The record shows                                                                                                                                     | Goes to the [Owner] under section 4                                                                                              |
| ------ | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| 3.1    | Name the [Class] the [Defect] belongs to, bounded both ways, after an independent search                    | The [Class], the [Hazard] sentence, the two search techniques, the next wider [Rule] not built, whether a [Runner] check pins the reported behaviour | Whether the next wider [Rule] is built, and whether the [Rule] stays narrower than the search showed                             |
| 3.2    | Express the [Class] as a [Rule] in the strongest kind of [Detector] that can express it, never a test       | The [Rule], its [Detector] and kind, why it is in a weaker kind than [Semantic] where it is, and any [Toolchain] gap that stopped a bespoke one      | Whether a [Class] that could be defended is left undefended, and a weaker kind where the project requires the [Owner]'s sign-off |
| 3.3    | Make the [Rule] fire, on the [Instance] or a [Fixture], in a commit of its own                              | The red run, and the sentence behind every [Narrowing]                                                                                               | Any exclusion whose sentence cannot be written: that is [Suppression]                                                            |
| 3.4    | [Sweep] the whole codebase, record the count, then fix every [Instance]                                     | The count, corroborated, and what was fixed by hand or by pattern                                                                                    | Any [Instance] left unfixed, and any [Baseline]                                                                                  |
| 3.5    | Make the [Rule] permanent and [Blocking] in the project's own checks                                        | The green run through the project's entry point, and the recorded decision behind any [Suppression]                                                  | Removing or disabling the [Rule], and any [Suppression] added to it                                                              |
| 3.6    | Print a terse [Message] with a stable [Identifier] that resolves to documentation versioned with the [Rule] | The [Remediation docs]                                                                                                                               | Nothing                                                                                                                          |

The fourth column is what an [Agent] or other [Practitioner] MUST NOT decide alone, clause by
clause; section 4 states the full list and governs where the two differ.

**Three of them turn on a judgement this specification deliberately does not close**: whether code
carries the [Hazard] (3.1, 3.3), whether a search was comprehensive (3.1, 3.4), and how broadly to
draw the [Class] (3.1). Any threshold given here would be calibrated to one codebase, one team and
one generation of [Toolchain], and would be wrong everywhere else.

**Those judgements are settled at project level, not in this document.** A project fixes its own
thresholds and parameters, in its configuration or in its [Toolchain]'s defaults, and where a case is
genuinely ambiguous it is raised, discussed and written down so the next person inherits the answer
rather than the argument. A project's accumulated decisions become part of its [Coverage] in the same
way its [Rules] do.

**A project MUST make those decisions discoverable**, by the same means as its [Defences], so that a
[Practitioner] arriving cold can find what has already been settled instead of guessing at it or
re-opening it. The burden is the project's, not the reader's: a decision that cannot be found by the
means clause 8.7 requires is, for the [Practitioner]'s purposes, a decision the project has not
recorded, and the paragraph below applies. The search is therefore bounded: enumerate the project's
[Defences] as clause 8.5 requires them to be enumerable, and whatever is reachable from there is the
record. Nothing further is owed before concluding that nothing is recorded.

**Where the project has recorded nothing, the [Practitioner] is not blocked.** Proceed on the
examples below, **state the [Calibration] assumed**, and record it with the [Remediation docs]
for the [Defence] being built, which clause 3.6 already requires to exist, to ship with the project
and to be reachable by a stable [Identifier]. That statement becomes the project's first recorded
decision on the point, and the next [Practitioner] inherits it.

A project MAY of course keep these decisions somewhere else and say so. **The default above exists
so that a [Practitioner] who finds no project convention still has one**, because "the project
decides" and "the project has decided nothing" would otherwise combine into a deadlock. What is
never acceptable is assuming silently, since a [Calibration] nobody knows was chosen cannot be
corrected.

Worked examples of the three, to calibrate against:

| Judgement         | Too little                                                                                         | Too much                                                                                   |
| ----------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **[Hazard]**      | Only counting things that crash. Error hiding and code nobody can safely change are [Hazards] too. | Counting every stylistic preference, so the [Rule] defends taste rather than the codebase. |
| **Comprehensive** | One text search for the exact token from the reported [Defect], then stopping.                     | Auditing the whole system by hand before writing a [Rule] at all.                          |
| **[Class]**       | A [Rule] matching the variable name in the original bug report.                                    | A [Rule] matching every use of the language feature the bug happened to involve.           |

### 3.1 Attribute the defect to a class

The [Practitioner] MUST first establish what [Class] the [Defect] belongs to. The question is not
"what went wrong here" but "what kind of thing is this an [Instance] of".

**In order, this clause is five steps**, each detailed below:

1. Name the [Class] as a pattern, and write the one sentence that says what [Hazard] it carries.
2. Search the codebase for other [Instances] by at least two independent techniques, chosen and
   run **before** the [Rule] is run against the codebase, until the last technique adds nothing.
3. Widen the [Class] if the search found [Instances] the [Rule] would miss; narrow it if the
   [Rule] would match code that does not carry the [Hazard].
4. Where the [Class] stays at the reported [Instance], name the next wider [Rule] and why it
   was not built.
5. Record all of that, the five things listed at the end of this clause, before clause 3.2.

A worked example. The [Defect]: a currency conversion's error was caught and its empty result
used as a total. Step 1 names the [Class] "a caught error whose call's result is then used as if
the call had succeeded", with the [Hazard] "silent wrong output". Step 2 searches twice: a text
search for every catch in the codebase, read for whether the result is used afterwards, and a
reading of every caller of the fallible conversion and payment functions; the second finds two
[Instances] the first missed, so a third technique, asking the team which other calls fail
quietly, is run and finds nothing new. Step 3 keeps the [Class] as named because the [Rule]
would catch all four. Step 4 names the wider [Rule], "any caught error that is not rethrown or
reported", and records that it was not built because most of the codebase's catches are
legitimate and the [Owner] has not decided whether that is a [Hazard]. Step 5 writes the four
things down.

A [Defect] usually belongs to several overlapping [Classes] at different levels of abstraction, and
choosing between them determines the [Rule], the [Instance] count and the scope of the remediation.
Two bounds apply, and both are checkable during the work rather than matters of taste:

- **Lower bound.** If the [Rule] catches only the originating [Instance], the [Class] is probably drawn
  too narrowly and MUST be widened, unless the independent search below has found no other
  [Instance] and the [Practitioner] records that the [Class] is genuinely singular today. A
  [Rule] that matches exactly one thing is an [Instance] [Detector] wearing a [Rule]'s clothes.
  A [Rule] drawn to the exact value or name found in the [Defect] is the same [Narrowing] however
  it is spelt, and where it stays because the search found nothing wider, the record MUST also name the next
  wider [Rule], the one with the [Instance]'s distinguishing detail dropped, and state why it was
  rejected. That rejection is tested as a [Narrowing] is under clause 3.3: a sentence stating why the
  [Hazard] cannot arise in the code the wider [Rule] would add, confirmed by search, with uncertainty
  routed upwards under section 4.
- **Upper bound.** If the [Rule] matches code that does not carry the [Hazard], the [Class] is drawn
  too broadly and MUST be narrowed. [False positives] destroy a [Defence]'s credibility faster than a
  missing [Rule] does. One report on code that does not carry the [Hazard] is enough; there is no
  tolerated rate.

**Both bounds measure breadth within one level; the level itself is a separate question, answered
against the report.**
Where the [Defect] was reported as a behaviour, a check that reported clean having run nothing, a
process that stopped before its work, the [Class] a [Detector] can read usually sits one level
below that [Hazard]: the mechanism of this [Instance], not the failure the report opens with.
That [Class] is the right one to build, because no [Detector] reads "this check still produces
its signal". It is not the whole answer. The record MUST state whether the reported behaviour is also
pinned by a check a [Runner] executes, under section 6, either one added with this remediation or
an existing one named, and where neither is offered, why not. A [Class] drawn at the mechanism with
the behaviour left unpinned defends the [Instance] thoroughly and the report not at all.

**Resolve the lower bound by searching independently, not by judgement.** The [Rule] is not the only
way to find [Instances], and it is the least trustworthy one whilst it is still unproven. Search for
other [Instances] by other means, whether that is a text search, reading the code, or asking someone
who knows the system, and, once the [Rule] exists under clause 3.2, confirm it catches what those
searches found. That confirmation is not one of the techniques.

That search MUST be a comprehensive one, carried out by a person, model or [Agent] competent to
carry it out. No fixed technique is prescribed, because what is comprehensive depends entirely on
the [Class] and the codebase. What is not acceptable is a cursory look that exists to discharge the
requirement, since this search is what the single-[Instance] conclusion and the [Sweep] count both
rest on, and a bad search validates a bad [Rule] silently.

**The stopping criterion is saturation, not effort.** Use at least two independent techniques, and
the search is complete when the last technique added found nothing the earlier ones had missed. A
second technique that turns up new [Instances] means a third is owed; a second that turns up nothing
new means the search is done. That is a test the [Practitioner] can apply and record without a
threshold, and it is the standard clause 3.4 refers back to.

Two techniques are independent when they would miss different things. A text search for the token
and a reading of the code paths that consume the value are independent; two text searches for two
spellings of the same token are one technique; asking someone who knows the system is a third. For a
[Class] defined by how code is spelt, writing the idiom the other ways it is commonly spelt and searching
the codebase for each of them is a technique, because a search for one spelling misses every other;
whether the [Rule] catches those spellings is the confirmation step, not the technique. The
[Rule] itself is never one of the techniques, because the search exists to check the [Rule].
A clean run of the [Rule] is therefore not evidence of saturation either: "the [Rule] found nothing
the reading had missed" is one technique and a [Rule] run, not two techniques, and the stopping
criterion cannot be applied to it.
The record MUST name both techniques, chosen before the [Rule] is run against the codebase, and
MUST state what each found that the other could not have checked. A technique counts only if it does
not invoke the [Detector] under proof in any configuration and would exist unchanged had the
[Rule] never been written; a [Sweep]-shaped run of the [Rule], however comprehensive it looks, is
never one of the two. Where a later technique shows that two earlier checks shared a blind spot, they
were one technique, and a third is owed.

That turns the single-[Instance] case into something checkable rather than a matter of taste. If an
independent search finds [Instances] the [Rule] missed, the [Class] was drawn too narrowly and the [Rule]
MUST be widened until it catches them. Fixing those [Instances] by hand does not discharge the
widening: they are still [Instances] the [Rule] missed, and where the [Practitioner] leaves the
[Rule] unwidened because the wider check is harder to build without [False positives], that is
the [Owner]'s decision under section 4 and is recorded as one. If an independent search finds nothing
the [Rule] did not already have, then one [Instance] is a reasonable conclusion rather than an
assumption, and the [Rule] is correct as written.

**What this clause leaves on the record**, before clause 3.2 begins: the [Class], spelt as a
pattern; the [Hazard] sentence; the two search techniques and what each found; the next wider
[Rule] that was not built, with the reason; and, where the [Defect] was reported as a behaviour,
whether a check a [Runner] executes pins that behaviour, or why not. A record missing any of the
five has not finished this clause.

**Why**: the [Class] is the unit of work. Everything downstream operates on it, so an error here
wastes all the effort that follows.

### 3.2 Build the net

The [Practitioner] MUST express the [Class] as a [Rule] in a [Detector], and the [Detector] MUST read code
rather than execute it.

A test MUST NOT serve as the [Detector]. A test proves that one input produces one wrong output; a
[Rule] finds the pattern wherever it occurs, including in code nobody thought to test.

Where an off-the-shelf [Rule] or a tightening of existing configuration genuinely detects the
[Class], using it [Conforms]. However, a [Toolchain] that supports only its own built-in [Rules] cannot
support the method in general, so **a [Conforming] [Toolchain] MUST allow bespoke custom [Rules]**.
The [Rules] that matter most are tightly coupled to the project and carry project-specific
knowledge, both in the pattern they match and, importantly, in the [Message] they emit.

Any tool that reads code and reports pattern matches qualifies, whatever its category. Static
[Detectors] such as PHPStan, ESLint, mypy and Clippy are the usual instruments; so are custom AST
walkers, architectural fitness checks, and [Detectors] that inspect a change before it lands. These
are examples rather than a permitted list, and a [Detector] is not disqualified for being unfamiliar.

**Qualifying is not the same as being the right host.** A [Rule] the [Practitioner] writes MUST
be written in the strongest kind of [Detector] that can express it: a [Semantic] one, else a
[Syntactic] one, else a [Textual] one. A weaker kind is allowed only where the stronger kind
cannot express the [Rule] within clause 3.1's bounds, or where the language has no [Detector]
of the stronger kind that accepts bespoke [Rules]; those are the two grounds. For example, the
stronger kind cannot express a [Rule] that reads a file the [Semantic] [Detector] does not
parse. The kind is that of the configuration the [Rule] runs in. Enabling an existing [Rule], or tightening its thresholds, is
not writing one, and the off-the-shelf paragraph above covers it; giving an existing [Rule] a
pattern of the project's own, or changing the pattern of a [Rule] the project wrote, is writing
one.

For the second ground, the language has a [Detector] of the stronger kind when the project
already runs one or the language's own [Detector] ecosystem offers one. A stronger configuration
of a [Detector] the project runs counts; a [Detector] or configuration that only a hosted service or a licence tier
provides does not. A [Detector] accepts bespoke [Rules] when the project can add its own [Rule]
to it through a documented extension point, such as a plugin interface; it need not meet the rest
of the [detector specification](DETECTOR-SPEC.md).

On the [Remediation docs] of a [Rule] they write, the [Practitioner] MUST name the [Detector] and
its kind. Where that kind is weaker than [Semantic], the [Practitioner] MUST also record the
grounds in two places, both of them: on the [Rule]'s [Remediation docs], and in the description
of a commit. That commit is the one that adds the [Rule] or moves it to another [Detector]; for
a [Rule] already in place when its grounds are first written down, it is the commit that writes
them. What is recorded, in both places, is which of the two grounds holds at each
step down the order [Semantic], [Syntactic], [Textual], and why, naming the [Detectors] of the
stronger kind that were considered. The [Practitioner] judges whether the grounds hold, as
section 4 gives them the choice of [Detector]. A project MAY require its [Owner]'s sign-off on
a weaker kind as well, by recording that requirement where it records its [Calibrations]; where
it has, the [Practitioner] MUST record the sign-off with the grounds, and where the project has
recorded no such requirement none is needed.

**A worked example.** Take a [Rule] against calling a method named `context` on one type. A
[Detector] that cannot see types reports every call named `context`, whatever type it is called
on, which is a [False positive] clause 3.1 does not tolerate; restricting it by the name of the
variable the call is made on misses the same call on a variable named otherwise, which is an
[Instance] the [Rule] never sees. A [Semantic] [Detector] decides by the type, so the bounds of
clause 3.1 can be held there.

Where the affected language has no extensible [Detector] available of a kind that can express the
[Rule] within clause 3.1's bounds, the [Practitioner] MAY build a
bespoke one: a program of the project's own that reads code and reports matches of the [Class], run
through the project's own entry point like any other [Detector]. This is a last resort rather than
a default, because a [Detector] the ecosystem maintains is cheaper to keep and easier for the next
[Practitioner] to find, but a bespoke [Detector] is far better than none: the [Class] is defended,
and every clause of this method applies to the bespoke [Detector] unchanged, including the proof in
3.3, the enforcement in 3.5 and the [Identifier] and documentation in 3.6.

Only where a bespoke [Detector] is not practical either is the [Class] undefended. That is a
**[Toolchain] gap and MUST be recorded as one**, with the reason a bespoke [Detector] was not
practical, rather than treated as the [Defect] being out of scope. The distinction matters, because
the first is a decision somebody can revisit and the second quietly disappears.

A gap is recorded in the project record where the [Toolchain] defines one, as the
[toolchain specification](TOOLING-SPEC.md) requires it to; only where it does not is the location the project's choice, and
then the only requirement is that somebody deciding what to invest in the [Toolchain] would find
it. A gap recorded where nobody looks has been forgotten with extra steps.

There are two kinds of gap and they lead to different work. The language having no extensible
[Detector] of a kind that can express the [Rule] within clause 3.1's bounds, and no practical
bespoke one, is the gap above, and the [Class] waits for one. The project's [Toolchain]
lacking a mechanism the [toolchain specification](TOOLING-SPEC.md) requires, such as a proving harness or an
[Identifier] resolver, is a mechanism gap, and it does not put the [Class] out of reach: the
[Practitioner] builds the [Rule], uses the substitutes clauses 3.3 and 3.6 already allow, and records
the mechanism gap alongside it. A mechanism gap that is also a breach of the [toolchain specification](TOOLING-SPEC.md)'s
own obligations, such as a [Toolchain] that cannot run its [Defences] on its own source, is recorded
against the [Toolchain]'s claim under section 7 as well, because that is the fact its [Owner] needs.
Where that record lives is the [Toolchain]'s own [Conformance] declaration, under the
[toolchain specification](TOOLING-SPEC.md)'s clause 9.2, or the [detector specification](DETECTOR-SPEC.md)'s clause 8.1 for a [Detector]; a [Practitioner] who cannot write there reports it by the channel
section 4 names, as a blocked decision.

**What happens next is not waiting.** Once the gap is recorded, the [Defect] is fixed conventionally
under section 2, with its reproduction test, and the work moves on. What the record changes is the
[Class], not the [Defect]: the [Class] is known to be undefended, and the next person to see an
[Instance] of it finds a decision to revisit rather than nothing.

**Why**: this clause is what distinguishes the method. Detection happens at the level of the
[Class], using an artefact that can be pointed at the whole codebase, and it happens before
anything is fixed.

### 3.3 Prove the net by making the rule fire

A new [Rule] MUST be proven to fire before it is trusted. It is never the goal to write a [Rule] and
be instantly green.

**This clause has three parts.** Part A: the proof, a red run kept as a commit of its own. Part B:
[Narrowing], which is the [Practitioner]'s to decide only when they can write down why the
[Hazard] cannot arise in what is excluded, and is [Suppression] for the [Owner] otherwise. Part C:
proving a [Rule] when the pattern is absent from the codebase, and what a [Rule]'s own code owes.

**Part A: the proof.**

**Proving and sweeping are two questions, not necessarily two runs.** Proving asks "does this [Rule]
work at all", and the answer is pass or fail. Sweeping, in clause 3.4, asks "how much of this [Class]
is present", and the answer is a count. A single execution of the [Rule] answers both, and no second
run is required. What matters is that the two answers are not confused with one another: a large
count does not make a [Rule] more proven, and a [Rule] that fired does not tell you the [Sweep] is
complete.

**The proof MUST survive as a commit of its own.** The [Defence] is committed with the originating
[Instance] still present, and the fix is committed after it. That first commit is the red run: it is
what a reviewer under section 7 checks out to reproduce the proof, and it is the only record that the
[Rule] fired on real code rather than on a [Fixture] alone. Where the [Defence] and the fix share a
commit, the red run can only be reconstructed by hand-reverting lines the reviewer has to guess at,
and the proof rests on that guess.
Surviving as a commit means a fresh checkout of that commit, followed by the project's own declared
setup, reproduces the proof. Dependency installation and generation driven by files the commit does
carry, a manifest, a lockfile, are that setup. Anything else the proof depends on that version control
does not carry, an empty directory, an ignored file, an artefact placed by hand, is state the commit
does not contain, and a reviewer under section 7
reproduces from a fresh checkout rather than from the [Practitioner]'s working tree, so a
proof that passes only there does not survive.
Both commits MUST remain individually reachable in the history the reviewer inspects. A merge that
flattens them into one destroys the proof, so a project whose merge policy does that MUST keep the
[Defence] commit reachable by another recorded reference, a tag or the retained branch, or MUST NOT
claim the remediation [Conforms]. How the project merges is its own business under section 8;
what must survive the merge is not.

**Part B: [Narrowing].**

**The decision comes first, and it turns on one sentence.** Can the [Practitioner] write down why
the [Hazard] cannot arise in the code being excluded? If they can, the exclusion is a [Narrowing],
it is theirs to make, and they record that sentence. If they cannot, or are not sure, the exclusion
is a [Suppression] and belongs to the [Owner] under section 4. Doubt is [Suppression]. The test is
the [Hazard], never the count. Two exclusions of the same [Rule] show the difference:

| Excluded code                                                             | The sentence                                                                        | Which it is                             | Who decides    |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------- | -------------- |
| A catch in test helpers that asserts on the caught error and returns it   | "The result is the error itself, so it cannot be used as if the call had succeeded" | [Narrowing]                             | [Practitioner] |
| A catch in a nightly export, excluded because the export "hardly matters" | Cannot be written: the caught result is still used, so the [Hazard] is present      | [Suppression], referred under section 4 | [Owner]        |

The sequence is: write the sentence; confirm it by search, the way clause 3.1's search confirms
the [Class], so that the excluded code is looked at and not assumed; record both. A sentence
that cannot be confirmed is a sentence that cannot be written, and the exclusion goes to the
[Owner]. Ask, for every exclusion, which row it sits in:

- If the excluded code **carries the [Hazard]**, the exclusion is [Suppression] and is forbidden to
  the [Practitioner], however many or few [Instances] it removes.
- If the excluded code **does not carry the [Hazard]**, the exclusion is precision and is required
  by clause 3.1's upper bound.
- If the [Practitioner] **is not sure which**, the exclusion is [Suppression], and it goes to the
  [Owner] with the doubt stated.

A [Rule] MUST NOT be narrowed because its [Instance] count is uncomfortably high. A high count is a
finding about the codebase, not a [Defect] in the [Rule]. **Firing on more than the originating
[Defect] is success.** A [Rule] that catches the reported [Instance] and forty-nine others has done
exactly what it was built to do, and the forty-nine are the reason the method exists.

**Then the proof, which runs the other way round, and both ways.** Where the change under proof is
that the [Rule] should stop firing on code that does not carry the [Hazard], three things are shown:
the [Rule] firing on that code before the change, from a commit still reachable in history and to the
same standard as a new [Rule]'s red run; the [Rule] not firing on it afterwards; and a retained
[Fixture] on which it still fires afterwards, the case that motivated the [Rule]. Where the narrower
shape was chosen from the outset and no wider [Rule] was ever built, the before state is a [Fixture]
of the wider pattern the [Rule] does not catch, retained as the record of what was left out.

A [Narrowing] reduces the [Practitioner]'s own work, which is why it is not left to self-report
alone. The sentence is recorded with the [Narrowing] as part of the [Rule]'s [Remediation docs];
the search confirms it. Code a [Narrowing] excludes MUST be searched to the standard of clause
3.1, and an [Instance] that search finds there disproves the sentence and reverses the
[Narrowing]. Every [Narrowing] MUST be enumerable by the
same means as the project's [Exceptions], so the [Owner] sees them in one place, and a
[Narrowing] that excludes more code than the [Rule] still covers MUST be reported to the [Owner]
as if it were a [Suppression]. The sentence remains the test of whether an exclusion is honest;
the search and the listing are what make a fluent dishonest one visible.
Where the [Toolchain]'s own listing shows the [Narrowing] with its sentence, that listing is
the record, and no separate prose is owed for it.

The sentence may turn out to be wrong. The method does not require the [Practitioner] to be
infallible; it requires the reasoning to be written down where the next reader, or the next
[Defect], can test it. A [Narrowing] with a recorded reason is correctable. One without is
indistinguishable from a [Suppression], which is why the sentence is the test and a percentage is
not.

**Part C: proving when the pattern is absent, and the [Rule]'s own code.**

**Where the pattern is present, prove the [Rule] against it.** At minimum the [Rule] MUST detect the
originating [Defect]. Where the [Defect] was found in code review rather than in production, the
pattern exists in the branch under review, so the [Rule] is written and proven there before the fix
lands.

**Where the pattern is genuinely absent, prove the [Rule] another way.** A [Rule] may legitimately be
green against the codebase: the branch that carried the pattern may have been rejected and
orphaned, the [Instance] may already have been fixed, or the [Rule] may be a purely proactive [Defence]
against a pattern that might occur but does not yet. In each of these cases the [Rule] MUST still be
proven, using [Fixture] code that demonstrates the pattern the [Rule] is meant to catch. That [Fixture]
SHOULD be kept as the [Rule]'s own test rather than deleted, so the proof re-runs whenever the [Rule] runs
instead of expiring the moment it succeeds.

**A green run proves nothing unless the [Rule] was loaded.** Before treating any run as evidence,
confirm the [Rule] was active in it, by enumerating the [Defences] that run was configured with or by
seeing it fire on a [Fixture] in the same run. A [Toolchain] analysing its own source with a
configuration that omits its own bundled [Rules] reports clean on every one of them, and both the
[Practitioner] and a reviewer have taken that for a pass.

**A [Rule] about how code is written may match its own source.** A [Rule] that forbids a construct in
[Rule] code will contain that construct, because it has to look for it. That match is not an
[Instance]: the construct there is the [Rule]'s subject, not its use, so the [Hazard] is absent and
clause 3.1's upper bound requires the [Narrowing]. Exclude it as precisely as possible, write the
sentence, and keep a test that proves the exclusion reaches nothing else.

**[Rules] are software, and SHOULD be built test-first like any other software.** How practical
that is varies considerably between [Detectors], some of which offer purpose-built [Rule]-testing harnesses
and some of which offer nothing, so this is a best-effort requirement rather than an absolute one.
Where a [Detector] makes [Fixture]-based testing impractical, an acceptance or smoke check that
demonstrates the [Rule] firing is an acceptable substitute.

[Fixtures] are part of the [Rule] and are versioned with it, under the co-location requirement in
clause 3.6. A [Fixture] is input to the [Rule]'s test, not part of the code the [Defence] enforces:
the [Sweep] excludes it under clause 3.4, and the enforced run does not report it, so there is no
circularity in a [Fixture] that deliberately fails the [Rule] it proves. Writing the [Fixture] is the [Practitioner]'s own decision under section 4, not something
to refer upwards.

Where they sit within the repository is the project's business. **Absent a convention, put them
where the project's existing tests live, and where there are none, alongside the [Rule]** - then say
which was chosen, as with any other [Calibration] under section 3.

**Why**: proving is the [Rule]'s own validation. A [Rule] that does not catch the [Instance] that
prompted it does not detect the [Class] it claims to, and shipping it would add a [Defence] that defends nothing
whilst defending nothing. This is the clause most easily skipped, because a green run feels like
success and looks like a clean codebase.

### 3.4 Sweep the codebase, then fix every instance

The [Practitioner] MUST run the [Rule] across the entire codebase and record the total [Instance]
count before fixing anything, and MUST then fix every [Instance] found rather than only the one
that was reported.

**"Entire codebase" means everywhere the pattern can occur, and nowhere it cannot.** For most
[Classes] that is a single language, because the pattern is a feature of that language, and sweeping
unrelated languages would be theatre. What the clause forbids is stopping at the package,
component or service that happened to report the [Defect] when the pattern plainly reaches further.
The question is where the pattern can exist, never where the bug was found.

Whether vendored dependencies, generated code and test [Fixtures] are in scope is the project's
decision, and it SHOULD be a recorded one rather than an implicit one, because a [Class] that is
excluded silently is indistinguishable from a [Class] that was never swept. **Where a project has
recorded no such decision, [Sweep] all first-party source in every language where the pattern can
occur, and exclude generated code and vendored dependencies**, then record that as the decision.
First-party source is code the project maintains in its own repository; generated code is what a
build step produces from other source; vendored code is a dependency copied in rather than
installed. Modified vendored code is first-party, because the project now maintains it. The two
exclusions are not a judgement that such code carries no [Hazard]. Unmodified vendored code is
somebody else's project, and an [Instance] found in it is reported upstream rather than fixed
in place; generated code carries an [Instance] only because its generator does, and the generator
is the first-party source the [Sweep] covers. An [Instance] the [Practitioner] notices in either
is recorded as a known [Instance] under section 4, not ignored.
A [Rule]'s own [Fixtures] under clause 3.3 are never [Instances]: they carry the pattern
deliberately, as the proof, and the [Sweep] count excludes them.

**The count MUST be corroborated independently**, to the standard set out in clause 3.1: a
comprehensive search by someone competent to make it. A [Rule] that is too narrow produces a small
count and a clean-looking [Sweep], and nothing inside the [Rule] can reveal that.

Where the independent search finds [Instances] the [Rule] did not, **clause 3.1 governs: the [Rule] was
drawn too narrowly and MUST be widened until it catches them.** The two are not permitted to
disagree, and the search is the authority, not the [Rule].

**Each fix MUST address the [Hazard] rather than the [Rule].** Throw on the absent case, or propagate
the absence explicitly so the caller decides: whatever the correct behaviour turns out to be. What is
forbidden is a change that turns the [Rule] green whilst leaving the failure mode intact, and
suppressing the [Rule] at the call site, which is not a fix at all.

**Supplying a default is usually the [Hazard] in another form**, not a fix. A default makes the absent
case look present, and the failure moves downstream to where nobody expects it and nothing names it.
A default is a fix only where absence is a legitimate state whose meaning the code defines, and then
the default states that meaning rather than filling the gap with a placeholder. This is the same
test as clause 3.3's [Narrowing] sentence and carries the same discipline: the [Practitioner]
MUST write down, with the fix, what the absence means and why the default is that meaning, and
where the sentence cannot be written the absence is an error and MUST be treated as one.

**Repetition is not the problem.** Where every [Instance] genuinely has the same correct answer,
applying that answer to all of them is right, and the fact that it looks mechanical is not an
objection. The requirement is that each [Instance] was actually examined and the same answer was
genuinely correct for it, rather than assumed correct because it was correct elsewhere. Where one
change is applied across many [Instances], the [Practitioner] MUST record which were examined
individually and which received the change by pattern, and MUST examine a sample of the latter
large enough, given how much the surrounding code varies, that a wrong answer there would have
been seen. "All examined" with nothing behind it is a claim, not a record.

**Fix them all.** There is no count at which a [Practitioner] stops fixing. A [Practitioner]
stops only when the next [Instance] needs a decision they lack the authority to make, and section 4
says which decisions those are; a hundred [Instances], or a thousand, is work rather than a
decision. A [Baseline] of the existing [Instances], so that the [Rule] blocks only new ones, MAY
be adopted where fixing every [Instance] is genuinely unfeasible, and the scale at which that
becomes true is tremendous. **No threshold is given here deliberately.** Any figure would be
calibrated to the [Toolchain] of the moment rather than to anything durable, and the honest measure is
the size of the change rather than the hours it would take, since the hours depend entirely on what
is doing the work.

**Under AI-assisted development a [Baseline] is almost never the right answer.** The historical case
for baselining was the cost of human hours, and that cost has largely collapsed: an [Agent] can work
through hundreds of [Instances] at a price that made a [Baseline] unavoidable a few years ago. Reaching
for one now is usually a habit rather than a judgement. This specification stops short of
forbidding [Baselines] outright, and only just.

Adopting one is a decision for whoever owns the codebase, taken after fixing has genuinely been
attempted rather than on the strength of the [Instance] count alone. "Attempted" is the
[Owner]'s judgement on the [Practitioner]'s report, not a threshold the [Practitioner] applies:
the [Practitioner] fixes until stopped by a decision they cannot make, and reports what was fixed and
what remains. That report MUST state the count fixed, the count remaining, what stopped the fixing,
and what would have to be tried next, so the [Owner] judges a record and not an adjective. **A
[Practitioner] executing this method does not have that authority** - see section 4. A baselined project SHOULD reduce the
[Baseline] over time and MUST NOT allow it to grow.

**Why**: consistency across a codebase is fundamental, and the [Sweep] is where this method
delivers most of its value. In the published worked example the reported [Defect] was one of
twenty-three; the other twenty-two were bugs waiting to surface in different contexts, reported
by different customers, at different times. A project that knows about twenty-three [Instances]
and fixes one has produced a documented list of [Defects] it has chosen to keep.

### 3.5 Enforce permanently, and block

The [Rule] MUST become a permanent part of the project's quality checks, and it MUST **fail** rather
than warn. Three things must hold for this clause to be met: the [Rule] is in the checks the
project runs to accept changes; it fails rather than warns; and every [Suppression] it carries is
covered by a recorded decision. A [Rule] that reports a violation without failing does not
[Conform], and neither does a [Rule] whose green run depends on a [Suppression] that no recorded
decision covers, since the checks are then passing around the [Instance] rather than enforcing
against it.

**Where those checks run, and what the project does when they fail, is out of scope.** Continuous
integration, git hooks, branch policy and release process are the project's own business; see
section 8. What this clause requires is that the [Defence] is permanent, applies to everyone rather
than to whoever remembers it, and produces a failure rather than a remark.

Removing a [Rule], or adding a [Suppression] for an [Instance], MUST be a recorded decision rather
than a silent edit, and belongs to the [Owner] under section 4, which governs. Where a [Rule]
carries a [Suppression] that no recorded decision covers, this clause is not followed until the
decision is recorded or the [Suppression] removed.

**Enforcement is demonstrated through the project's own entry point.** Running the [Detector]
directly shows that the [Rule] can fire; it does not show that the project's checks will run it.
The [Practitioner] MUST run the invocation the project uses to accept changes, over everything
that invocation covers, and see the [Rule] reported there. A [Toolchain] that wraps its
[Detectors] in its own command is asking for that command to be used, and a [Practitioner]
who bypasses it has proven the [Rule] and not the [Defence].

For a [Bundled defence], the project whose entry point demonstrates it is any project the
[Toolchain] is genuinely installed into, the [Consuming project] included, since that is
where the [Rule] will be enforced. A mechanism gap that stops the [Toolchain] running its own entry
point on its own source moves the demonstration to such a project; it does not weaken it. The
substitutes clause 3.2 allows, for proving under clause 3.3 and for resolution under clause 3.6, do
not extend to this clause: the demonstration here is through an entry point or it is not made. A
project created for the purpose, with the [Toolchain] installed into it from the source under
test, is such a project, so a [Toolchain] with no consumer yet is not excused.

**Why**: the purpose is that the mistakes of the past become structurally impossible to repeat, and
a [Warning] is not structure. A [Warning] is a suggestion, and sugges

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 070: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

The exact prompt every reader received, part 2 of 2

tions decay under deadline
pressure, which is the condition under which the original [Defect] was written.

### 3.6 Make the failure message terse, and point it at real documentation

The failure [Message] MUST be terse, and it MUST carry a stable [Identifier] that resolves to
[Remediation docs] shipped with the project.

That documentation MUST state three things, besides what clause 3.2 adds for a [Rule] the
[Practitioner] writes: what the [Rule] is about, why it exists, and how to
fix a violation correctly using the project's preferred approach.

A [Practitioner] who finds that an existing [Rule]'s printed [Identifier] does not resolve records
it where clause 3.2's gaps are recorded, naming the [Rule] and the [Identifier], with no [Rule] build of
their own to attach it to. It does not block the remediation. Where the mechanism cannot resolve
[Identifiers] at all, it is a [Toolchain] gap and blocks the [Toolchain]'s claim under section 7
until fixed; where the mechanism resolves others and only this [Rule]'s documentation is missing, it is a
fault in that [Rule]'s [Remediation docs], owned by whoever wrote the [Rule], and bears on
the project's claim rather than the [Toolchain]'s.

**The split between the two is by job, not by length.** The [Message] carries what was detected,
where, and the [Identifier]; it is read under interruption by somebody trying to get on with
something else. The documentation carries the reasoning and the remedy; it is read once, by
somebody who has decided to understand the [Rule], and it is maintained as the project's thinking
develops. Anything that would grow over time belongs in the documentation.

**A [Rule] and its documentation are one artefact and MUST be versioned as one.** They live in the
same repository and are committed together, or in a library the project depends on at a clearly
tracked version. A [Rule] carries knowledge specific to the project it defends, and its
documentation is where most of that knowledge actually sits, so anything that lets the two drift
apart destroys the value of both. The same applies to a [Rule]'s [Fixtures] under clause 3.3.

An external URL [Conforms] only where the project controls it and versions it alongside the [Rule]. A
link into a third-party wiki or a general article does not, because neither can be relied upon to
still describe this [Rule].

**Repository structure is left to the project.** Where these artefacts sit, how they are foldered,
whether [Rules] are vendored or depended upon: none of that is this specification's business. What
is required is that they move together.

The [Identifier] MAY be a [Rule] ID resolved by a command, an anchor in a documentation file, or a
URL. It MUST continue to resolve for as long as any released version can emit it, which is a
stronger requirement than surviving the current release. Where a [Rule] is redesigned such that its
meaning materially changes, a new [Identifier] SHOULD be issued and the old one SHOULD keep
resolving to an explanation of what became of it.

A [Message] that names a pattern without leading anywhere does not [Conform]. "Pattern X detected"
teaches nothing.

**Why**: the [Message] is read at the moment of failure, by someone who wants to get on with their
work, so it has to be short enough to read and specific enough to act on, whilst the reasoning
has to live somewhere it can be maintained and can grow. Splitting it this way keeps the [Message]
terse without losing the teaching, and the documentation becomes the project's accumulated
engineering knowledge rather than a comment nobody revisits. The best custom [Rules] are
opinionated documentation encoded as automation.

## 4. Authority: which decisions belong to whom

The method is frequently executed by somebody who does not own the codebase, including an [Agent]
acting under instruction. This section states which decisions that person takes alone and which
they do not.

**Decisions the [Practitioner] takes alone**, without seeking approval:

- What [Class] the [Defect] belongs to, and where its bounds sit (clause 3.1).
- Which [Detector] to use and how to express the [Rule] in it (clause 3.2), unless the project
  requires the [Owner]'s sign-off on a weaker kind of [Detector], as clause 3.2 allows.
- How to prove the [Rule], and what [Fixture] to prove it against (clause 3.3).
- What the correct behaviour is at each [Instance], and therefore what each fix should be (clause
  3.4).
- The wording of the failure [Message] and the [Remediation docs] (clause 3.6).

**Decisions that MUST go to whoever owns the codebase:**

- Adopting a [Baseline], in whole or in part (clause 3.4).
- Suppressing an [Instance], or removing or disabling an existing [Rule] (clauses 3.4 and 3.5).
- Accepting a known [Instance] as unfixed for any reason.
- Deciding that a [Class] will not be defended at all, where a [Rule] for it is achievable.
- Leaving unbuilt the next wider [Rule] that clause 3.1 required the [Practitioner] to name, where
  it was rejected because no [Instance] of it exists today rather than because the [Hazard] cannot
  arise there. The absence of an [Instance] is the [Practitioner]'s reason not to widen unasked; it
  is not authority to exclude the extension, and recording it as a known gap does not change whose
  decision it is. This does not reopen a [Class] whose bounds a [Hazard] sentence has settled, and
  it reaches no further than the one wider [Rule] already on the record.
- Leaving a [Rule] narrower than an independent search under clause 3.1 showed it should be, where
  the reason is that the wider check is harder to build without [False positives] rather
  than that the [Hazard] is absent from what it would add. The [Instances] the search found are
  fixed regardless; what stays with the [Owner] is the [Class] left partly undefended.

The dividing line is that the [Practitioner] decides **how the [Defence] is built**, save a weaker
kind of [Detector] where the project requires the [Owner]'s sign-off on it, and the [Owner]
decides **what the codebase is permitted to keep**. An [Agent] MUST NOT [Baseline], suppress or
knowingly leave an [Instance] unfixed on its own authority, whatever the [Instance] count turns out to
be.

**The default position is that none of these are permitted at all.** [Suppressions], [Baselines] and
every other means of evading a [Defence] are not techniques with a threshold governing their use; they are
[Exceptions] to the method, and the standing answer is no. An [Exception] exists only where a human has
discussed it, agreed it and documented it for that project. Nothing an [Agent] concludes on its own
creates one.

**Uncertainty is itself an [Escalation] trigger.** Where the [Practitioner] is not confident that code
they are about to exclude is free of the [Hazard], that exclusion is a [Suppression] and belongs to the
[Owner], whatever it is called. This is deliberately asymmetric: [Narrowing] is the [Practitioner]'s
decision only whilst they are sure, and doubt routes it upwards. Without this, the [Hazard] judgement
in clause 3.3 would let a [Practitioner] suppress an [Instance] whilst never touching the clause that
would have escalated it.

**Where the work is entirely [Agent]-driven, do the work.** Fixing [Instances] is cheap now, and an
[Agent] that reaches for an [Exception] has almost always found a shortcut rather than a genuine
obstacle.

**What to do whilst waiting.** A decision awaiting the [Owner] MUST NOT stall the rest of the work.
The [Practitioner] completes everything within their own authority, reports the blocked decision
with the [Instance] count and what it would cost to fix, and leaves the [Rule] unmerged rather than
merged in a weakened form. Where the only thing pending is whether one matched case carries the
[Hazard], the [Rule] MAY merge at full width with that case recorded as a known [Instance]
awaiting the [Owner], enumerable under clause 8.7: the [Rule] is not weakened, the case is not
hidden, and the [Practitioner] is not pushed towards declaring that no pattern exists. Reporting a
[Sweep] of four hundred [Instances] and stopping is a useful outcome; quietly baselining them is not.

Report by whatever channel carries the rest of the [Practitioner]'s work: for an [Agent], its
output to whoever instructed it. An [Instance] in code the [Practitioner] is not permitted to
change is reported the same way, as a blocked decision with its count, and is never a reason to
narrow the [Rule] so that it stops seeing it.

**The [Instance] count alone never triggers [Escalation].** A large [Sweep] is work, not a blocked
decision. Stopping to report is what the [Practitioner] does when an [Owner] decision is genuinely
required, which means an [Exception] is being contemplated, or when they are uncertain whether an
exclusion carries the [Hazard]. It is not what they do because the number is uncomfortable. Five
hundred [Instances] with no [Exception] needed is five hundred [Instances] to fix.

A remediation MAY be delivered across several changes where the volume warrants it, **provided the
[Rule] does not become [Blocking] until every [Instance] is fixed**, since a [Blocking] [Rule] merged over a
codebase that still violates it either fails continuously or has been weakened to avoid doing so.
No [Instance] may be knowingly left unfixed at the end without the [Owner]'s decision.

**Why**: without this, an executing [Agent] either stalls at the first judgement call it cannot
authorise or takes the decision unilaterally, and the second failure is much harder to notice than
the first. Both were observed in cold readings of an earlier draft of this specification.

## 5. Ordering the quality checks

Defence Before Fix presumes quality checks ordered as follows, and the ordering is enforcing rather
than advisory:

1. [Detectors] - type checking, linting, custom [Rules]
2. Automated tests - unit, integration, functional
3. Build verification - services start, dependencies resolve
4. Human acceptance testing - visual review, workflow validation

[Detectors] MUST run first, and a failure at that level MUST stop the levels below it from
being treated as meaningful.

This constrains the **sequence of checks**, not the infrastructure that runs them. How the project
expresses that sequence is out of scope, per section 8. It is a property of the project, not a step
in any one remediation: a [Practitioner] handling a [Defect] is not required to reorder the
project's checks, and a project whose checks run in another order is a project that does not
[Conform], which is a finding to record, not work to do on the way to a fix.

**Why**: a [Detector] is preventive and a test is diagnostic. A [Detector] reads every file, every
time it runs, so it cannot miss a file merely because nobody thought to write a test for it.
Failures at a lower level also produce confusing results at the levels above, so any other order
costs time diagnosing symptoms of a problem the first level would have named directly.

## 6. Relationship to test-driven development

Defence Before Fix neither replaces nor competes with TDD. They operate at different levels, and
a full remediation uses both: a test, executed by a [Runner], pins the [Instance]; a [Rule], evaluated by
a [Detector], catches the [Class].

| Concern            | TDD                                | Defence Before Fix                           |
| ------------------ | ---------------------------------- | -------------------------------------------- |
| Operates on        | The specific [Defect]              | The [Class] the [Defect] belongs to          |
| Artefact           | A test                             | A [Rule] in a [Detector]                     |
| Proves correctness | For one behaviour, by executing it | For a pattern, by reading the code           |
| Answers            | "Is this bug fixed?"               | "Can this kind of bug still exist anywhere?" |

The specific [Defect] is still reproduced with a test and proven fixed, exactly as normal. What
this method adds is the phase before that work begins, in which the codebase's permanent [Defence]
[Coverage] is extended using the evidence the [Defect] has just supplied.

The name records the ordering. The [Defence] comes first, because once the fix has landed the
evidence is gone and the opportunity closes with it.

## 7. Conformance

[Conformance] is claimed at one of four levels. Partial [Conformance] MUST NOT be described as
[Conformance].

**A remediation [Conforms]** if all six clauses of section 3 were followed for that [Defect], and
the verdict that they were rests on reproduction, as this section closes by requiring, not on the
report.

**A [Defence] [Conforms]** if it satisfies each of clauses 3.1, 3.2, 3.3, 3.5 and 3.6 in full.
In brief: it is drawn to a
[Class] within both bounds and not to the reported [Instance], it is evaluated by reading code, in
the kind of [Detector] clause 3.2 requires and with the [Detector], its kind and any grounds
recorded as that clause requires, it was proven to fire against either the originating
[Instance] or [Fixture] code, it fails rather
than warns, and its [Message] is terse and resolves to [Remediation docs] versioned alongside
it. A [Defence] can [Conform] whilst the [Sweep] is incomplete; what that describes is a
[Conforming] [Defence] over a codebase with known [Instances], which section 4 requires to be
recorded, not a [Conforming] remediation.

**A [Toolchain] [Conforms]** if it permits bespoke custom [Rules], supports the ordering in section 5,
and satisfies clauses 8.1 to 8.5 and 8.7: the [Practitioner] can run it, the result reaches them in
the output they are reading, [Identifiers] resolve mechanically, documentation states the correct
construction, and both the [Defences] and the project's recorded decisions can be enumerated. Clause
8.6 is a SHOULD and does not bear on [Conformance], deliberately: it is the one clause that acts
before a mistake rather than after, so no failing run can prove it absent, and [Conformance] is
claimed only over what a run can prove. The [toolchain specification](TOOLING-SPEC.md) names a
[Toolchain] that also satisfies it as [Conforming] with [Agent] support, which is the claim to
make when it is true. Those obligations fall in two documents: what each [Detector] the
[Toolchain] routes a [Defence] through must offer is stated in the
[detector specification](DETECTOR-SPEC.md), and what the assembled [Toolchain] must add is stated
in the [toolchain specification](TOOLING-SPEC.md), which requires the first as its own opening clause,
carries the ordering in section 5 and the correct construction of clause 8.4 as clauses of its own,
and states the listing and the project record that clauses 8.5 and 8.7 require.

**A project [Conforms]** if its [Defences] and [Remediation docs] [Conform], and if its recorded
decisions under section 3 are discoverable. Nothing here constrains how the project runs its checks
or what it does when they fail.

[Toolchain] [Conformance] is stated in full in the [detector specification](DETECTOR-SPEC.md)
and the [toolchain specification](TOOLING-SPEC.md) together, which give each of the obligations above as a
clause with its own reasoning, and add what a [Detector] and a [Toolchain] must provide so
that a project can meet its own. The summary here is normative and sufficient to judge a
[Toolchain] by; the companion documents are where a [Detector] maintainer and a
[Toolchain] author should work from.

A verdict on a remediation or a [Defence] MUST rest on reproduction, not on the report: the reviewer
reruns the [Defence] red at the commit that introduced it and green at the final commit, through the
project's own entry point for accepting changes, and reruns the originating symptom against the fix.
Where the [Rule] was proven against a [Fixture] under clause 3.3, the red run is against the
retained [Fixture]. Where the originating symptom cannot be reproduced through the entry point,
an incident or an observation at production scale, the reviewer says so and verifies the red and green
runs alone. A verdict on a [Toolchain] or a project rests on the reviewer exercising the clauses
named above, not on the claimant's report of having done so. A report that reads as [Conforming]
has not been shown to be.

A [Toolchain] MAY additionally audit its own [Rules] against clause 3.6, failing its own release if any
[Rule] fails something without resolving to [Remediation docs]. This is the strongest
available demonstration that the clause is honoured rather than asserted.

## 8. Operating a defence under AI-assisted development

*This section is normative.*

### The argument these clauses rest on

For a human developer, a failure [Message] that teaches is good practice. They may read it, may
internalise it, may ignore it, and which of those happens depends on their seniority, their
workload and how many times they have seen the [Message] before.

For an [Agent], the failure [Message] is the entire remediation loop. It is consumed as instruction,
in the same turn, every time, with no fatigue and no seniority gradient. A [Message] that resolves
to documentation explaining the correct approach does not simply block the [Agent], it redirects it,
and it does so identically on the thousandth occurrence as on the first.

That reframes the usual complaint about AI-written code. The difficulty was never that [Agents] make
mistakes, since people do too. The difficulty is that nobody built the channel to correct them at
the level of the [Class] rather than the [Instance].

If the value of the method depends on that loop closing, then what closing it requires has to be
stated rather than assumed. The clauses below are what a [Defence] must do to be usable by an [Agent]
at all.

### Scope: this specifies a defence, not a pipeline

**Continuous integration, git hooks, branch policy, review process and release management are out
of scope.** How a project chooses to run its quality checks, and what it does when they fail, is
the project's own business and no part of this specification.

What is in scope is the [Defence] itself: a [Rule] that reads code, the documentation that explains it,
and the requirement that the [Practitioner] can run it and act on the result. A specification that
told projects how to run their checks would be overreaching, and would be ignored for it.

### 8.1 The practitioner MUST be able to run the defence themselves

A [Rule] that only reports through infrastructure the [Practitioner] cannot invoke is not usable by
them, whatever it does for anyone else.

**Why**: an [Agent] that cannot check its own work against a [Defence] cannot iterate against it, so
the loop never closes in the turn where the mistake was made, which is the only moment it is cheap
to fix.

### 8.2 The result MUST reach the practitioner in the output they are already reading

The [Detector]'s own output, at the point of the work. Not exclusively a dashboard, a report artefact or
a summary elsewhere.

**Why**: a [Message] that teaches nobody, because nobody sees it, is the same as no [Message].

### 8.3 The identifier MUST resolve without a human

By a command the [Practitioner] can run, a file they can read, or a URL they can fetch.

**Why**: clause 3.6 requires the [Identifier] to resolve. This requires it to resolve *for the
reader*, which for an [Agent] means mechanically. An explanation that lives in a colleague's head
resolves for nobody at three in the morning either.

### 8.4 Documentation MUST state the correct construction, not only the prohibition

Explaining why the pattern is dangerous is not sufficient. The documentation has to show what to do
instead, specifically enough to act on.

**Why**: this is the difference between [Blocking] an [Agent] and redirecting it. A prohibition alone
leaves it to guess at the replacement, and it will guess.

### 8.5 A project's defences MUST be enumerable

A [Practitioner] MUST be able to list what defends this codebase, and read each [Defence]'s
documentation, without triggering it first.

**Why**: an [Agent] arriving at a codebase has no colleague to ask and no memory of last time.
Without this, a project's standards can only be learned by violating them one at a time.

### 8.6 A project SHOULD publish a summary of its defences suitable for an agent's context

One terse line per [Defence], stating the [Rule] as a standing instruction rather than as a failure
report, each linked to its full documentation. *"Error hiding is forbidden"* is the shape.

**Why**: everything else in this method operates after the mistake. This operates before it. A
summary small enough to sit in an [Agent]'s working context turns the accumulated [Defences] from a
series of ambushes into a description of how this project expects code to be written, and it costs
one table.

### 8.7 Recorded project decisions MUST be discoverable by the same means

The judgements section 3 delegates to project level, once agreed and written down, MUST be
reachable exactly as the [Defences] are.

**Why**: section 3 says a project's accumulated decisions become part of its [Coverage]. [Coverage]
nobody can find is not [Coverage], and every [Practitioner] who arrives after a decision will otherwise
re-open it.

## 9. Citation

> Edmonds, Joseph. *Defence Before Fix*, version 1.2.0-dev. First published 22 February 2026.
> <https://ltscommerce.dev>

## Appendix A: Instructing an agent

Where an [Agent] is expected to follow this method, give it the clauses rather than the article.
This appendix restates sections 2 to 4; where the two differ, the sections govern.

> When you find a [Defect] of any kind, do not fix it yet.
>
> First work out what [Class] it belongs to: the pattern, style, idiom or configuration that allowed
> it. Do not decide in advance whether that is possible, attempt it. If you cannot write a [Rule]
> for it, say so and fix the [Defect] conventionally.
>
> Write a custom [Rule] that detects the [Class], in a tool that reads code rather than running it.
> Put it in a [Semantic] [Detector], one in which a [Rule] can ask the type of any expression and
> what any name refers to, across the whole program; failing that, one that sees syntax; and only
> then one that sees text. Use a weaker one only where the stronger one cannot express the [Rule],
> or the language has none that takes custom [Rules]: none the project runs, in any configuration
> it can use without a hosted service or a licence tier, and none its ecosystem offers without
> one either. Name the [Detector] and its kind in the [Rule]'s documentation. Where you use a weaker one,
> write down which of those two grounds holds, and why, naming the stronger [Detectors] you
> considered, there and in the description of the commit that adds the [Rule], moves it to another
> [Detector], or first records the grounds, and where the project requires its [Owner]'s sign-off
> on a weaker one, get it and record it with the grounds. Draw
> the [Rule] so that it catches more than the single [Instance] you started from, but never so
> broadly that it matches code which does not carry the [Hazard].
>
> Do not trust the [Rule] as your only way of finding [Instances]. Search independently as well, by
> text search and by reading the code, and check the [Rule] catches what you found by hand. Make that
> search a thorough one rather than a gesture, because everything downstream rests on it. If your
> own search turns up [Instances] the [Rule] missed, widen the [Rule] until it catches them; the search
> wins, not the [Rule]. If it turns up nothing new, then one [Instance] is a supported conclusion rather
> than a guess.
>
> The [Hazard] is whatever harm the [Class] does, and it need not be a failure. Error hiding counts.
> So does something merely sloppy that makes the code harder to reason about safely.
>
> Prove the [Rule] fires before you trust it. It must catch the originating [Defect]. If the pattern
> is not present in the codebase, because it was already fixed or because you are defending
> against it pre-emptively, prove the [Rule] against [Fixture] code that demonstrates the pattern and
> keep that [Fixture] as the [Rule]'s test.
>
> Then run the [Rule] everywhere the pattern can occur, which usually means one language but never
> just the component the bug was reported in, and report the [Instance] count. Catching far more
> [Instances] than you expected is success, not evidence the [Rule] is too broad. Only ever narrow a
> [Rule] to exclude code that does not carry the [Hazard]; never to make the number smaller.
>
> Fix every [Instance]. Examine each one; where the same answer is genuinely right for all of them,
> applying it to all of them is correct. Never satisfy the [Rule] whilst leaving the [Hazard] in place,
> and never suppress the [Rule] at the call site.
>
> You do not have the authority to [Baseline], to suppress an [Instance], or to leave a known [Instance]
> unfixed, however large the count turns out to be. Those belong to whoever owns the codebase, and
> the standing answer is no unless a human has already agreed and documented an [Exception] for this
> project. If you hit one, finish everything else, then report the count and what fixing it would
> take, and leave the [Rule] unmerged rather than merging it weakened.
>
> A [Defect] you find and do not fix now is recorded where the project keeps its other decisions,
> naming the [Class] if you can already see it. Mentioning it in your output is not a record.
>
> If you are unsure whether code you want to exclude carries the [Hazard], you are suppressing it, so
> refer it upwards rather than deciding. Narrow only where you are confident.
>
> Otherwise, just do the work. Fixing [Instances] is cheap for you, and reaching for an [Exception] is
> almost always a shortcut rather than a real obstacle.
>
> Make the [Rule] a permanent part of the project's quality checks, failing rather than [Warning]. Write
> its failure [Message] terse, carrying a stable [Identifier] that resolves to documentation shipped
> with the project saying what the [Rule] is about, why it exists and how to fix a violation
> correctly. Check you can run the [Rule] yourself and read its output, because if you cannot, nor can
> the next [Agent].
>
> Only then fix the original [Defect] in the normal way, with a test that reproduces it.

## Changelog

| Version | Date       | Change                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1.0.0   | 2026-09-08 | Initial specification, formalising the method published on 22 February 2026. Revised before publication after three independent cold readers understood the method correctly and still could not execute its judgement calls.                                                                                                                                                                                                                                                                                                                                                        |
| 1.0.1   | 2026-09-08 | Clarity, no obligation changed: section 3 opens with a map of the six clauses; clause 3.1 opens with its five steps and closes with what it leaves on the record; clause 3.3 is in three named parts and its [Narrowing] part opens with the decision. The header and the terminology entries for [Detector], [Toolchain] and [Conform] name the [detector specification](DETECTOR-SPEC.md) 1.0.0 alongside the [toolchain specification](TOOLING-SPEC.md) 0.2.0, and [Conform] extends to the companion specification being claimed. Accepted under [ACCEPTANCE.md](ACCEPTANCE.md). |
| 1.1.0   | 2026-10-02 | New obligation: a [Defect] whose fix is deferred is recorded where the project's other decisions are enumerable under clause 8.7, naming the [Class] where apparent; whether it stays unfixed is the [Owner]'s decision under section 4, and a mention in conversation is not a record. Appendix A carries the sentence for an [Agent]. Section 7's definition of a [Conforming] remediation names reproduction in the same sentence. Accepted under [ACCEPTANCE.md](ACCEPTANCE.md).                                                                                                 |

<!-- Term link definitions -->

[agent]: #agent
[agents]: #agent
[baseline]: #baseline
[baselines]: #baseline
[blocking]: #blocking
[bundled defence]: TOOLING-SPEC.md#bundled-defence
[calibration]: #calibration
[calibrations]: #calibration
[class]: #class
[classes]: #class
[conform]: #conform
[conformance]: #conform
[conforming]: #conform
[conforms]: #conform
[consuming project]: TOOLING-SPEC.md#consuming-project
[coverage]: #coverage
[dbf]: #dbf
[defect]: #defect
[defects]: #defect
[defence]: #defence
[defences]: #defence
[detector]: #detector
[detectors]: #detector
[escalation]: #escalation
[exception]: #exception
[exceptions]: #exception
[false positive]: #false-positive
[false positives]: #false-positive
[fixture]: #fixture
[fixtures]: #fixture
[hazard]: #hazard
[hazards]: #hazard
[identifier]: #identifier
[identifiers]: #identifier
[instance]: #instance
[instances]: #instance
[message]: #message
[narrowing]: #narrowing
[owner]: #owner
[practitioner]: #practitioner
[remediation docs]: #remediation-docs
[rule]: #rule
[rules]: #rule
[runner]: #runner
[runners]: #runner
[semantic]: #semantic
[suppress]: #suppression
[suppression]: #suppression
[suppressions]: #suppression
[sweep]: #sweep
[syntactic]: #syntactic
[textual]: #textual
[toolchain]: #toolchain
[warning]: #warning

===== FILE: fixtures/remediation-ledger.md =====
# Fixture: the ledger remediation

A fictional remediation, described as the pull request and its commits would show it. Grade it
against the document you were given. Where the description does not say, answer from what is
written.

## What the pull request shows

A defect: `Ledger::total()` silently returned zero when a currency conversion failed, because the
conversion call's error was caught and the empty result treated as a valid amount.

The practitioner, a coding agent, did the following, in this order, over five commits.

1. **Commit A.** Wrote a note in the pull request naming the class as "a fallible call whose
   error is caught and whose result is then used as if the call had succeeded", stating that
   the hazard is silent wrong output rather than a crash, and naming the next wider class it
   did not build, "any caught error that is not rethrown or reported", as not attempted because
   the codebase has hundreds of such catches and most are legitimate.
2. **Commit B.** Wrote a rule in the project's static analyser, identifier `proj.caught-then-used`,
   that flags a caught error followed by a use of the call's result in the same function. Added
   the rule to the project's analyser configuration. The commit also contains a fixture file
   reproducing the `Ledger::total()` shape, and the CI run for this commit is red on that rule
   with five findings: the fixture, `Ledger::total()`, and three other functions.
3. **Commit C.** Fixed `Ledger::total()` and two of the three others, so that the failure is
   propagated. For the third, `Report::render()`, the agent added an inline `// ignore proj.caught-then-used` comment with the note "rendering must never throw", and did not raise
   it with anyone. The CI run is green.
4. **Commit D.** Wrote the rule's documentation as a page on the company wiki, at a URL the rule's
   failure message prints in full. The page states what the rule forbids and why, and shows the
   propagating construction to write instead. The failure message is one line: the identifier,
   the file and line, and the wiki URL.
5. **Commit E.** Confirmed in the pull request description that the rule is set to fail the
   build, not warn, and that the whole quality invocation was run locally and passed.

The pull request was merged with a merge commit; all five commits remain in the history.

The project's recorded decisions say nothing about inline ignores. The project has a named owner.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 071: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Command and working directory

ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt, each reader in a fresh empty directory made by mktemp -d outside the repository

Model version each reader reported

reader 1: claude-haiku-5-5
reader 2: claude-haiku-5-5
reader 3: claude-haiku-5-5
reader 4: claude-haiku-5-5
reader 5: claude-haiku-5-5

The delegated marker's task prompt

MODE: mark

Worktree: <worktree> (branch `editor/semantic-detectors`). The readers read commit befe52c. Read, at that commit and in that worktree: ACCEPTANCE.md in full (it is the process as pull request 22 proposes it, merged into this branch; the run is made under that process change before it merges, at commit f3ec69e, named here before the readers started), acceptance/runs/README.md, acceptance/detector/QUESTIONS.md, acceptance/detector/KEY.md, acceptance/fixtures/lintomatic.md and DETECTOR-SPEC.md. You may read acceptance/runs/066-detector-route-rule-run-3.md for the shape of a record only; ACCEPTANCE.md says a judgement an earlier run recorded is not a rule. Do not take any judgement from the records of runs 19 to 30, which were made under the earlier rules on an earlier text of this change.

Run: 071. Document: DETECTOR-SPEC.md. Change: semantic detectors (pull request 14): a new clause 8.2 that a verdict on a detector names its kind, semantic, syntactic or textual, in each configuration it grades; section 9's sentence on that obligation; section 2's table rows for the three kinds and for Message. Pull request: 14, open on this branch; runs 20, 22, 24, 26, 28 and 30 read this document on an earlier, larger text of the change under the earlier acceptance rules. Run within the pull request: 14 (runs 19 to 30 and run 70 came before). Record file to write: <worktree>/acceptance/runs/071-semantic-detectors-run-14.md. Do not commit it.

Sheets: <run-dir>/sheet-1.md to sheet-5.md, each the `result` of reader-N.json. Prompt the readers received: <run-dir>/prompt.txt; its sha256 is d45ab8c1ff1dc2ecabd1bad36eca32c3b0858327c5079cd4b6aea2abcb4e5cc3, and it is byte for byte the question set, DETECTOR-SPEC.md and the fixture at befe52c, each after one `===== FILE: <name> =====` line, one blank line between files. Model: every reader reported claude-haiku-5-5 (<run-dir>/models.txt); the cohort's model named for the run is claude-haiku-5-5. Every reader's JSON has is_error false and subtype success; no reader is set aside.

Facts the editor supplies for the record: cohort of five readers, none reused from any earlier run; each a separate `claude -p` process started through the `ccy-claude` wrapper (which only reattaches the session's credential) in a fresh empty directory made by `mktemp -d` outside any repository, with the packet on standard input, command `ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt`; no tools, no settings, no project context, no saved session; the machine applies no managed settings; the editor attests the rest of the isolation. Marked by a delegated marker, a fresh `dbf-spec-editor` agent (Claude Opus) given this instruction and its agent definition, having seen neither the drafting nor the review.

Text the change wrote or altered, for "Where a finding lies": the output of `git -C <worktree> diff 9e914fe befe52c -- DETECTOR-SPEC.md` (9e914fe is the merge base with `next`). Run it and read it. A finding in a paragraph that diff adds, rewords, moves, deletes a sentence from, or changes a word, term or link in, or that a reader sets against such a paragraph, blocks; any other finding is routed to acceptance/ROUTED.md, and the record says so for each.

Mark every sheet, keyed item by keyed item, by ACCEPTANCE.md's Marking section and the key alone. Write the record stating what acceptance/runs/README.md lists, in its order, with the marks table, each miss with the reader's reasoning in brief, every passage named under question 4 with its words or description, clause and readers, each finding and where it lies, any open point and the direction you marked it, the process-change commit f3ec69e, the verdict, and a placeholder line for the link to the posted sheets. Reply with the verdict and the findings only, under thirty lines.

The agent definition the marker ran under

---
name: dbf-spec-editor
description: Drafts, reviews, key-updates, marks or records a change to the Defence Before Fix specification, one mode per dispatch, following the specification repository's CONTRIBUTING.md, SCOPE.md and ACCEPTANCE.md exactly. Dispatched by the dbf-spec-change skill; it cannot run a cohort itself.
tools: Read, Write, Edit, Grep, Glob, Bash
model: opus
---

You work on one change to the Defence Before Fix specification, in the worktree path you are
given. The process is defined by that repository's `CONTRIBUTING.md`, `SCOPE.md`,
`ACCEPTANCE.md` and `acceptance/README.md`; read the ones your mode needs, in full, before you
act. You invent no steps. You never merge, push to `next` or `main`, release, tag or publish,
and you never change a question, threshold or fixture. Your prompt names one mode.

Write as the specification is written: British spelling, no em dashes, defined terms linked as
`spec-qa.py` requires, every clause or section number linked to its heading. Commit on the
worktree's branch.

## MODE: draft

1. Read `SCOPE.md`, `DECLINED.md`, and every clause the change touches, with the terminology
   entries they use.
2. Decide the kind. A change to the sentences carrying MUST, SHOULD or MAY in a numbered section
   or appendix is an obligation; anything else is a clarity edit.
3. For an obligation, answer the five SCOPE tests in a sentence or two each, and name the
   section the obligation extends. If a test fails, or an existing clause already covers it, say
   so and stop without editing.
4. Make the smallest edit that does the job, in the clause that owns the subject.
5. Add one line under `Unreleased` in `CHANGELOG.md`, saying what changed and why, with a
   placeholder `run NNN`. Change no version.
6. If Appendix A changed, run `python3 build-agent-surfaces.py` and commit its outputs.
7. Run `python3 spec-qa.py` and `python3 -m unittest discover -s tools -p 'test_*.py'`; both
   clean.
8. Reply with: kind, SCOPE answers, files changed, commit, QA result, and a filled
   `.github/PULL_REQUEST_TEMPLATE.md` body. Under forty lines.

## MODE: review

You have not seen the drafting. Read the whole changed document and the diff against
`origin/next`. Report, numbered, with clause numbers: contradictions with other clauses, terms
used outside their definition, a sentence a reader could take two ways, an obligation an owner
could not verify from the record. Do not edit.

## MODE: keys

Read `acceptance/README.md` and the `KEY.md` for each changed document. Update every quotation of
a changed sentence to the new wording, verbatim, in a new commit; run `python3 spec-qa.py` until
`key-quote-stale` is gone. If the change adds a load-bearing obligation no keyed question
covers, propose the question and its key in your reply; add it only if your prompt says the owner
has decided to.

## MODE: mark

You have not seen the drafting or the review. Read `ACCEPTANCE.md` in full, the `KEY.md` for the
document, the document at the commit named, and the sheets under `<run-dir>/`.

- Mark each sheet against the key, keyed item by keyed item, by the rules under "Marking" in
  `ACCEPTANCE.md` and nothing else: the key's required elements, accepted grades, conditions and
  wrong readings. Invent no judgement; where neither the rules nor the key settles a mark, mark
  it in the direction the open-point rule gives and record the point.
- A single miss under questions 1 and 3 counts against the document only where the key names
  that reading; say which.
- For question 4, record every passage named, with its words or description and its clause.
  Group the passages by paragraph, count the readers, and for each finding say whether it lies in
  text the change wrote or altered, read against the diff the prompt gives you, or is routed to
  `acceptance/ROUTED.md`. Ignore any suggestion a reader volunteers.
- Apply the pass criteria exactly.
- Write `acceptance/runs/NNN-<slug>-run-<k>.md` stating what `acceptance/runs/README.md` lists,
  in its order. Do not commit it. Reply with the verdict and the findings only.

## MODE: record

Commit the run record, replace the `run NNN` placeholder in `Unreleased` with a link to it, and
for an obligation set the document header to `<next version>-dev, unpublished` in the same
commit if it still names a published version. Run `python3 spec-qa.py` and
`python3 tools/changes.py --base origin/next`; both clean. Reply with the commit.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 071: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Answer sheets, each reader's whole output as its process returned it

reader-1

{"duration_api_ms":45373,"stop_reason":"end_turn","session_id":"41c775f3-3a34-41ac-8e35-95bedf5f7ca7","total_cost_usd":0.007737029999999999,"usage":{"input_tokens":2,"cache_creation_input_tokens":11569,"cache_read_input_tokens":2153,"output_tokens":10803,"output_tokens_details":{"thinking_tokens":8677},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":11569,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":10803,"cache_read_input_tokens":2153,"cache_creation_input_tokens":11569,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":11569},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":10803,"cacheReadInputTokens":2153,"cacheCreationInputTokens":11569,"webSearchRequests":0,"costUSD":0.007737029999999999,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":8677,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let a project write its own rules in a form it has not anticipated, and run them with the same standing as bundled rules (4.1).\n- It must provide a harness that runs one rule against supplied code and reports whether that rule fired and what it printed (4.2).\n- Each rule carries a stable identifier, not derived from file path, class name or position, and the detector prints it unaltered with every finding (4.3).\n- The practitioner can run the detector locally, with no infrastructure, on a single file as well as a whole codebase, and sees results in the output of the command they ran. No finding may be reportable only through a hosted service, licence tier or CI-only mode (5.1 to 5.4).\n- Every printed identifier must resolve to documentation. For bundled rules, that documentation must be on disk in the installed copy, shipped with the rule at a matching version, and cover every identifier the rule can print (6.1 to 6.3).\n- An inline suppression route may exist, but the detector must make it either disableable or detectable (7.1).\n\n## 2. Load-bearing statements\n\n**2a.** Conformance means satisfying every MUST in sections 4 to 7. A detector that predates the document, and whose maintainer has never read it, can conform. Section 8.1: \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence by anyone who exercises the clauses above against it.\" The same section makes the declaration optional: \"The declaration is optional. It is how a maintainer claims [Conformance]; it is not a condition of it.\"\n\n**2b.** The detector must do at least one of two things: make the route disableable by configuration, or make it detectable by a rule or a documented mechanical check. Doing one is enough. Section 7.1: \"For each one it MUST do at least one of two things, and either alone satisfies this clause.\" Lintomatic's `ignore_comments = false` is a configuration switch, so it meets the clause by itself.\n\n**2c.** The documentation must ship with the rule, at a version tracked together with it (6.3: \"A [Bundled rule]'s documentation MUST ship with the rule, at a version tracked together.\"). It must be reachable without network access (6.2: \"Resolution of a [Bundled rule]'s [Identifier] MUST work from the installed copy, without network access.\").\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule, bundled or project, on a path, and exits 1/0/2 by outcome. |\n| 4.3 | Yes | Bundled `LM-NNNN` is fixed at first release. Project names come from configuration and are not rewritten. The one-line output prints them. The docs do not say what the HTML report prints, but they name no other machine-readable format. |\n| 5.1 | Yes | \"runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Partial | The one-line stdout is a usable summary. But the HTML detail is written to `.lintomatic/report.html`, and the docs never say stdout gives its location. |\n| 5.4 | Yes | \"every finding it shows is also printed by the command line,\" and CI uses the same binary. |\n| 6.1 | Partial | `explain <identifier>` is keyed on the identifier as printed and works for bundled and project rules. But two of the forty bundled rules print \"No documentation declared\", so for them the identifier does not resolve to documentation. |\n| 6.2 | Yes | `explain` reads \"from the installed package,\" and the docs are \"versioned with the package; there is no separate website.\" |\n| 6.3 | No | \"One [Identifier] that does not resolve fails this clause, however many others do.\" Two bundled identifiers do not resolve. |\n| 7.1 | Yes | `// lintomatic-ignore` can be turned off with `ignore_comments = false`. |\n\n**Conformance under section 8:** Lintomatic does **not** conform. Section 8 requires every MUST in sections 4 to 7 to be satisfied, and 6.3 is a plain No. Two others are Partial (5.3, 6.1), which section 8 also does not count as conformance. Two notes:\n- The verdict does not name Lintomatic's kind (Semantic, Syntactic or Textual) as section 8.2 asks. The fixture does not say what a rule can ask, so I cannot name it. That is a gap in this verdict, not in Lintomatic's conformance.\n- Gaps outside the MUSTs, which do not affect the verdict: 7.2 (SHOULD requiring a reason) is unmet, since the comment \"takes no reason.\"\n\n**Does 6.4 change the verdict?** No. Clause 6.4 is a SHOULD, and section 8 counts only MUSTs. Its absence is why the 6.3 failure went unnoticed, since the repository's CI does not check that each `LM-NNNN` has an `explain` entry. The fixture also doesn't say Lintomatic ships inside a Toolchain. Even if it did, section 9 says the detector and toolchain verdicts are \"separate and neither implies the other,\" so the detector verdict would not change.\n\n## 4. Confusion\n\n- **8.1, declaration optional versus gap record:** \"The declaration is optional\" and \"a [Detector] that has learnt ... that it fails a MUST ... MUST record that gap alongside the version it declares.\" A detector that never declares has no declaration in which to record a gap, so I could not tell whether the MUST then applies.\n- **5.3, \"a usable summary\":** \"the invoked command's own output MUST carry both a usable summary and the location of the remainder.\" I could not tell what makes a summary \"usable,\" and that is exactly what decided my Partial grade for Lintomatic.\n- **Terms defined only in the method specification:** Section 2 says \"Terms from the method specification carry over unchanged,\" and terms like [Class], [Owner], and \"the method specification's clause 3.2\" point to `SPEC.md`, which I was not given. I read those as ordinary English, so my reading of 4.1, 4.4 and 8.2 may not match the intended one.","ttft_ms":37769,"type":"result","duration_ms":45775,"uuid":"b01d9058-66be-4cee-a8bd-56480b14c014","ttft_stream_ms":1181,"time_to_request_ms":406,"first_content_frame_ms":1181,"queued_turn_count":0,"result_index":0}

reader-2

{"duration_api_ms":54144,"stop_reason":"end_turn","session_id":"494a7892-da32-45b6-adff-b08bae7b83f3","total_cost_usd":0.00894803,"usage":{"input_tokens":2,"cache_creation_input_tokens":11569,"cache_read_input_tokens":2153,"output_tokens":13225,"output_tokens_details":{"thinking_tokens":11301},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":11569,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":13225,"cache_read_input_tokens":2153,"cache_creation_input_tokens":11569,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":11569},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":13225,"cacheReadInputTokens":2153,"cacheCreationInputTokens":11569,"webSearchRequests":0,"costUSD":0.00894803,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":11301,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project write its own rules and run them with the same standing as bundled rules (§4.1).\n- It must provide a harness that runs one rule against supplied code, whether the rule is bundled or the project's own (§4.2).\n- Every rule carries a stable identifier, not derived from its path, class name or position, and the detector prints it unaltered with every finding in its default output and every machine-readable format (§4.3).\n- The detector runs locally with no infrastructure, including on a single file, and its results appear in the command's own output. No finding may be reportable only through a hosted service, licence tier or CI-only mode (§5.1–5.4).\n- A printed identifier must resolve to its documentation. For bundled rules, that means from the installed copy, offline, keyed on the identifier alone, with every printable identifier documented (§6.1–6.3). For project rules, the detector prints the identifier unaltered (§6.1).\n- An inline suppression route must be disableable or detectable (§7.1), and should require a reason (§7.2). Conformance means every MUST in sections 4 to 7 is met (§8).\n\n## 2. Load-bearing statements\n\n**2a.** Conforming means satisfying every MUST in sections 4 to 7. A detector written before the document can conform, because the declaration is optional: \"A Detector that predates this document, or whose maintainer has never read it, MAY be graded Conforming on evidence by anyone who exercises the clauses above against it\" (§8.1). Also: \"A Detector Conforms if it satisfies every MUST in sections 4 to 7\" (§8).\n\n**2b.** The detector must make the route either disableable by configuration or detectable. One of these is enough: \"For each one it MUST do at least one of two things, and either alone satisfies this clause\" (§7.1). \"A route that can be neither switched off nor seen does not Conform\" (§7.1).\n\n**2c.** The documentation must ship with the installed rule, at a version tracked together with it, and must resolve without network access. \"A Bundled rule's documentation MUST ship with the rule, at a version tracked together\" (§6.3). \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access\" (§6.2). A website does not satisfy this: \"A catalogue on the Detector's website, however complete, is the right documentation in the wrong place\" (§6.2, Why).\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, and \"first class in every report,\" so they are not limited to a fixed catalogue. |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule against a path, accepts bundled or project rules, and exits 1 when it fires. The text does not explicitly say `--only` prints the Message, though it is a form of `check`, which prints one line per finding. |\n| 4.3 | Yes | Bundled identifiers are fixed at first release, project identifiers are the names given in config, and \"Lintomatic does not add or rewrite any part of the name.\" The one-line output prints them. No machine-readable format is described, so the \"every machine-readable format\" limb has nothing to apply to. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Partial | The one-line summary is printed to standard output, but the text does not say that output gives the location of `.lintomatic/report.html`, which holds the fuller detail. |\n| 5.4 | Yes | The hosted dashboard shows nothing that is not \"also printed by the command line.\" |\n| 6.1 | Partial | `lintomatic explain <identifier>` resolves an identifier presented alone, but two of the forty bundled rules print \"No documentation declared,\" so for those identifiers the mechanism resolves to nothing. |\n| 6.2 | Yes | `explain` prints \"the documentation for a bundled rule from the installed package,\" and \"there is no separate website.\" |\n| 6.3 | No | Documentation is \"versioned with the package,\" but two bundled identifiers have no page. \"One Identifier that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `ignore_comments = false` disables `// lintomatic-ignore` project-wide, which meets the disableable limb on its own. |\n\n**Conformance under §8: No.** Conformance requires every MUST in sections 4 to 7 to be met. Lintomatic fails 6.3 outright, and 5.3 and 6.1 are only partial. Partial conformance must not be described as conformance (§8).\n\nThe fixture does not state the detector's kind (Semantic, Syntactic or Textual), so this verdict cannot name one as §8.2 requires. That gap does not affect the finding, which stands regardless (§8.2).\n\n**Does 6.4 change the verdict? No.** Clause 6.4 is a SHOULD, and §8 counts only MUSTs. The fixture says Lintomatic's repository does not check that every identifier has an `explain` entry, so the release check that would have caught the 6.3 failure is absent. That is a separate gap and leaves the MUST failure where it was.\n\n## 4. Confusion\n\n- §7.1: \"or make the route detectable, by a [Rule] the project can write in the [Detector] itself.\" I could not tell whether this rule is one the project adds to the detector's own codebase or one it adds to its configuration.\n- §8.2: \"the same configurations the method specification's clause 3.2 counts when it asks for the strongest kind.\" This refers to the method specification, which I was not given, so I could not check which configurations it means.","ttft_ms":48170,"type":"result","duration_ms":54442,"uuid":"23fc3edd-5a03-450f-8182-31e58e73dbdb","ttft_stream_ms":955,"time_to_request_ms":295,"first_content_frame_ms":955,"queued_turn_count":0,"result_index":0}

reader-3

{"duration_api_ms":54324,"stop_reason":"end_turn","session_id":"4e8238d2-7777-4309-a056-b62c29948b85","total_cost_usd":0.00916033,"usage":{"input_tokens":2,"cache_creation_input_tokens":11568,"cache_read_input_tokens":2153,"output_tokens":13650,"output_tokens_details":{"thinking_tokens":11470},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":11568,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":13650,"cache_read_input_tokens":2153,"cache_creation_input_tokens":11568,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":11568},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":13650,"cacheReadInputTokens":2153,"cacheCreationInputTokens":11568,"webSearchRequests":0,"costUSD":0.00916033,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":11470,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project write its own rules, register them, and run them with the same standing as bundled rules (4.1).\n- It must provide a harness that runs one rule against supplied code and reports whether it fired and what it printed (4.2).\n- Every rule carries a stable identifier, not derived from its file path, class name, or position. The detector prints it unaltered with every finding (4.3).\n- The detector runs locally with no infrastructure, supports at least single-file invocation, and puts results in the output of the command the practitioner ran. No finding may be reportable only through a hosted service, licence tier, or CI-only mode (5.1–5.4).\n- A printed identifier must resolve to its documentation. For bundled rules the detector ships that documentation, works offline from the installed copy, and every identifier it can print must resolve (6.1–6.3).\n- Inline suppression may be offered, but it must be switchable off or detectable (7.1).\n- A detector conforms only if it meets every MUST in sections 4 to 7. Partial conformance is not conformance (8).\n\n## 2. Load-bearing statements\n\n**2a.** Conformance means satisfying every MUST in sections 4 to 7. A detector that predates the document can conform. Section 8.1 says: \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence by anyone who exercises the clauses above against it.\" It adds: \"The declaration is optional. It is how a maintainer claims [Conformance]; it is not a condition of it.\" Section 8 gives the test: \"**A [Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.\"\n\n**2b.** The detector must do at least one of two things: make the route disableable by configuration, or make it detectable. Doing one is enough. Clause 7.1 says: \"For each one it MUST do at least one of two things, and either alone satisfies this clause.\" It also says: \"A route that can be neither switched off nor seen does not [Conform].\" Reasons for a suppression comment are only a SHOULD, under 7.2.\n\n**2c.** The documentation must ship with the rule, in the installed package, at a version tracked together with it. It must be reachable without network access. Clause 6.3 says: \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\" Clause 6.2 says resolution \"MUST work from the installed copy, without network access.\" Clause 6.2's rationale says a website catalogue is \"the right documentation in the wrong place.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | A project writes a Zig module implementing `Rule` and lists it in `lintomatic.toml`. Project rules are \"first class in every report,\" and `--only` accepts them. |\n| 4.2 | Yes | `check --only <identifier> <path>` runs one rule on a path, and exit codes 1, 0 and 2 report fired, not fired, and unknown. I read \"what it printed\" as the per-finding output of `check`, since the fixture does not describe `--only` output separately. |\n| 4.3 | Yes | `LM-NNNN` is \"fixed at the rule's first release,\" and project names are chosen in config with \"Lintomatic does not add or rewrite any part of the name.\" The one-line output prints identifiers as-is. The fixture names no other machine-readable format and does not say whether the HTML report prints identifiers. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` analyses \"a directory or a single file.\" |\n| 5.3 | Partial | The stdout output gives a usable summary, one line per finding. Fuller detail goes to `.lintomatic/report.html`, but the command's output as described does not give the location of that file. The clause requires both. |\n| 5.4 | Yes | The dashboard is sold separately, but \"every finding it shows is also printed by the command line,\" and CI runs the same binary. |\n| 6.1 | Partial | `lintomatic explain <identifier>` is keyed on the identifier alone and reads the installed package, so the mechanism exists. But two of the forty bundled rules print \"No documentation declared,\" so for those two identifiers there is no documentation to resolve to. |\n| 6.2 | Yes | \"The documentation is versioned with the package; there is no separate website,\" and `explain` reads from the installed package. |\n| 6.3 | No | Every identifier a bundled rule can print must resolve to a shipped page. Two do not. Clause 6.3 says \"One [Identifier] that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `ignore_comments = false` in `lintomatic.toml` disables the comment route project-wide, which is \"disableable by configuration.\" |\n\n**Conformance.** Lintomatic does not conform. Clause 6.3 fails outright, and 5.3 and 6.1 are partial. Section 8 requires every MUST in sections 4 to 7 to be met, and partial conformance \"MUST NOT be described as [Conformance].\"\n\nClause 8.2 also requires a verdict to name the detector's kind, Semantic, Syntactic or Textual, in each configuration it grades. The fixture does not state a kind, so I cannot name one from what is written. This verdict therefore does not meet 8.2. Per 8.2, \"what it finds about [Conformance] still stands.\"\n\nI graded only against the two documents supplied. The spec cross-references `SPEC.md` and `TOOLING-SPEC.md`, which I did not have.\n\n**Effect of 6.4.** It does not change the verdict. Clause 6.4 is a SHOULD, and section 8 counts only MUSTs. Lintomatic's repository does not check that every `LM-NNNN` identifier has an `explain` entry, so 6.4 is not met. That gap is also why the 6.3 failure went unnoticed. The verdict is No because of 6.3 alone.\n\n## 4. Confusion\n\n- 7.1: \"make the route detectable, by a [Rule] the project can write in the [Detector] itself or by a mechanical check the [Detector] documents.\" I could not tell with confidence whether \"a [Rule] the project can write in the [Detector] itself\" means the bespoke-rule route in 4.1 or something separate.\n- 8.2: \"the same configurations the method specification's clause 3.2 counts when it asks for the strongest kind.\" This points to a document I was not given, so I could not tell which configurations are meant.","ttft_ms":47186,"type":"result","duration_ms":54646,"uuid":"608668f2-5710-455b-a55d-38cdd7d0df83","ttft_stream_ms":1300,"time_to_request_ms":326,"first_content_frame_ms":1300,"queued_turn_count":0,"result_index":0}

reader-4

{"duration_api_ms":57049,"stop_reason":"end_turn","session_id":"f9a792fb-7e16-43b1-9f67-7c95cbd50fe8","total_cost_usd":0.00934483,"usage":{"input_tokens":2,"cache_creation_input_tokens":11568,"cache_read_input_tokens":2153,"output_tokens":14019,"output_tokens_details":{"thinking_tokens":12028},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":11568,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":14019,"cache_read_input_tokens":2153,"cache_creation_input_tokens":11568,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":11568},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":14019,"cacheReadInputTokens":2153,"cacheCreationInputTokens":11568,"webSearchRequests":0,"costUSD":0.00934483,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":12028,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project write its own rules and register them with the same standing as bundled rules (4.1).\n- It must offer a harness that runs one rule against supplied code and reports whether it fired and what it printed (4.2).\n- Each rule's identifier must be stable, chosen once by the rule author, not derived from path, class name or position, and printed unaltered with every finding (4.3).\n- It must run locally with no infrastructure, on a subset down to a single file, with the result in the invoked command's own output. No finding may be reportable only through a hosted service, licence tier or CI-only mode (5.1 to 5.4).\n- It must resolve a printed identifier, presented alone, to its documentation. For bundled rules that documentation ships on disk at the same version and resolves offline (6.1 to 6.3).\n- An inline suppression route must be either disableable by configuration or detectable (7.1). A detector conforms if it meets every MUST in sections 4 to 7 (§8).\n\n## 2. Load-bearing statements\n\n**2a.** Conforming means satisfying every MUST in sections 4 to 7. A detector written before the document can conform, because the declaration is optional and a verdict can rest on evidence. \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence by anyone who exercises the clauses above against it.\" Also: \"The declaration is optional. It is how a maintainer claims [Conformance]; it is not a condition of it.\"\n\n**2b.** The detector must make the route either disableable by configuration or detectable by a rule or a documented mechanical check. Doing one of these is enough: \"For each one it MUST do at least one of two things, and either alone satisfies this clause.\" Also: \"A route that can be neither switched off nor seen does not [Conform].\"\n\n**2c.** The documentation must ship with the rule, at a version tracked with it, and resolution must work offline. \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\" And: \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access.\" The §3 table puts it as \"The lookup, and the documentation, on disk.\"\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n| --- | --- | --- |\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, and are \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` \"runs a single rule\" for bundled or project rules, with exit codes 1, 0 and 2. The fixture does not say `--only` prints the finding lines, so I take \"what it printed\" to be `check`'s one-line stdout. |\n| 4.3 | Yes | Bundled IDs are `LM-NNNN`, \"fixed at the rule's first release.\" Project names are the project's own, and \"Lintomatic does not add or rewrite any part of the name.\" The one-line stdout prints them. The fixture names no other machine-readable format, and the HTML report is not one. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Partial | The one-line summary is on stdout, and fuller detail goes to `.lintomatic/report.html` when enabled. The fixture does not say stdout gives the location of that report, which 5.3 requires. |\n| 5.4 | Yes | The hosted dashboard is extra, and \"every finding it shows is also printed by the command line.\" CI runs the same binary. |\n| 6.1 | Partial | `lintomatic explain <identifier>` resolves 38 of the 40 bundled identifiers. For the other two it prints \"No documentation declared,\" which is not documentation. This is the same defect as 6.3, and I count it there too. |\n| 6.2 | Yes | `explain` reads \"from the installed package,\" and \"there is no separate website.\" |\n| 6.3 | No | \"Two of the forty rules currently print `No documentation declared`.\" I read the forty as the bundled rules, so two bundled identifiers do not resolve. One such identifier fails the clause. |\n| 7.1 | Yes | `ignore_comments = false` disables the `// lintomatic-ignore` route by configuration, which alone satisfies the clause. |\n\n**Conformance:** Lintomatic does not conform. §8 requires every MUST in sections 4 to 7 to be satisfied. 6.3 fails outright, and 5.3 and 6.1 are Partial, which is also not satisfied. §8.2 also requires a verdict to name the detector's kind (Syntactic, Semantic or Textual) for each configuration graded. The fixture does not say which kind Lintomatic is, so I cannot name it from the document and the verdict is incomplete on that point. That does not alter the conformance result. Under 8.2, \"what it finds about [Conformance] still stands.\"\n\n**6.4:** It does not change the verdict. It is a SHOULD, §8 counts only MUSTs, and 6.3 already fails. What it changes is the record. A release check of the kind 6.4 describes would have caught the two undocumented rules, and the fixture says the repository runs no such check. If Lintomatic makes a §8.1 declaration, that gap belongs in its `known-gaps`. Separately, 7.2 (rejecting reasonless suppressions) is a SHOULD and is also unmet here, which does not change the verdict either.\n\n## 4. Confusion\n\nClause 8.1: \"a [Detector] that has learnt, from its own checks or from a [Practitioner]'s report under the method's clause 3.2, that it fails a MUST in sections 4 to 7 MUST record that gap alongside the version it declares\". Read next to \"The declaration is optional\" and \"it is not a condition of it,\" I can't tell whether this MUST binds a detector that makes no declaration.","ttft_ms":51231,"type":"result","duration_ms":57505,"uuid":"c3992305-dfb9-46bd-b435-9b9579814806","ttft_stream_ms":1248,"time_to_request_ms":452,"first_content_frame_ms":1249,"queued_turn_count":0,"result_index":0}

reader-5

{"duration_api_ms":36273,"stop_reason":"end_turn","session_id":"a7976f9f-3a50-4ef7-bbe5-5ab9aeedad96","total_cost_usd":0.0066887299999999995,"usage":{"input_tokens":2,"cache_creation_input_tokens":11570,"cache_read_input_tokens":2153,"output_tokens":8706,"output_tokens_details":{"thinking_tokens":6645},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":11570,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":8706,"cache_read_input_tokens":2153,"cache_creation_input_tokens":11570,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":11570},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":8706,"cacheReadInputTokens":2153,"cacheCreationInputTokens":11570,"webSearchRequests":0,"costUSD":0.0066887299999999995,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":6645,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project that runs it write its own rules, register them, and run them with the same standing as bundled rules (4.1).\n- It must offer a harness that runs one rule against supplied code and reports whether it fired and what it printed, for bundled and project rules alike (4.2).\n- Each rule carries a stable identifier that is not derived from file path, class name or position, and the detector prints it unaltered with every finding (4.3).\n- Practitioners must be able to run the detector locally, with no infrastructure, on a subset down to a single file, and see results in the output of the command they ran. No finding may be available only through a hosted service, licence tier or CI-only mode (5.1–5.4).\n- A printed identifier must resolve to its documentation. For bundled rules, that documentation ships on disk with the rule, at the same version, and resolves without network access (6.1–6.3).\n- Any inline suppression route must be either disableable by configuration or detectable by a rule or documented mechanical check (7.1).\n\n## 2. Load-bearing statements\n\n**2a.** A detector conforms when it satisfies every MUST in sections 4 to 7. A detector written before the document, by a maintainer who has never read it, can conform, because a declaration is optional and the verdict rests on evidence.\n> \"A [Detector] [Conforms] if it satisfies every MUST in sections 4 to 7.\" (§8)\n> \"The declaration is optional. It is how a maintainer claims [Conformance]; it is not a condition of it.\" (§8.1)\n> \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence by anyone who exercises the clauses above against it, and a verdict on any [Detector], declared or not, rests on that exercise and not on the claim.\" (§8.1)\n\n**2b.** The detector must make the route either disableable by configuration or detectable by a rule or documented mechanical check. Doing one of those is enough.\n> \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a [Rule] the project can write in the [Detector] itself or by a mechanical check the [Detector] documents.\" (§7.1)\n\n**2c.** The documentation must live in the installed copy, at the same version as the rule, and resolve with no network access. A website does not satisfy this.\n> \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access\" (§6.2)\n> \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\" (§6.3)\n> \"A catalogue on the [Detector]'s website, however complete, is the right documentation in the wrong place.\" (§6.2)\n\n## 3. Application\n\nLintomatic's verdicts are based only on the fixture.\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, \"loaded on every run and are first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` \"runs a single rule\", accepts \"bundled or project\" rules, and exits 1 on fire, 0 on no fire, 2 on unknown name. |\n| 4.3 | Yes | Bundled identifiers are \"fixed at the rule's first release\", project identifiers come from configuration, and \"Lintomatic does not add or rewrite any part of the name.\" The one-line output prints the identifier. The fixture describes no machine-readable format, so the \"every format\" limb has nothing to fail on. It does not say whether the HTML report prints identifiers. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | \"`lintomatic check <path>` analyses a directory or a single file.\" |\n| 5.3 | Partial | The HTML report is \"fuller detail\" written to `.lintomatic/report.html`, so the clause applies. The stdout example and prose give a one-line summary but never say the command's output carries the report's location. |\n| 5.4 | Yes | \"every finding it shows is also printed by the command line.\" |\n| 6.1 | Yes | `lintomatic explain <identifier>` takes the identifier alone and prints bundled-rule documentation. |\n| 6.2 | Yes | `explain` \"prints the documentation … from the installed package\", and \"there is no separate website.\" |\n| 6.3 | No | Two of the forty bundled rules \"currently print `No documentation declared`.\" Those identifiers do not resolve to a page, and \"one Identifier that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | The `// lintomatic-ignore` route \"can be disabled project-wide with `ignore_comments = false`.\" |\n\n**Conformance (§8):** Lintomatic does not conform. It fails 6.3 outright, and 5.3 is Partial, which under §8 is not conformance either. Its verdict also does not name its kind as §8.2 requires. The fixture never says whether its `Rule` interface is Syntactic, Semantic or Textual, so I have not guessed. Under §8.2 this verdict has not met that clause, though the findings above still stand.\n\n**Does 6.4 change the verdict?** No. Clause 6.4 is a SHOULD, and §8 counts only MUSTs. The fixture shows the gap 6.4 targets: the repository \"does not check that every `LM-NNNN` identifier has an `explain` entry,\" which is how the two undocumented rules shipped. Its absence explains the 6.3 failure but does not add to it.\n\n## 4. Confusion\n\n§8.1, on whether the gap-recording duty applies to a detector that does not declare a version:\n> \"a [Detector] that has learnt, from its own checks or from a [Practitioner]'s report under the method's clause 3.2, that it fails a MUST in sections 4 to 7 MUST record that gap alongside the version it declares, in the same file or one it names.\"\n\nRead alone, the first half is unconditional. The phrase \"the version it declares\" makes it read as conditional on declaring. I could not decide which reading is meant.","ttft_ms":29255,"type":"result","duration_ms":36686,"uuid":"e4ee9200-da50-4a14-99db-3816cfffa3dc","ttft_stream_ms":1151,"time_to_request_ms":412,"first_content_frame_ms":1152,"queued_turn_count":0,"result_index":0}

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 071: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

The exact prompt every reader received, part 1 of 1

===== FILE: QUESTIONS.md =====
# Questions for a cold reader of DETECTOR-SPEC.md

You have been given `DETECTOR-SPEC.md` and the fixture `fixtures/lintomatic.md`, and nothing
else. Read the document once, then answer every question below in order. Quote the document
where a question asks for a quotation. Do not consult anything outside what you were given.

## 1. Restatement

In your own words and in at most six bullets, what does this document require of a detector?

## 2. Load-bearing statements

Answer each part of each question in a sentence or two, then quote the sentence or sentences of
the document you are relying on.

- 2a. What does it mean for a detector to conform, and can a detector that was written before
  this document existed, and has never heard of it, conform?
- 2b. A detector ships an inline ignore comment. What must the detector do about that route for
  it to conform, and is doing one of those things enough?
- 2c. Where must the documentation for a bundled rule's identifier live, at what version, and
  must it be reachable without network access?

## 3. Application

Grade Lintomatic 4.2, as described in the fixture, against every MUST clause in sections [4](../../DETECTOR-SPEC.md#4-authoring-rules) to [7](../../DETECTOR-SPEC.md#7-suppression):
[4.1](../../DETECTOR-SPEC.md#41-the-detector-must-support-bespoke-rules-written-by-the-project-that-runs-it), [4.2](../../DETECTOR-SPEC.md#42-the-detector-must-provide-a-harness-that-runs-a-single-rule-against-supplied-code), [4.3](../../DETECTOR-SPEC.md#43-the-detector-must-allow-a-rule-to-carry-a-stable-identifier-and-must-print-it-with-every-finding), [5.1](../../DETECTOR-SPEC.md#51-the-detector-must-be-invocable-by-the-practitioner-locally-with-no-infrastructure), [5.2](../../DETECTOR-SPEC.md#52-the-detector-must-support-invocation-over-a-subset-at-minimum-a-single-file), [5.3](../../DETECTOR-SPEC.md#53-the-result-must-reach-the-practitioner-in-the-output-of-the-command-they-ran), [5.4](../../DETECTOR-SPEC.md#54-a-finding-must-not-be-reportable-only-through-a-hosted-service-licence-tier-or-ci-only-mode-the-practitioner-cannot-invoke-locally), [6.1](../../DETECTOR-SPEC.md#61-the-detector-must-provide-a-mechanism-that-resolves-a-printed-identifier-to-its-documentation), [6.2](../../DETECTOR-SPEC.md#62-resolution-of-a-bundled-rules-identifier-must-work-from-the-installed-copy-without-network-access), [6.3](../../DETECTOR-SPEC.md#63-a-bundled-rules-documentation-must-ship-with-the-rule-at-a-version-tracked-together), [7.1](../../DETECTOR-SPEC.md#71-a-detector-may-offer-an-inline-suppression-route-but-must-make-it-detectable-or-disableable). For each give Yes, Partial or No and one
line of reasoning. Then state whether Lintomatic conforms under section [8](../../DETECTOR-SPEC.md#8-conformance), and why. Finally,
state whether clause [6.4](../../DETECTOR-SPEC.md#64-the-detector-should-fail-its-own-release-if-a-bundled-rule-lacks-resolvable-documentation) changes the verdict, and why.

## 4. Confusion

Quote any passage you found confusing on your one reading, with its clause number. Do not say what
would make it clearer and do not propose any change. Write "none" if there was none.

===== FILE: DETECTOR-SPEC.md =====
# Defence Before Fix: Detector Specification

**Version**: 1.1.0-dev, unpublished
**Companion to**: [the method specification](SPEC.md), version 1.1.0, and [the toolchain specification](TOOLING-SPEC.md), version 0.2.0
**Author**: [Joseph Edmonds](https://ltscommerce.dev), [Edmonds Commerce](https://edmondscommerce.co.uk)
**Coined**: 22 February 2026, in [the original article](https://ltscommerce.dev/articles/defence-before-fix-static-analysis)

## 1. What this document is for

The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT and MAY are to be interpreted as
described in RFC 2119.

[The method specification](SPEC.md) states what a [Practitioner] does when a
[Defect] is found. This document states what a [Detector] must offer so that they
can do it: write a [Rule], prove it, run it and resolve what it prints. It is addressed to
whoever maintains a [Detector], which the method specification defines as a tool that reads
code without executing it and reports occurrences of a pattern.

A [Detector] is one part of a project's [Toolchain], and this document judges it
on what it offers alone. What the assembled [Toolchain] of a project must add on top, the
[Project record], the listing of active [Defences] and the
governance of [Suppression], is stated in [the toolchain specification](TOOLING-SPEC.md).
The two are separable because they are built by different people: a [Detector] is authored once
and installed into projects its maintainer will never see, whilst governance is decided by each
project for itself. A document that asked a [Detector] to enforce a project's governance would
fail every [Detector] in use and would not make any project better governed.

**The governing principle**: where the method specification requires a [Practitioner] to do
something with a [Rule], a [Conforming] [Detector] MUST make that possible
without the project building the mechanism first. A [Detector] that leaves the project to
construct the means has moved the obligation rather than met it.

## 2. Terminology

Terms from the method specification carry over unchanged. Every capitalised term links to its
definition; the ones this document leans on most are, in short:

| Term                               | In one line                                                                                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Detector]                         | A tool that reads code without executing it and reports what it finds                                                                               |
| [Semantic], [Syntactic], [Textual] | What a [Rule] can ask in a configuration: the type of any expression and what any name refers to, program-wide; a parse tree short of that; or text |
| [Rule]                             | One check a [Detector] evaluates                                                                                                                    |
| [Message]                          | The text a [Rule] prints when it fires                                                                                                              |
| [Identifier]                       | The stable name printed with a finding that leads to its documentation                                                                              |
| [Practitioner]                     | Whoever is doing the work, a person or an [Agent]                                                                                                   |
| [Toolchain]                        | Everything a project assembles to run its checks through, [Detectors] included                                                                      |
| [Suppression]                      | Making a finding go away without fixing it                                                                                                          |
| [Baseline]                         | A recorded set of existing findings a [Rule] is told to ignore                                                                                      |

These are additional.

#### Rule author

Whoever writes a [Rule]. May be the [Detector]'s maintainer, a [Toolchain]'s maintainer or the project that runs it.

#### Bundled rule

A [Rule] the [Detector] ships, or fetches on the [Practitioner]'s behalf from a source the [Detector]'s maintainer controls. Its [Rule author] will never see the codebases it runs in, so everything a [Practitioner] needs in order to act on it has to travel with it.

#### Harness

The route by which one [Rule] is run against supplied code and its findings observed, without the project's own test suite and without every other [Rule] running alongside it.

The distinction between a [Bundled rule] and a project's own [Rule] matters throughout.
The [Detector] owns the documentation of the first and can be held to shipping it; it cannot
know the documentation of the second, and what it owes there is the mechanism that lets the project
attach its own.

## 3. The division of responsibility

Each mechanism the method needs has two halves. Stating only one of them is what produces a
[Detector] that is *nearly* usable.

| Requirement                          | The [Detector] MUST provide                             | The [Rule author] supplies                              |
| ------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------- |
| Bespoke [Rules]                      | The authoring and registration route                    | The [Rules]                                             |
| The red proof                        | A [Harness]                                             | The [Fixture] and the red run                           |
| Running the [Rule]                   | A local invocation, including subsets                   | Running it                                              |
| The [Identifier]                     | A place to carry it, and printing it with every finding | Choosing it once                                        |
| Resolution of a [Bundled rule]       | The lookup, and the documentation, on disk              | Nothing                                                 |
| Resolution of a project's own [Rule] | The [Identifier] printed unaltered                      | The documentation and, with the [Toolchain], the lookup |
| Inline [Suppression]                 | A route the project can detect or disable               | The decision whether to forbid it                       |

Read down the middle column and the shape of a [Conforming] [Detector] is already
visible. Read across any row and the failure mode is visible too: either half alone leaves the
[Practitioner] stuck.

## 4. Authoring rules

### 4.1 The detector MUST support bespoke rules written by the project that runs it

Configuration of an existing [Rule] set is not sufficient. The project must be able to express a
pattern the [Detector]'s authors have never anticipated, and register it so that it runs with
the same standing as a [Bundled rule].

**Why**: the method turns a specific [Defect] into a [Class] [Defence], and the
[Classes] that matter most to a project are the ones peculiar to it. A [Detector] offering
only a fixed catalogue can defend against the industry's known [Hazards] and none of the
project's own, which is the half that carries its institutional knowledge.

### 4.2 The detector MUST provide a harness that runs a single rule against supplied code

A [Harness] reports, for a given input, whether the [Rule] fired and what it printed, without
executing the project's own test suite and without the other [Rules] obscuring the answer.
This applies to every [Rule] the [Detector] runs, bundled or bespoke; a
[Detector] failing 4.1 is graded here on its [Bundled rules].

**Why**: clause 3.3 of the method specification requires the [Rule] to be proven by making it go
red. A [Practitioner] who can only observe a [Rule]'s behaviour by running every
[Rule] over the whole codebase cannot demonstrate that a [Rule] fires on the pattern rather
than on something incidental.

### 4.3 The detector MUST allow a rule to carry a stable identifier, and MUST print it with every finding

The [Identifier] MUST be stable across releases and MUST NOT be derived from the [Rule]'s
file path, [Class] name or position in a configuration file. The [Rule author] chooses it
once. The [Detector] MUST print it, unaltered, alongside every finding the [Rule]
reports, in its default output and in every machine-readable format it offers. A prefix the
[Detector] adds from the invoking directory or the [Rule]'s location is derived from
the file path. A namespace the [Rule author] assigns once in configuration is not.

| [Identifier]       | Where it comes from                                                         | Stable? |
| ------------------ | --------------------------------------------------------------------------- | ------- |
| `proj.no-raw-sql`  | Written once by the [Rule author] in configuration, printed as is           | Yes     |
| `LM-0107`          | Assigned once by the [Detector]'s maintainer at the [Rule]'s first release  | Yes     |
| `rules/no-raw-sql` | Produced from where the [Rule]'s file sits; changes when the file moves     | No      |
| `NoRawSqlRule`     | The [Rule]'s [Class] name; changes when the [Rule] is renamed               | No      |
| `rule 7`           | The [Rule]'s position in a configuration file; changes when one is inserted | No      |

**Why**: the [Identifier] is the only string that reaches the [Practitioner] and the
only key their lookup can use. An [Identifier] that changes when a [Rule] is renamed
silently breaks every reference to it, including references written down by people who have left.
An [Identifier] the [Detector] carries but does not print is one the
[Practitioner] was never given.

### 4.4 The detector SHOULD enforce 4.3 with a rule of its own

A [Rule] over the [Rules], failing any [Rule] that reports without a stable
[Identifier].

**Why**: this is the method applied to the [Detector] itself, and it is cheap. The worked
example is `php-qa-ci`'s `RequireRuleIdentifierConstantRule`, a [Rule] hosted in PHPStan by a
[Toolchain] built on it: it rejects a magic-string [Identifier] and names the
constant to declare instead. Nothing about it needed to live outside the [Detector].

## 5. Reporting

### 5.1 The detector MUST be invocable by the practitioner, locally, with no infrastructure

**Why**: method specification clause 8.1. An [Agent] that cannot check its own work cannot
iterate against a [Defence], so the loop never closes in the turn where it is cheap to close.

### 5.2 The detector MUST support invocation over a subset, at minimum a single file

**Why**: 5.1 is satisfied in principle by a whole-codebase run and defeated in practice by one.
Checking a single edited file has to be fast enough to do on every edit, or it will not be done on
any.

### 5.3 The result MUST reach the practitioner in the output of the command they ran

Where the [Detector] writes fuller detail elsewhere, the invoked command's own output MUST
carry both a usable summary and the location of the remainder.

**Why**: method specification clause 8.2. A report the [Practitioner] has to go and find is a
report that arrives after the decision it was meant to inform.

### 5.4 A finding MUST NOT be reportable only through a hosted service, licence tier or CI-only mode the practitioner cannot invoke locally

A finding that the [Detector] reports only through a hosted service, a licence tier or a
continuous-integration-only mode the [Practitioner] cannot invoke locally does not
[Conform], whatever the same [Rule] reports there.

**Why**: a [Rule] that fires only in an environment the [Practitioner] has no access to
teaches nobody anything and blocks them anyway, which is the worst combination available.

## 6. Resolving an identifier

### 6.1 The detector MUST provide a mechanism that resolves a printed identifier to its documentation

Two cases, by who wrote the [Rule]:

| The [Rule]        | Who supplies the documentation | What this clause asks of the [Detector]                                   |
| ----------------- | ------------------------------ | ------------------------------------------------------------------------- |
| A [Bundled rule]  | The [Detector]                 | Resolve the [Identifier], presented alone, to that documentation          |
| The project's own | The project                    | Print the [Identifier] unaltered; the lookup is the [Toolchain]'s to give |

In both, the mechanism is keyed on **the [Identifier] exactly as printed**. A command, an index
file or a URL are all acceptable forms. Each case in turn:

**For a [Bundled rule]**, the [Detector] supplies the documentation and the
lookup, and clauses 6.2 and 6.3 say where. The mechanism MUST resolve an [Identifier]
presented alone, because the reader who needs it most has the [Identifier] from a log, a
ticket or a colleague and not the [Message]. A [Message] that carries its own
documentation path resolves that [Message], not the [Identifier].

**For a project's own [Rule]**, the [Detector] cannot know the documentation, so what
it owes is the half it can give: the [Identifier] printed unaltered under clause 4.3, and no
transformation of it. Where the [Identifier] is itself a URL, as method clause 3.6 allows,
the [Detector] prints that URL and has done what this clause asks. What this clause forbids is
a documentation path printed alongside a shorter [Identifier] that cannot be looked up on its
own. The lookup for such
a [Rule] is an obligation on the project's assembled [Toolchain], under clause 4.2
of the [toolchain specification](TOOLING-SPEC.md), and a [Detector] that offers it as well has
gone further than this clause asks.

**Why**: method specification clause 8.3 requires the [Identifier] to resolve without a human.
An index keyed on anything else does not resolve it. This is the most commonly failed clause in this
document and it fails in a specific way: documentation exists, is genuinely good, and is keyed on the
[Rule]'s [Class] or file name, which is a string the [Practitioner] was never
given. The lookup they can actually perform is the only one that counts.

### 6.2 Resolution of a bundled rule's identifier MUST work from the installed copy, without network access

**Why**: an [Agent] working offline, behind a proxy, or against a URL that has since moved needs
the answer to be on disk. A dependency the project already installed is on disk by definition. A
catalogue on the [Detector]'s website, however complete, is the right documentation in the wrong
place.

### 6.3 A bundled rule's documentation MUST ship with the rule, at a version tracked together

Every [Identifier] a [Bundled rule] can print MUST resolve, under clause 6.1,
to a page in that shipped documentation. One [Identifier] that does not resolve fails this
clause, however many others do. A page per [Identifier] satisfies this; a page per family of
[Identifiers] MAY be used instead, under clause 6.5.

**Why**: method specification clause 3.6. A [Bundled rule] travels into codebases its author
will never see. If its documentation lives only in the [Detector]'s repository or on its
website, then every project that installs it is one link rot away from a [Rule] that blocks
without explaining.

The failure to guard against is not the absent document but the **dangling one**: a reference to
documentation that was planned and never written is worse than no reference, because it consumes
the [Practitioner]'s attention before failing them.

### 6.4 The detector SHOULD fail its own release if a bundled rule lacks resolvable documentation

An automated check over every [Identifier] a [Bundled rule] can print, applying the
family list or pattern of clause 6.5 where one is used, that blocks the release when any of them
lands on no page.

**Why**: clause 6.1 is the clause most easily believed to be satisfied whilst being broken, because
the documentation is written by the same person who wrote the [Rule] and its absence is
invisible from the inside. Where the [Detector] is shipped inside a [Toolchain],
the [toolchain specification](TOOLING-SPEC.md)'s self-audit makes this check a MUST for the [Toolchain]; a
[Detector] released on its own is asked for it as a SHOULD because the same [Class] of
[Defect], a [Rule] that blocks without explaining, is detectable mechanically there too.

### 6.5 One page MAY document a family of identifiers, and SHOULD name its members so a check can confirm them

Where several [Bundled rules] share a prefix, one page MAY document them all. Such a page
SHOULD let the check clause 6.4 asks for confirm that each member resolves to it. Two forms do
that and one does not:

| The page                                                                                                  | A check can confirm each member?          |
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| Lists every full [Identifier] in the family, `LM-0100`, `LM-0101`, and so on                              | Yes                                       |
| Carries a regular expression the check can run, `^LM-01[0-9]{2}$`, matching every member and nothing else | Yes                                       |
| Says in prose that everything under `LM-01` is documented here                                            | No: a person can infer it, a check cannot |

The list or the regular expression lives in the installed file, since clause 6.2 requires
resolution to work from the installed copy, not only in a rendered web page.

**Why**: a family page is a convenience for the author, and the cost of it must not fall on the
check. A page that a person can see covers `LM-0107` but a check cannot leaves clause 6.4 with
nothing to run.

## 7. Suppression

### 7.1 A detector MAY offer an inline suppression route, but MUST make it detectable or disableable

An inline ignore comment, a per-line directive and a generated [Baseline] are all such
routes. The [Detector] MAY ship them. For each one it MUST do at least one of two things, and either
alone satisfies this clause: make the route disableable by configuration; or make the route
detectable, by a [Rule] the project can write in the [Detector] itself or by a mechanical check
the [Detector] documents. Either lets a project which decides to forbid the route enforce that
decision. A route that can be neither switched off nor seen does not [Conform].

**Why**: the method specification's position is that [Suppression] is an [Owner]
decision under its clause 3.4 and section 4, and an [Owner] cannot decide something they are
never shown. The [Detector] is not the [Owner] and does not know the project's
governance, so it is not asked to enforce it; what it is asked is not to hide the route. Every
[Detector] in wide use ships an inline ignore, and a clause that forbade them would fail all of
them without governing any project better. Whether the route is forbidden is the project's decision,
enforced through its [Toolchain] under clause 4.3 of the [toolchain specification](TOOLING-SPEC.md).

### 7.2 An inline suppression route SHOULD require a written reason

The [Detector] SHOULD reject, or be configurable to reject, an inline [Suppression]
that carries no reason, and SHOULD NOT supply a default one, including each entry of a generated
[Baseline].

**Why**: a [Suppression] without a reason is indistinguishable from one nobody would defend,
and the person who could tell them apart is usually gone. Requiring the sentence costs the author a
minute at the moment they have the reason in mind, and it is the only thing that makes the
[Suppression] reviewable later. PHPStan's `reportIgnoresWithoutComments` is the shape of it.

## 8. Conformance

**A [Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.

Partial [Conformance] MUST NOT be described as [Conformance]. A [Detector]
that satisfies most of this document is in a normal and respectable condition; it is not
[Conforming], and describing it as such removes the only value the word has.

This document defines one level. The clauses of the method specification's section 8 that reach
beyond what sections 4 to 7 here already secure, the listing of active [Defences] and the
summary for an [Agent]'s context, are obligations on a project's assembled [Toolchain]
and are stated in the [toolchain specification](TOOLING-SPEC.md), so there is no separate [Agent]-support level
for a [Detector].

### 8.1 A maintainer MAY declare the version of this document the detector conforms to, and the declaration records known gaps

The declaration is machine-readable, in whatever form the [Detector]'s ecosystem uses to
record dependencies. The same declaration is where a gap against this document is recorded once it is
known: a [Detector] that has learnt, from its own checks or from a [Practitioner]'s
report under the method's clause 3.2, that it fails a MUST in sections 4 to 7 MUST record that gap
alongside the version it declares, in the same file or one it names. A declaration with a non-empty
gap record is a statement of where the [Detector] stands and is not a claim of
[Conformance].

The declaration is optional. It is how a maintainer claims [Conformance]; it is not a
condition of it. A [Detector] that predates this document, or whose maintainer has never read
it, MAY be graded [Conforming] on evidence by anyone who exercises the clauses above against
it, and a verdict on any [Detector], declared or not, rests on that exercise and not on the
claim. A declaration tells the reader what the maintainer believes and what they know to be missing;
the reader still runs the [Harness].

The shape is illustrative rather than prescribed. In a Composer manifest:

```json
"extra": {
  "defence-before-fix": {
    "method": "1.0.1",
    "detector": "1.0.0",
    "toolchain": null,
    "known-gaps": []
  }
}
```

In a package.json, the equivalent is a top-level `defenceBeforeFix` object with the same keys. A
[Detector] that is not also shipped as a [Toolchain] leaves `toolchain` empty;
a project that ships both declares both. Each entry in `known-gaps` names the clause and states the
gap in a sentence.

**Why**: a [Conformance] claim in a README is a sentence; a claim in a manifest is a fact about
a specific installed artefact, checkable by anyone, including mechanically, and it fixes what
"[Conforming]" meant at the point the claim was made. Making the claim a condition of
[Conformance], though, would mean a [Detector] with every mechanism in place could
never [Conform] until its maintainer had heard of this document, which grades the
maintainer's reading rather than the [Detector].

### 8.2 A verdict on a detector MUST name its kind in each configuration it grades

A verdict on a [Detector] MUST name its kind, [Semantic], [Syntactic] or [Textual], in each
configuration the verdict grades. A configuration here is one way of setting the [Detector] up to
run, and one [Detector] can have several: ESLint, for example, is [Syntactic] run without type
information and [Semantic] run with it. The kind is what a [Rule] can ask in that configuration,
as section 2 glosses it. The kind named is the one the [Detector] has without a hosted service or
a licence tier, the same configurations the method specification's clause 3.2 counts when it
asks for the strongest kind.

This clause binds whoever gives the verdict, anyone grading a [Detector] under clause 8.1
included, and asks nothing of the [Detector] itself; a maintainer who gives a verdict on their
own [Detector] is bound by it like anyone else. A declaration under clause 8.1 is the
maintainer's claim, not a verdict, and this clause asks nothing of it. A [Detector] is judged on
sections 4 to 7, as section 8 says, and the kind is not a level of [Conformance] and does not
change the verdict: a [Textual] [Detector] can [Conform], and a [Semantic] one can fail to. A
verdict that leaves the kind out has not met this clause, but what it finds about [Conformance]
still stands.

**Why**: the method specification's clause 3.2 requires a [Rule] to be written in the strongest
kind of [Detector] that can express it, and a verdict of [Conforming] alone presents a [Textual]
[Detector] as an equal host.

## 9. Relationship to the other specifications

This document adds no obligation to the method a [Practitioner] follows, and relaxes none.
Clause 8.2 adds one on whoever gives a verdict on a [Detector], a [Practitioner] included when
they give one: to name its kind. Every clause here
exists to make a clause of the method specification achievable with a [Detector] in hand.

Where this document and the method specification disagree, the method specification governs. It
describes the method, which is the thing being specified; this describes one piece of the equipment.

The [toolchain specification](TOOLING-SPEC.md) states what a project's assembled [Toolchain] must offer beyond
what each [Detector] in it offers, and it requires every [Detector] a [Defence]
is routed through to [Conform] to this one. A [Detector] shipped inside a
[Toolchain] is measured here as a [Detector] and there as part of the
[Toolchain]; the two verdicts are separate and neither implies the other.

Nothing here requires a project to use a [Conforming] [Detector]. A project can
[Conform] to the method specification on a [Detector] that [Conforms] to
none of this, at the cost of building the missing mechanisms itself. This document exists so that it
does not have to.

<!-- Term link definitions -->

[agent]: SPEC.md#agent
[baseline]: SPEC.md#baseline
[bundled rule]: #bundled-rule
[bundled rules]: #bundled-rule
[class]: SPEC.md#class
[classes]: SPEC.md#class
[conform]: SPEC.md#conform
[conformance]: SPEC.md#conform
[conforming]: SPEC.md#conform
[conforms]: SPEC.md#conform
[defect]: SPEC.md#defect
[defence]: SPEC.md#defence
[defences]: SPEC.md#defence
[detector]: SPEC.md#detector
[detectors]: SPEC.md#detector
[fixture]: SPEC.md#fixture
[harness]: #harness
[hazards]: SPEC.md#hazard
[identifier]: SPEC.md#identifier
[identifiers]: SPEC.md#identifier
[message]: SPEC.md#message
[owner]: SPEC.md#owner
[practitioner]: SPEC.md#practitioner
[project record]: TOOLING-SPEC.md#project-record
[rule]: SPEC.md#rule
[rule author]: #rule-author
[rules]: SPEC.md#rule
[semantic]: SPEC.md#semantic
[suppression]: SPEC.md#suppression
[syntactic]: SPEC.md#syntactic
[textual]: SPEC.md#textual
[toolchain]: SPEC.md#toolchain

===== FILE: fixtures/lintomatic.md =====
# Fixture: Lintomatic 4.2

A fictional detector, described only as far as its documentation would describe it. Grade it
against the document you were given. Where the description does not say, answer from what is
written, not from what a real tool would probably do.

## What the documentation says

Lintomatic is a static analyser for the Zig language. It ships forty rules. A project can add its
own rules by writing a Zig module that implements the `Rule` interface and listing it in
`lintomatic.toml`; project rules are loaded on every run and are first class in every report.

The command `lintomatic check <path>` analyses a directory or a single file and prints one line
per finding to standard output:

```
src/db.zig:41:7  LM-0107  Result of fallible call discarded
src/db.zig:88:3  proj.no-raw-sql  Query built by string concatenation
```

Fuller detail, including a snippet, is written to `.lintomatic/report.html`. The one-line form is
printed whether or not the HTML report is enabled.

`lintomatic check --only <identifier> <path>` runs a single rule. It accepts any rule name,
bundled or project, and exits 1 when the rule fires, 0 when it does not, and 2 when the rule name
is unknown.

Rule identifiers for bundled rules are of the form `LM-NNNN` and are fixed at the rule's first
release. Project rules are identified by the name the project gives them in `lintomatic.toml`,
which the documentation recommends prefixing with `proj.`; Lintomatic does not add or rewrite
any part of the name.

`lintomatic explain <identifier>` prints the documentation for a bundled rule from the installed
package: what the rule finds, why, and the recommended construction. For a project rule it prints
the `doc` string the rule module declares, or `No documentation declared` when there is none.
The documentation is versioned with the package; there is no separate website.

Lintomatic honours the comment `// lintomatic-ignore <identifier>` on the line before a finding.
The comment can be disabled project-wide with `ignore_comments = false` in `lintomatic.toml`.
The comment takes no reason and the documentation does not mention one.

The tool runs locally with no account, no server and no network, and the same binary is what CI
runs. There is a hosted dashboard, sold separately, that aggregates reports across repositories;
every finding it shows is also printed by the command line.

Lintomatic's own repository runs `lintomatic check src` in CI but does not check that every
`LM-NNNN` identifier has an `explain` entry; two of the forty rules currently print
`No documentation declared`.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 074: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Command and working directory

ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt, each reader in a fresh empty directory made by mktemp -d outside the repository

Model version each reader reported

reader 1: claude-haiku-5-5
reader 2: claude-haiku-5-5
reader 3: claude-haiku-5-5
reader 4: claude-haiku-5-5
reader 5: claude-haiku-5-5

The delegated marker's task prompt

MODE: mark

Worktree: <worktree> (branch `editor/semantic-detectors`). The readers read commit f7e55e1. Read, at that commit and in that worktree: ACCEPTANCE.md in full (it is the process as pull request 22 proposes it, merged into this branch; the run is made under that process change before it merges, at commit bc6ae14, named here before the readers started), acceptance/runs/README.md, acceptance/detector/QUESTIONS.md, acceptance/detector/KEY.md, acceptance/fixtures/lintomatic.md and DETECTOR-SPEC.md. You may read acceptance/runs/071-semantic-detectors-run-14.md for the shape of a record only; ACCEPTANCE.md says a judgement an earlier run recorded is not a rule. Do not take any judgement from the records of runs 19 to 30, which were made under the earlier rules on an earlier text of this change.

Run: 074. Document: DETECTOR-SPEC.md. Change: semantic detectors (pull request 14): a new clause 8.2 that a verdict on a detector names its kind, semantic, syntactic or textual, in each configuration it grades; section 9's sentence on that obligation; section 2's table row for the three kinds. Since run 71 the branch has merged `editor/detector-route-rule` (pull request 23), which reworked clauses 5.3, 6.1, 7.1 and 8.1 and added section 2's Message row; those paragraphs are that change's text and not this one's. Clause 8.2's sentence on which configuration's kind is named was reworded after run 71. Pull request: 14, open on this branch. Run within the pull request: 15 (runs 19 to 30, 70 and 71 came before). Record file to write: <worktree>/acceptance/runs/074-semantic-detectors-run-15.md. Do not commit it.

Sheets: <run-dir>/sheet-1.md to sheet-5.md, each the `result` of reader-N.json. Prompt the readers received: <run-dir>/prompt.txt; its sha256 is 003d030ade886b2008da3abc90e75d8c62be8f02f79c3b4aec05818379bcc550, and it is byte for byte the question set, DETECTOR-SPEC.md and the fixture at f7e55e1, each after one `===== FILE: <name> =====` line, one blank line between files. Model: every reader reported claude-haiku-5-5 (<run-dir>/models.txt); the cohort's model named for the run is claude-haiku-5-5. Every reader's JSON has is_error false and subtype success; no reader is set aside.

Facts the editor supplies for the record: cohort of five readers, none reused from any earlier run; each a separate `claude -p` process started through the `ccy-claude` wrapper (which only reattaches the session's credential) in a fresh empty directory made by `mktemp -d` outside any repository, with the packet on standard input, command `ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt`; no tools, no settings, no project context, no saved session; the machine applies no managed settings; the editor attests the rest of the isolation. Marked by a delegated marker, a fresh `dbf-spec-editor` agent (Claude Opus) given this instruction and its agent definition, having seen neither the drafting nor the review.

Text the change wrote or altered, for "Where a finding lies": the output of `git -C <worktree> diff d67174c f7e55e1 -- DETECTOR-SPEC.md`, which is this change's text over and above the merged detector change at its commit d67174c (the merge base with `next` is 9e914fe; `git -C <worktree> diff 9e914fe f7e55e1 -- DETECTOR-SPEC.md` shows both changes together, and a paragraph only the detector change altered is not this change's text). Run both and read them. A finding in a paragraph the first diff adds, rewords, moves, deletes a sentence from, or changes a word, term or link in, or that a reader sets against such a paragraph, blocks; any other finding is routed to acceptance/ROUTED.md, and the record says so for each.

Mark every sheet, keyed item by keyed item, by ACCEPTANCE.md's Marking section and the key alone. Write the record stating what acceptance/runs/README.md lists, in its order, with the marks table, each miss with the reader's reasoning in brief, every passage named under question 4 with its words or description, clause and readers, each finding and where it lies, any open point and the direction you marked it, the process-change commit bc6ae14, the verdict, and a placeholder line for the link to the posted sheets. Reply with the verdict and the findings only, under thirty lines.

The agent definition the marker ran under

---
name: dbf-spec-editor
description: Drafts, reviews, key-updates, marks or records a change to the Defence Before Fix specification, one mode per dispatch, following the specification repository's CONTRIBUTING.md, SCOPE.md and ACCEPTANCE.md exactly. Dispatched by the dbf-spec-change skill; it cannot run a cohort itself.
tools: Read, Write, Edit, Grep, Glob, Bash
model: opus
---

You work on one change to the Defence Before Fix specification, in the worktree path you are
given. The process is defined by that repository's `CONTRIBUTING.md`, `SCOPE.md`,
`ACCEPTANCE.md` and `acceptance/README.md`; read the ones your mode needs, in full, before you
act. You invent no steps. You never merge, push to `next` or `main`, release, tag or publish,
and you never change a question, threshold or fixture. Your prompt names one mode.

Write as the specification is written: British spelling, no em dashes, defined terms linked as
`spec-qa.py` requires, every clause or section number linked to its heading. Commit on the
worktree's branch.

## MODE: draft

1. Read `SCOPE.md`, `DECLINED.md`, and every clause the change touches, with the terminology
   entries they use.
2. Decide the kind. A change to the sentences carrying MUST, SHOULD or MAY in a numbered section
   or appendix is an obligation; anything else is a clarity edit.
3. For an obligation, answer the five SCOPE tests in a sentence or two each, and name the
   section the obligation extends. If a test fails, or an existing clause already covers it, say
   so and stop without editing.
4. Make the smallest edit that does the job, in the clause that owns the subject.
5. Add one line under `Unreleased` in `CHANGELOG.md`, saying what changed and why, with a
   placeholder `run NNN`. Change no version.
6. If Appendix A changed, run `python3 build-agent-surfaces.py` and commit its outputs.
7. Run `python3 spec-qa.py` and `python3 -m unittest discover -s tools -p 'test_*.py'`; both
   clean.
8. Reply with: kind, SCOPE answers, files changed, commit, QA result, and a filled
   `.github/PULL_REQUEST_TEMPLATE.md` body. Under forty lines.

## MODE: review

You have not seen the drafting. Read the whole changed document and the diff against
`origin/next`. Report, numbered, with clause numbers: contradictions with other clauses, terms
used outside their definition, a sentence a reader could take two ways, an obligation an owner
could not verify from the record. Do not edit.

## MODE: keys

Read `acceptance/README.md` and the `KEY.md` for each changed document. Update every quotation of
a changed sentence to the new wording, verbatim, in a new commit; run `python3 spec-qa.py` until
`key-quote-stale` is gone. If the change adds a load-bearing obligation no keyed question
covers, propose the question and its key in your reply; add it only if your prompt says the owner
has decided to.

## MODE: mark

You have not seen the drafting or the review. Read `ACCEPTANCE.md` in full, the `KEY.md` for the
document, the document at the commit named, and the sheets under `<run-dir>/`.

- Mark each sheet against the key, keyed item by keyed item, by the rules under "Marking" in
  `ACCEPTANCE.md` and nothing else: the key's required elements, accepted grades, conditions and
  wrong readings. Invent no judgement; where neither the rules nor the key settles a mark, mark
  it in the direction the open-point rule gives and record the point.
- A single miss under questions 1 and 3 counts against the document only where the key names
  that reading; say which.
- For question 4, record every passage named, with its words or description and its clause.
  Group the passages by paragraph, count the readers, and for each finding say whether it lies in
  text the change wrote or altered, read against the diff the prompt gives you, or is routed to
  `acceptance/ROUTED.md`. Ignore any suggestion a reader volunteers.
- Apply the pass criteria exactly.
- Write `acceptance/runs/NNN-<slug>-run-<k>.md` stating what `acceptance/runs/README.md` lists,
  in its order. Do not commit it. Reply with the verdict and the findings only.

## MODE: record

Commit the run record, replace the `run NNN` placeholder in `Unreleased` with a link to it, and
for an obligation set the document header to `<next version>-dev, unpublished` in the same
commit if it still names a published version. Run `python3 spec-qa.py` and
`python3 tools/changes.py --base origin/next`; both clean. Reply with the commit.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 074: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Answer sheets, each reader's whole output as its process returned it

reader-1

{"duration_api_ms":44552,"stop_reason":"end_turn","session_id":"0a9ad56d-f634-466a-88ce-3e71f7dd5b0c","total_cost_usd":0.00798963,"usage":{"input_tokens":2,"cache_creation_input_tokens":12087,"cache_read_input_tokens":2153,"output_tokens":11101,"output_tokens_details":{"thinking_tokens":8981},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12087,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":11101,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12087,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12087},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":11101,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12087,"webSearchRequests":0,"costUSD":0.00798963,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":8981,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project write its own rules and register them with the same standing as bundled rules (4.1).\n- A detector must provide a harness that runs one rule against supplied code and reports whether it fired and what it printed, for bundled and project rules alike (4.2).\n- Each rule's identifier is chosen once by its author, is not derived from file path, class name or position, and is printed unaltered with every finding (4.3).\n- The detector must run locally with no infrastructure, on a subset down to a single file, and its results must appear in the command's own output. No finding may be reportable only through a hosted service, licence tier or CI-only mode (5.1–5.4).\n- A printed identifier must resolve to its documentation. For bundled rules that means lookup by the identifier alone, from the installed copy with no network, and every identifier the rule can print must have a page in documentation shipped with it (6.1–6.3).\n- Any inline suppression route must be either disableable by configuration or detectable. Either alone is enough (7.1).\n\n## 2. Load-bearing statements\n\n**2a.** A detector conforms when it satisfies every MUST in sections 4 to 7. A detector that predates the document and has never heard of it can conform. Its conformance is judged by checking it against the clauses, not by a claim it makes. The document says: \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\" It also says: \"The declaration is how a maintainer claims [Conformance]; it is not a condition of it, and a [Detector] without one can still be found to [Conform].\" The conformance definition is in section 8: \"**A [Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.\"\n\n**2b.** The detector must make the inline ignore route either disableable or detectable. Doing one of these is enough. The text says: \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a bespoke [Rule] the project writes for the [Detector] under clause 4.1 or by a mechanical check the [Detector] documents.\" The detectable-by-rule option is limited: \"That way is open only where the [Detector]'s [Rules] can see the route: a [Detector] whose [Rules] never see comments cannot make an ignore comment detectable this way.\"\n\n**2c.** The documentation must ship with the rule, at a version tracked together with it. Its location is the installed copy, and it must be reachable without network access. The heading of 6.3 says: \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\" The heading of 6.2 says: \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are written as a Zig module implementing `Rule`, listed in `lintomatic.toml`, and are \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs a single rule, bundled or project. The fixture does not describe `--only`'s printed output, so I take it from `check`, which prints one line per finding. |\n| 4.3 | Yes | Bundled identifiers are `LM-NNNN`, fixed at first release. Project names come from configuration and \"Lintomatic does not add or rewrite any part of the name.\" The one-line output prints them as they are. The fixture describes no machine-readable format, so that limb is not tested by what it says. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | The one-line form is printed to standard output with location and identifier for each finding, and \"The one-line form is printed whether or not the HTML report is enabled.\" Nothing is left out, so there is no remainder. |\n| 5.4 | Yes | The dashboard is paid, but \"every finding it shows is also printed by the command line,\" and CI runs the same binary. |\n| 6.1 | Yes | `lintomatic explain <identifier>` takes the identifier alone and prints the bundled rule's documentation. The two undocumented bundled rules fail 6.3, not this clause. |\n| 6.2 | Yes | `explain` reads \"from the installed package,\" and \"there is no separate website.\" |\n| 6.3 | **No** | Two of the forty bundled rules print `No documentation declared`. The clause says \"One [Identifier] that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `// lintomatic-ignore` can be turned off with `ignore_comments = false`, which makes it disableable by configuration. |\n\n**Conformance under section 8: No.** Lintomatic fails 6.3, and section 8 requires every MUST in sections 4 to 7 to hold. The partial result cannot be described as conformance. Section 8 also requires a verdict to name the detector's kind (8.2). The fixture does not say what a Lintomatic rule can ask (semantic, syntactic or textual), so I cannot name it. That gap does not change the finding about conformance.\n\n**Clause 6.4: No, it does not change the verdict.** 6.4 is a SHOULD, and section 8 counts only MUSTs. The fixture does show that the release check 6.4 describes is missing: the repository \"does not check that every `LM-NNNN` identifier has an `explain` entry.\" That is a recorded SHOULD miss. It is also why the 6.3 failure is still there, but it is not a separate reason for the verdict.\n\n## 4. Confusion\n\nThe clause that confused me most is 6.1, which relies on a clause outside what I was given:\n\n- 6.1: \"Where the [Identifier] is itself a URL, as method clause 3.6 allows, the [Detector] prints that URL and has done what this clause asks.\"\n\nI could not check what method clause 3.6 allows, since I only had this document and the fixture. The same applies to other references to the method and toolchain specifications, such as \"the method's clause 3.2\" in 8.1. I took those references on trust.","ttft_ms":37083,"type":"result","duration_ms":44926,"uuid":"fe322391-acbd-41ce-b7aa-d09639eba4c0","ttft_stream_ms":1259,"time_to_request_ms":368,"first_content_frame_ms":1259,"queued_turn_count":0,"result_index":0}

reader-2

{"duration_api_ms":36242,"stop_reason":"end_turn","session_id":"4a8652ef-66eb-42b4-904a-db62accdb0b8","total_cost_usd":0.00693713,"usage":{"input_tokens":2,"cache_creation_input_tokens":12087,"cache_read_input_tokens":2153,"output_tokens":8996,"output_tokens_details":{"thinking_tokens":7070},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12087,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":8996,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12087,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12087},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":8996,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12087,"webSearchRequests":0,"costUSD":0.00693713,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":7070,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- The detector must let a project write its own rules and run them with the same standing as bundled rules (4.1).\n- It must provide a harness that runs one rule against supplied code and reports whether it fired and what it printed (4.2).\n- Each rule carries a stable identifier chosen by its author, not derived from path, class name or position, and the detector prints it unaltered with every finding (4.3).\n- Checks must run locally with no infrastructure, including on a single file, and every finding must appear in the command's own output and not only in a hosted, licensed or CI-only channel (5.1 to 5.4).\n- A printed identifier must resolve to its documentation. For bundled rules, that documentation must work offline from the installed copy, ship with the rule at a tracked version, and cover every identifier the rule can print (6.1 to 6.3).\n- Any inline suppression route must be either switchable off by configuration or detectable (7.1). A detector conforms when it meets every MUST in sections 4 to 7 (8).\n\n## 2. Load-bearing statements\n\n**2a.** Conformance means satisfying every MUST in sections 4 to 7. A detector written before this document, and unaware of it, can still conform: a verdict rests on checking the clauses, not on the maintainer's claim.\n> \"**A [Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.\"\n> \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\"\n\n**2b.** The detector must make the inline route either disableable by configuration or detectable. Doing one of those is enough.\n> \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a bespoke [Rule] … or by a mechanical check the [Detector] documents.\"\n\n**2c.** The documentation must ship on disk with the installed rule, at a version tracked together with it, and must be resolvable without network access.\n> \"### 6.3 A bundled rule's documentation MUST ship with the rule, at a version tracked together\"\n> \"### 6.2 Resolution of a bundled rule's identifier MUST work from the installed copy, without network access\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n| --- | --- | --- |\n| 4.1 | Yes | Projects write a Zig module implementing `Rule`, list it in `lintomatic.toml`, and it is \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule, bundled or project, and exits 1 when it fires. The description never says `--only` prints the finding line, so \"what it printed\" rests on the exit code alone. I read that as meeting the clause, but it is the one open point. |\n| 4.3 | Yes | Bundled identifiers are `LM-NNNN`, fixed at first release. Project identifiers are the configured name, and \"Lintomatic does not add or rewrite any part of the name.\" Every finding is printed with its identifier. The description names no other machine-readable format, so none is in scope. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | The one-line form, with location and identifier, \"is printed whether or not the HTML report is enabled,\" so there is no remainder. |\n| 5.4 | Yes | The hosted dashboard is extra, and \"every finding it shows is also printed by the command line.\" |\n| 6.1 | Yes | `lintomatic explain <identifier>` is keyed on the identifier alone and resolves bundled and project rules. The two bundled rules with no documentation fail 6.3, not this clause. |\n| 6.2 | Yes | Documentation is read from \"the installed package,\" and \"there is no separate website.\" |\n| 6.3 | No | \"Two of the forty rules currently print `No documentation declared`.\" Every identifier a bundled rule can print must resolve, so two failures are enough. Versioning with the package is met; coverage is not. |\n| 7.1 | Yes | `ignore_comments = false` in `lintomatic.toml` disables the `// lintomatic-ignore` route project-wide, which is the first of the two permitted ways. |\n\n**Conformance (section 8):** Lintomatic does not conform. It fails 6.3, and 8 requires every MUST in sections 4 to 7 to hold. The fixture does not say what kind of detector Lintomatic is (Textual, Syntactic or Semantic), so my verdict cannot name it, as 8.2 requires. That gap does not change the finding that Lintomatic fails conformance.\n\n**Clause 6.4:** It does not change the verdict. 6.4 is a SHOULD, and section 8 grades only MUSTs in sections 4 to 7. Lintomatic's repository does not check that every `LM-NNNN` has an `explain` entry, so it does not meet 6.4 in practice, but that gap is recorded as a SHOULD shortfall. The verdict already fails on 6.3. The fixture also does not say whether Lintomatic ships inside a toolchain, which would make 6.4 a MUST for that toolchain.\n\n## 4. Confusion\n\nOne passage, under clause 8.2:\n> \"The kind named is the one the [Detector] has in a configuration that needs no hosted service and no licence tier.\"\n\nOn my reading it is unclear whether a verdict that grades a hosted-service configuration must name the kind of the local configuration or the kind of the hosted one.","ttft_ms":29918,"type":"result","duration_ms":36631,"uuid":"16b9b7ff-2f83-4e8f-ac2c-3dbc0b6f8fae","ttft_stream_ms":1092,"time_to_request_ms":384,"first_content_frame_ms":1093,"queued_turn_count":0,"result_index":0}

reader-3

{"duration_api_ms":35402,"stop_reason":"end_turn","session_id":"ea0522b7-8edf-4987-af89-e35cd21830df","total_cost_usd":0.00674023,"usage":{"input_tokens":2,"cache_creation_input_tokens":12085,"cache_read_input_tokens":2153,"output_tokens":8603,"output_tokens_details":{"thinking_tokens":6580},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12085,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":8603,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12085,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12085},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":8603,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12085,"webSearchRequests":0,"costUSD":0.00674023,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":6580,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project write its own rules, register them, and run them with the same standing as bundled rules (4.1).\n- It must provide a harness that runs one rule against supplied code and reports whether it fired and what it printed (4.2).\n- Each rule's identifier must be stable, not derived from path, class name, or position, and printed unaltered with every finding (4.3).\n- It must run locally with no infrastructure, on a single file as well as a whole codebase, and its results must appear in the output of the command run. No finding may be reportable only through a hosted service, licence tier, or CI-only mode (5.1–5.4).\n- It must map a printed identifier to its documentation, keyed on the identifier alone. Bundled rules' documentation must ship with the rule, work offline, and cover every identifier the rule can print (6.1–6.3).\n- An inline suppression route must be either disableable by configuration or detectable (7.1).\n\n## 2. Load-bearing statements\n\n**2a.** Conformance means satisfying every MUST in sections 4 to 7. A detector written before the document, and unaware of it, can conform. It is graded on evidence. Section 8: \"A **[Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.\" Section 8.1: \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\" It adds: \"The declaration is how a maintainer claims [Conformance]; it is not a condition of it, and a [Detector] without one can still be found to [Conform].\"\n\n**2b.** The detector must do at least one of two things: make the route disableable by configuration, or make it detectable. Doing one is enough. Section 7.1: \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable…\" Detectability by a bespoke rule is only available where the detector's rules can see the route. Section 7.1 notes: \"a [Detector] whose [Rules] never see comments cannot make an ignore comment detectable this way.\"\n\n**2c.** The documentation must ship with the rule, at a version tracked together with it, and must resolve without network access. Section 6.3: \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\" Section 6.2: \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access.\" A website alone does not satisfy this. Section 6.2 says a catalogue on the detector's website is \"the right documentation in the wrong place.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, and are \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule on a path and exits 1 when it fires. The fixture does not explicitly say `--only` prints findings, so \"what it printed\" is inferred from the one-line output of `check`. |\n| 4.3 | Yes | Bundled identifiers are `LM-NNNN`, fixed at first release. Project names are used as written, and \"Lintomatic does not add or rewrite any part of the name.\" The fixture describes the one-line output and the HTML report but no machine-readable format, so the \"every machine-readable format\" limb has nothing to check. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | The one-line form prints every finding with location and identifier to stdout, so no remainder is needed. |\n| 5.4 | Yes | The dashboard is hosted, but \"every finding it shows is also printed by the command line.\" |\n| 6.1 | Yes | `lintomatic explain <identifier>` resolves an identifier presented alone, for bundled and project rules. |\n| 6.2 | Yes | `explain` prints \"from the installed package\"; \"there is no separate website.\" |\n| 6.3 | No | Two of the forty bundled rules print \"No documentation declared.\" Section 6.3: \"One [Identifier] that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `// lintomatic-ignore` can be turned off with `ignore_comments = false`, which is disableable by configuration. |\n\n**Conformance (section 8):** No. Clause 6.3 is a MUST and fails, so Lintomatic does not conform. The two bundled rules with no documentation are enough on their own. Section 8.2 also requires the verdict to name the detector's kind (Semantic, Syntactic, or Textual). The fixture does not say what a rule can ask, so I cannot name the kind. That leaves this verdict short of 8.2, though the finding on conformance stands. There is no 8.1 declaration described, so none is assessed.\n\n**Does 6.4 change the verdict?** No. Clause 6.4 is a SHOULD (\"The detector SHOULD fail its own release if a bundled rule lacks resolvable documentation\"). Section 8 counts only MUSTs. The fixture says Lintomatic's CI does not run this check, which is why the two gaps went unblocked. That is a shortfall against a SHOULD and it does not alter the verdict, which rests on 6.3. The document also says that a detector shipped inside a toolchain is held to 6.4 as a MUST under the toolchain specification. I was not given that specification and have not assessed it.\n\n## 4. Confusion\n\n- **8.2:** \"The kind named is the one the [Detector] has in a configuration that needs no hosted service and no licence tier.\" On my reading it is unclear whether this means the kind must be the strongest available locally or simply the kind of whichever local configuration is named. The sentence does not settle which.","ttft_ms":28445,"type":"result","duration_ms":35695,"uuid":"45db9970-666e-4f79-a625-8bf2f469d462","ttft_stream_ms":1031,"time_to_request_ms":287,"first_content_frame_ms":1031,"queued_turn_count":0,"result_index":0}

reader-4

{"duration_api_ms":48201,"stop_reason":"end_turn","session_id":"4e202166-fbf2-47ce-8c8d-30f60ce19e38","total_cost_usd":0.00838683,"usage":{"input_tokens":2,"cache_creation_input_tokens":12088,"cache_read_input_tokens":2153,"output_tokens":11895,"output_tokens_details":{"thinking_tokens":9931},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12088,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":11895,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12088,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12088},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":11895,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12088,"webSearchRequests":0,"costUSD":0.00838683,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":9931,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project write its own rules and register them with the same standing as bundled rules (4.1).\n- It must provide a harness that runs one rule against supplied code, with fire/no-fire and printed output observable (4.2).\n- Each rule needs a stable identifier, not derived from file path, class name, or position, and the detector must print it unaltered with every finding (4.3).\n- The check must run locally with no infrastructure, on a single file as well as a whole codebase, and its results must appear in the output of the command run. No finding may be reportable only through a hosted, licence-tier, or CI-only route (5.1–5.4).\n- A printed identifier must resolve to its documentation. For bundled rules that means offline lookup from the installed copy, with docs shipped at the same version (6.1–6.3).\n- An inline suppression route is optional, but it must be disableable by configuration or detectable (7.1).\n\n## 2. Load-bearing statements\n\n**2a.** Conformance means satisfying every MUST in sections 4 to 7. A detector written before the document can conform, because grading rests on checking the detector against the clauses, not on the maintainer having heard of the document.\n\n> \"A [Detector] **Conforms** if it satisfies every MUST in sections 4 to 7.\" (§8)\n>\n> \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\" (§8.1)\n\n**2b.** The detector must do at least one of two things: make the route disableable by configuration, or make it detectable. Doing one is enough.\n\n> \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a bespoke [Rule] the project writes for the [Detector] under clause 4.1 or by a mechanical check the [Detector] documents.\" (§7.1)\n\n**2c.** The documentation must ship with the installed rule, at a version tracked with it, and it must resolve offline.\n\n> \"A [Bundled rule]'s documentation MUST ship with the rule, at a version tracked together.\" (§6.3)\n>\n> \"Every [Identifier] a [Bundled rule] can print MUST resolve, under clause 6.1, to a page in that shipped documentation.\" (§6.3)\n>\n> \"Resolution of a [Bundled rule]'s [Identifier] MUST work from the installed copy, without network access.\" (§6.2)\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n| --- | --- | --- |\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, and are \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs a single rule, accepts bundled or project names, and exits 1 on fire, 0 on no fire, and 2 on unknown. Its printed lines show what it printed. |\n| 4.3 | Yes | Bundled `LM-NNNN` IDs are fixed at first release. Project IDs are chosen in config, and \"Lintomatic does not add or rewrite any part of the name.\" The fixture names no other machine-readable format, so the \"every machine-readable format\" limb has nothing to test here. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | One line per finding goes to stdout \"whether or not the HTML report is enabled,\" and each line carries location and identifier, so there is no remainder. |\n| 5.4 | Yes | The dashboard is extra, and \"every finding it shows is also printed by the command line\"; CI runs the same binary. |\n| 6.1 | Yes | `lintomatic explain <identifier>` takes the identifier alone and returns bundled docs from the installed package, or the project's declared `doc` string. |\n| 6.2 | Yes | Docs come \"from the installed package,\" are \"versioned with the package,\" and \"there is no separate website.\" |\n| 6.3 | No | Two of the forty bundled rules print \"No documentation declared,\" so their identifiers resolve to nothing. The clause is all-or-nothing, and the project's CI does not check for this. |\n| 7.1 | Yes | `ignore_comments = false` disables `// lintomatic-ignore`, which is disableable by configuration and so satisfies the clause alone. |\n\n**Conformance (§8):** Lintomatic does not conform. Clause 6.3 is a MUST in section 6 and fails, and §8 requires every MUST in sections 4 to 7. The other ten clauses pass. There is no §8.1 declaration in the fixture, which is optional and does not affect the verdict.\n\n**Kind (§8.2):** The fixture does not say what a `Rule` can ask (its type information, name resolution, or only text or parse tree), so I cannot name Lintomatic's kind. This verdict therefore does not meet §8.2. The conformance finding above still stands.\n\n**Does 6.4 change the verdict?** No. Clause 6.4 is a SHOULD, and §8 counts only MUSTs. Lintomatic's CI lacks the check, which is an unmet SHOULD, and it shows the 6.3 failure was mechanically detectable and went undetected. If Lintomatic were shipped inside a Toolchain, the toolchain spec would make that check a MUST for the Toolchain, but §9 keeps that verdict separate. The fixture does not say it is in one.\n\n## 4. Confusion\n\n§8.2: \"The kind named is the one the [Detector] has in a configuration that needs no hosted service and no licence tier.\"","ttft_ms":41637,"type":"result","duration_ms":48537,"uuid":"5443a6a7-bc72-4ec0-8a3a-185bc7ead569","ttft_stream_ms":1228,"time_to_request_ms":335,"first_content_frame_ms":1228,"queued_turn_count":0,"result_index":0}

reader-5

{"duration_api_ms":51869,"stop_reason":"end_turn","session_id":"d7cd5ad5-5c0b-4b6b-8983-b4de8c3eeff7","total_cost_usd":0.008804629999999999,"usage":{"input_tokens":2,"cache_creation_input_tokens":12087,"cache_read_input_tokens":2153,"output_tokens":12731,"output_tokens_details":{"thinking_tokens":10483},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12087,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":12731,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12087,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12087},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":12731,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12087,"webSearchRequests":0,"costUSD":0.008804629999999999,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":10483,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A Detector must let the project write its own rules, registered with the same standing as bundled rules, and must provide a harness that runs one rule against supplied code (4.1, 4.2).\n- Every rule carries a stable identifier, not derived from its path, class name or position. The Detector prints it unaltered with every finding, in default and machine-readable output (4.3).\n- The Detector runs locally with no infrastructure, including on a single file, and puts results in the output of the command the practitioner ran (5.1 to 5.3). No finding may be reportable only through a hosted service, licence tier or CI-only mode (5.4).\n- A printed identifier must resolve to its documentation through a lookup keyed on the identifier alone. Bundled rules' documentation ships in the installed copy, at a version tracked with the rule, and every identifier a bundled rule can print must resolve (6.1 to 6.3).\n- Any inline suppression route must be either disableable by configuration or detectable (7.1).\n- A Detector conforms only if it satisfies every MUST in sections 4 to 7, and any verdict on it must name its kind (8, 8.2).\n\n## 2. Load-bearing statements\n\n**2a.** Conformance means satisfying every MUST in sections 4 to 7. A Detector written before the document, and unaware of it, can conform. Anyone can grade it against the clauses, and the verdict rests on that check rather than on any declaration.\n- \"A [Detector] [Conforms] if it satisfies every MUST in sections 4 to 7.\"\n- \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\"\n\n**2b.** The Detector must do at least one of two things: make the route disableable by configuration, or make it detectable. Doing either one is enough. Detectability through a bespoke rule works only where the Detector's rules can see comments. The written-reason requirement is a SHOULD (7.2), so it is not needed for conformance.\n- \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a bespoke [Rule] the project writes for the [Detector] under clause 4.1 or by a mechanical check the [Detector] documents.\"\n- \"That way is open only where the [Detector]'s [Rules] can see the route: a [Detector] whose [Rules] never see comments cannot make an ignore comment detectable this way.\"\n\n**2c.** The documentation must ship with the rule, at a version tracked together with it, and resolution must work from the installed copy without network access. A website catalogue does not meet this.\n- \"A [Bundled rule]'s documentation MUST ship with the rule, at a version tracked together.\"\n- \"Resolution of a [Bundled rule]'s [Identifier] MUST work from the installed copy, without network access.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are written as a Zig module implementing `Rule`, listed in `lintomatic.toml`, and are \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule, bundled or project, on a given path, and exits 1 or 0 on fired or not. The fixture does not say explicitly that the findings are printed under `--only`, but I read that as implied. |\n| 4.3 | Yes | `LM-NNNN` is fixed at first release. Project names are chosen by the project and \"Lintomatic does not add or rewrite any part of the name.\" The one-line output prints the identifier. The fixture describes no machine-readable format, so none needs printing. |\n| 5.1 | Yes | \"runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | The one-line output carries location, identifier and message for every finding, so no remainder exists. |\n| 5.4 | Yes | \"every finding it shows is also printed by the command line.\" |\n| 6.1 | Yes | `lintomatic explain <identifier>` resolves an identifier as printed, for bundled and project rules. The two bundled rules that print \"No documentation declared\" are a 6.3 failure, not a 6.1 failure. |\n| 6.2 | Yes | `explain` reads \"from the installed package,\" and \"there is no separate website.\" |\n| 6.3 | No | Two of the forty bundled identifiers resolve to \"No documentation declared\" and so to no page. Under this clause one unresolved identifier fails it, however many others resolve. |\n| 7.1 | Yes | `ignore_comments = false` in `lintomatic.toml` disables `// lintomatic-ignore`. |\n\nI read \"two of the forty rules\" as bundled rules, since the fixture says the tool ships forty. If they were project rules, 6.3 would not apply.\n\n**Verdict under section 8: Lintomatic does not conform.** It meets every MUST except 6.3, and conformance requires all of them. Partial conformance may not be described as conformance. The verdict also does not meet 8.2, since the fixture does not say whether a Lintomatic rule is Syntactic, Textual or Semantic. The clause on kind is binding on the verdict-giver, so I have to flag the omission. The finding about conformance still stands.\n\n**Clause 6.4:** No, it does not change the verdict. It is a SHOULD, and section 8 counts only MUSTs. The missing release check is a gap, but 6.3 already decides the verdict. The fixture also does not say Lintomatic is shipped inside a Toolchain, so the self-audit MUST does not apply.\n\n## 4. Confusion\n\nClauses 6.3 and 6.5 leave me unsure whether a prose-only family page can satisfy 6.3's MUST:\n\n- 6.3: \"One [Identifier] that does not resolve fails this clause, however many others do. A page per [Identifier] satisfies this; a page per family of [Identifiers] MAY be used instead, under clause 6.5.\"\n- 6.5: \"Such a page SHOULD let the check clause 6.4 asks for confirm that each member resolves to it.\" The prose row in its table is marked \"No: a person can infer it, a check cannot.\"\n\n6.3 allows a family page, yet 6.1 requires a lookup keyed on the identifier exactly as printed, and 6.5 makes checkability only a SHOULD and lists prose as not checkable. I could not tell from the text whether a prose-only family page meets 6.3's MUST.","ttft_ms":44027,"type":"result","duration_ms":52248,"uuid":"fbee4b39-3b85-44a2-aa63-f364276d79ec","ttft_stream_ms":989,"time_to_request_ms":374,"first_content_frame_ms":989,"queued_turn_count":0,"result_index":0}

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 074: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

The exact prompt every reader received, part 1 of 1

===== FILE: QUESTIONS.md =====
# Questions for a cold reader of DETECTOR-SPEC.md

You have been given `DETECTOR-SPEC.md` and the fixture `fixtures/lintomatic.md`, and nothing
else. Read the document once, then answer every question below in order. Quote the document
where a question asks for a quotation. Do not consult anything outside what you were given.

## 1. Restatement

In your own words and in at most six bullets, what does this document require of a detector?

## 2. Load-bearing statements

Answer each part of each question in a sentence or two, then quote the sentence or sentences of
the document you are relying on.

- 2a. What does it mean for a detector to conform, and can a detector that was written before
  this document existed, and has never heard of it, conform?
- 2b. A detector ships an inline ignore comment. What must the detector do about that route for
  it to conform, and is doing one of those things enough?
- 2c. Where must the documentation for a bundled rule's identifier live, at what version, and
  must it be reachable without network access?

## 3. Application

Grade Lintomatic 4.2, as described in the fixture, against every MUST clause in sections [4](../../DETECTOR-SPEC.md#4-authoring-rules) to [7](../../DETECTOR-SPEC.md#7-suppression):
[4.1](../../DETECTOR-SPEC.md#41-the-detector-must-support-bespoke-rules-written-by-the-project-that-runs-it), [4.2](../../DETECTOR-SPEC.md#42-the-detector-must-provide-a-harness-that-runs-a-single-rule-against-supplied-code), [4.3](../../DETECTOR-SPEC.md#43-the-detector-must-allow-a-rule-to-carry-a-stable-identifier-and-must-print-it-with-every-finding), [5.1](../../DETECTOR-SPEC.md#51-the-detector-must-be-invocable-by-the-practitioner-locally-with-no-infrastructure), [5.2](../../DETECTOR-SPEC.md#52-the-detector-must-support-invocation-over-a-subset-at-minimum-a-single-file), [5.3](../../DETECTOR-SPEC.md#53-the-result-must-reach-the-practitioner-in-the-output-of-the-command-they-ran), [5.4](../../DETECTOR-SPEC.md#54-a-finding-must-not-be-reportable-only-through-a-hosted-service-licence-tier-or-ci-only-mode-the-practitioner-cannot-invoke-locally), [6.1](../../DETECTOR-SPEC.md#61-the-detector-must-provide-a-mechanism-that-resolves-a-printed-identifier-to-its-documentation), [6.2](../../DETECTOR-SPEC.md#62-resolution-of-a-bundled-rules-identifier-must-work-from-the-installed-copy-without-network-access), [6.3](../../DETECTOR-SPEC.md#63-a-bundled-rules-documentation-must-ship-with-the-rule-at-a-version-tracked-together), [7.1](../../DETECTOR-SPEC.md#71-a-detector-may-offer-an-inline-suppression-route-but-must-make-it-detectable-or-disableable). For each give Yes, Partial or No and one
line of reasoning. Then state whether Lintomatic conforms under section [8](../../DETECTOR-SPEC.md#8-conformance), and why. Finally,
state whether clause [6.4](../../DETECTOR-SPEC.md#64-the-detector-should-fail-its-own-release-if-a-bundled-rule-lacks-resolvable-documentation) changes the verdict, and why.

## 4. Confusion

Quote any passage you found confusing on your one reading, with its clause number. Do not say what
would make it clearer and do not propose any change. Write "none" if there was none.

===== FILE: DETECTOR-SPEC.md =====
# Defence Before Fix: Detector Specification

**Version**: 1.1.0-dev, unpublished
**Companion to**: [the method specification](SPEC.md), version 1.1.0, and [the toolchain specification](TOOLING-SPEC.md), version 0.2.0
**Author**: [Joseph Edmonds](https://ltscommerce.dev), [Edmonds Commerce](https://edmondscommerce.co.uk)
**Coined**: 22 February 2026, in [the original article](https://ltscommerce.dev/articles/defence-before-fix-static-analysis)

## 1. What this document is for

The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT and MAY are to be interpreted as
described in RFC 2119.

[The method specification](SPEC.md) states what a [Practitioner] does when a
[Defect] is found. This document states what a [Detector] must offer so that they
can do it: write a [Rule], prove it, run it and resolve what it prints. It is addressed to
whoever maintains a [Detector], which the method specification defines as a tool that reads
code without executing it and reports occurrences of a pattern.

A [Detector] is one part of a project's [Toolchain], and this document judges it
on what it offers alone. What the assembled [Toolchain] of a project must add on top, the
[Project record], the listing of active [Defences] and the
governance of [Suppression], is stated in [the toolchain specification](TOOLING-SPEC.md).
The two are separable because they are built by different people: a [Detector] is authored once
and installed into projects its maintainer will never see, whilst governance is decided by each
project for itself. A document that asked a [Detector] to enforce a project's governance would
fail every [Detector] in use and would not make any project better governed.

**The governing principle**: where the method specification requires a [Practitioner] to do
something with a [Rule], a [Conforming] [Detector] MUST make that possible
without the project building the mechanism first. A [Detector] that leaves the project to
construct the means has moved the obligation rather than met it.

## 2. Terminology

Terms from the method specification carry over unchanged. Every capitalised term links to its
definition; the ones this document leans on most are, in short:

| Term                               | In one line                                                                                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Detector]                         | A tool that reads code without executing it and reports what it finds                                                                               |
| [Semantic], [Syntactic], [Textual] | What a [Rule] can ask in a configuration: the type of any expression and what any name refers to, program-wide; a parse tree short of that; or text |
| [Rule]                             | One check a [Detector] evaluates                                                                                                                    |
| [Message]                          | The text a [Rule] prints when it fires                                                                                                              |
| [Identifier]                       | The stable name printed with a finding that leads to its documentation                                                                              |
| [Practitioner]                     | Whoever is doing the work, a person or an [Agent]                                                                                                   |
| [Toolchain]                        | Everything a project assembles to run its checks through, [Detectors] included                                                                      |
| [Suppression]                      | Making a finding go away without fixing it                                                                                                          |
| [Baseline]                         | A recorded set of existing findings a [Rule] is told to ignore                                                                                      |

These are additional.

#### Rule author

Whoever writes a [Rule]. May be the [Detector]'s maintainer, a [Toolchain]'s maintainer or the project that runs it.

#### Bundled rule

A [Rule] the [Detector] ships, or fetches on the [Practitioner]'s behalf from a source the [Detector]'s maintainer controls. Its [Rule author] will never see the codebases it runs in, so everything a [Practitioner] needs in order to act on it has to travel with it.

#### Harness

The route by which one [Rule] is run against supplied code and its findings observed, without the project's own test suite and without every other [Rule] running alongside it.

The distinction between a [Bundled rule] and a project's own [Rule] matters throughout.
The [Detector] owns the documentation of the first and can be held to shipping it; it cannot
know the documentation of the second, and what it owes there is the mechanism that lets the project
attach its own.

## 3. The division of responsibility

Each mechanism the method needs has two halves. Stating only one of them is what produces a
[Detector] that is *nearly* usable.

| Requirement                          | The [Detector] MUST provide                             | The [Rule author] supplies                              |
| ------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------- |
| Bespoke [Rules]                      | The authoring and registration route                    | The [Rules]                                             |
| The red proof                        | A [Harness]                                             | The [Fixture] and the red run                           |
| Running the [Rule]                   | A local invocation, including subsets                   | Running it                                              |
| The [Identifier]                     | A place to carry it, and printing it with every finding | Choosing it once                                        |
| Resolution of a [Bundled rule]       | The lookup, and the documentation, on disk              | Nothing                                                 |
| Resolution of a project's own [Rule] | The [Identifier] printed unaltered                      | The documentation and, with the [Toolchain], the lookup |
| Inline [Suppression]                 | A route the project can detect or disable               | The decision whether to forbid it                       |

Read down the middle column and the shape of a [Conforming] [Detector] is already
visible. Read across any row and the failure mode is visible too: either half alone leaves the
[Practitioner] stuck.

## 4. Authoring rules

### 4.1 The detector MUST support bespoke rules written by the project that runs it

Configuration of an existing [Rule] set is not sufficient. The project must be able to express a
pattern the [Detector]'s authors have never anticipated, and register it so that it runs with
the same standing as a [Bundled rule].

**Why**: the method turns a specific [Defect] into a [Class] [Defence], and the
[Classes] that matter most to a project are the ones peculiar to it. A [Detector] offering
only a fixed catalogue can defend against the industry's known [Hazards] and none of the
project's own, which is the half that carries its institutional knowledge.

### 4.2 The detector MUST provide a harness that runs a single rule against supplied code

A [Harness] reports, for a given input, whether the [Rule] fired and what it printed, without
executing the project's own test suite and without the other [Rules] obscuring the answer.
This applies to every [Rule] the [Detector] runs, bundled or bespoke; a
[Detector] failing 4.1 is graded here on its [Bundled rules].

**Why**: clause 3.3 of the method specification requires the [Rule] to be proven by making it go
red. A [Practitioner] who can only observe a [Rule]'s behaviour by running every
[Rule] over the whole codebase cannot demonstrate that a [Rule] fires on the pattern rather
than on something incidental.

### 4.3 The detector MUST allow a rule to carry a stable identifier, and MUST print it with every finding

The [Identifier] MUST be stable across releases and MUST NOT be derived from the [Rule]'s
file path, [Class] name or position in a configuration file. The [Rule author] chooses it
once. The [Detector] MUST print it, unaltered, alongside every finding the [Rule]
reports, in its default output and in every machine-readable format it offers. A prefix the
[Detector] adds from the invoking directory or the [Rule]'s location is derived from
the file path. A namespace the [Rule author] assigns once in configuration is not.

| [Identifier]       | Where it comes from                                                         | Stable? |
| ------------------ | --------------------------------------------------------------------------- | ------- |
| `proj.no-raw-sql`  | Written once by the [Rule author] in configuration, printed as is           | Yes     |
| `LM-0107`          | Assigned once by the [Detector]'s maintainer at the [Rule]'s first release  | Yes     |
| `rules/no-raw-sql` | Produced from where the [Rule]'s file sits; changes when the file moves     | No      |
| `NoRawSqlRule`     | The [Rule]'s [Class] name; changes when the [Rule] is renamed               | No      |
| `rule 7`           | The [Rule]'s position in a configuration file; changes when one is inserted | No      |

**Why**: the [Identifier] is the only string that reaches the [Practitioner] and the
only key their lookup can use. An [Identifier] that changes when a [Rule] is renamed
silently breaks every reference to it, including references written down by people who have left.
An [Identifier] the [Detector] carries but does not print is one the
[Practitioner] was never given.

### 4.4 The detector SHOULD enforce 4.3 with a rule of its own

A [Rule] over the [Rules], failing any [Rule] that reports without a stable
[Identifier].

**Why**: this is the method applied to the [Detector] itself, and it is cheap. The worked
example is `php-qa-ci`'s `RequireRuleIdentifierConstantRule`, a [Rule] hosted in PHPStan by a
[Toolchain] built on it: it rejects a magic-string [Identifier] and names the
constant to declare instead. Nothing about it needed to live outside the [Detector].

## 5. Reporting

### 5.1 The detector MUST be invocable by the practitioner, locally, with no infrastructure

**Why**: method specification clause 8.1. An [Agent] that cannot check its own work cannot
iterate against a [Defence], so the loop never closes in the turn where it is cheap to close.

### 5.2 The detector MUST support invocation over a subset, at minimum a single file

**Why**: 5.1 is satisfied in principle by a whole-codebase run and defeated in practice by one.
Checking a single edited file has to be fast enough to do on every edit, or it will not be done on
any.

### 5.3 The result MUST reach the practitioner in the output of the command they ran

Where the [Detector] writes fuller detail elsewhere, the invoked command's own output MUST
carry a usable summary and, where the summary leaves any finding out, the location of the
remainder. The remainder is the findings the summary leaves out. Where the output prints every finding, each with where it is and its
[Identifier], there is no remainder. A fuller report written elsewhere, such as a page with a
code snippet for each finding, only adds detail to findings the output already carries; it is
not a remainder, and the output need not name it.

**Why**: method specification clause 8.2. A report the [Practitioner] has to go and find is a
report that arrives after the decision it was meant to inform.

### 5.4 A finding MUST NOT be reportable only through a hosted service, licence tier or CI-only mode the practitioner cannot invoke locally

A finding that the [Detector] reports only through a hosted service, a licence tier or a
continuous-integration-only mode the [Practitioner] cannot invoke locally does not
[Conform], whatever the same [Rule] reports there.

**Why**: a [Rule] that fires only in an environment the [Practitioner] has no access to
teaches nobody anything and blocks them anyway, which is the worst combination available.

## 6. Resolving an identifier

### 6.1 The detector MUST provide a mechanism that resolves a printed identifier to its documentation

Two cases, by who wrote the [Rule]:

| The [Rule]        | Who supplies the documentation | What this clause asks of the [Detector]                                   |
| ----------------- | ------------------------------ | ------------------------------------------------------------------------- |
| A [Bundled rule]  | The [Detector]                 | Resolve the [Identifier], presented alone, to that documentation          |
| The project's own | The project                    | Print the [Identifier] unaltered; the lookup is the [Toolchain]'s to give |

In both, the mechanism is keyed on **the [Identifier] exactly as printed**. A command, an index
file or a URL are all acceptable forms. This clause asks for the mechanism, not for every page:
where the mechanism works but a [Bundled rule]'s [Identifier] has no documentation to resolve
to, that fails clause 6.3, not this clause. Each case in turn:

**For a [Bundled rule]**, the [Detector] supplies the documentation and the
lookup, and clauses 6.2 and 6.3 say where. The mechanism MUST resolve an [Identifier]
presented alone, because the reader who needs it most has the [Identifier] from a log, a
ticket or a colleague and not the [Message]. A documentation path printed in the [Message] is
therefore not enough on its own: it helps only a reader who has the [Message], and the
[Identifier] alone still leads nowhere.

**For a project's own [Rule]**, the [Detector] cannot know the documentation, so what
it owes is the half it can give: the [Identifier] printed unaltered under clause 4.3, and no
transformation of it. Where the [Identifier] is itself a URL, as method clause 3.6 allows,
the [Detector] prints that URL and has done what this clause asks. What this clause forbids is
a documentation path printed alongside a shorter [Identifier] that cannot be looked up on its
own. The lookup for such
a [Rule] is an obligation on the project's assembled [Toolchain], under clause 4.2
of the [toolchain specification](TOOLING-SPEC.md), and a [Detector] that offers it as well has
gone further than this clause asks.

**Why**: method specification clause 8.3 requires the [Identifier] to resolve without a human.
An index keyed on anything else does not resolve it. This is the most commonly failed clause in this
document and it fails in a specific way: documentation exists, is genuinely good, and is keyed on the
[Rule]'s [Class] or file name, which is a string the [Practitioner] was never
given. The lookup they can actually perform is the only one that counts.

### 6.2 Resolution of a bundled rule's identifier MUST work from the installed copy, without network access

**Why**: an [Agent] working offline, behind a proxy, or against a URL that has since moved needs
the answer to be on disk. A dependency the project already installed is on disk by definition. A
catalogue on the [Detector]'s website, however complete, is the right documentation in the wrong
place.

### 6.3 A bundled rule's documentation MUST ship with the rule, at a version tracked together

Every [Identifier] a [Bundled rule] can print MUST resolve, under clause 6.1,
to a page in that shipped documentation. One [Identifier] that does not resolve fails this
clause, however many others do. A page per [Identifier] satisfies this; a page per family of
[Identifiers] MAY be used instead, under clause 6.5.

**Why**: method specification clause 3.6. A [Bundled rule] travels into codebases its author
will never see. If its documentation lives only in the [Detector]'s repository or on its
website, then every project that installs it is one link rot away from a [Rule] that blocks
without explaining.

The failure to guard against is not the absent document but the **dangling one**: a reference to
documentation that was planned and never written is worse than no reference, because it consumes
the [Practitioner]'s attention before failing them.

### 6.4 The detector SHOULD fail its own release if a bundled rule lacks resolvable documentation

An automated check over every [Identifier] a [Bundled rule] can print, applying the
family list or pattern of clause 6.5 where one is used, that blocks the release when any of them
lands on no page.

**Why**: clause 6.3 is the clause most easily believed to be satisfied whilst being broken, because
the documentation is written by the same person who wrote the [Rule] and its absence is
invisible from the inside. Where the [Detector] is shipped inside a [Toolchain],
the [toolchain specification](TOOLING-SPEC.md)'s self-audit makes this check a MUST for the [Toolchain]; a
[Detector] released on its own is asked for it as a SHOULD because the same [Class] of
[Defect], a [Rule] that blocks without explaining, is detectable mechanically there too.

### 6.5 One page MAY document a family of identifiers, and SHOULD name its members so a check can confirm them

Where several [Bundled rules] share a prefix, one page MAY document them all. Such a page
SHOULD let the check clause 6.4 asks for confirm that each member resolves to it. Two forms do
that and one does not:

| The page                                                                                                  | A check can confirm each member?          |
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| Lists every full [Identifier] in the family, `LM-0100`, `LM-0101`, and so on                              | Yes                                       |
| Carries a regular expression the check can run, `^LM-01[0-9]{2}$`, matching every member and nothing else | Yes                                       |
| Says in prose that everything under `LM-01` is documented here                                            | No: a person can infer it, a check cannot |

The list or the regular expression lives in the installed file, since clause 6.2 requires
resolution to work from the installed copy, not only in a rendered web page.

**Why**: a family page is a convenience for the author, and the cost of it must not fall on the
check. A page that a person can see covers `LM-0107` but a check cannot leaves clause 6.4 with
nothing to run.

## 7. Suppression

### 7.1 A detector MAY offer an inline suppression route, but MUST make it detectable or disableable

An inline ignore comment, a per-line directive and a generated [Baseline] are all such
routes. The [Detector] MAY ship them. For each one it MUST do at least one of two things, and either
alone satisfies this clause: make the route disableable by configuration; or make the route
detectable, by a bespoke [Rule] the project writes for the [Detector] under clause 4.1 or by a
mechanical check the [Detector] documents. Either lets a project which decides to forbid the
route enforce that decision. A route that can be neither switched off nor seen does not [Conform].

A route is detectable when the project can find every use of it. The first way is a bespoke
[Rule]: the project registers a [Rule] of its own, as clause 4.1 provides for, that matches the
route wherever it is used, and the [Detector] runs it like any other bespoke [Rule]; the
[Detector] ships no such [Rule] itself, and need not. That way is open only where the
[Detector]'s [Rules] can see the route: a [Detector] whose [Rules] never see comments cannot make
an ignore comment detectable this way. The second way is a mechanical check the [Detector]
documents, such as a text search for the route's spelling that the [Detector]'s documentation
gives.

**Why**: the method specification's position is that [Suppression] is an [Owner]
decision under its clause 3.4 and section 4, and an [Owner] cannot decide something they are
never shown. The [Detector] is not the [Owner] and does not know the project's
governance, so it is not asked to enforce it; what it is asked is not to hide the route. Every
[Detector] in wide use ships an inline ignore, and a clause that forbade them would fail all of
them without governing any project better. Whether the route is forbidden is the project's decision,
enforced through its [Toolchain] under clause 4.3 of the [toolchain specification](TOOLING-SPEC.md).

### 7.2 An inline suppression route SHOULD require a written reason

The [Detector] SHOULD reject, or be configurable to reject, an inline [Suppression]
that carries no reason, and SHOULD NOT supply a default one, including each entry of a generated
[Baseline].

**Why**: a [Suppression] without a reason is indistinguishable from one nobody would defend,
and the person who could tell them apart is usually gone. Requiring the sentence costs the author a
minute at the moment they have the reason in mind, and it is the only thing that makes the
[Suppression] reviewable later. PHPStan's `reportIgnoresWithoutComments` is the shape of it.

## 8. Conformance

**A [Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.

Partial [Conformance] MUST NOT be described as [Conformance]. A [Detector]
that satisfies most of this document is in a normal and respectable condition; it is not
[Conforming], and describing it as such removes the only value the word has.

This document defines one level. The clauses of the method specification's section 8 that reach
beyond what sections 4 to 7 here already secure, the listing of active [Defences] and the
summary for an [Agent]'s context, are obligations on a project's assembled [Toolchain]
and are stated in the [toolchain specification](TOOLING-SPEC.md), so there is no separate [Agent]-support level
for a [Detector].

### 8.1 A maintainer MAY declare the version of this document the detector conforms to, and the declaration records known gaps

The declaration is machine-readable, in whatever form the [Detector]'s ecosystem uses to
record dependencies. The same declaration is where a gap against this document is recorded once it is
known: a [Detector] that declares a version and has learnt, from its own checks or from a
[Practitioner]'s report under the method's clause 3.2, that it fails a MUST in sections 4 to 7
MUST record that gap alongside that version, in the same file or one it names. A declaration
with a non-empty gap record is a statement of where the [Detector] stands and is not a claim of
[Conformance].

The declaration is optional, and so the duty above falls only on a [Detector] that has one; a
gap a [Practitioner] finds is recorded in their own project's record under the method's clause
3.2 in any case. The declaration is how a maintainer claims [Conformance]; it is not a condition
of it, and a [Detector] without one can still be found to [Conform]. A [Detector] that predates
this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence:
anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on
any [Detector], declared or not, rests on that check and not on the claim. A declaration tells the reader what the maintainer believes and what they know to be missing;
the reader still runs the [Harness].

The shape is illustrative rather than prescribed. In a Composer manifest:

```json
"extra": {
  "defence-before-fix": {
    "method": "1.0.1",
    "detector": "1.0.0",
    "toolchain": null,
    "known-gaps": []
  }
}
```

In a package.json, the equivalent is a top-level `defenceBeforeFix` object with the same keys. A
[Detector] that is not also shipped as a [Toolchain] leaves `toolchain` empty;
a project that ships both declares both. Each entry in `known-gaps` names the clause and states the
gap in a sentence.

**Why**: a [Conformance] claim in a README is a sentence; a claim in a manifest is a fact about
a specific installed artefact, checkable by anyone, including mechanically, and it fixes what
"[Conforming]" meant at the point the claim was made. Making the claim a condition of
[Conformance], though, would mean a [Detector] with every mechanism in place could
never [Conform] until its maintainer had heard of this document, which grades the
maintainer's reading rather than the [Detector].

### 8.2 A verdict on a detector MUST name its kind in each configuration it grades

A verdict on a [Detector] MUST name its kind, [Semantic], [Syntactic] or [Textual], in each
configuration the verdict grades. A configuration here is one way of setting the [Detector] up to
run, and one [Detector] can have several: ESLint, for example, is [Syntactic] run without type
information and [Semantic] run with it. The kind is what a [Rule] can ask in that configuration,
as section 2 glosses it. The kind named is the one the [Detector] has in a configuration that
needs no hosted service and no licence tier.

This clause binds whoever gives the verdict, anyone grading a [Detector] under clause 8.1
included, and asks nothing of the [Detector] itself; a maintainer who gives a verdict on their
own [Detector] is bound by it like anyone else. A declaration under clause 8.1 is the
maintainer's claim, not a verdict, and this clause asks nothing of it. A [Detector] is judged on
sections 4 to 7, as section 8 says, and the kind is not a level of [Conformance] and does not
change the verdict: a [Textual] [Detector] can [Conform], and a [Semantic] one can fail to. A
verdict that leaves the kind out has not met this clause, but what it finds about [Conformance]
still stands.

**Why**: the method specification's clause 3.2 requires a [Rule] to be written in the strongest
kind of [Detector] that can express it, and a verdict of [Conforming] alone presents a [Textual]
[Detector] as an equal host.

## 9. Relationship to the other specifications

This document adds no obligation to the method a [Practitioner] follows, and relaxes none.
Clause 8.2 adds one on whoever gives a verdict on a [Detector], a [Practitioner] included when
they give one: to name its kind. Every clause here
exists to make a clause of the method specification achievable with a [Detector] in hand.

Where this document and the method specification disagree, the method specification governs. It
describes the method, which is the thing being specified; this describes one piece of the equipment.

The [toolchain specification](TOOLING-SPEC.md) states what a project's assembled [Toolchain] must offer beyond
what each [Detector] in it offers, and it requires every [Detector] a [Defence]
is routed through to [Conform] to this one. A [Detector] shipped inside a
[Toolchain] is measured here as a [Detector] and there as part of the
[Toolchain]; the two verdicts are separate and neither implies the other.

Nothing here requires a project to use a [Conforming] [Detector]. A project can
[Conform] to the method specification on a [Detector] that [Conforms] to
none of this, at the cost of building the missing mechanisms itself. This document exists so that it
does not have to.

<!-- Term link definitions -->

[agent]: SPEC.md#agent
[baseline]: SPEC.md#baseline
[bundled rule]: #bundled-rule
[bundled rules]: #bundled-rule
[class]: SPEC.md#class
[classes]: SPEC.md#class
[conform]: SPEC.md#conform
[conformance]: SPEC.md#conform
[conforming]: SPEC.md#conform
[conforms]: SPEC.md#conform
[defect]: SPEC.md#defect
[defence]: SPEC.md#defence
[defences]: SPEC.md#defence
[detector]: SPEC.md#detector
[detectors]: SPEC.md#detector
[fixture]: SPEC.md#fixture
[harness]: #harness
[hazards]: SPEC.md#hazard
[identifier]: SPEC.md#identifier
[identifiers]: SPEC.md#identifier
[message]: SPEC.md#message
[owner]: SPEC.md#owner
[practitioner]: SPEC.md#practitioner
[project record]: TOOLING-SPEC.md#project-record
[rule]: SPEC.md#rule
[rule author]: #rule-author
[rules]: SPEC.md#rule
[semantic]: SPEC.md#semantic
[suppression]: SPEC.md#suppression
[syntactic]: SPEC.md#syntactic
[textual]: SPEC.md#textual
[toolchain]: SPEC.md#toolchain

===== FILE: fixtures/lintomatic.md =====
# Fixture: Lintomatic 4.2

A fictional detector, described only as far as its documentation would describe it. Grade it
against the document you were given. Where the description does not say, answer from what is
written, not from what a real tool would probably do.

## What the documentation says

Lintomatic is a static analyser for the Zig language. It ships forty rules. A project can add its
own rules by writing a Zig module that implements the `Rule` interface and listing it in
`lintomatic.toml`; project rules are loaded on every run and are first class in every report.

The command `lintomatic check <path>` analyses a directory or a single file and prints one line
per finding to standard output:

```
src/db.zig:41:7  LM-0107  Result of fallible call discarded
src/db.zig:88:3  proj.no-raw-sql  Query built by string concatenation
```

Fuller detail, including a snippet, is written to `.lintomatic/report.html`. The one-line form is
printed whether or not the HTML report is enabled.

`lintomatic check --only <identifier> <path>` runs a single rule. It accepts any rule name,
bundled or project, and exits 1 when the rule fires, 0 when it does not, and 2 when the rule name
is unknown.

Rule identifiers for bundled rules are of the form `LM-NNNN` and are fixed at the rule's first
release. Project rules are identified by the name the project gives them in `lintomatic.toml`,
which the documentation recommends prefixing with `proj.`; Lintomatic does not add or rewrite
any part of the name.

`lintomatic explain <identifier>` prints the documentation for a bundled rule from the installed
package: what the rule finds, why, and the recommended construction. For a project rule it prints
the `doc` string the rule module declares, or `No documentation declared` when there is none.
The documentation is versioned with the package; there is no separate website.

Lintomatic honours the comment `// lintomatic-ignore <identifier>` on the line before a finding.
The comment can be disabled project-wide with `ignore_comments = false` in `lintomatic.toml`.
The comment takes no reason and the documentation does not mention one.

The tool runs locally with no account, no server and no network, and the same binary is what CI
runs. There is a hosted dashboard, sold separately, that aggregates reports across repositories;
every finding it shows is also printed by the command line.

Lintomatic's own repository runs `lintomatic check src` in CI but does not check that every
`LM-NNNN` identifier has an `explain` entry; two of the forty rules currently print
`No documentation declared`.

LTSCommerce and others added 2 commits October 12, 2026 14:33
Every keyed item at threshold, one miss on clause 4.2 tolerated. No finding in text the change
wrote; clause 4.2's sentence on a detector failing 4.1, named by two readers, routed to
acceptance/ROUTED.md. The changelog names the run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every keyed item five of five; no passage named by more than one reader. The changelog names
runs 70 and 79 as the accepting runs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 079: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Command and working directory

ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt, each reader in a fresh empty directory made by mktemp -d outside the repository

Model version each reader reported

reader 1: claude-haiku-5-5
reader 2: claude-haiku-5-5
reader 3: claude-haiku-5-5
reader 4: claude-haiku-5-5
reader 5: claude-haiku-5-5

The delegated marker's task prompt

MODE: mark

Worktree: <worktree> (branch `editor/semantic-detectors`). The readers read commit 23f5bf0. Read, at that commit and in that worktree: ACCEPTANCE.md in full (it is the process as pull request 22 proposes it, merged into this branch; the run is made under that process change before it merges, at commit 13b497b, named here before the readers started), acceptance/runs/README.md, acceptance/detector/QUESTIONS.md, acceptance/detector/KEY.md, acceptance/fixtures/lintomatic.md and DETECTOR-SPEC.md. You may read acceptance/runs/074-semantic-detectors-run-15.md for the shape of a record only; ACCEPTANCE.md says a judgement an earlier run recorded is not a rule. Do not take any judgement from the records of runs 19 to 30, which were made under the earlier rules on an earlier text of this change.

Run: 079. Document: DETECTOR-SPEC.md. Change: semantic detectors (pull request 14): a new clause 8.2 that a verdict on a detector names its kind, semantic, syntactic or textual, in each configuration it grades; section 9's sentence on that obligation; section 2's table row for the three kinds. The branch carries `editor/detector-route-rule` (pull request 23) at its commit d67174c, which reworked clauses 5.3, 6.1, 7.1 and 8.1 and added section 2's Message row; those paragraphs are that change's text and not this one's. After run 74, clause 8.2's sentence on which configuration's kind is named (one needing no hosted service and no licence tier) was removed; the clause now ends at "as section 2 glosses it". Pull request: 14, open on this branch. Run within the pull request: 16 (runs 19 to 30, 70, 71 and 74 came before). Record file to write: <worktree>/acceptance/runs/079-semantic-detectors-run-16.md. Do not commit it.

Sheets: <run-dir>/sheet-1.md to sheet-5.md, each the `result` of reader-N.json. Prompt the readers received: <run-dir>/prompt.txt; its sha256 is 7b94f116b3f875170cdfd9fd61f845b3cf7f233723bbd749dd38e93adb6a1fd9, and it is byte for byte the question set, DETECTOR-SPEC.md and the fixture at 23f5bf0, each after one `===== FILE: <name> =====` line, one blank line between files. Model: every reader reported claude-haiku-5-5 (<run-dir>/models.txt); the cohort's model named for the run is claude-haiku-5-5. Every reader's JSON has is_error false and subtype success; no reader is set aside.

Facts the editor supplies for the record: cohort of five readers, none reused from any earlier run; each a separate `claude -p` process started through the `ccy-claude` wrapper (which only reattaches the session's credential) in a fresh empty directory made by `mktemp -d` outside any repository, with the packet on standard input, command `ccy-claude -p --model haiku --safe-mode --tools '' --setting-sources '' --strict-mcp-config --no-session-persistence --output-format json < prompt.txt`; no tools, no settings, no project context, no saved session; the machine applies no managed settings; the editor attests the rest of the isolation. Marked by a delegated marker, a fresh `dbf-spec-editor` agent (Claude Opus) given this instruction and its agent definition, having seen neither the drafting nor the review.

Text the change wrote or altered, for "Where a finding lies": the output of `git -C <worktree> diff d67174c 23f5bf0 -- DETECTOR-SPEC.md`, which is this change's text over and above the merged detector change at its commit d67174c (the merge base with `next` is 9e914fe; `git -C <worktree> diff 9e914fe 23f5bf0 -- DETECTOR-SPEC.md` shows both changes together, and a paragraph only the detector change altered is not this change's text). Run both and read them. A finding in a paragraph the first diff adds, rewords, moves, deletes a sentence from, or changes a word, term or link in, or that a reader sets against such a paragraph, blocks; any other finding is routed to acceptance/ROUTED.md, and the record says so for each.

Mark every sheet, keyed item by keyed item, by ACCEPTANCE.md's Marking section and the key alone. Write the record stating what acceptance/runs/README.md lists, in its order, with the marks table, each miss with the reader's reasoning in brief, every passage named under question 4 with its words or description, clause and readers, each finding and where it lies, any open point and the direction you marked it, the process-change commit 13b497b, the verdict, and a placeholder line for the link to the posted sheets. Reply with the verdict and the findings only, under thirty lines.

The agent definition the marker ran under

---
name: dbf-spec-editor
description: Drafts, reviews, key-updates, marks or records a change to the Defence Before Fix specification, one mode per dispatch, following the specification repository's CONTRIBUTING.md, SCOPE.md and ACCEPTANCE.md exactly. Dispatched by the dbf-spec-change skill; it cannot run a cohort itself.
tools: Read, Write, Edit, Grep, Glob, Bash
model: opus
---

You work on one change to the Defence Before Fix specification, in the worktree path you are
given. The process is defined by that repository's `CONTRIBUTING.md`, `SCOPE.md`,
`ACCEPTANCE.md` and `acceptance/README.md`; read the ones your mode needs, in full, before you
act. You invent no steps. You never merge, push to `next` or `main`, release, tag or publish,
and you never change a question, threshold or fixture. Your prompt names one mode.

Write as the specification is written: British spelling, no em dashes, defined terms linked as
`spec-qa.py` requires, every clause or section number linked to its heading. Commit on the
worktree's branch.

## MODE: draft

1. Read `SCOPE.md`, `DECLINED.md`, and every clause the change touches, with the terminology
   entries they use.
2. Decide the kind. A change to the sentences carrying MUST, SHOULD or MAY in a numbered section
   or appendix is an obligation; anything else is a clarity edit.
3. For an obligation, answer the five SCOPE tests in a sentence or two each, and name the
   section the obligation extends. If a test fails, or an existing clause already covers it, say
   so and stop without editing.
4. Make the smallest edit that does the job, in the clause that owns the subject.
5. Add one line under `Unreleased` in `CHANGELOG.md`, saying what changed and why, with a
   placeholder `run NNN`. Change no version.
6. If Appendix A changed, run `python3 build-agent-surfaces.py` and commit its outputs.
7. Run `python3 spec-qa.py` and `python3 -m unittest discover -s tools -p 'test_*.py'`; both
   clean.
8. Reply with: kind, SCOPE answers, files changed, commit, QA result, and a filled
   `.github/PULL_REQUEST_TEMPLATE.md` body. Under forty lines.

## MODE: review

You have not seen the drafting. Read the whole changed document and the diff against
`origin/next`. Report, numbered, with clause numbers: contradictions with other clauses, terms
used outside their definition, a sentence a reader could take two ways, an obligation an owner
could not verify from the record. Do not edit.

## MODE: keys

Read `acceptance/README.md` and the `KEY.md` for each changed document. Update every quotation of
a changed sentence to the new wording, verbatim, in a new commit; run `python3 spec-qa.py` until
`key-quote-stale` is gone. If the change adds a load-bearing obligation no keyed question
covers, propose the question and its key in your reply; add it only if your prompt says the owner
has decided to.

## MODE: mark

You have not seen the drafting or the review. Read `ACCEPTANCE.md` in full, the `KEY.md` for the
document, the document at the commit named, and the sheets under `<run-dir>/`.

- Mark each sheet against the key, keyed item by keyed item, by the rules under "Marking" in
  `ACCEPTANCE.md` and nothing else: the key's required elements, accepted grades, conditions and
  wrong readings. Invent no judgement; where neither the rules nor the key settles a mark, mark
  it in the direction the open-point rule gives and record the point.
- A single miss under questions 1 and 3 counts against the document only where the key names
  that reading; say which.
- For question 4, record every passage named, with its words or description and its clause.
  Group the passages by paragraph, count the readers, and for each finding say whether it lies in
  text the change wrote or altered, read against the diff the prompt gives you, or is routed to
  `acceptance/ROUTED.md`. Ignore any suggestion a reader volunteers.
- Apply the pass criteria exactly.
- Write `acceptance/runs/NNN-<slug>-run-<k>.md` stating what `acceptance/runs/README.md` lists,
  in its order. Do not commit it. Reply with the verdict and the findings only.

## MODE: record

Commit the run record, replace the `run NNN` placeholder in `Unreleased` with a link to it, and
for an obligation set the document header to `<next version>-dev, unpublished` in the same
commit if it still names a published version. Run `python3 spec-qa.py` and
`python3 tools/changes.py --base origin/next`; both clean. Reply with the commit.

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 079: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

Answer sheets, each reader's whole output as its process returned it

reader-1

{"duration_api_ms":50460,"stop_reason":"end_turn","session_id":"bce83790-8c30-4173-8d4d-08a95f26923a","total_cost_usd":0.00878723,"usage":{"input_tokens":2,"cache_creation_input_tokens":12050,"cache_read_input_tokens":2153,"output_tokens":12711,"output_tokens_details":{"thinking_tokens":10597},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12050,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":12711,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12050,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12050},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":12711,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12050,"webSearchRequests":0,"costUSD":0.00878723,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":10597,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector must let the project write its own rules, register them, and run them with the same standing as bundled rules.\n- It must offer a harness that runs one rule against supplied code and reports whether it fired and what it printed.\n- Each rule carries a stable identifier chosen by its author, not derived from its path, class name, or position, and the detector prints that identifier unaltered with every finding.\n- The practitioner must be able to run the detector locally on a whole codebase or a single file, and results must appear in the command's own output, never only through a hosted, licence-tier, or CI-only route.\n- Every identifier that can be printed must resolve, by itself and offline, to documentation. For bundled rules that documentation ships with the installed package, at the same version, and covers every identifier the rule can print.\n- Any inline suppression route must either be switchable off by configuration or be detectable by a project rule or a documented check.\n\n## 2. Load-bearing statements\n\n**2a.** A detector conforms when it satisfies every MUST in sections 4 to 7, and a detector written before the document can still conform, since conformance is judged on evidence rather than on a declaration.\n> \"A [Detector] [Conforms] if it satisfies every MUST in sections 4 to 7.\" (§8)\n\n> \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\" (§8.1)\n\n> \"The declaration is how a maintainer claims [Conformance]; it is not a condition of it, and a [Detector] without one can still be found to [Conform].\" (§8.1)\n\n**2b.** The detector must do one of two things: make the route disableable by configuration, or make it detectable. Doing either one is enough. A route that is disableable by configuration satisfies the clause without any detection mechanism. Detection by a bespoke rule is only available where the detector's rules can see comments at all.\n> \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a bespoke [Rule] … or by a mechanical check the [Detector] documents.\" (§7.1)\n\n> \"A route that can be neither switched off nor seen does not [Conform].\" (§7.1)\n\n> \"a [Detector] whose [Rules] never see comments cannot make an ignore comment detectable this way.\" (§7.1)\n\n**2c.** The documentation must ship inside the installed package, at the same version as the rule, and must be resolvable offline. A website does not satisfy this.\n> \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\" (§6.3)\n\n> \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access.\" (§6.2)\n\n> \"A catalogue on the [Detector]'s website, however complete, is the right documentation in the wrong place.\" (§6.2, Why)\n\n## 3. Application\n\n| Clause | Grade | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are written as a Zig module implementing `Rule` and registered in `lintomatic.toml`, and are \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule against a path and exits 1 on fire, 0 on no fire, 2 on unknown. The fixture does not explicitly say `--only` prints findings, but it is a flag on `check`, which prints one line per finding. |\n| 4.3 | Yes | Bundled identifiers are `LM-NNNN`, fixed at first release. Project identifiers are names set in configuration, and \"Lintomatic does not add or rewrite any part of the name.\" The one-line output prints them. The fixture names no other machine-readable format to check. |\n| 5.1 | Yes | \"runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | The one-line form prints every finding with location and identifier to standard output, regardless of the HTML report, so there is no remainder. |\n| 5.4 | Yes | The dashboard is sold separately, but \"every finding it shows is also printed by the command line,\" and CI runs the same binary. |\n| 6.1 | Yes | `lintomatic explain <identifier>` takes the identifier alone and returns the bundled documentation, or the project's `doc` string, or \"No documentation declared.\" |\n| 6.2 | Yes | Documentation is printed \"from the installed package,\" \"there is no separate website,\" and the tool runs with no network. |\n| 6.3 | No | Two of the forty bundled rules print \"No documentation declared.\" Versioning is fine, but \"One [Identifier] that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `ignore_comments = false` in `lintomatic.toml` disables `// lintomatic-ignore` project-wide, which alone satisfies the clause. |\n\n**Conformance under §8:** Lintomatic does not conform. Clause 6.3 is a MUST, and two bundled identifiers resolve to nothing, so the detector fails it. Partial satisfaction of the other MUSTs does not change that.\n\nI also cannot complete §8.2 for this verdict. The verdict must name the detector's kind (Semantic, Syntactic, or Textual) for each configuration it grades, and the fixture never says which kind applies. I have not guessed. The Conformance finding above still stands, but the verdict is incomplete on this point.\n\n**Does 6.4 change the verdict?** No. Clause 6.4 is a SHOULD, and §8 counts only MUSTs. The verdict rests on 6.3 alone. The fixture also shows that the repository does not run the check 6.4 describes, so 6.4 is a SHOULD that is not met. That gap is the same failure 6.3 captures, and it is recorded separately without altering the verdict. If Lintomatic were shipped inside a Toolchain, the toolchain specification would make the check a MUST for that Toolchain, but §9 treats that as a separate verdict. The fixture does not say whether it is shipped that way, so I have not assumed it.\n\n## 4. Confusion\n\nnone","ttft_ms":43068,"type":"result","duration_ms":50856,"uuid":"dda27ccb-36d7-4acf-9e82-173f38403be3","ttft_stream_ms":1309,"time_to_request_ms":396,"first_content_frame_ms":1309,"queued_turn_count":0,"result_index":0}

reader-2

{"duration_api_ms":47942,"stop_reason":"end_turn","session_id":"0ba3944b-a54c-4bb3-a85d-690d23051428","total_cost_usd":0.00834903,"usage":{"input_tokens":2,"cache_creation_input_tokens":12054,"cache_read_input_tokens":2153,"output_tokens":11833,"output_tokens_details":{"thinking_tokens":9594},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12054,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":11833,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12054,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12054},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":11833,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12054,"webSearchRequests":0,"costUSD":0.00834903,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":9594,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- The project must be able to write its own rules and register them to run with the same standing as bundled rules (4.1).\n- The detector must provide a harness that runs one rule against supplied code and reports whether it fired and what it printed (4.2).\n- Each rule carries a stable identifier, not derived from its file path, class name, or position, and the detector prints it unaltered with every finding, in default and machine-readable output (4.3; the self-check in 4.4 is a SHOULD).\n- The detector must be runnable locally with no infrastructure, on a subset down to a single file, with results in the output of the command run. No finding may be reportable only through a hosted service, licence tier, or CI-only mode (5.1–5.4).\n- A printed identifier must resolve to its documentation through a lookup keyed on the identifier alone. For bundled rules, the documentation ships in the installed copy, works without network access, and covers every identifier the rule can print. For project rules, the identifier is printed unaltered (6.1–6.3).\n- An inline ignore route, if offered, must be either disableable by configuration or detectable (7.1). Requiring a reason is a SHOULD (7.2).\n\n## 2. Load-bearing statements\n\n**2a.** A detector conforms when it satisfies every MUST in sections 4 to 7. A detector written before the document, which has never heard of it, can conform. §8: \"A [Detector] [Conforms] if it satisfies every MUST in sections 4 to 7.\" §8.1: \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7.\" Also: \"The declaration is how a maintainer claims [Conformance]; it is not a condition of it, and a [Detector] without one can still be found to [Conform].\"\n\n**2b.** The detector must make the route either disableable by configuration or detectable, and one of those is enough. §7.1: \"For each one it MUST do at least one of two things, and either alone satisfies this clause.\" Detectable means a bespoke rule or a documented mechanical check. The bespoke-rule route is only open where the rules can see comments: \"a [Detector] whose [Rules] never see comments cannot make an ignore comment detectable this way.\" The reason requirement is 7.2, which is a SHOULD, so it is not needed for conformance.\n\n**2c.** The documentation lives in the shipped documentation, at a version tracked with the rule, and it must be reachable offline. §6.3: \"A [Bundled rule]'s documentation MUST ship with the rule, at a version tracked together.\" Also: \"Every [Identifier] a [Bundled rule] can print MUST resolve, under clause 6.1, to a page in that shipped documentation.\" §6.2: \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, and \"first class in every report.\" The fixture places no limit on what a module can express. |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule, bundled or project, and exits 1 on fire, 0 on no fire. The fixture does not say explicitly that `--only` prints the finding lines. I took \"what it printed\" as satisfied by the check-format output; if `--only` prints nothing, this would be Partial. |\n| 4.3 | Yes | Bundled identifiers are `LM-NNNN`, \"fixed at the rule's first release.\" Project names are set in `lintomatic.toml`, and \"Lintomatic does not add or rewrite any part of the name.\" The one-line output prints them. The fixture names no other machine-readable format and does not say whether the HTML report prints identifiers. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" Speed is not a MUST. |\n| 5.3 | Yes | \"The one-line form is printed whether or not the HTML report is enabled.\" Each line carries location and identifier, so there is no remainder. |\n| 5.4 | Yes | The dashboard shows only findings that \"are also printed by the command line,\" and CI runs \"the same binary.\" |\n| 6.1 | Yes | `lintomatic explain <identifier>` is keyed on the identifier. It covers bundled rules and project rules, and project identifiers are printed unaltered. |\n| 6.2 | Yes | \"The documentation is versioned with the package; there is no separate website,\" and `explain` reads \"from the installed package.\" |\n| 6.3 | **No** | The tool ships forty rules, and \"two of the forty rules currently print `No documentation declared`.\" Those two bundled identifiers do not resolve to a page. Under 6.3, \"One [Identifier] that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | The route is `// lintomatic-ignore <identifier>`, and it can be disabled with `ignore_comments = false`. |\n\n**Conformance: No.** §8 requires every MUST in sections 4 to 7, and 6.3 fails. Lintomatic does not conform until every bundled identifier resolves to a page.\n\n**Kind (§8.2):** The fixture does not say whether Lintomatic's rules are Syntactic, Semantic, or Textual. This verdict therefore does not name a kind, so it does not meet 8.2. The finding of non-conformance still stands.\n\n**Effect of 6.4:** It does not change the verdict. 6.4 is a SHOULD, and §8 grades only MUSTs in sections 4 to 7. The repository's lack of a release check is a SHOULD gap. The failure comes from 6.3 itself. The fixture also does not say whether Lintomatic ships inside a toolchain, so the toolchain-level MUST in 6.4 does not apply here.\n\n## 4. Confusion\n\n§6.4: \"Where the [Detector] is shipped inside a [Toolchain], the [toolchain specification](TOOLING-SPEC.md)'s self-audit makes this check a MUST for the [Toolchain]; a [Detector] released on its own is asked for it as a SHOULD because the same [Class] of [Defect], a [Rule] that blocks without explaining, is detectable mechanically there too.\"\n\nThe \"because\" clause gives a reason for the SHOULD that reads just as well as a reason for a MUST. The fixture does not say whether Lintomatic sits inside a toolchain, so I read it as standalone.","ttft_ms":40949,"type":"result","duration_ms":48371,"uuid":"54d5bb80-2d14-43bf-845e-f59cd006e997","ttft_stream_ms":1202,"time_to_request_ms":433,"first_content_frame_ms":1203,"queued_turn_count":0,"result_index":0}

reader-3

{"duration_api_ms":42951,"stop_reason":"end_turn","session_id":"826a5fff-2459-4960-971c-13813821abb4","total_cost_usd":0.00754513,"usage":{"input_tokens":2,"cache_creation_input_tokens":12052,"cache_read_input_tokens":2153,"output_tokens":10226,"output_tokens_details":{"thinking_tokens":8210},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12052,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":10226,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12052,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12052},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":10226,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12052,"webSearchRequests":0,"costUSD":0.00754513,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":8210,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- Let the project write its own rules, registered with the same standing as bundled rules (4.1).\n- Provide a harness that runs one rule against supplied code and reports whether it fired and what it printed (4.2).\n- Give each rule a stable identifier that is not derived from file path, class name or position, and print it unaltered with every finding (4.3).\n- Run locally with no infrastructure, including on a single file, and put results in the command's own output. No finding may be reportable only through a hosted, licence-tier or CI-only route (5.1–5.4).\n- Resolve a printed identifier to its documentation. For bundled rules, the lookup works from the installed copy with no network, and every identifier the rule can print must resolve to shipped documentation (6.1–6.3).\n- If an inline suppression route is offered, make it disableable or detectable (7.1). A detector conforms when it meets every MUST in sections 4 to 7 (8).\n\n## 2. Load-bearing statements\n\n**2a.** Conforming means meeting every MUST in sections 4 to 7. A detector written before the document, and never read against it, can still conform, because conformance can be graded on evidence.\n- §8: \"**A [Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.\"\n- §8.1: \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\"\n\n**2b.** The detector must make the route either disableable by configuration or detectable. Doing either one is enough.\n- §7.1: \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a bespoke [Rule] … or by a mechanical check the [Detector] documents.\" It also says: \"A route that can be neither switched off nor seen does not [Conform].\"\n- The bespoke-rule route is open only if the detector's rules can see comments: \"a [Detector] whose [Rules] never see comments cannot make an ignore comment detectable this way.\"\n\n**2c.** The documentation must ship with the rule, at a version tracked together with it. It must resolve from the installed copy, without network access.\n- §6.3: \"A [Bundled rule]'s documentation MUST ship with the rule, at a version tracked together.\"\n- §6.2: \"Resolution of a [Bundled rule]'s [Identifier] MUST work from the installed copy, without network access.\"\n- §6.5 adds that a family list or pattern \"lives in the installed file, since clause 6.2 requires resolution to work from the installed copy, not only in a rendered web page.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | Project rules are Zig modules implementing `Rule`, listed in `lintomatic.toml`, and \"first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule, bundled or project, against a path. Its exit codes report whether it fired. |\n| 4.3 | Yes | Bundled IDs are `LM-NNNN`, fixed at first release. Project IDs are the configured name, unaltered. The fixture describes no machine-readable format, so the \"every machine-readable format\" limb has nothing to test. The HTML report is not machine-readable. |\n| 5.1 | Yes | Runs locally with \"no account, no server and no network,\" and CI uses the same binary. |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | One line per finding, with location and identifier, is printed to stdout whether or not the HTML report is enabled. Nothing is left as a remainder. |\n| 5.4 | Yes | The hosted dashboard is extra. \"every finding it shows is also printed by the command line.\" |\n| 6.1 | Yes | `lintomatic explain <identifier>` resolves a bundled ID alone, and prints the `doc` string or the \"No documentation declared\" line for project rules. The two undocumented bundled rules fall under 6.3, not here. |\n| 6.2 | Yes | \"The documentation is versioned with the package; there is no separate website.\" `explain` reads from the installed package. |\n| 6.3 | **No** | Two of the forty bundled rules \"currently print `No documentation declared`.\" The clause is all-or-nothing: \"One [Identifier] that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `ignore_comments = false` disables `// lintomatic-ignore` project-wide, which is the disableable route. |\n\n**Verdict under §8:** Lintomatic does **not** conform. Clause 6.3 fails, and §8 requires every MUST in sections 4 to 7 to hold.\n\n**Kind (§8.2):** The fixture does not say what a rule can ask: types, bindings, parse tree, or text. I cannot name the kind from it. So this verdict does not meet §8.2, though the findings above stand, as §8.2 says they do.\n\n**Does 6.4 change the verdict?** No. Clause 6.4 is a SHOULD, not a MUST, and §8 counts only MUSTs in sections 4 to 7. Lintomatic's CI running `lintomatic check src` without checking identifier documentation is therefore a SHOULD gap, not a conformance failure. The verdict rests on 6.3 alone. Clause 6.4 is the check that would catch the failure, but its absence does not add a failure, and its presence would not rescue one.\n\n## 4. Confusion\n\n- §7.1: \"is an [Owner] decision under its clause 3.4 and section 4.\" \"Owner\" is defined in the method specification, which was not supplied, so I could not resolve it.\n- §4.1: \"the [Classes] that matter most to a project…\" \"Class\" is likewise defined only in the method specification.","ttft_ms":36066,"type":"result","duration_ms":43455,"uuid":"67a2348a-1e22-4989-8e43-4db5b22fec75","ttft_stream_ms":1273,"time_to_request_ms":504,"first_content_frame_ms":1273,"queued_turn_count":0,"result_index":0}

reader-4

{"duration_api_ms":42019,"stop_reason":"end_turn","session_id":"5d8a42f7-47fb-4328-b7f8-31707704ae82","total_cost_usd":0.00760483,"usage":{"input_tokens":2,"cache_creation_input_tokens":12053,"cache_read_input_tokens":2153,"output_tokens":10345,"output_tokens_details":{"thinking_tokens":8265},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12053,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":10345,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12053,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12053},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":10345,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12053,"webSearchRequests":0,"costUSD":0.00760483,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":8265,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A project must be able to write its own rules and register them to run with the same standing as bundled rules (4.1).\n- The detector must provide a harness that runs one rule against supplied code and reports whether it fired and what it printed (4.2).\n- Each rule must carry a stable identifier, not derived from its file path, class name or position, and the detector must print it unaltered with every finding, in default and machine-readable output (4.3).\n- The detector must run locally with no infrastructure, including over a single file, and the command's own output must carry the findings or a usable summary plus the location of the remainder. No finding may be reportable only through a hosted, licence-tier or CI-only route (5.1 to 5.4).\n- A printed identifier, presented alone, must resolve to its documentation. For bundled rules that documentation must work from the installed copy without network access and ship at the same version as the rule, with every identifier the rule can print resolving (6.1 to 6.3).\n- Any inline suppression route must be either disableable by configuration or detectable (7.1).\n\n## 2. Load-bearing statements\n\n**2a.** A detector conforms when it satisfies every MUST in sections 4 to 7. A detector written before the document, and never checked against it, can conform. Section 8.1 says: \"A Detector that predates this document, or whose maintainer has never read it, MAY be graded Conforming on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7.\" It also says a detector \"without one can still be found to Conform.\"\n\n**2b.** The detector must either make the route disableable by configuration or make it detectable. One of those is enough. Section 7.1 says: \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable…\" It adds: \"A route that can be neither switched off nor seen does not Conform.\" The detectable route by a bespoke rule is only open if the detector's rules can see the route: \"a Detector whose Rules never see comments cannot make an ignore comment detectable this way.\"\n\n**2c.** The documentation must be on disk in the installed package, at a version tracked with the rule, and resolution must not need the network. Clause 6.2 says: \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access.\" Clause 6.3 says: \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n|---|---|---|\n| 4.1 | Yes | \"A project can add its own rules by writing a Zig module that implements the `Rule` interface… project rules are loaded on every run and are first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule, bundled or project, and exits 1 or 0 on fire or not. The fixture does not say `--only` prints its findings, but it is a flag on `check`, which prints one line per finding. |\n| 4.3 | Yes | Bundled identifiers are \"fixed at the rule's first release\"; project names are used \"as is\" and Lintomatic \"does not add or rewrite any part of the name\"; sample output prints the identifier on every line. The fixture describes no machine-readable format, so the \"every machine-readable format\" limb is untested. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | The one-line form, with location, identifier and message, \"is printed whether or not the HTML report is enabled.\" Every finding is in the output, so no remainder is needed. |\n| 5.4 | Yes | The CLI prints \"every finding\" the dashboard shows, and \"the same binary is what CI runs.\" |\n| 6.1 | Yes | `lintomatic explain <identifier>` takes the identifier alone. It prints bundled documentation, a project rule's `doc` string, or \"No documentation declared.\" |\n| 6.2 | Yes | `explain` \"prints the documentation… from the installed package,\" and \"there is no separate website.\" |\n| 6.3 | **No** | \"Two of the forty rules currently print `No documentation declared`.\" Those two identifiers resolve to no page. Clause 6.3 says \"One Identifier that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `ignore_comments = false` in `lintomatic.toml` disables the `// lintomatic-ignore` route project-wide. |\n\n**Conformance (section 8):** Lintomatic does not conform. Clause 6.3 fails, and section 8 requires every MUST in sections 4 to 7.\n\n**Kind (clause 8.2):** This verdict is incomplete under 8.2, which binds whoever gives a verdict and requires naming the detector's kind as Semantic, Syntactic or Textual in each configuration graded. The fixture does not say which kind Lintomatic is, and I was told not to fill that from what a real tool would probably do, so I cannot name it. The finding on conformance still stands, as 8.2 provides.\n\n**Clause 6.4:** No, it does not change the verdict. Clause 6.4 is a SHOULD, not a MUST, and section 8 judges conformance on MUSTs. Lintomatic's repository not running a documentation-resolution check is a gap against a SHOULD. Even if the check existed, it would detect the two dangling identifiers at release rather than resolve them, and 6.3 would still fail.\n\n## 4. Confusion\n\n- **7.1**, the paragraph on the detectable route: \"That way is open only where the [Detector]'s [Rules] can see the route: a [Detector] whose [Rules] never see comments cannot make an ignore comment detectable this way.\" Read with \"the [Detector] ships no such [Rule] itself, and need not,\" I was unsure how the detector's MUST is discharged when the project does the work.\n- **4.3**, on prefixes: \"A prefix the [Detector] adds from the invoking directory or the [Rule]'s location is derived from the file path. A namespace the [Rule author] assigns once in configuration is not.\" I could not tell from this whether a detector-added prefix that is not derived from location would be allowed.","ttft_ms":34980,"type":"result","duration_ms":42520,"uuid":"7afbbc2a-c3c3-4a2b-a2a2-86fe20c7c8a0","ttft_stream_ms":1069,"time_to_request_ms":507,"first_content_frame_ms":1070,"queued_turn_count":0,"result_index":0}

reader-5

{"duration_api_ms":55513,"stop_reason":"end_turn","session_id":"09e6ebf2-126c-413e-ac75-fca5df807daf","total_cost_usd":0.00920473,"usage":{"input_tokens":2,"cache_creation_input_tokens":12050,"cache_read_input_tokens":2153,"output_tokens":13546,"output_tokens_details":{"thinking_tokens":11384},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":12050,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":13546,"cache_read_input_tokens":2153,"cache_creation_input_tokens":12050,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":12050},"type":"message"}],"speed":"standard","fallback_credit":null},"modelUsage":{"claude-haiku-5-5":{"inputTokens":2,"outputTokens":13546,"cacheReadInputTokens":2153,"cacheCreationInputTokens":12050,"webSearchRequests":0,"costUSD":0.00920473,"contextWindow":1000000,"maxOutputTokens":128000,"thinkingTokens":11384,"canonicalModel":"claude-haiku-5-5","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"safety_stops":0,"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"## 1. Restatement\n\n- A detector **conforms** when it meets every MUST in sections 4 to 7 (section 8).\n- The project that runs it must be able to write its own rules and register them with the same standing as bundled rules (4.1).\n- It must offer a harness that runs one rule against supplied code and shows whether it fired and what it printed (4.2).\n- Every rule has a stable identifier, not derived from its path, class name or position, and the detector prints that identifier unaltered with every finding (4.3).\n- A practitioner can run the detector locally, on a subset including a single file, and the findings appear in that command's output. No finding may be reportable only through a hosted, licence-gated or CI-only route (5.1 to 5.4).\n- A printed identifier resolves to its documentation. For bundled rules, that works from the installed copy, offline, and every identifier the rule set can print must resolve (6.1 to 6.3). An inline suppression route must be switchable off or detectable (7.1).\n\n## 2. Load-bearing statements\n\n**2a.** Conformance means meeting every MUST in sections 4 to 7. A detector that predates the document can conform. Section 8.1 says: \"A [Detector] that predates this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence: anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on any [Detector], declared or not, rests on that check and not on the claim.\" Section 8 says: \"A [Detector] [Conforms] if it satisfies every MUST in sections 4 to 7.\"\n\n**2b.** The detector must make the route either disableable by configuration or detectable. One of those is enough. Section 7.1 says: \"For each one it MUST do at least one of two things, and either alone satisfies this clause: make the route disableable by configuration; or make the route detectable, by a bespoke [Rule] the project writes for the [Detector] under clause 4.1 or by a mechanical check the [Detector] documents.\" Requiring a written reason is a SHOULD, not a MUST, so it is not needed for conformance. Section 7.2 says: \"The [Detector] SHOULD reject, or be configurable to reject, an inline [Suppression] that carries no reason.\"\n\n**2c.** The documentation must ship with the rule, at a version tracked together with it, and must resolve without network access. Section 6.3 is headed: \"A bundled rule's documentation MUST ship with the rule, at a version tracked together.\" Section 6.2 is headed: \"Resolution of a bundled rule's identifier MUST work from the installed copy, without network access.\"\n\n## 3. Application\n\n| Clause | Verdict | Reasoning |\n| --- | --- | --- |\n| 4.1 | Yes | Project rules are written as a Zig module implementing `Rule`, registered in `lintomatic.toml`, and \"are first class in every report.\" |\n| 4.2 | Yes | `lintomatic check --only <identifier> <path>` runs one rule, bundled or project, and exits 1 when it fires and 0 when it does not. The fixture does not explicitly say `--only` prints findings. I have read it as a form of `check`, which \"prints one line per finding.\" |\n| 4.3 | Yes | Bundled identifiers are `LM-NNNN`, fixed at first release. Project names come from `lintomatic.toml`, and \"Lintomatic does not add or rewrite any part of the name.\" Both are printed in the one-line output. The fixture describes no machine-readable format, so that limb is not engaged on what is written. If the HTML report counts as machine-readable, the fixture does not say it prints identifiers. |\n| 5.1 | Yes | \"The tool runs locally with no account, no server and no network.\" |\n| 5.2 | Yes | `lintomatic check <path>` \"analyses a directory or a single file.\" |\n| 5.3 | Yes | The one-line output prints every finding with its location and identifier, so there is no remainder. The HTML report only adds detail. |\n| 5.4 | Yes | The hosted dashboard shows only findings that \"are also printed by the command line,\" and CI runs the same binary. The fixture mentions no licence tier or CI-only rules. |\n| 6.1 | Yes | `lintomatic explain <identifier>` takes the identifier alone, for both bundled and project rules. The two \"No documentation declared\" cases are bundled rules, and under 6.1 such cases fail 6.3, not this clause. |\n| 6.2 | Yes | Explain reads \"from the installed package,\" and \"there is no separate website.\" |\n| 6.3 | No | Two of the forty bundled rules print \"No documentation declared,\" so their identifiers resolve to no page. Under 6.3, \"One [Identifier] that does not resolve fails this clause, however many others do.\" |\n| 7.1 | Yes | `ignore_comments = false` in `lintomatic.toml` disables `// lintomatic-ignore` project-wide. |\n\n**Conformance under section 8: No.** Lintomatic fails the MUST in 6.3, and section 8 requires every MUST in sections 4 to 7 to hold.\n\nSection 8.2 also applies to my verdict. It requires naming the detector's kind, [Semantic], [Syntactic] or [Textual], in each configuration graded. The fixture says a rule implements a Zig `Rule` interface but does not say what that interface can ask, so I cannot name the kind from what is written. Under 8.2, a verdict that leaves the kind out \"has not met this clause, but what it finds about [Conformance] still stands.\" The No verdict stands.\n\n**Clause 6.4: no change to the verdict.** Clause 6.4 is a SHOULD, and section 8 counts only MUSTs. The fixture says Lintomatic's own CI does not check identifiers against `explain` entries, which is a gap against that SHOULD. The 6.4 clause also says \"a [Detector] released on its own is asked for it as a SHOULD.\" The fixture does not say Lintomatic ships inside a Toolchain, so the Toolchain MUST does not apply. The failure of 6.3 is already decisive without 6.4.\n\n## 4. Confusion\n\n- 6.1: \"The [Detector] MUST provide a mechanism that resolves a printed identifier to its documentation.\" Later, for a project's own rule: \"what it owes is the half it can give: the [Identifier] printed unaltered under clause 4.3.\" I was unsure whether the mechanism MUST in the heading applies to project rules at all, or only to bundled rules.","ttft_ms":48582,"type":"result","duration_ms":55987,"uuid":"1dda4a07-5d6b-4ada-9ed7-ff769bd8510d","ttft_stream_ms":2347,"time_to_request_ms":470,"first_content_frame_ms":2347,"queued_turn_count":0,"result_index":0}

@LTSCommerce

Copy link
Copy Markdown
Member Author

Run 079: evidence under ACCEPTANCE.md, The record. Posted once; never edited, hidden or deleted.

The exact prompt every reader received, part 1 of 1

===== FILE: QUESTIONS.md =====
# Questions for a cold reader of DETECTOR-SPEC.md

You have been given `DETECTOR-SPEC.md` and the fixture `fixtures/lintomatic.md`, and nothing
else. Read the document once, then answer every question below in order. Quote the document
where a question asks for a quotation. Do not consult anything outside what you were given.

## 1. Restatement

In your own words and in at most six bullets, what does this document require of a detector?

## 2. Load-bearing statements

Answer each part of each question in a sentence or two, then quote the sentence or sentences of
the document you are relying on.

- 2a. What does it mean for a detector to conform, and can a detector that was written before
  this document existed, and has never heard of it, conform?
- 2b. A detector ships an inline ignore comment. What must the detector do about that route for
  it to conform, and is doing one of those things enough?
- 2c. Where must the documentation for a bundled rule's identifier live, at what version, and
  must it be reachable without network access?

## 3. Application

Grade Lintomatic 4.2, as described in the fixture, against every MUST clause in sections [4](../../DETECTOR-SPEC.md#4-authoring-rules) to [7](../../DETECTOR-SPEC.md#7-suppression):
[4.1](../../DETECTOR-SPEC.md#41-the-detector-must-support-bespoke-rules-written-by-the-project-that-runs-it), [4.2](../../DETECTOR-SPEC.md#42-the-detector-must-provide-a-harness-that-runs-a-single-rule-against-supplied-code), [4.3](../../DETECTOR-SPEC.md#43-the-detector-must-allow-a-rule-to-carry-a-stable-identifier-and-must-print-it-with-every-finding), [5.1](../../DETECTOR-SPEC.md#51-the-detector-must-be-invocable-by-the-practitioner-locally-with-no-infrastructure), [5.2](../../DETECTOR-SPEC.md#52-the-detector-must-support-invocation-over-a-subset-at-minimum-a-single-file), [5.3](../../DETECTOR-SPEC.md#53-the-result-must-reach-the-practitioner-in-the-output-of-the-command-they-ran), [5.4](../../DETECTOR-SPEC.md#54-a-finding-must-not-be-reportable-only-through-a-hosted-service-licence-tier-or-ci-only-mode-the-practitioner-cannot-invoke-locally), [6.1](../../DETECTOR-SPEC.md#61-the-detector-must-provide-a-mechanism-that-resolves-a-printed-identifier-to-its-documentation), [6.2](../../DETECTOR-SPEC.md#62-resolution-of-a-bundled-rules-identifier-must-work-from-the-installed-copy-without-network-access), [6.3](../../DETECTOR-SPEC.md#63-a-bundled-rules-documentation-must-ship-with-the-rule-at-a-version-tracked-together), [7.1](../../DETECTOR-SPEC.md#71-a-detector-may-offer-an-inline-suppression-route-but-must-make-it-detectable-or-disableable). For each give Yes, Partial or No and one
line of reasoning. Then state whether Lintomatic conforms under section [8](../../DETECTOR-SPEC.md#8-conformance), and why. Finally,
state whether clause [6.4](../../DETECTOR-SPEC.md#64-the-detector-should-fail-its-own-release-if-a-bundled-rule-lacks-resolvable-documentation) changes the verdict, and why.

## 4. Confusion

Quote any passage you found confusing on your one reading, with its clause number. Do not say what
would make it clearer and do not propose any change. Write "none" if there was none.

===== FILE: DETECTOR-SPEC.md =====
# Defence Before Fix: Detector Specification

**Version**: 1.1.0-dev, unpublished
**Companion to**: [the method specification](SPEC.md), version 1.1.0, and [the toolchain specification](TOOLING-SPEC.md), version 0.2.0
**Author**: [Joseph Edmonds](https://ltscommerce.dev), [Edmonds Commerce](https://edmondscommerce.co.uk)
**Coined**: 22 February 2026, in [the original article](https://ltscommerce.dev/articles/defence-before-fix-static-analysis)

## 1. What this document is for

The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT and MAY are to be interpreted as
described in RFC 2119.

[The method specification](SPEC.md) states what a [Practitioner] does when a
[Defect] is found. This document states what a [Detector] must offer so that they
can do it: write a [Rule], prove it, run it and resolve what it prints. It is addressed to
whoever maintains a [Detector], which the method specification defines as a tool that reads
code without executing it and reports occurrences of a pattern.

A [Detector] is one part of a project's [Toolchain], and this document judges it
on what it offers alone. What the assembled [Toolchain] of a project must add on top, the
[Project record], the listing of active [Defences] and the
governance of [Suppression], is stated in [the toolchain specification](TOOLING-SPEC.md).
The two are separable because they are built by different people: a [Detector] is authored once
and installed into projects its maintainer will never see, whilst governance is decided by each
project for itself. A document that asked a [Detector] to enforce a project's governance would
fail every [Detector] in use and would not make any project better governed.

**The governing principle**: where the method specification requires a [Practitioner] to do
something with a [Rule], a [Conforming] [Detector] MUST make that possible
without the project building the mechanism first. A [Detector] that leaves the project to
construct the means has moved the obligation rather than met it.

## 2. Terminology

Terms from the method specification carry over unchanged. Every capitalised term links to its
definition; the ones this document leans on most are, in short:

| Term                               | In one line                                                                                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Detector]                         | A tool that reads code without executing it and reports what it finds                                                                               |
| [Semantic], [Syntactic], [Textual] | What a [Rule] can ask in a configuration: the type of any expression and what any name refers to, program-wide; a parse tree short of that; or text |
| [Rule]                             | One check a [Detector] evaluates                                                                                                                    |
| [Message]                          | The text a [Rule] prints when it fires                                                                                                              |
| [Identifier]                       | The stable name printed with a finding that leads to its documentation                                                                              |
| [Practitioner]                     | Whoever is doing the work, a person or an [Agent]                                                                                                   |
| [Toolchain]                        | Everything a project assembles to run its checks through, [Detectors] included                                                                      |
| [Suppression]                      | Making a finding go away without fixing it                                                                                                          |
| [Baseline]                         | A recorded set of existing findings a [Rule] is told to ignore                                                                                      |

These are additional.

#### Rule author

Whoever writes a [Rule]. May be the [Detector]'s maintainer, a [Toolchain]'s maintainer or the project that runs it.

#### Bundled rule

A [Rule] the [Detector] ships, or fetches on the [Practitioner]'s behalf from a source the [Detector]'s maintainer controls. Its [Rule author] will never see the codebases it runs in, so everything a [Practitioner] needs in order to act on it has to travel with it.

#### Harness

The route by which one [Rule] is run against supplied code and its findings observed, without the project's own test suite and without every other [Rule] running alongside it.

The distinction between a [Bundled rule] and a project's own [Rule] matters throughout.
The [Detector] owns the documentation of the first and can be held to shipping it; it cannot
know the documentation of the second, and what it owes there is the mechanism that lets the project
attach its own.

## 3. The division of responsibility

Each mechanism the method needs has two halves. Stating only one of them is what produces a
[Detector] that is *nearly* usable.

| Requirement                          | The [Detector] MUST provide                             | The [Rule author] supplies                              |
| ------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------- |
| Bespoke [Rules]                      | The authoring and registration route                    | The [Rules]                                             |
| The red proof                        | A [Harness]                                             | The [Fixture] and the red run                           |
| Running the [Rule]                   | A local invocation, including subsets                   | Running it                                              |
| The [Identifier]                     | A place to carry it, and printing it with every finding | Choosing it once                                        |
| Resolution of a [Bundled rule]       | The lookup, and the documentation, on disk              | Nothing                                                 |
| Resolution of a project's own [Rule] | The [Identifier] printed unaltered                      | The documentation and, with the [Toolchain], the lookup |
| Inline [Suppression]                 | A route the project can detect or disable               | The decision whether to forbid it                       |

Read down the middle column and the shape of a [Conforming] [Detector] is already
visible. Read across any row and the failure mode is visible too: either half alone leaves the
[Practitioner] stuck.

## 4. Authoring rules

### 4.1 The detector MUST support bespoke rules written by the project that runs it

Configuration of an existing [Rule] set is not sufficient. The project must be able to express a
pattern the [Detector]'s authors have never anticipated, and register it so that it runs with
the same standing as a [Bundled rule].

**Why**: the method turns a specific [Defect] into a [Class] [Defence], and the
[Classes] that matter most to a project are the ones peculiar to it. A [Detector] offering
only a fixed catalogue can defend against the industry's known [Hazards] and none of the
project's own, which is the half that carries its institutional knowledge.

### 4.2 The detector MUST provide a harness that runs a single rule against supplied code

A [Harness] reports, for a given input, whether the [Rule] fired and what it printed, without
executing the project's own test suite and without the other [Rules] obscuring the answer.
This applies to every [Rule] the [Detector] runs, bundled or bespoke; a
[Detector] failing 4.1 is graded here on its [Bundled rules].

**Why**: clause 3.3 of the method specification requires the [Rule] to be proven by making it go
red. A [Practitioner] who can only observe a [Rule]'s behaviour by running every
[Rule] over the whole codebase cannot demonstrate that a [Rule] fires on the pattern rather
than on something incidental.

### 4.3 The detector MUST allow a rule to carry a stable identifier, and MUST print it with every finding

The [Identifier] MUST be stable across releases and MUST NOT be derived from the [Rule]'s
file path, [Class] name or position in a configuration file. The [Rule author] chooses it
once. The [Detector] MUST print it, unaltered, alongside every finding the [Rule]
reports, in its default output and in every machine-readable format it offers. A prefix the
[Detector] adds from the invoking directory or the [Rule]'s location is derived from
the file path. A namespace the [Rule author] assigns once in configuration is not.

| [Identifier]       | Where it comes from                                                         | Stable? |
| ------------------ | --------------------------------------------------------------------------- | ------- |
| `proj.no-raw-sql`  | Written once by the [Rule author] in configuration, printed as is           | Yes     |
| `LM-0107`          | Assigned once by the [Detector]'s maintainer at the [Rule]'s first release  | Yes     |
| `rules/no-raw-sql` | Produced from where the [Rule]'s file sits; changes when the file moves     | No      |
| `NoRawSqlRule`     | The [Rule]'s [Class] name; changes when the [Rule] is renamed               | No      |
| `rule 7`           | The [Rule]'s position in a configuration file; changes when one is inserted | No      |

**Why**: the [Identifier] is the only string that reaches the [Practitioner] and the
only key their lookup can use. An [Identifier] that changes when a [Rule] is renamed
silently breaks every reference to it, including references written down by people who have left.
An [Identifier] the [Detector] carries but does not print is one the
[Practitioner] was never given.

### 4.4 The detector SHOULD enforce 4.3 with a rule of its own

A [Rule] over the [Rules], failing any [Rule] that reports without a stable
[Identifier].

**Why**: this is the method applied to the [Detector] itself, and it is cheap. The worked
example is `php-qa-ci`'s `RequireRuleIdentifierConstantRule`, a [Rule] hosted in PHPStan by a
[Toolchain] built on it: it rejects a magic-string [Identifier] and names the
constant to declare instead. Nothing about it needed to live outside the [Detector].

## 5. Reporting

### 5.1 The detector MUST be invocable by the practitioner, locally, with no infrastructure

**Why**: method specification clause 8.1. An [Agent] that cannot check its own work cannot
iterate against a [Defence], so the loop never closes in the turn where it is cheap to close.

### 5.2 The detector MUST support invocation over a subset, at minimum a single file

**Why**: 5.1 is satisfied in principle by a whole-codebase run and defeated in practice by one.
Checking a single edited file has to be fast enough to do on every edit, or it will not be done on
any.

### 5.3 The result MUST reach the practitioner in the output of the command they ran

Where the [Detector] writes fuller detail elsewhere, the invoked command's own output MUST
carry a usable summary and, where the summary leaves any finding out, the location of the
remainder. The remainder is the findings the summary leaves out. Where the output prints every finding, each with where it is and its
[Identifier], there is no remainder. A fuller report written elsewhere, such as a page with a
code snippet for each finding, only adds detail to findings the output already carries; it is
not a remainder, and the output need not name it.

**Why**: method specification clause 8.2. A report the [Practitioner] has to go and find is a
report that arrives after the decision it was meant to inform.

### 5.4 A finding MUST NOT be reportable only through a hosted service, licence tier or CI-only mode the practitioner cannot invoke locally

A finding that the [Detector] reports only through a hosted service, a licence tier or a
continuous-integration-only mode the [Practitioner] cannot invoke locally does not
[Conform], whatever the same [Rule] reports there.

**Why**: a [Rule] that fires only in an environment the [Practitioner] has no access to
teaches nobody anything and blocks them anyway, which is the worst combination available.

## 6. Resolving an identifier

### 6.1 The detector MUST provide a mechanism that resolves a printed identifier to its documentation

Two cases, by who wrote the [Rule]:

| The [Rule]        | Who supplies the documentation | What this clause asks of the [Detector]                                   |
| ----------------- | ------------------------------ | ------------------------------------------------------------------------- |
| A [Bundled rule]  | The [Detector]                 | Resolve the [Identifier], presented alone, to that documentation          |
| The project's own | The project                    | Print the [Identifier] unaltered; the lookup is the [Toolchain]'s to give |

In both, the mechanism is keyed on **the [Identifier] exactly as printed**. A command, an index
file or a URL are all acceptable forms. This clause asks for the mechanism, not for every page:
where the mechanism works but a [Bundled rule]'s [Identifier] has no documentation to resolve
to, that fails clause 6.3, not this clause. Each case in turn:

**For a [Bundled rule]**, the [Detector] supplies the documentation and the
lookup, and clauses 6.2 and 6.3 say where. The mechanism MUST resolve an [Identifier]
presented alone, because the reader who needs it most has the [Identifier] from a log, a
ticket or a colleague and not the [Message]. A documentation path printed in the [Message] is
therefore not enough on its own: it helps only a reader who has the [Message], and the
[Identifier] alone still leads nowhere.

**For a project's own [Rule]**, the [Detector] cannot know the documentation, so what
it owes is the half it can give: the [Identifier] printed unaltered under clause 4.3, and no
transformation of it. Where the [Identifier] is itself a URL, as method clause 3.6 allows,
the [Detector] prints that URL and has done what this clause asks. What this clause forbids is
a documentation path printed alongside a shorter [Identifier] that cannot be looked up on its
own. The lookup for such
a [Rule] is an obligation on the project's assembled [Toolchain], under clause 4.2
of the [toolchain specification](TOOLING-SPEC.md), and a [Detector] that offers it as well has
gone further than this clause asks.

**Why**: method specification clause 8.3 requires the [Identifier] to resolve without a human.
An index keyed on anything else does not resolve it. This is the most commonly failed clause in this
document and it fails in a specific way: documentation exists, is genuinely good, and is keyed on the
[Rule]'s [Class] or file name, which is a string the [Practitioner] was never
given. The lookup they can actually perform is the only one that counts.

### 6.2 Resolution of a bundled rule's identifier MUST work from the installed copy, without network access

**Why**: an [Agent] working offline, behind a proxy, or against a URL that has since moved needs
the answer to be on disk. A dependency the project already installed is on disk by definition. A
catalogue on the [Detector]'s website, however complete, is the right documentation in the wrong
place.

### 6.3 A bundled rule's documentation MUST ship with the rule, at a version tracked together

Every [Identifier] a [Bundled rule] can print MUST resolve, under clause 6.1,
to a page in that shipped documentation. One [Identifier] that does not resolve fails this
clause, however many others do. A page per [Identifier] satisfies this; a page per family of
[Identifiers] MAY be used instead, under clause 6.5.

**Why**: method specification clause 3.6. A [Bundled rule] travels into codebases its author
will never see. If its documentation lives only in the [Detector]'s repository or on its
website, then every project that installs it is one link rot away from a [Rule] that blocks
without explaining.

The failure to guard against is not the absent document but the **dangling one**: a reference to
documentation that was planned and never written is worse than no reference, because it consumes
the [Practitioner]'s attention before failing them.

### 6.4 The detector SHOULD fail its own release if a bundled rule lacks resolvable documentation

An automated check over every [Identifier] a [Bundled rule] can print, applying the
family list or pattern of clause 6.5 where one is used, that blocks the release when any of them
lands on no page.

**Why**: clause 6.3 is the clause most easily believed to be satisfied whilst being broken, because
the documentation is written by the same person who wrote the [Rule] and its absence is
invisible from the inside. Where the [Detector] is shipped inside a [Toolchain],
the [toolchain specification](TOOLING-SPEC.md)'s self-audit makes this check a MUST for the [Toolchain]; a
[Detector] released on its own is asked for it as a SHOULD because the same [Class] of
[Defect], a [Rule] that blocks without explaining, is detectable mechanically there too.

### 6.5 One page MAY document a family of identifiers, and SHOULD name its members so a check can confirm them

Where several [Bundled rules] share a prefix, one page MAY document them all. Such a page
SHOULD let the check clause 6.4 asks for confirm that each member resolves to it. Two forms do
that and one does not:

| The page                                                                                                  | A check can confirm each member?          |
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| Lists every full [Identifier] in the family, `LM-0100`, `LM-0101`, and so on                              | Yes                                       |
| Carries a regular expression the check can run, `^LM-01[0-9]{2}$`, matching every member and nothing else | Yes                                       |
| Says in prose that everything under `LM-01` is documented here                                            | No: a person can infer it, a check cannot |

The list or the regular expression lives in the installed file, since clause 6.2 requires
resolution to work from the installed copy, not only in a rendered web page.

**Why**: a family page is a convenience for the author, and the cost of it must not fall on the
check. A page that a person can see covers `LM-0107` but a check cannot leaves clause 6.4 with
nothing to run.

## 7. Suppression

### 7.1 A detector MAY offer an inline suppression route, but MUST make it detectable or disableable

An inline ignore comment, a per-line directive and a generated [Baseline] are all such
routes. The [Detector] MAY ship them. For each one it MUST do at least one of two things, and either
alone satisfies this clause: make the route disableable by configuration; or make the route
detectable, by a bespoke [Rule] the project writes for the [Detector] under clause 4.1 or by a
mechanical check the [Detector] documents. Either lets a project which decides to forbid the
route enforce that decision. A route that can be neither switched off nor seen does not [Conform].

A route is detectable when the project can find every use of it. The first way is a bespoke
[Rule]: the project registers a [Rule] of its own, as clause 4.1 provides for, that matches the
route wherever it is used, and the [Detector] runs it like any other bespoke [Rule]; the
[Detector] ships no such [Rule] itself, and need not. That way is open only where the
[Detector]'s [Rules] can see the route: a [Detector] whose [Rules] never see comments cannot make
an ignore comment detectable this way. The second way is a mechanical check the [Detector]
documents, such as a text search for the route's spelling that the [Detector]'s documentation
gives.

**Why**: the method specification's position is that [Suppression] is an [Owner]
decision under its clause 3.4 and section 4, and an [Owner] cannot decide something they are
never shown. The [Detector] is not the [Owner] and does not know the project's
governance, so it is not asked to enforce it; what it is asked is not to hide the route. Every
[Detector] in wide use ships an inline ignore, and a clause that forbade them would fail all of
them without governing any project better. Whether the route is forbidden is the project's decision,
enforced through its [Toolchain] under clause 4.3 of the [toolchain specification](TOOLING-SPEC.md).

### 7.2 An inline suppression route SHOULD require a written reason

The [Detector] SHOULD reject, or be configurable to reject, an inline [Suppression]
that carries no reason, and SHOULD NOT supply a default one, including each entry of a generated
[Baseline].

**Why**: a [Suppression] without a reason is indistinguishable from one nobody would defend,
and the person who could tell them apart is usually gone. Requiring the sentence costs the author a
minute at the moment they have the reason in mind, and it is the only thing that makes the
[Suppression] reviewable later. PHPStan's `reportIgnoresWithoutComments` is the shape of it.

## 8. Conformance

**A [Detector] [Conforms]** if it satisfies every MUST in sections 4 to 7.

Partial [Conformance] MUST NOT be described as [Conformance]. A [Detector]
that satisfies most of this document is in a normal and respectable condition; it is not
[Conforming], and describing it as such removes the only value the word has.

This document defines one level. The clauses of the method specification's section 8 that reach
beyond what sections 4 to 7 here already secure, the listing of active [Defences] and the
summary for an [Agent]'s context, are obligations on a project's assembled [Toolchain]
and are stated in the [toolchain specification](TOOLING-SPEC.md), so there is no separate [Agent]-support level
for a [Detector].

### 8.1 A maintainer MAY declare the version of this document the detector conforms to, and the declaration records known gaps

The declaration is machine-readable, in whatever form the [Detector]'s ecosystem uses to
record dependencies. The same declaration is where a gap against this document is recorded once it is
known: a [Detector] that declares a version and has learnt, from its own checks or from a
[Practitioner]'s report under the method's clause 3.2, that it fails a MUST in sections 4 to 7
MUST record that gap alongside that version, in the same file or one it names. A declaration
with a non-empty gap record is a statement of where the [Detector] stands and is not a claim of
[Conformance].

The declaration is optional, and so the duty above falls only on a [Detector] that has one; a
gap a [Practitioner] finds is recorded in their own project's record under the method's clause
3.2 in any case. The declaration is how a maintainer claims [Conformance]; it is not a condition
of it, and a [Detector] without one can still be found to [Conform]. A [Detector] that predates
this document, or whose maintainer has never read it, MAY be graded [Conforming] on evidence:
anyone may grade it, by checking it against each clause of sections 4 to 7, and a verdict on
any [Detector], declared or not, rests on that check and not on the claim. A declaration tells the reader what the maintainer believes and what they know to be missing;
the reader still runs the [Harness].

The shape is illustrative rather than prescribed. In a Composer manifest:

```json
"extra": {
  "defence-before-fix": {
    "method": "1.0.1",
    "detector": "1.0.0",
    "toolchain": null,
    "known-gaps": []
  }
}
```

In a package.json, the equivalent is a top-level `defenceBeforeFix` object with the same keys. A
[Detector] that is not also shipped as a [Toolchain] leaves `toolchain` empty;
a project that ships both declares both. Each entry in `known-gaps` names the clause and states the
gap in a sentence.

**Why**: a [Conformance] claim in a README is a sentence; a claim in a manifest is a fact about
a specific installed artefact, checkable by anyone, including mechanically, and it fixes what
"[Conforming]" meant at the point the claim was made. Making the claim a condition of
[Conformance], though, would mean a [Detector] with every mechanism in place could
never [Conform] until its maintainer had heard of this document, which grades the
maintainer's reading rather than the [Detector].

### 8.2 A verdict on a detector MUST name its kind in each configuration it grades

A verdict on a [Detector] MUST name its kind, [Semantic], [Syntactic] or [Textual], in each
configuration the verdict grades. A configuration here is one way of setting the [Detector] up to
run, and one [Detector] can have several: ESLint, for example, is [Syntactic] run without type
information and [Semantic] run with it. The kind is what a [Rule] can ask in that configuration,
as section 2 glosses it.

This clause binds whoever gives the verdict, anyone grading a [Detector] under clause 8.1
included, and asks nothing of the [Detector] itself; a maintainer who gives a verdict on their
own [Detector] is bound by it like anyone else. A declaration under clause 8.1 is the
maintainer's claim, not a verdict, and this clause asks nothing of it. A [Detector] is judged on
sections 4 to 7, as section 8 says, and the kind is not a level of [Conformance] and does not
change the verdict: a [Textual] [Detector] can [Conform], and a [Semantic] one can fail to. A
verdict that leaves the kind out has not met this clause, but what it finds about [Conformance]
still stands.

**Why**: the method specification's clause 3.2 requires a [Rule] to be written in the strongest
kind of [Detector] that can express it, and a verdict of [Conforming] alone presents a [Textual]
[Detector] as an equal host.

## 9. Relationship to the other specifications

This document adds no obligation to the method a [Practitioner] follows, and relaxes none.
Clause 8.2 adds one on whoever gives a verdict on a [Detector], a [Practitioner] included when
they give one: to name its kind. Every clause here
exists to make a clause of the method specification achievable with a [Detector] in hand.

Where this document and the method specification disagree, the method specification governs. It
describes the method, which is the thing being specified; this describes one piece of the equipment.

The [toolchain specification](TOOLING-SPEC.md) states what a project's assembled [Toolchain] must offer beyond
what each [Detector] in it offers, and it requires every [Detector] a [Defence]
is routed through to [Conform] to this one. A [Detector] shipped inside a
[Toolchain] is measured here as a [Detector] and there as part of the
[Toolchain]; the two verdicts are separate and neither implies the other.

Nothing here requires a project to use a [Conforming] [Detector]. A project can
[Conform] to the method specification on a [Detector] that [Conforms] to
none of this, at the cost of building the missing mechanisms itself. This document exists so that it
does not have to.

<!-- Term link definitions -->

[agent]: SPEC.md#agent
[baseline]: SPEC.md#baseline
[bundled rule]: #bundled-rule
[bundled rules]: #bundled-rule
[class]: SPEC.md#class
[classes]: SPEC.md#class
[conform]: SPEC.md#conform
[conformance]: SPEC.md#conform
[conforming]: SPEC.md#conform
[conforms]: SPEC.md#conform
[defect]: SPEC.md#defect
[defence]: SPEC.md#defence
[defences]: SPEC.md#defence
[detector]: SPEC.md#detector
[detectors]: SPEC.md#detector
[fixture]: SPEC.md#fixture
[harness]: #harness
[hazards]: SPEC.md#hazard
[identifier]: SPEC.md#identifier
[identifiers]: SPEC.md#identifier
[message]: SPEC.md#message
[owner]: SPEC.md#owner
[practitioner]: SPEC.md#practitioner
[project record]: TOOLING-SPEC.md#project-record
[rule]: SPEC.md#rule
[rule author]: #rule-author
[rules]: SPEC.md#rule
[semantic]: SPEC.md#semantic
[suppression]: SPEC.md#suppression
[syntactic]: SPEC.md#syntactic
[textual]: SPEC.md#textual
[toolchain]: SPEC.md#toolchain

===== FILE: fixtures/lintomatic.md =====
# Fixture: Lintomatic 4.2

A fictional detector, described only as far as its documentation would describe it. Grade it
against the document you were given. Where the description does not say, answer from what is
written, not from what a real tool would probably do.

## What the documentation says

Lintomatic is a static analyser for the Zig language. It ships forty rules. A project can add its
own rules by writing a Zig module that implements the `Rule` interface and listing it in
`lintomatic.toml`; project rules are loaded on every run and are first class in every report.

The command `lintomatic check <path>` analyses a directory or a single file and prints one line
per finding to standard output:

```
src/db.zig:41:7  LM-0107  Result of fallible call discarded
src/db.zig:88:3  proj.no-raw-sql  Query built by string concatenation
```

Fuller detail, including a snippet, is written to `.lintomatic/report.html`. The one-line form is
printed whether or not the HTML report is enabled.

`lintomatic check --only <identifier> <path>` runs a single rule. It accepts any rule name,
bundled or project, and exits 1 when the rule fires, 0 when it does not, and 2 when the rule name
is unknown.

Rule identifiers for bundled rules are of the form `LM-NNNN` and are fixed at the rule's first
release. Project rules are identified by the name the project gives them in `lintomatic.toml`,
which the documentation recommends prefixing with `proj.`; Lintomatic does not add or rewrite
any part of the name.

`lintomatic explain <identifier>` prints the documentation for a bundled rule from the installed
package: what the rule finds, why, and the recommended construction. For a project rule it prints
the `doc` string the rule module declares, or `No documentation declared` when there is none.
The documentation is versioned with the package; there is no separate website.

Lintomatic honours the comment `// lintomatic-ignore <identifier>` on the line before a finding.
The comment can be disabled project-wide with `ignore_comments = false` in `lintomatic.toml`.
The comment takes no reason and the documentation does not mention one.

The tool runs locally with no account, no server and no network, and the same binary is what CI
runs. There is a hosted dashboard, sold separately, that aggregates reports across repositories;
every finding it shows is also printed by the command line.

Lintomatic's own repository runs `lintomatic check src` in CI but does not check that every
`LM-NNNN` identifier has an `explain` entry; two of the forty rules currently print
`No documentation declared`.

LTSCommerce and others added 11 commits October 12, 2026 14:34
…parted paragraph is the change's text

Settles the open points of runs 76, 77, 78 and 80. ACCEPTANCE.md says a remark that another
document was not supplied, or that a term is defined only there, is a note however much of the
document it quotes to locate the reference; and that a paragraph the pull request parts from
what it refers to, by inserting a paragraph between them or moving either, is the change's
text. The Lintomatic fixture says `check --only` prints its findings as `check` does, and the
detector key's 4.2 row says so and which case a conditional grade is for. ROUTED gains the
toolchain 6.1 Project record sentence (run 76) and the detector 4.2 sentence on a detector
failing 4.1 (run 78, taken by editor/defined-terms); the 4.3 row links the section, whose
heading the toolchain change rewrites.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… 6.2 and 6.3 speak only of exceptions

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, from run 83

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es 6.1, 6.2 and 6.4

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… 6.4 again

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…inst 6.2 new

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant