Skip to content

Check the published marker basis against the publishers own domain endpoints, instead of transcribing it #77

Description

@ChelseaKR

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.pySource 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.pyBasis (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

  1. 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.
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfeatureMajor new capability proposal

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions