One Rule, End to End

The code works and the tests pass. That is roughly the halfway point. This chapter follows a single rule — TWP-FIELD-001 — from an empty workshop document to a live task in a research assistant's queue, through eleven separately approved steps.

Chapter 7 · Part III · The Audit Layer
Organization of Power Lab
Worked example · TWP-FIELD-001
~24 min read

A rule is not a feature. A feature that is wrong wastes an afternoon. A rule that is wrong generates hundreds of careful human judgements answering the wrong question, and those judgements become evidence rows that are expensive to retract.

Section 1The gap after “it works”

In ordinary software, the distance between a passing test suite and a deployed feature is short: merge, deploy, watch the error rate.

For a rule it is not short, and the reason is that a rule does something no ordinary feature does. It reaches into a production research database holding a quarter of a million rows that represent years of work, and it changes them — through a form, operated by a person, one record at a time, permanently.

Consider what TWP-FIELD-001 actually requires before its first violation can appear:

  • Eleven new columns must exist on a 50,902-row table.
  • An existing column must be renamed, preserving every value.
  • A new evidence foundation — three tables — must exist.
  • 41 obsolete rule versions must be archived without losing their 9,010 historical violations.
  • The rule must be registered with its exact bindings, dependencies, and segments.
  • A bootstrap must set a status on every existing official.
  • A scan must run and produce violations.

Each of those touches production. Each can fail in a way that is expensive or impossible to reverse. So each is a separate, separately approved step — and this chapter is about why that is discipline rather than bureaucracy.

Section 2The workshop document

Every rule begins as a document, before any code. It answers six questions, and the first two are the ones that most often reveal that a rule should not be built at all.

Checklist The rule workshop questions
#QuestionWhy it comes first
Q0aWhat does a research assistant actually do when this fires?If it cannot be written in four sentences, the rule is not ready.
Q0bHow many violations, and how long does one take?40,000 violations at three minutes each is a wall, not a queue.
Q1What evaluation support does it need — new fields, a view, derived attributes?Determines the migrations before you write the evaluator.
Q2How is record provenance kept separate from violation evidence?Chapter 4. Merging them is unrecoverable.
Q3What is the future-delivery transaction, and what is the retroactive bootstrap?Two different code paths. Chapter 5.
Q4What makes a decision on this rule stale?Get it wrong and resolved violations silently rot.

TWP-FIELD-001's answers, in brief:

Q0a. A researcher opens an official whose birth status is unreviewed. They see the raw source string, the legacy value, and the cited source. They either record a validated birth value with its precision and calendar, or select a status — unknown, withheld, conflicting — with a rationale. They submit.

Q0b. Every one of 50,902 officials starts unreviewed, so the unfiltered violation count is 50,902. That is the wall. The answer is priority segments: party leaders, legislative leaders, and family-record principals are high priority; everyone else is low. The high segments are the queue; the long tail waits, indefinitely if necessary.

Q4. Any change to the birth fields themselves. If someone later corrects birth_year, a decision made against the old value is stale and must be re-evaluated.

Section 3The manifest

The workshop document becomes a manifest — a machine-readable declaration of everything the rule is. This is the single source of truth; the registry rows in PostgreSQL are a projection of it, not an independent definition.

Code snapshot backend/audits/field_completeness_v1.py
RULE_MANIFEST = {
    "rule_code": "TWP-FIELD-001",
    "name": "Official birth information is reviewed and complete "
            "at its supported precision",
    "rule_type":    "field",
    "rule_subtype": "completeness",
    "version_number": 1,
    "base_priority": "low",
    "can_be_batch": True,
    "batch_mode":   "group_display",
    "schema_name":  "twpolitics",
    "table_name":   "officials",
    "subject_key":  "official_id",
    "executor_key":     "twpolitics.official_birth_completeness",
    "executor_version": "twp-field-001-v1",
    "resolution_adapter_key": "field.completeness.birth.v1",
    "context_adapter_key":    "official.birth_profile.v1",
    "segments": SEGMENTS,

    "criteria_read": (
        {"relation": "twpolitics.officials",
         "fields": ("official_id", *READ_BIRTH_FIELDS)},
    ),
    "resolution_write": (
        {"relation": "twpolitics.officials",
         "fields": WRITE_BIRTH_FIELDS},
    ),
    "context_read": (
        {"relation": "twpolitics.officials",
         "fields": ("official_id", "official_name",
                    "legacy_birthyear", *READ_BIRTH_FIELDS)},
        {"relation": "twpolitics.record_evidence",
         "fields": ("record_evidence_id", "source_id",
                    "citation_snapshot")},
    ),
    "revalidation_triggers": (
        {"relation": "twpolitics.officials",
         "fields": READ_BIRTH_FIELDS},
    ),
    "outcomes": (
        "save_known", "save_unknown", "save_withheld",
        "save_conflicting", "confirm_current",
        "insufficient_evidence", "reopen",
    ),
}
The real manifest, lightly reformatted. Every concept from Chapter 6 appears here as data.

