Expanded-table publication protocol
scripts/expanded_promotion.py supplies the first implementation of explicit
reviewed snapshot publication. Canonical publication has been exercised on Eliot, Sabattus, Canterbury, Nottingham, Wiscasset, North Berwick, Wells, Peru, Brownfield and Freedom, including an
explicit successor result observation that preserves the original. The
generated results view supplies current counts. Earlier
generations remain immutable history. Operative legal intervals and instrument
links remain unresolved. Other census candidates, broader calibration, exports and
release work remain open; these initial batches do not close P3 or P6.
The canonical path is data/expanded, a relative symlink to a complete immutable
generation in data/expanded-snapshots/<snapshot-sha256>/. Every generation contains
all fifteen <table>.jsonl files and publication.json. This preserves the planned
data/expanded/<table>.jsonl interface while allowing a single atomic pointer swap
instead of fifteen independently visible table replacements. Git must retain both
the relative symlink and its referenced generation. Old generations remain available.
Readers must resolve the directory once before opening multiple tables;
expanded_data.validate and read_snapshot do this. Consumers that independently
resolve each filename during a publication can mix generations and need adaptation.
A plan is one explicit JSON file with exactly:
schema_version:1.0.0.expected_sha256: fingerprint of the previous canonical snapshot, or null.registry_sha256: SHA-256 of the complete reviewed identity registry.snapshot_sha256: fingerprint of the complete proposed snapshot.reviewer, timezone-bearingreviewed_at, andrationale.record_reviews: exactly one publication review for every supplied record.
Each record review contains exactly table, id, record_sha256,
previous_sha256, accepted_fields, unresolved_fields and publication.
Record fingerprints use sorted-key compact UTF-8 JSON, with ensure_ascii=False.
The baseline fingerprint is null for a new record. Both field lists must be unique,
disjoint and together cover every field except the primary ID and schema version.
Unresolved fields must contain null, unknown, unresolved, an empty list or an
empty object; populated factual assertions cannot hide behind an unresolved label.
Publication requires an explicit rights disposition of public_record,
reviewed_excerpt or reviewed_metadata. These are human review decisions, not an
automatic licensing determination. Never mark a publisher's full article as an
approved excerpt.
Every record ID must be allocated for its table in the identity registry. Retired or ambiguous IDs cannot receive new facts. An unchanged historical record may retain its earlier ID; no merge silently rewrites its facts. All existing records must remain present. Historical observations, claims and review decisions cannot be rewritten; corrections use new records and explicit supersession. Mutable entity records require the exact previous-record fingerprint.
Every accepted field of a jurisdiction, election, measure, provision, result,
restriction, instrument event or measure/instrument link needs one current scoped
accepted review decision. The supporting claims may transcribe source wording used in a reviewed
interpretation, such as classifying bindingness. The structural validator requires
acceptance of each supporting claim at its own predicate and checks its subject
against the exact decision target. A field decision never automatically accepts
a supporting claim or a different field. claim_ids is a mechanically checked reference
list and does not need a separate factual field decision. Record publication reviews
pin source/claim/review/search metadata separately; they never establish legal force.
All source versions and all claim quotations must independently match retained bounded-client evidence or a verified visual-review receipt. Evidence directories are explicit CLI inputs, not recursively discovered. Integrity errors fail the whole batch. Visual receipts must use canonical claim fingerprints; candidate receipts cannot be reused after changing IDs without a separately verified mapping/review.
PDF receipts pin pdftoppm, page, DPI and reproduced image hash. Native PNG
receipts instead use render_parameters: {"renderer": "native-png", "width": W,
"height": H} and pdf_page: null. Their render_sha256 must equal the original
source-body hash: no crop, scaling, synthetic PDF or other transformation is
accepted. The verifier checks PNG container/chunk checksums and dimensions,
then compares the retained bytes. This verifies evidence identity, not pixel
interpretation, reviewer authenticity, result certification or legal effect.
Peru's real native-image exercise
records a successful evidence audit without canonical publication.
Native JPEG receipts use renderer: native-jpeg plus stored width, height
and exif_orientation (1–8), with pdf_page: null. Original bytes and the
orientation must match; a display-oriented image's dimensions must not be
substituted for its stored dimensions. Strict Pillow decoding is required, and
truncated input/lenient decoding or transformed image hashes are rejected.
Pillow is included by make install. No transposition, crop, resizing or
synthetic PDF changes the source. The
Brownfield/Freedom exercise
checks real retained JPEGs; this does not authenticate account authority,
certify election results or accept current legal force.
~/projects/bc/bc-modules/.venv/bin/python scripts/expanded_promotion.py \
--candidate REVIEWED_TABLE_DIRECTORY --plan REVIEWED_PLAN.json \
--evidence-directory PRIVATE_RETAINED_JOB_DIRECTORY
# Add --apply only for the reviewed batch intended for canonical publication.
Dry-run is the default and writes no publication, staging directory or lock. Apply locks the stable publication lock and takes a shared lock on the identity registry's stable lock while rechecking all gates. It writes/fsyncs a complete generation, renames/fsyncs that generation, then atomically replaces the relative pointer and fsyncs its parent. The publication receipt retains the exact plan and verified quote/source hashes without private text paths. No API calls or automatic retries occur. A retained exact snapshot and identical plan replay as a no-op; altered acceptance text is not an identical replay.
An interruption before the pointer swap leaves the previous publication intact (or absent during initialization). A completed but unreferenced generation is retained for inspection; automatic retry refuses to overwrite it. Compare its fifteen table hashes, publication plan and receipt against the intended plan before recovering. Do not delete the active generation or silently redirect the pointer. Failures before publication can leave ignored lock files or private staging debris; they do not justify claiming a partial batch was accepted.
This is local canonical publication. It does not create a Git commit, GitHub release, public export or website deployment. Those retain the separate P6 gates.
Offline publication checks
make check now runs scripts/check_expanded_publication.py. It resolves the
relative canonical pointer once, checks the exact fifteen-table contract and byte
fingerprints, validates all tables and references, checks the retained plan and
claim-proof coverage, and rechecks record/field acceptance against the identity
registry version pinned at publication. A valid later registry append does not
rewrite or invalidate that earlier baseline. Predecessor snapshots are checked
recursively; missing or cyclic history fails. Tables and receipts must be retained
regular files in the generation directory.
This read-only check needs no private acquisition files and writes no lock or publication. It detects inconsistent byte, plan, proof, identity-history and pointer changes, including alterations that leave the candidate schema valid. It does not reacquire evidence, repeat PDF rendering or visual judgment, authenticate a reviewer, or establish that a legal status is current. Self-consistent fingerprints are not a cryptographic signature. Git review, source verification and the separate release manifest/remote-verification gates remain required.
Regression tests exercise the first immutable real snapshot in a temporary copy, including byte drift, missing tables, changed review partitions, missing/wrong-source proofs, false support flags, appended versus altered identity history, nonportable pointers, redirected files, unsupported plans and cyclic predecessors.
Event validity maintenance
Publication profile 1.0.2 adds explicit event-validity chains, narrowly predecessor-bound historical_fields, a public evidence-proof fingerprint and monotonic maintenance review chronology. It authenticates predecessor history before promotion and rechecks proofs/history on exact replay. That five-event migration selected profile 1.0.2. The compound-condition publication subsequently selected 1.0.3, and the current qualified policy-succession publication selects 1.0.4 with ten retained historical events. Broader later-history, freshness and consumer/delivery gates remain open.