Access Policy and Filter Manual
This is the user- and administrator-facing manual named in the TOR ("User Documentation: Access Policy and Filter Manual"). It covers the access model and the Policy Filter together, and explains how the two relate, what a user experiences, and what an administrator sees and does. For the internals of what the filter catches, see the Filter Policy Documentation.
Two independent layers
The platform protects a query answer with two orthogonal layers that are both
always on for /query and /query/stream:
| Layer | Question it answers | Applies to |
|---|---|---|
| Access control (RBAC + ABAC) | May this user see this document / use this capability? | The request and the retrieved corpus |
| Government Policy Guard (the Policy Filter) | Is this generated answer policy-safe to emit? | Query-answer output |
Access control decides what a user can reach; the Policy Guard decides whether the answer produced is allowed to be sent. They are enforced at different points and neither substitutes for the other — an answer built entirely from documents a user is authorised to see is still passed through the guard before it returns.
The access model
Access is RBAC + ABAC (ADR-0001; see Security and Governance). The working roles are:
| Role | Scope |
|---|---|
admin | System administration, user/role management, ingestion config, policy config |
operator | Run ingestion, monitor pipelines, view dashboards |
user | Query the chatbot, view own conversation history |
ABAC attributes — department and document classification — refine RBAC for document-level visibility. These attributes shape which chunks a query can retrieve, and so shape the grounding an answer is built from; they do not change the Policy Guard's behaviour, which is identical for every role.
What a user experiences
- A user queries the chatbot and receives a grounded, cited answer.
- If the Policy Guard blocks the answer, the user receives a fixed refusal
message instead of the generated text: "I cannot provide that response because
it violates the Government Policy Guard", with a citation appended when a
grounded fallback chunk exists (
src/aceh_rag/policy.py:96-121). - The refusal is reported as a complete response, not a truncated draft, so a user is not invited to "retry with more tokens" against a policy block.
- The offending output is never shown, and never stored. On the chat path the
blocked answer text is replaced by the refusal before the assistant message is
persisted (
src/aceh_rag/services.py:1185-1200, 1229), so the offending generation does not survive in the conversation history.
The guard cannot be turned off for a query, a user, or a role
(src/aceh_rag/policy.py:5). There is no per-user allowlist that skips it.
What an administrator sees
Guard decisions for persisted chat answers are written to the audit log — for allowed answers as much as blocked ones, so "allowed" is evidence too, not just silence. Timeout persistence exceptions follow ADR-0041. An administrator reviewing the audit trail sees, per stored answer:
- the action (
allowedorblocked), - the classifier version that decided it (
deterministic-rules-v2), - the policy categories that fired (
sara_inflammatory,jailbreak) (src/aceh_rag/services.py:1277-1289).
On the chat path the same fields are also attached to the assistant message
metadata under policy_guard (src/aceh_rag/services.py:1270-1274). The
ungrounded POST /generate primitive is outside this control: it neither runs the
guard nor writes a policy-audit row (ADR-0050).
Policy findings deliberately carry no matched text — the reason field records
"protected-group reference within an incitement construction", never the
offending sentence (src/aceh_rag/policy.py:81-84). Audit rows and logs must not
carry answer content. An auditor sees that a block happened and which
category, not the text that triggered it.
What an administrator does
The admin role scope includes policy config. In practice an administrator's
interaction with the Policy Filter is:
- Read the rules. The entire guard is inspectable Bahasa/English/Acehnese
word rules in one file (
src/aceh_rag/policy.py) — there is no opaque model to interrogate. - Edit the customization surface — the lexicon and construction lists documented in Filter Policy → Rules-customization surface.
- Prove the change by running
make eval-slice SLICE=policy, which reports the catch and false-refusal rates and fails on any undocumented regression. A rule change is never shipped on judgement alone. - Monitor the audit log for the balance of
blockedvsalloweddecisions and for categories that fire unexpectedly.
The full change loop — evasion → corpus case → rule → gate → ship — is the Periodic Policy Updates process.
Two Policy Filter deliverables in the TOR are client-delivery activities owned by humans, not tasks the engineering team or this manual completes on its own:
- the rules-customization working session with Pemprov Aceh stakeholders, where the boundary between blocking harm and refusing legitimate work is agreed using the measured rates, and
- security-administrator training.
They are named here so the reader knows they exist and who owns them; the material above is the reference those activities draw on.
Related
- Filter Policy Documentation — what the guard catches and what it deliberately does not.
- Periodic Policy Updates — how a rule changes without regressing.
- Security and Governance — data sovereignty, audit log, OCR governance.