Read it against Chapter 6 and the correspondence is exact. The four declared sets are criteria_read, resolution_write, context_read, and revalidation_triggers. The type and subtype select the form framework. base_priority plus segments is the queueing policy.

Two details reward a closer look.

context_read includes record_evidence. The researcher sees where the value came from — and the manifest declares that as read-only context, so the resolution form cannot alter provenance while recording a decision. Chapter 4's separation, enforced structurally.

There are seven outcomes, not two. Four save a value or status; confirm_current affirms an existing value without changing it; insufficient_evidence defers honestly; reopen supersedes a previous decision. Each is declared, and nothing outside the list can be chosen.

Section 4Checksums

The manifest is hashed. Its digest becomes the rule version's identity.

Code snapshot Deterministic digest
def digest(value) -> str:
    encoded = json.dumps(
        value,
        sort_keys=True,            # order must not matter
        separators=(",", ":"),     # no incidental whitespace
        ensure_ascii=False,
        default=_json_default,
    ).encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()

DEFINITION_CHECKSUM = digest(RULE_MANIFEST)

sort_keys=True and the tight separators matter: without them, reordering a dictionary or reformatting the file would change the hash and make the rule appear to have changed when it had not.

But the manifest is only a declaration. The behaviour also depends on the code that reads it. So the registration packet pins a SHA-256 for every participating file:

Inventory Pinned implementation and migration hashes
ArtifactSHA-256 (first 12)
audits/field_completeness_v1.py762525575c57
audits/field_completeness_executor.py0e91ca95c985
audits/field_completeness_forms.pyca6850455316
audits/field_completeness_services.py0be774312f98
audits/field_completeness_orchestration.py5d5179e6ff44
audits/legacy_archive.py6c40537dc5c0
provenance/migrations/0001_initial.pyaa156708d4f4
audits/migrations/0007_add_evidence_foundation.py388c80ca7250
audits/migrations/0008_add_rule_registry_contract.pyc2d30b74c14a
twpolitics/migrations/0005_add_record_evidence.pyf5acc0b125c1
twpolitics/migrations/0006_add_..._constraint.py37c063742171
twpolitics/migrations/0007_rename_..._birthyear.py47d545e62351
Fourteen artifacts in total, plus the rehearsal harness itself at a5ab4bd8e611. Truncated here for legibility; the packet records full digests.

These are stored on the rule version, and runtime refuses to preview or apply if any of them has moved.

The consequence is worth stating plainly: a developer cannot change the behaviour of a live rule by editing a Python file. The change is detected and the rule stops. Reactivating it requires a new version, a new checksum, and a new approval.

Why this is not paranoia

A researcher's decision is only interpretable if you know what question they were answering. If the evaluator could be edited underneath a completed decision, the database would contain a judgement whose meaning cannot be recovered — you would know that Emily chose save_known, but not what save_known required at the moment she chose it.

The packet is explicit that byte changes are never waved through: any byte change requires new hashes and a repeat rehearsal; it must not be accepted as harmless drift.

Section 5Fifty-one dependencies

The four declared sets are not stored as free-form JSON. They are expanded into normalised rows — one per field per set — so that they can be indexed, queried, and validated against the live database catalogue.

Code snapshot backend/audits/models_evidence.py
class RuleDependency(models.Model):
    """One normalized, versioned relation or field dependency of a rule."""

    class DependencyKind(models.TextChoices):
        CRITERIA_READ        = "criteria_read", ...
        RESOLUTION_WRITE     = "resolution_write", ...
        CONTEXT_READ         = "context_read", ...
        REVALIDATION_TRIGGER = "revalidation_trigger", ...

    rule_version    = models.ForeignKey("RuleVersion", ...)
    dependency_kind = models.CharField(choices=DependencyKind)
    schema_name     = models.CharField(max_length=63)
    relation_name   = models.CharField(max_length=63)
    field_name      = models.CharField(max_length=63)
    semantic_role   = models.CharField(max_length=80)
    ordinal         = models.PositiveSmallIntegerField()
    contract        = models.JSONField()

