Rules, Versions, and Bindings

Every audit system has to answer one awkward question before it can do anything useful: what, exactly, does “clean” mean? This chapter takes that question apart into five objects, and explains the one most people get wrong.

Chapter 6 · Part III · The Audit Layer
Organization of Power Lab
Worked example · twpolitics
~22 min read

A research assistant opens the audit dashboard and sees a list of things to fix. Behind each line sits a small stack of objects that most data systems never separate — and the separation is what lets the lab say, two years later, exactly what standard a given record was judged against.

Section 1What “clean” cannot mean

Start with a record from the Taiwan Politics database. An official — a person who has stood for election, led a party branch, or chaired a legislative committee. The database holds 50,902 of them.

Here is one, as it looked the day it was loaded:

Data snapshot twpolitics.officials, immediately after load
official_idofficial_namelegacy_birthyearbirth_status
3021林文德民國45年unreviewed
3104陳淑芬NULLunreviewed
3417黃建宏1961unreviewed
3418黃建宏1963unreviewed
Illustrative records. Names and values are invented; table and column names are real.

Four rows, four different problems. Row 3021 has a birth year written in Republic of China era notation, which is correct in its source and unusable in a calculation. Row 3104 has nothing at all — but we cannot tell whether that means nobody looked, or somebody looked and failed to find it. Rows 3417 and 3418 might be two men who share a common name, or one man entered twice from two sources that disagreed by two years.

Now try to write down what it would mean for this table to be “clean.” You cannot, because the question is malformed. Cleanliness is not a property a row has. It is a property a row has with respect to a particular standard, at a particular moment. Row 3021 is perfectly clean if your standard is “preserve what the source said.” It is unusable if your standard is “every official has a comparable birth date.”

So the audit system never asks whether data is clean. It asks a narrower question, over and over: does this specific record satisfy this specific criterion, as that criterion was defined on this specific date? Everything in this chapter exists to make that question precise enough to answer mechanically, and stable enough to still mean something next year.

Section 2Four kinds of rule

A rule is a positive criterion — something the data ought to satisfy. Writing rules as positives rather than as error descriptions is a small discipline with a large payoff: it forces you to say what good looks like, and it means a rule can be satisfied, which is a reportable fact.

The lab's audit system recognises exactly four types. Not five, not a general-purpose “other.”

Taxonomy The four canonical rule types
TypeTokenThe question it asks a researcher
uniquenessUNIQDo these identifiers denote the same entity, different entities, or is it still unresolved?
referentialREFWhich target is correct, or is no candidate a match?
fieldFIELDWhat evidence-backed value or permitted status completes this field?
consistencyCONSWhich underlying fact is wrong, or is an exception supported?

Classify a new rule by working down that list in order and stopping at the first match. Is it a collision under some identity key? That is uniqueness. Is it about the existence or target of a relationship? Referential. Is it about one atomic value standing on its own? Field. Is it a predicate over two or more values that are already established? Consistency.

The order matters because the categories overlap if you let them. Two officials with the same name and conflicting birth years could be filed as a consistency problem — two values that disagree. Filing it that way would be a mistake, because the actual question a researcher must answer is whether these are one person or two, and that is an identity question. Resolve it as uniqueness and the birth years take care of themselves.

A category that eats everything

Consistency is the dangerous one. Because it is defined as “a predicate over established values,” almost anything can be forced into it. The system's architecture contract draws an explicit fence: consistency does not decide identity, uniqueness, relationship targets, or the independent validity of one field. If a rule you are drafting seems to be a consistency rule but its resolution would change who a record refers to, you have mis-typed it.

Section 3Definition, version, binding

This is the section that repays careful reading. A “rule” in ordinary speech is actually three nested objects, and collapsing them is the most common design failure in audit systems.

Diagram How one idea becomes many live checks
DEFINITION The reusable idea. Changes almost never. VERSION 1 Immutable. Evaluator, contracts, checksums. VERSION 2 Not yet selected. BINDING A officials.birth active BINDING B sessions.start active BINDING C leaders.appt_end inactive SEGMENTS party-leader · high everyone else · low VIOLATIONS What a researcher actually opens. In the dashboard, a researcher sees only the binding — and it is simply called a rule.

The definition

The reusable idea, in words: “Official birth information should be reviewed and complete at its supported precision.” A definition has no code and no table attached. It is the thing you would explain to a colleague over coffee. It changes almost never.

The version

An immutable pinning of everything needed to execute that idea: which evaluator runs, which resolution form appears, what counts as an acceptable outcome, what priority policy applies — and a checksum over all of it.

Immutable is meant literally. You never edit a version to change what a rule means. You create a new one. This is the single most important constraint in the whole design, and it exists for one reason: a decision a research assistant made in October must still be interpretable in March. If the rule could be quietly edited underneath them, their judgement would become unreadable — you would know what they decided but not what question they were answering.

In the code

