Compliance and Audit Patterns
Compliance patterns address regulatory and organizational requirements: classifying contracts with tags, tracking contract bundle versions, rolling out new c...
Right page if: you need to tag contracts for regulatory classification, track contract bundle versions for audit trails, or deploy new contracts safely with observe mode. Wrong page if: you need the full EU AI Act / SOC 2 compliance mapping -- see https://docs.edictum.ai/docs/security/compliance. Gotcha: tags on contract actions appear in every audit event and can be filtered downstream. Use `observe_alongside` to run a new contract version in observe mode alongside your enforced version -- both produce audit events, but only the enforced version denies.
Compliance patterns address regulatory and organizational requirements: classifying contracts with tags, tracking contract bundle versions, rolling out new contracts safely with observe mode testing, and filtering audit events downstream.
Regulatory Tags
Use the tags field on contract actions to classify contracts by regulatory or organizational concern. Tags appear in every audit event and can be filtered, aggregated, and reported on downstream.
When to use: You need to demonstrate compliance with specific regulations or internal policies, and your audit system needs to categorize events by concern.
apiVersion: edictum/v1
kind: ContractBundle
metadata:
name: tagged-compliance
defaults:
mode: enforce
contracts:
- id: pii-output-scan
type: post
tool: "*"
when:
output.text:
matches_any:
- '\\b\\d{3}-\\d{2}-\\d{4}\\b'
- '\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b'
then:
effect: warn
message: "PII detected in output. Redact before downstream use."
tags: [pii, compliance, data-protection]
- id: sensitive-data-access
type: pre
tool: query_database
when:
args.table:
in: [user_profiles, payment_records, access_logs]
then:
effect: deny
message: "Access to '{args.table}' requires explicit authorization."
tags: [compliance, sensitive-data, audit-required]
metadata:
severity: high
regulation: internal-policyimport re
from edictum import Verdict, precondition
from edictum.contracts import postcondition
@postcondition("*")
def pii_output_scan(envelope, tool_response):
if not isinstance(tool_response, str):
return Verdict.pass_()
patterns = [
r"\b\d{3}-\d{2}-\d{4}\b",
r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",
]
for pat in patterns:
if re.search(pat, tool_response):
return Verdict.fail(
"PII detected in output. Redact before downstream use.",
tags=["pii", "compliance", "data-protection"],
)
return Verdict.pass_()
@precondition("query_database")
def sensitive_data_access(envelope):
table = envelope.args.get("table", "")
if table in ("user_profiles", "payment_records", "access_logs"):
return Verdict.fail(
f"Access to '{table}' requires explicit authorization.",
tags=["compliance", "sensitive-data", "audit-required"],
)
return Verdict.pass_()How tags work:
- Tags are arrays of strings attached to the
thenblock. They are stamped into theVerdictand everyAuditEventproduced by the contract. - Tags are free-form. Use a consistent naming convention across your bundles (e.g.,
pii,compliance,dlp,change-control). - Downstream systems can filter audit events by tag. For example, a compliance dashboard could show all events tagged
piiorcompliance.
Gotchas:
- Tags do not affect contract evaluation. They are metadata only. A contract tagged
[compliance]behaves identically to one with no tags. - There is no validation of tag values. Typos in tags (e.g.,
compliancinstead ofcompliance) will not produce errors but will silently break downstream filtering.
Contract Bundle Versioning
Every YAML bundle gets a SHA256 hash computed at load time. This hash is stamped as policy_version on every AuditEvent and OpenTelemetry span, creating an immutable link between any audit record and the exact contract bundle that produced it.
When to use: You need to prove which version of a contract bundle was active when an event occurred. This is essential for audits, incident investigations, and regulatory compliance.
apiVersion: edictum/v1
kind: ContractBundle
metadata:
name: versioned-policy
description: "Production policy v2.3 -- approved 2025-01-15."
defaults:
mode: enforce
contracts:
- id: block-sensitive-reads
type: pre
tool: read_file
when:
args.path:
contains_any: [".env", "credentials", ".pem"]
then:
effect: deny
message: "Sensitive file denied."
tags: [secrets, dlp]
metadata:
severity: highfrom edictum import Edictum
# Load a versioned YAML bundle — the SHA256 hash is computed
# automatically and stamped on every audit event.
guard = Edictum.from_yaml("policy.yaml")
# The policy_version attribute contains the hash
# print(guard.policy_version) # "a1b2c3d4..."How versioning works:
Edictum.from_yaml("policy.yaml")reads the raw YAML bytes.- A SHA256 hash is computed from the bytes.
- Every audit event produced by this bundle includes
policy_version: <hash>. - If the YAML file changes by even one byte, the hash changes, creating a new version.
Gotchas:
- The hash is computed from the raw file bytes, not the parsed structure. Whitespace changes, comment additions, and reordering produce different hashes.
- Store your YAML files in version control. The hash tells you which version was active; the VCS history tells you what changed and who changed it.
- The
metadata.descriptionfield is a good place to record human-readable version information, but it is not used in the hash computation -- the hash covers the entire file.
Dual-Mode Deployment
Roll out new contracts safely by starting in observe mode and switching to enforce after verifying the contract behaves as expected. Observed denials are logged as CALL_WOULD_DENY audit events without denying tool calls.
When to use: You are adding a new contract to an existing production bundle and want to validate it against real traffic before enforcing it.
apiVersion: edictum/v1
kind: ContractBundle
metadata:
name: dual-mode-rollout
defaults:
mode: enforce
contracts:
# Existing enforced contract
- id: block-sensitive-reads
type: pre
tool: read_file
when:
args.path:
contains_any: [".env", "credentials", ".pem"]
then:
effect: deny
message: "Sensitive file denied."
tags: [secrets, dlp]
# New contract in observe mode -- observe mode testing
- id: experimental-cost-gate
type: pre
mode: observe
tool: query_database
when:
args.query: { matches: '\\bJOIN\\b.*\\bJOIN\\b.*\\bJOIN\\b' }
then:
effect: deny
message: "Query with 3+ JOINs detected (observe mode). Consider optimizing."
tags: [cost, experimental]from edictum import Edictum, Verdict, precondition
from edictum.audit import FileAuditSink
@precondition("read_file")
def block_sensitive_reads(envelope):
path = envelope.args.get("path", "")
for s in (".env", "credentials", ".pem"):
if s in path:
return Verdict.fail("Sensitive file denied.")
return Verdict.pass_()
# Enforced guard (blocks tool calls)
enforced_guard = Edictum(contracts=[block_sensitive_reads])
# Observe guard (logs but never blocks)
observe_guard = Edictum(
mode="observe",
contracts=[block_sensitive_reads],
audit_sink=FileAuditSink("audit.jsonl"),
)How dual-mode works:
- Set
defaults.mode: enforcefor the bundle. - On the new contract, add
mode: observeto override the bundle default. - When the observe-mode contract matches, it emits a
CALL_WOULD_DENYaudit event. The tool call proceeds normally. - Review
CALL_WOULD_DENYevents in your audit logs. If the contract fires correctly with no false positives, change it tomode: enforce(or remove the override to inherit the bundle default).
Gotchas:
- Observe mode applies to all contract types. For postconditions, when
mode: observeis set and the condition matches, effects (redact/deny) are downgraded to a warning and the message is prefixed with[observe]. The tool output is not modified. - A
CALL_WOULD_DENYevent contains the same information as a real deny event (contract ID, message, tags, metadata). The only difference is the event type. - Do not leave contracts in observe mode indefinitely. Unreviewed observe-mode contracts accumulate audit noise without providing protection.
Tag-Based Filtering Downstream
Tags enable downstream systems to filter, route, and aggregate audit events by concern. This pattern shows how to design tags for common compliance workflows.
Recommended tag taxonomy:
| Tag | Use Case |
|---|---|
pii | Events involving personally identifiable information |
secrets | Events involving credentials, tokens, or keys |
dlp | Data loss prevention events |
compliance | Events relevant to regulatory compliance |
change-control | Events related to production changes |
rate-limit | Session limit events |
cost | Events related to resource cost |
experimental | Observe-mode contracts |
Example: filtering audit events in Python:
from edictum.audit import FileAuditSink
sink = FileAuditSink("audit.jsonl")
# After loading audit events, filter by tag:
# events = [e for e in all_events if "pii" in e.tags]Gotchas:
- Tags are arrays, so a single event can have multiple tags. An event tagged
[pii, compliance]appears in both filters. - Define your tag taxonomy before writing contracts. Inconsistent tagging across bundles makes downstream filtering unreliable.
Last updated on
Rate Limiting Patterns
Rate limiting contracts use session-level counters to govern cumulative agent behavior across tool calls.
Advanced Patterns
This page covers patterns that combine multiple Edictum features: nested boolean logic, regex composition, principal claims, template composition, wildcards,...