TWP-FIELD-001 expands to exactly 51 dependency rows. Registration asserts that number, and asserts it again on replay.

Why insist on an exact count? Because “the rule registered successfully” is a much weaker claim than “the rule registered with exactly one version, 51 dependencies, three segments, one inactive binding, and a current drift state.” The first is compatible with a partial registration; the second is not.

semantic_role is the field that makes dependencies useful rather than merely present. It records what part the field plays — the subject key, the value, the precision, the status — so the resolution form can render an appropriate control for each without hard-coding column names.

Section 6The rehearsal

Before any of this runs against production, the entire ordered sequence is rehearsed against a disposable restore: a throwaway PostgreSQL cluster built from a frozen snapshot of the real database.

The snapshot is pinned by digest, so there is no ambiguity about what was rehearsed against:

Evidence The frozen production snapshot
archive
production-source-<timestamp>-<digest>
sha-256
c0e9001057e48742d406c98e0749e69df87dda5f817243fbc9ca8a0ea2ceeda4
officials
50,902 rows
birth-state digest
0604cd0044fb6ebbd6eaad4027e9de14da38501f5596b5ea68f4a48cc08589b5

The rehearsal must prove six things. Only the first is obvious.

Checklist What a rehearsal proves
  1. 1It worksThe sequence runs to completion and produces the intended state.
  2. 2It reversesForward, backward, forward again, for every reversible migration.
  3. 3It preserves identityTable OIDs, sequence OIDs, row counts, and content fingerprints unchanged where they should be.
  4. 4It refuses on driftA deliberate drift probe fails for the intended reason and rolls back completely.
  5. 5It replaysRunning twice changes nothing the second time, and says so.
  6. 6The backup restoresThe pre-operation backup is actually restorable, proven by restoring it.

Item 4 is the one that separates a rehearsal from a demonstration. It is easy to show that a sequence works when everything is as expected. What matters is whether it refuses when something is not — because in production, something eventually will not be.

In the code

The harness is .ai/scripts/test_twp_field_001_postgresql.py, 1,135 lines, and it passes 13 of 13 checks. It exercises the real management command in read-only mode, refuses registry drift and stale archive selections, verifies the exact list of changed identifiers, and proves protected state is preserved.

Note that this is separate from the Django test suite, which passes 20 focused tests and 862 overall. The Django suite runs on in-memory SQLite, so PostgreSQL-specific behaviour — raw check constraints, OID preservation, jsonb semantics, query-plan inspection — is not covered by a green run. Neither suite substitutes for the other, and a rule needs both.

Section 7Eleven gates

With the rehearsal passed, the production path is fixed. Every numbered step needs its own explicit approval.

Timeline From frozen candidate to live rule
  1. 1 · Freeze and recomputeOne exact source candidate. Recompute every pinned hash; abort on any mismatch.
  2. 2 · Push, PR, mergeA bounded pull request. Merge only after CI passes.
  3. 3 · Deploy with the runtime disabledThe code is live; the rule is not running.
  4. 4 · Repeat the read-only preflightConfirm production has not drifted since the rehearsal.
  5. 5 · Back up and prove restorationNot “take a backup” — restore it somewhere and verify it.
  6. 6 · Apply only the additive migrationsEvidence foundation, registry contract, record evidence, constraint. Do not let a blanket migrate reach the rename.
  7. 7 · Re-read everythingSchema, counts, fingerprints, registry defaults, and the exact archive selection.
  8. 8 · Archive cutover41 versions, 33 bindings. Assert the exact changed-ID list; prove idempotent replay.
  9. 9 · The preserving renameRequire identical row count and birth-state fingerprint before and after.
  10. 10 · Register inactiveOne version, 51 dependencies, three segments, one inactive binding, current drift state, no change to history.
  11. 11 · Backfill, bootstrap, scan, activateEach one separately reviewed. None implied by any earlier gate.
Steps 1–4 are reversible or read-only. Steps 5–11 touch production data.

Two of these deserve comment.

Step 5 says “prove restoration,” not “take a backup.” An unverified backup is a belief, not a safety net. The only evidence that a backup works is a restore.

