Skip to content

Compliance and Audit Patterns

Compliance patterns address regulatory and organizational requirements: classifying contracts with tags, tracking contract bundle versions, rolling out new contracts safely with observe mode, 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-policy
import 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 then block. They are stamped into the Verdict and every AuditEvent produced 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 pii or compliance.

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., complianc instead of compliance) 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: high
from 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: 1. Edictum.from_yaml("policy.yaml") reads the raw YAML bytes. 2. A SHA256 hash is computed from the bytes. 3. Every audit event produced by this bundle includes policy_version: <hash>. 4. 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.description field 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 -- shadow 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: 1. Set defaults.mode: enforce for the bundle. 2. On the new contract, add mode: observe to override the bundle default. 3. When the observe-mode contract matches, it emits a CALL_WOULD_DENY audit event. The tool call proceeds normally. 4. Review CALL_WOULD_DENY events in your audit logs. If the contract fires correctly with no false positives, change it to mode: enforce (or remove the override to inherit the bundle default).

Gotchas: - Observe mode applies to all contract types. For postconditions, when mode: observe is 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_DENY event 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.