The lab's first rebuilt rule, TWP-FIELD-001, is declared in backend/audits/field_completeness_v1.py. Its registration packet pins a SHA-256 for that file and for every other file that participates in evaluating or resolving it — the executor, the forms, the services, and the four migrations that create its columns. At runtime, preview and apply both refuse to proceed if any of those hashes has moved.

That means 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 — which is the point.

The binding

And here is the word that gives people trouble.

A binding applies one selected immutable version to one exact place in the database.

The version says what the rule means. The binding says where it is being applied. One definition, with one version, can have many bindings — and that is not an edge case, it is the normal situation.

Section 4One idea, five places

The clearest example in the Taiwan catalogue is a rule the lab has since retired, but which illustrates the structure perfectly. Its idea was: a date should be present, complete, and standardised. One idea. One implementation. Five places it applied:

Worked example One definition expanded into five bindings
#schematablefieldActive?
1twpoliticsofficialsbirthyes
2twpoliticslegislative_sessionssession_startyes
3twpoliticslegislative_sessionssession_endyes
4twpoliticsparty_leadersappointment_startyes
5twpoliticsparty_leadersappointment_endyes
Each binding activates, deactivates, scans, and reports independently. This is why the lab's catalogue counts “22 definitions expanding to 46 applications.”

Why go to this trouble rather than writing five separate rules? Three reasons, and they compound.

  • One implementation to get right. The hard part of a date-completeness rule is the precision handling — year versus month versus day, Gregorian versus era calendars. You want to solve that once.
  • Independent control. Party-leadership dates might be ready to audit while legislative-session dates are still being restructured. Five bindings let you activate one and hold four.
  • Honest reporting. “This rule found 400 problems” is much less useful than “this rule found 400 problems, 380 of them in one table.”
In the code

The runtime table is audits.rule_applications, and the older codebase calls a binding an application. The two words mean the same thing; newer documentation prefers binding because “application” collides with the Django sense of the word. Both appear in the database today, and the glossary treats them as synonyms.

One more thing about bindings, and it is a kindness to the people who use the system: a research assistant never sees this three-level structure. In the dashboard, a binding is simply called a rule. The distinction between definition, version, and binding exists for reuse, for history, and for drift control — not to be explained to someone trying to fix a birth date.

Section 5What a binding declares

A binding is not just a table name. It declares four sets, and these four sets are what make the system safe to let researchers loose on.

Contract The four declared sets
SetWhat it controls
criteria_read_setThe exact schema, table, key, fields, and semantic roles used to evaluate the criterion. Nothing else is consulted.
resolution_write_setThe exact records and fields the resolution form may write. Nothing outside this set is writable, by anyone, through this rule.
context_read_setWhat is displayed to help the researcher decide — profiles, linked records, raw sources, history — but never mutated by this rule.
revalidation_trigger_setThe fields whose change makes a current observation or decision stale, scheduling re-evaluation.

The second one is the safety mechanism. A resolution form is not a database editor. When a researcher opens a birth-completeness violation, the form can write to the birth fields on that official and to nothing else — not the name, not the party, not another official's record. That constraint is declared data, checked at registration against the live PostgreSQL catalogue, and enforced at write time.

The fourth one is the subtle one, and it is where audit systems quietly rot. Suppose a researcher decides that two officials are different people, partly because their recorded birth years are four years apart. Later, someone corrects one of those birth years, and now they match. The earlier decision was reasonable when it was made and is wrong now. Unless the birth year is in the revalidation_trigger_set, nothing will ever reopen it, and a stale judgement will sit in the database forever wearing the costume of a resolved one.

Design rule

Every rule workshop must answer the question “what makes a decision on this rule stale?” before any code is written. Getting it wrong is not visible in testing — the system looks fine and slowly accumulates confident, obsolete answers.

Section 6Segments: making a big rule small

Here is a practical problem. TWP-FIELD-001 applies to all 50,902 officials, and on the day it is switched on, every single one of them has birth_status = 'unreviewed'. That is 50,902 violations.

At three minutes each, that is roughly 2,500 hours of work. Handing that queue to five research assistants is not a task list; it is a demoralising wall.

A priority segment is a versioned subset of a single binding — not a separate rule — with its own priority and label. TWP-FIELD-001 has three:

Worked example TWP-FIELD-001 priority segments
CodeSlugPriorityMembership predicate
…-S1party-leaderHighAn extant party_leaders.official_id reference
…-S2legislative-leaderHighAn extant legislative_leaders.official_id reference
…-S3political-familyHighAn extant family_records principal reference
—(base)LowEveryone else
Membership may overlap. Effective priority is high when any segment matches.

The segments are chosen to match what the lab actually publishes on. Officials who led a party, chaired a legislative body, or appear in the political-families data are the people whose birth dates end up in papers. The long tail can wait, indefinitely if necessary, without blocking anything.

