Periodic Policy Updates
The Government Policy Guard is not a fixed artefact. Language moves, new evasions appear, and the line a government wants drawn can shift. This page documents the periodic policy-update process — the TOR/KAK Policy Filter line item that keeps the guard current — and, just as important, the discipline that stops an improvement in one place from quietly opening a hole in another.
The whole loop is anchored on one command:
make eval-policy
which measures the guard's catch rate and false-refusal rate against the
labelled adversarial corpus (eval/policy_guard_adversarial.json) via
run_policy_eval (src/aceh_rag/evaluation.py).
Why a process, and not just "edit the rules"
The guard's rules are the lexicon and construction lists themselves (see the rules-customization surface). That makes every edit powerful and blunt: widening a rule to catch one more evasion routinely refuses a class of legitimate answers, and narrowing it to stop a false refusal opens an evasion. In a province where questions about religion, ethnicity and Qanun implementation are the day job, a careless widening is not a cosmetic regression — it silently starts refusing the legitimate work.
So a rule is never shipped on judgement alone. It is shipped through the loop below, which forces the trade-off to be measured against real labelled cases before it lands.
The loop
- Observe. A new evasion slips through, or a legitimate answer is wrongly refused — from a review, a client report, or a red-team pass.
- Label it. Add the exact string to
eval/policy_guard_adversarial.jsonwith its correct label (inflammatory-and-should-block, or legitimate-and-should-pass) and its language (id/en/ace/mixed) and surface (/queryor/generate). The corpus is the specification of what the guard must do. - Let the gate fail.
make eval-policynow reports a disagreement between the label and the guard's behaviour. This is the point: the run exits non-zero on any undocumented disagreement, so a new evasion or a new false refusal is a build failure, not a percentage that quietly moved. The gap is now visible and cannot be ignored. - Resolve — one of two ways, both explicit:
- Fix the rule (
src/aceh_rag/policy.py) so the case is handled, then re-runmake eval-policy. It passes only if the fix did not break any other labelled case — the false-refusal suite is weighted equally with the evasion suite, so a fix that catches the new evasion by refusing legitimate answers will fail the gate. This is the regression guard. - Accept the gap where catching one more evasion would cost a legitimate refusal. Record the case as a documented accepted limitation (with its case id) so the gate passes and the limitation is written down — never a silent miss. These accepted gaps are exactly the ones named in what the guard deliberately does not catch and in ADR-0008's limitations section.
- Fix the rule (
- Version + ship. The classifier reports a versioned name
(
deterministic-rules-v2,src/aceh_rag/policy.py:69-73), so audit rows written before and after a rules change are distinguishable. Bump it when the rule set changes materially, so a reviewer can tell which guard version produced any past decision.
The result is a guard that can be improved forever without regressing: every edit is a measured trade-off against a growing set of real cases, and every accepted weakness is on the record with a case id rather than hidden in a moved number.
What still needs a human
Two parts of "keeping the policy current" are deliberately not an engineering loop:
- Where the line is drawn is a client decision. Deciding that Pemprov Aceh wants a particular euphemism blocked, or a particular topic left open, is the "rules customization with stakeholders" working session named in the TOR — a client-delivery activity, not something the engineering team settles unilaterally. This process gives that session a concrete, testable artefact to steer: add the agreed cases to the corpus, and the gate enforces them thereafter.
- The corpus is ground truth only once it is validated. Every label and every
line of Bahasa Indonesia and Acehnese in
eval/policy_guard_adversarial.jsonwas authored by an AI agent and prints that warning next to every rate it reports. Sign-off from a native Bahasa Indonesia speaker, a native Acehnese speaker, and a Pemprov Aceh government-communication-policy owner is required before the measured rates are an external measurement of safety rather than a regression signal on the guard's own behaviour. Onboarding those reviewers into this loop — so their judgement becomes labelled cases — is the standing task that makes the process trustworthy.
Related
- Filter Policy Documentation — what the guard catches, what it does not, and the rules-customization surface this loop edits.
- Access Policy and Filter Manual — the administrator-facing view.
- ADR-0008 — the authoritative rationale and the limitations list this process maintains.