Step 6 contains a warning about migrate. Django's default behaviour is to apply every pending migration. Here, the additive migrations are safe and the rename at step 9 is not yet approved — so a blanket migrate would execute an ungated production schema change. The migrations must be applied by explicit name.

That is a general hazard worth internalising: a tool's convenient default is often a gate you did not know you were passing through.

Section 8Register, then activate

Steps 10 and 11 are separate, and the separation is one of the more useful ideas in the whole design.

Registering inactive writes the rule into the registry — version, dependencies, segments, binding — with the binding switched off. Nothing evaluates. No violations appear. But everything the registration asserts is now verifiable against the live database: every schema, table, column, and type named in those 51 dependencies is checked against PostgreSQL's catalogue.

This is a real test that cannot be run any other way. A rule can pass every unit test against a fixture and still reference a column that does not exist in production, because the fixture and production were built from different assumptions. Registering inactive finds that, harmlessly.

Activating switches the binding on and permits a scan. Only then do violations exist, and only then can researchers act.

Read the numbers before onboarding anyone

After the first scan, stop. Count the violations per segment. If the high-priority segments yield fewer than about fifty violations, the queue is too thin to occupy a team and the segment predicates need widening. If they yield more than a few thousand, the first tranche needs tightening.

Q0b in §2 asked this question at the workshop stage as an estimate. This is where the estimate meets the data, and it is much cheaper to adjust a segment predicate before five researchers have started work than after.

Section 9Drift

One concept underlies almost every guard in this chapter.

Drift is any difference between a recorded expectation and observed reality: a checksum that has moved, a row count that does not match, a registry state that is not what the last operation left behind, a schema that has changed since the rehearsal.

The rule about drift is absolute:

Drift is always a refusal. Never a warning you proceed past.

This is harder to hold to than it sounds, because drift usually appears at the least convenient moment — mid-sequence, with an approval already granted and a researcher waiting. The temptation to decide that this particular difference is probably harmless is exactly when the discipline matters, because the whole value of the guard is that it fires when your judgement about harmlessness is untested.

The pattern in code is fail closed: on any guard failure, missing precondition, or unexpected value, refuse and roll back rather than continuing on a best-effort basis. Partial completion of an eleven-step production sequence is worse than no completion, because it leaves the system in a state nobody designed.

In the code

TWP-FIELD-001's production preflight is a good example of drift-checking done properly. It ran in read-only, repeatable-read transactions and verified: that it was connected to the expected production database under the expected application role; the exact PostgreSQL major version; that transaction_read_only was on; that audits.0006 and twpolitics.0004 were applied and the six later migrations were not; that official_birthyear existed as a nullable integer; that legacy_birthyear was absent; that TWP-FIELD-001 was absent; and that every additive relation was absent.

It also recorded exact history counts that later steps must preserve: 6,228 runs, 258,405 records, 9,010 violations, 19,091 observations, 19,407 actions, and 6,134 changes. Those numbers are how step 8's archive cutover proves it archived rules without touching their history.

Eleven gates for one rule is a lot. It gets cheaper: the second rule of the same subtype reuses the framework, the forms, the templates, and the proven sequence. What does not get cheaper is the workshop, because that is where the thinking happens — and Chapter 8 takes on the hardest case of all, where the thing being decided is not a value but an identity.

Terminology introduced in this chapter

manifest
The machine-readable declaration of everything a rule version is. The source of truth; registry rows are its projection.
definition checksum
A deterministic SHA-256 over the manifest, computed with sorted keys and no incidental whitespace.
pinned implementation hash
A SHA-256 of a participating source or migration file, stored on the rule version. A mismatch stops the rule.
rule dependency
One normalised row per field per declared set, validated against the live catalogue at registration.
semantic role
What part a field plays in a rule — subject key, value, precision, status — so forms need not hard-code column names.
disposable restore
A throwaway database built from a digest-pinned production snapshot, used to rehearse an operation.
drift probe
A deliberately corrupted rehearsal that must fail for the intended reason and roll back completely.
protected state fingerprint
An aggregate hash of data an operation must not change, captured before and after to prove it did not.
register inactive
Writing a rule into the registry with its binding switched off, so its declarations can be validated without evaluating anything.
drift
Any difference between a recorded expectation and observed reality. Always a refusal.
fail closed
On any guard failure, refuse and roll back rather than proceeding on a best-effort basis.