What
Twelve of the twenty-eight declared markers in docs/MARKERS.md carry basis published, meaning the value appears in a coded-value domain the publisher maintains. The evidence for all twelve is a person reading four documents on 2026-08-07 and typing the result into src/perimeter/schema.py. Two of those four documents are machine-readable — D1 and D3 in the audit's sources table are ArcGIS layer metadata at ?f=pjson, which serve the coded-value domains themselves as JSON.
This pins those two metadata documents as retrievals alongside the two data files — fetched by the same hand-run path, hashed, dated, recorded in PROVENANCE.md — parses their domains[].codedValues out, and gates every Basis.PUBLISHED declaration against them. A declared value that is not in the publisher's current domain, or a published domain that has gained or lost a code since the pin, becomes a build failure rather than a two-year-old sentence.
Why this project
Everywhere else this repository refuses to let a claim stand without a gate that can fail. tests/test_release_claims.py reads git tag --list rather than believing the status line. tests/test_data_cards.py fails when a retrieval outlives the SLA its card states. SentinelDriftError refuses a marker nobody declared. ADR-0004 is literally titled "a gate must be able to fail".
The largest ungated claim left is the one the three-state model rests on: which markers are the publisher's and which are ours. The README says so plainly about the standards table — "this is a transcription with a date... Nothing in CI can check it against the source" — and docs/MARKERS.md is the same shape at higher stakes, because the twelve/sixteen split is published on the pages and in marker_basis in both artifacts.
It is also the claim most likely to rot invisibly. CAL FIRE can add a coded value, retire one, or re-word a domain description without touching a single record, so a refresh would produce identical counts and a silently wrong basis. Today the only thing standing between that and a published figure is somebody remembering to re-read a PDF.
Concrete seams
src/perimeter/sources.py — Source is the single reviewed provenance record; it gains the domain document (endpoint, hash, retrieval date) alongside endpoint/sha256/raw_bytes.
src/perimeter/acquire.py — the only code that touches the network, hand-run and never in CI. The metadata fetch belongs here and keeps that property.
src/perimeter/schema.py — Basis (line 51), FieldSpec (line 70), FRAP_CAUSE_CODES, FRAP_COLLECTION_METHOD_CODES, FRAP_OBJECTIVE_CODES, FRAP_AGENCY_CODES, DINS_DAMAGE_VALUES, and every FieldSpec declaring a published marker.
- A new test module in the shape of
tests/test_data_cards.py, plus committed metadata fixtures so make verify stays offline and network-free.
docs/MARKERS.md "Sources this audit was checked against", PROVENANCE.md, and a new ADR recording the split below.
What must be decided first
- What a moved domain is. A publisher adding a code is not the same event as a publisher retiring one this project declares.
SchemaDriftError is the fail-closed precedent; make diff and ALLOW_REMOVALS=1 are the deliberate-refresh precedent. Which each case gets is a judgment.
- D2 and D4 are PDFs. The FRAP metadata PDF and the DINS Database Dictionary cannot be checked in CI, so this cannot make all twelve machine-checked. The honest outcome is a split: fields whose evidence is D1/D3 become gated, fields whose only evidence is a PDF stay a dated transcription — which probably wants a third
Basis value meaning "published, evidence not machine-checkable" rather than leaving the two indistinguishable. That is a change to a value already carried in both published artifacts.
- Whether the metadata retrieval is part of
make acquire or a separate step, given that a metadata pin that drifts from its data pin is its own defect.
Size
Multi-session. The fetch and the parse are small; the honest part is the taxonomy of what counts as drift, the third basis value if it is taken, the fixtures, and the migration of marker_basis in two published artifacts and on three pages.
Proposed with AI assistance.
What
Twelve of the twenty-eight declared markers in
docs/MARKERS.mdcarry basis published, meaning the value appears in a coded-value domain the publisher maintains. The evidence for all twelve is a person reading four documents on 2026-08-07 and typing the result intosrc/perimeter/schema.py. Two of those four documents are machine-readable — D1 and D3 in the audit's sources table are ArcGIS layer metadata at?f=pjson, which serve the coded-value domains themselves as JSON.This pins those two metadata documents as retrievals alongside the two data files — fetched by the same hand-run path, hashed, dated, recorded in
PROVENANCE.md— parses theirdomains[].codedValuesout, and gates everyBasis.PUBLISHEDdeclaration against them. A declared value that is not in the publisher's current domain, or a published domain that has gained or lost a code since the pin, becomes a build failure rather than a two-year-old sentence.Why this project
Everywhere else this repository refuses to let a claim stand without a gate that can fail.
tests/test_release_claims.pyreadsgit tag --listrather than believing the status line.tests/test_data_cards.pyfails when a retrieval outlives the SLA its card states.SentinelDriftErrorrefuses a marker nobody declared. ADR-0004 is literally titled "a gate must be able to fail".The largest ungated claim left is the one the three-state model rests on: which markers are the publisher's and which are ours. The README says so plainly about the standards table — "this is a transcription with a date... Nothing in CI can check it against the source" — and
docs/MARKERS.mdis the same shape at higher stakes, because the twelve/sixteen split is published on the pages and inmarker_basisin both artifacts.It is also the claim most likely to rot invisibly. CAL FIRE can add a coded value, retire one, or re-word a domain description without touching a single record, so a refresh would produce identical counts and a silently wrong basis. Today the only thing standing between that and a published figure is somebody remembering to re-read a PDF.
Concrete seams
src/perimeter/sources.py—Sourceis the single reviewed provenance record; it gains the domain document (endpoint, hash, retrieval date) alongsideendpoint/sha256/raw_bytes.src/perimeter/acquire.py— the only code that touches the network, hand-run and never in CI. The metadata fetch belongs here and keeps that property.src/perimeter/schema.py—Basis(line 51),FieldSpec(line 70),FRAP_CAUSE_CODES,FRAP_COLLECTION_METHOD_CODES,FRAP_OBJECTIVE_CODES,FRAP_AGENCY_CODES,DINS_DAMAGE_VALUES, and everyFieldSpecdeclaring a published marker.tests/test_data_cards.py, plus committed metadata fixtures somake verifystays offline and network-free.docs/MARKERS.md"Sources this audit was checked against",PROVENANCE.md, and a new ADR recording the split below.What must be decided first
SchemaDriftErroris the fail-closed precedent;make diffandALLOW_REMOVALS=1are the deliberate-refresh precedent. Which each case gets is a judgment.Basisvalue meaning "published, evidence not machine-checkable" rather than leaving the two indistinguishable. That is a change to a value already carried in both published artifacts.make acquireor a separate step, given that a metadata pin that drifts from its data pin is its own defect.Size
Multi-session. The fetch and the parse are small; the honest part is the taxonomy of what counts as drift, the third basis value if it is taken, the fixtures, and the migration of
marker_basisin two published artifacts and on three pages.Proposed with AI assistance.