Provenance Required Policy
The provenance-required policy enforces OWASP LLM09b (misinformation / overreliance, sub-control "provenance"). It is the stricter cousin of the Grounding Policy: where grounding accepts a threshold like "groundedness score >= 0.6 is fine," this handler is binary — cite-or-fail.
Three independent checks:
- Unsupported-claim tolerance — how many claims may go uncited before the policy fires.
- Minimum citation count — total citations across the run must meet a floor.
- Source-type allowlist — every citation must reference an approved source type (e.g.,
knowledge_base,verified_corpus,internal_doc).
Aligns with NIST AI RMF MS-2.1 (Measure → Truthfulness).
Rules
| Rule | Type | Default | Description |
|---|---|---|---|
require_citations_per_claim | boolean | true | When true, unsupported claims are counted against max_unsupported_claims |
max_unsupported_claims | integer | 0 | Tolerance — claims allowed without citation (0 = strict) |
min_citations | integer | 1 | Minimum total citation count across the run |
allowed_source_types | string[] | [] | Empty = any type accepted; non-empty = citations must reference one of these |
action_on_violation | string | "block" | "block" or "warn" |
Optional: scan_mid_execution (boolean, default false) — enables defensive mid-run check on streaming surfaces.
How It Works
| Phase | What it does |
|---|---|
before_workflow | Stashes rules on context._provenance_rules. No enforcement (claims don't exist yet). |
mid_execution (opt-in) | Same evaluation as after_workflow — runs only if scan_mid_execution=true. |
after_workflow | Primary enforcement — full claim/citation inventory check. |
Evaluation order (short-circuits on first violation):
- Unsupported-claim tolerance — if
len(unsupported_claims) > max_unsupported_claims. - Minimum citation count — if
len(citations) < min_citations. - Source-type allowlist — first citation with disallowed source type.
Context Attributes Read
| Attribute | Phase | Purpose |
|---|---|---|
context.citations | mid, after | List of citation objects (str OR dict with source_type/type/kind/source) |
context.unsupported_claims | mid, after | List of claim strings, OR an integer count |
context.grounding_entries | mid, after | Optional per-step grounding records (read for richer metadata) |
Example Policy
Regulated knowledge agent — every claim must cite an internal knowledge-base or verified corpus source, no exceptions:
{
"require_citations_per_claim": true,
"max_unsupported_claims": 0,
"min_citations": 1,
"allowed_source_types": [
"knowledge_base",
"verified_corpus",
"internal_doc"
],
"action_on_violation": "block"
}
SDK Integration
import waxell_observe as waxell
waxell.init()
@waxell.observe(agent_name="compliance-assistant", enforce_policy=True)
async def answer(question: str) -> str:
# Populate citations as you generate the response.
async with waxell.WaxellContext(agent_name="compliance-assistant") as ctx:
result = await retrieve_and_answer(question)
ctx.set_citations([
{"source_type": "knowledge_base", "id": "kb-1234", "title": "..."},
{"source_type": "verified_corpus", "id": "vc-9876"},
])
# after_workflow: blocks if no citations or wrong source type
return result
Observability
| Field | Example |
|---|---|
| Category | provenance-required |
| Action | block |
| Reason | "3 unsupported claim(s) detected; tolerance is 0." |
| Metadata | {"phase": "after", "signal": "unsupported_claims", "count": 3, "limit": 0, "owasp": "LLM09"} |
Disallowed source type:
| Field | Example |
|---|---|
| Reason | "Citation source type 'web_search' not in approved list ['knowledge_base', 'verified_corpus']." |
| Metadata | {"signal": "disallowed_source_type", "source_type": "web_search", "owasp": "LLM09"} |
Common Gotchas
unsupported_claimscan be either a list or an integer count. The handler handles both. If your runtime emits a count, the metadatacountfield reflects that integer; if it emits a list,countislen(list).min_citations=0disables the citation-floor check. Set to1(the default) to require at least one citation.- Source-type lookup tries four keys on dict citations. In order:
source_type→type→kind→source. Plain-string citations are treated as the source type itself. - Case-insensitive matching on source types.
"Knowledge_Base"and"knowledge_base"are equivalent. - Citations with empty/missing source type are skipped silently during the allowlist check — they don't fire a violation. To enforce a present source type, use a separate validator.
- Grounding vs Provenance. Grounding scores claims for support strength; provenance demands a citation regardless of score. Often deployed together — grounding warns at low scores, provenance blocks at zero citations.
before_workflowis a no-op for enforcement. It only stashes the rules; the actual check happens atafter_workflowonce claims exist.
Next Steps
- Policy Categories — All 49 categories
- Grounding Policy — Citation thresholds + abstention (soft enforcement)
- Retrieval Policy — Controls over RAG sources
- Model Card Required — Companion OWASP LLM03 supply-chain control