Note what segments are not. They do not change what the rule means, and they do not change whether a record is in violation. A low-priority violation is just as real as a high-priority one. Segments affect queueing only — which is exactly why they are versioned subsets of one binding rather than separate rules that would fragment the reporting.

Section 7Three states, not one

Most task systems give an item one status field: open, in progress, done. An audit system cannot, and the reason is worth understanding because it is the design decision that makes the rest of the system honest.

Consider a researcher who examines officials 3417 and 3418 — the two 黃建宏 records — and concludes they are the same man, entered twice. She records that decision. What is the status of this violation now?

  • She has decided. That is true.
  • But the two records have not been merged, so nothing has been applied.
  • And because nothing has been merged, the collision still exists — the rule, re-run right now, still evaluates false.

One status column cannot express that, and yet it is a completely ordinary state that may persist for weeks. So the system tracks three independent dimensions:

States Three orthogonal dimensions
DimensionWhat it describesValues
EvaluationWhat the evaluator found, last time it ransatisfied · violated · indeterminate · error
DecisionWhat a human concludedundecided · decided · deferred · superseded
ResolutionWhat has actually happened to the datanot_attempted · planned · applied · pending_revalidation · validated · failed · stale

The one that surprises people is indeterminate. It is not a polite word for “violated.” It means the evaluator genuinely could not tell.

An example: a consistency rule checks that an election took place after a candidate was born. One record has a birth date precise to the day; another has only a year. Comparing them is not a failure of the data and not a violation — it is a comparison that cannot be made at the available precision. The system records indeterminate, and the interface is forbidden from presenting it as a proven error. Getting this wrong means sending researchers to fix things that are not broken, which is the fastest way to lose their trust in the system.

In the code

Every evaluator returns the same result envelope regardless of type: the binding and version identifiers, a stable subject fingerprint, the evaluation_state, the observed values, the subject revisions, a timestamp, and the executable checksum. Uniform envelopes are what let one violation-index page list findings from four different rule types without special-casing any of them.

Section 8What a researcher sees

All of that structure exists to produce one screen. Here is what 林文德's birth violation looks like when Emily opens it:

Interface Violation detail, field.completeness framework
twpolitics · Audit Dashboard / Violation 41,882
Violated High · party-leader TWP-FIELD-001 v1

林文德 · official 3021

Criterion: official birth information is reviewed and complete at its supported precision.

legacy_birthyear
民國45年
birth_raw_value
民國45年
birth_status
unreviewed
source
CEC candidate bulletin, 1998 municipal — retrieved 2024-11-02

Republic of China era year 45 corresponds to Gregorian 1956. Record the conversion, or select a status if the value cannot be established.

Illustrative. Layout follows the shared field-completeness form framework; values are invented.

Every element on that screen traces back to something declared earlier. The criterion sentence comes from the definition. The version badge comes from the immutable version. The fields shown come from the criteria_read_set; the source line comes from the context_read_set. The three buttons are exactly the outcomes the version's decision contract permits — no more.

And crucially, “Mark unknown” satisfies the rule. If Emily searches and cannot establish a birth year, recording unknown with a rationale closes the violation honestly. A guessed value is never required to clear a task. This is the rule that protects the data from the system: without it, a completeness audit becomes a machine for manufacturing plausible fiction.

In the code

Submitting the form does not write directly to twpolitics.officials. It calls an allowlisted Django service that takes an optimistic-concurrency lock, renders a write preview, checks permissions, writes the domain fields inside the resolution_write_set, creates a violation_decision row, attaches an evidence_target pointing at the cited source, appends an immutable change record, and then re-runs the evaluator. Only if that re-evaluation returns satisfied does the violation close.

The closed violation is not deleted. Its observations, decisions, evidence, and changes stay exactly where they are, which is how the lab can later state which rule version governed a given field in a published dataset.

In the next chapter we follow this single rule all the way through its lifecycle: the migrations that create its columns, the manifest that pins its behaviour, the rehearsal against a disposable copy of the production database, and the eleven separately approved steps between “the code works” and “a researcher can use it.”

Terminology introduced in this chapter

rule
A positive criterion that in-scope data ought to satisfy. Informally, the thing a researcher works; formally, three nested objects.
definition
The reusable idea and its decision vocabulary. No code, no table. Changes almost never.
version
An immutable pinning of evaluator, adapters, contracts, priority policy, and checksums. A semantic change creates a new version and never edits one.
binding
One selected version applied to one exact schema, table, key, and field set. Synonym: application. What the interface calls a rule.
priority segment
A versioned subset of one binding with its own priority. Affects queueing only, never truth.
violation
A concrete subject for which the binding evaluates false. Legacy synonym: issue.
indeterminate
An evaluation that genuinely could not be made — not a violation, and never displayed as one.
criteria read set
Exactly what the evaluator consults.
resolution write set
Exactly what the resolution form may write. Nothing else is writable.
context read set
What is shown to support a decision but never mutated.
revalidation trigger set
Fields whose change makes a decision stale and schedules re-evaluation.