Compliance Audit Policy — p24-infra

Owner: Radek Konarski (p24-infra admin)
Effective date: 2026-06-28
Review frequency: every 6 months or after any major incident
Audit registry: public.dev_r_compliance_audits (Supabase) — every audit MUST produce a row
Conducted by: Claude (p24-infra session) unless otherwise stated
Ordered by: radieu (owner) or triggered automatically by schedule


Purpose

This policy defines which areas of p24-infra must be audited, how often, and at what severity.
It is a policy document — it does not describe how to conduct each audit (that lives in the referenced playbook).
Every completed audit must be recorded in dev_r_compliance_audits with result, summary, findings, and doc_url.


Risk Classification

LevelDefinitionExample impact
CRITICALFailure causes immediate data loss, security breach, or legal violationBackup unrestorable, credentials exposed, GDPR breach
HIGHFailure causes service outage or serious security gap discoverable within daysKey isolation missing, SSH access not audited, CVE unpatched
MEDIUMFailure causes degraded service or compliance gap, discoverable within weeksStale playbook, MCP reference in docs, cost overrun
LOWQuality issue, no operational or security impactNaming inconsistency, missing doc link

Domain 1 — Credentials & Secrets

Risk: CRITICAL | Audit type: credential

Audit scopeFrequencyTrigger for immediate audit
Completeness: all active credentials in dev_r_services with last_rotated + next_dueQuarterly (90d)Incident, new service onboarded
Rotation log integrity: every rotation has a row in secrets-rotation-log.mdQuarterly (90d)After any bulk rotation
Key isolation: no single credential shared by >2 consumersQuarterly (90d)New consumer added, rotation incident
SOPS file integrity: all secrets/*.env.sops decrypt without errorMonthlyBefore any SOPS write, after server restore
Overdue rotations: next_due < today in dev_r_servicesWeekly (automated alert)
Credentials NOT in SOPS: any key found only in .env.local or GH SecretsQuarterly (90d)After onboarding new key

Playbooks: credential-rotation-policy.md · sops-edit-operations.md · supabase-service-key-rotation.md · secrets-design-standard.md


Domain 2 — Infrastructure Changes

Risk: HIGH | Audit type: infrastructure

Audit scopeFrequencyTrigger for immediate audit
Server OS versions: no EOL OS in production (bms-1 Ubuntu 20.04 is known exception, tracked)Quarterly (90d)After OS-level incident
Docker image versions: no images >6 months old, no known CRITICAL CVEs unpatchedMonthlyAfter Trivy alert fires
Network/firewall rules: UFW allowlist on bms-2/bms-3 matches documented list; no extra open portsQuarterly (90d)After server provisioning change
Cloudflare DNS records: no dangling A/CNAME records, no unexpected zones (known exception, tracked: email-api.zintegrowana.online192.0.2.1 proxied is a required Worker-route record, not a placeholder — see cloudflare-security-operations.md §4.1; do NOT flag or delete)Quarterly (90d)After DNS change or domain event
Ansible playbook drift: applied config matches what Ansible would produceSemi-annual (6 months)After manual config change on server
dev_r_services completeness: every running container/service has a registry rowMonthlyAfter new service deployment
vps-h1 protection: no new services/ports added to WAHA-only hostMonthlyAny change on vps-h1

Playbooks: ionos-api-server-management.md · bms-server-root-ssh-lockout-recovery.md · dev-r-services-insert.md


Domain 3 — Backup & Disaster Recovery

Risk: CRITICAL | Audit type: infrastructure

Audit scopeFrequencyTrigger for immediate audit
Supabase backup freshness: last backup < 25 hours agoDaily (Prometheus alert)Alert fires
Wasabi backup freshness: latest infra-ops/*.jsonl archive < 24h oldDaily (Prometheus alert)Alert fires
Backup restore test: restore Supabase backup to staging schema, verify row countsMonthlyAfter any schema migration
DR drill: full bms-1 restore simulation from Wasabi snapshotQuarterly (90d)After any data loss event
Wasabi bucket growth: growth rate within expected band (no >2× spike)MonthlyAlert fires
MongoDB replica set health: all 3 members healthy, no replication lag >30sWeekly (Prometheus alert)Alert fires or rs.status() shows errors

Playbooks: supabase-backup-stale.md · bms1-dr-plan.md · wasabi-bucket-growth-spike.md · mongodb-admin-password-recovery.md


Domain 4 — Security & Access Control

Risk: CRITICAL | Audit type: security

Audit scopeFrequencyTrigger for immediate audit
SSH authorized_keys: all servers have exactly the expected keys (root + claude-runner)MonthlyAfter staff change, after key rotation
GitHub collaborator access: only active collaborators with minimum necessary permissionsQuarterly (90d)After staff change
Cloudflare token scope audit: all CF tokens scoped to minimum zones/permissionsQuarterly (90d)After rotation or token incident
Admin action investigation: review admin-actions-investigation.md for any undocumented changesMonthlyAfter alert or unusual activity
Mezmo log coverage: all servers and all critical containers shipping logsQuarterly (90d)After server provisioning change
Agent session anomaly: review dev_r_agent_sessions for sessions outside expected patternMonthlyAfter security alert or unusual spend
Supabase RLS coverage: all dev_r_* tables have anon=denied policyQuarterly (90d)After schema migration
Tooling inventory: no deprecated/removed tools referenced in playbooks or CLAUDE.mdQuarterly (90d)After tool decommission (e.g. MCP, Infisical)

Playbooks: ssh-key-rotation.md · admin-actions-investigation.md · static-api-key-incident-rotation.md · repo-consistency-audit.md


Domain 5 — CVE & Container Security

Risk: HIGH | Audit type: security

Audit scopeFrequencyTrigger for immediate audit
Trivy scan results: no unacknowledged CRITICAL CVEs in production imagesMonthly (GH Actions nightly Trivy)CRITICAL alert in scan
Base image freshness: FROM images in all Dockerfiles not older than 6 monthsMonthlyAfter Trivy CRITICAL alert
Node.js / Python dependency audit: npm audit / pip-audit in all reposQuarterly (90d)After dependency CVE disclosure
Container runtime versions: Docker Engine, Docker Compose, containerd up to dateSemi-annual (6 months)After Docker security advisory

Playbooks: GH Actions trivy-scan.yml (nightly) · bms1-ubuntu-2204-upgrade.md


Domain 6 — EU AI Act Compliance

Risk: HIGH (legal) | Audit type: eu_ai_act

Audit scopeFrequencyTrigger for immediate audit
AI system registry completeness: all AI-powered components in dev_r_ai_systems and docs/eu-ai-act-compliance.mdSemi-annual (6 months)New AI system deployed
High-risk classification review: Annex III applicability checked for each systemSemi-annual (6 months)New AI regulation guidance published
Human oversight controls: documented and verified for all high-risk systemsSemi-annual (6 months)Before regulatory deadline
Serious incident reporting capability: process exists and testedAnnualRegulatory inquiry

Deadline: 2026-08-02 (Annex III) — next audit due before that date.

Playbook: docs/eu-ai-act-compliance.md


Domain 7 — Agent & Automation Health

Risk: MEDIUM | Audit type: tooling

Audit scopeFrequencyTrigger for immediate audit
Worker queue health: dispatchers running, no stuck jobs >1h, heartbeats currentWeekly (Prometheus alert)Alert fires or dispatch stops
Hourly triage health: last run < 2h ago, no consecutive failuresWeekly (Prometheus alert)Alert fires
n8n workflow completeness: all scheduled workflows have last execution < expected intervalMonthlyGPS stale alert, workflow failure
Claude runner OAuth freshness: both vps-i1 and bms-4 runners authenticatedMonthlyRunner goes offline
MCP/tool inventory: no deprecated tools referenced in CLAUDE.md or agent-promptsQuarterly (90d)After tool decommission
dev_r_agent_sessions schema consistency: columns match current agent registration codeQuarterly (90d)After schema migration
n8n Supabase credential validity: all 6 credentials respond 200 OKMonthly (sync script)After any Supabase key rotation

Playbooks: agent-watchdog-alert-response.md · worker-queue-operations.md · hourly-triage-watchdog.md · n8n/n8n-supabase-credential-rotation.md


Domain 8 — Cost Control

Risk: MEDIUM | Audit type: custom

Audit scopeFrequencyTrigger for immediate audit
Monthly infrastructure costs: within ±15% of baseline (baseline: ~655 EUR/month)MonthlyCost spike alert or invoice arrives
Claude API token spend: within monthly budget per projectMonthlyUnusual agent activity
Wasabi storage growth: growth rate matches expected fleet data volumeMonthly (Prometheus)Growth >2× month-on-month
Unused resources: stopped containers still consuming storage, stale Wasabi objectsQuarterly (90d)Cost audit reveals anomaly

Playbook: token-cost-optimization.md


Domain 9 — Process & Playbook Consistency

Risk: MEDIUM | Audit type: process

Audit scopeFrequencyTrigger for immediate audit
Playbook accuracy: spot-check 10% of playbooks against live system stateQuarterly (90d)After incident where playbook was wrong
CLAUDE.md consistency: project CLAUDE.md matches current stack (no decommissioned services referenced)Quarterly (90d)After major stack change
Memory file hygiene: no stale references to removed tools/services in memory/*.mdQuarterly (90d)After tool decommission
Incident post-mortems: every P1/P2 incident has a playbook within 48hAfter every incident
New service onboarding checklist: dev_r_services row + ops doc + compliance_workbook=yesPer deploymentNew service in production

Playbooks: repo-consistency-audit.md · claude-md-management.md · testing-standards.md


Domain 10 — Database Performance (Slow Queries)

Risk: HIGH | Audit type: infrastructure

Covers both Supabase (PostgreSQL managed) and MongoDB replica set (bms-2 PRIMARY / bms-3 / bms-4 arbiter).

10a — Supabase / PostgreSQL

Audit scopeFrequencyTrigger for immediate audit
Slow query log: queries mean_exec_time > 500ms in pg_stat_statements — count, identity, trendMonthlyGPS data stale, API p99 latency spike
Missing indexes: tables with seq_scan rising faster than idx_scan (seq_pct > 80%)MonthlySlow query audit finds candidate
Table bloat: dead tuple ratio n_dead_tup / n_live_tup > 10% on any high-write tableMonthlyAutovacuum lag or query regression
Connection pool saturation: active connections > 80% of max_connectionsMonthly (Prometheus)Connection pool alert fires
RPC / Edge Function p95 latency within SLA for top-10 RPCsQuarterly (90d)Latency regression in Grafana
pg_stat_statements baseline intact: stats not reset without documented reasonQuarterly (90d)After Supabase maintenance window

Queries to run (bms-4 via psql — Supabase Postgres connection string from SOPS):

-- Top slow queries
SELECT query, calls, round(mean_exec_time::numeric,1) AS mean_ms,
       round(total_exec_time::numeric,0) AS total_ms
FROM pg_stat_statements
WHERE mean_exec_time > 500
ORDER BY mean_exec_time DESC LIMIT 20;
 
-- Tables with seq_scan dominance (index candidates)
SELECT relname, seq_scan, idx_scan,
       round(seq_scan::numeric / nullif(seq_scan+idx_scan,0)*100,1) AS seq_pct
FROM pg_stat_user_tables
WHERE seq_scan + idx_scan > 100
ORDER BY seq_pct DESC LIMIT 20;
 
-- Table bloat (dead tuple ratio)
SELECT relname, n_live_tup, n_dead_tup,
       round(n_dead_tup::numeric / nullif(n_live_tup,0)*100,1) AS dead_pct
FROM pg_stat_user_tables
WHERE n_live_tup > 1000
ORDER BY dead_pct DESC LIMIT 10;

10b — MongoDB Replica Set (rs0: bms-2 PRIMARY / bms-3)

Audit scopeFrequencyTrigger for immediate audit
Slow op log: operations durationMillis > 100ms — top offenders, collection, index usedMonthlyGPS sync lag, Pinbox24 slow response, mongotimeout alert
Index coverage: high-frequency queries use index, no COLLSCAN on hot collectionsMonthlySlow op audit finds COLLSCAN
Replication lag: secondary oplog lag < 30s across all membersWeekly (Prometheus alert)Alert fires
Oplog window: covers at least 24h of writes (prevent full resync on reconnect)MonthlySecondary was offline, lag alert
Collection bloat: storageSize / dataSize > 3 on any collection > 1GBQuarterly (90d)Disk usage anomaly
Compound / wildcard index health: indexes match query patterns after schema changesQuarterly (90d)After Pinbox24 data model change
Profiling level active: slowOpThresholdMs: 100, profile: 1 on PRIMARYQuarterly (90d)After MongoDB upgrade or mongod.conf change

Commands to run (mongosh on bms-2 PRIMARY):

// Top slow ops from profile (last 24h)
db.system.profile.find(
  { ts: { $gt: new Date(Date.now()-86400000) }, millis: { $gt: 100 } },
  { op:1, ns:1, millis:1, planSummary:1, ts:1 }
).sort({ millis:-1 }).limit(20)
 
// Replica set lag
rs.printSecondaryReplicationInfo()
 
// Oplog window in hours
var ol = db.getSiblingDB("local").oplog.rs
var first = ol.find().sort({$natural:1}).limit(1).next()
var last  = ol.find().sort({$natural:-1}).limit(1).next()
print("Oplog window h:", (last.ts.t - first.ts.t)/3600)
 
// Collection bloat (top 10 by storageSize/dataSize)
db.adminCommand({listDatabases:1}).databases.forEach(d => {
  var s = db.getSiblingDB(d.name).stats()
  if (s.dataSize > 0) print(d.name, "ratio:", (s.storageSize/s.dataSize).toFixed(2))
})

Playbooks: mongodb-slow-queries-index.md · mongodb-compound-wildcard-indexes.md · mongotimeout-false-positive.md


Domain 11 — Audit & Incident History (Meta-Audit)

Risk: MEDIUM | Audit type: process

This domain audits the audit log itself and the incident/rotation history to detect systemic patterns — are we rotating more under pressure than on schedule? Are the same areas failing repeatedly?

Audit scopeFrequencyTrigger for immediate audit
Forced rotation rate: trigger=exposure rows in secrets-rotation-log.md per quarter — rising trend signals systemic secret hygiene problemQuarterly (90d)2nd exposure incident in same quarter
Rotation incident clusters: same credential rotated >1× per 90d cycle — indicates key shared too broadly or access gapQuarterly (90d)Repeated rotation of same key
dev_r_compliance_audits coverage: all 12 policy domains have ≥1 pass or partial row in last 12 monthsSemi-annual (6 months)Annual compliance review
Incident-to-playbook ratio: every closed P1/P2 GH issue has a linked playbook — gaps predict recurrenceQuarterly (90d)Incident closed without playbook
Time-to-rotation after exposure: all trigger=exposure rotations completed within SLA (1h for CRITICAL)Quarterly (90d)After any SLA breach
Repeat alert pattern: same Prometheus/Alertmanager alert fires >3× per month — unresolved root causeMonthly (Prometheus)Same alert fires 3rd time in 30 days
This policy freshness: compliance-audit-policy.md reviewed and still reflects current stackSemi-annual (6 months)Major stack change or new domain added

Queries to run:

-- Audit coverage per domain (last 12 months)
SELECT audit_type, result, count(*) AS cnt,
       max(ts) AS last_audit
FROM dev_r_compliance_audits
WHERE ts > now() - interval '12 months'
GROUP BY audit_type, result
ORDER BY audit_type, result;
 
-- Domains with no passing audit in last 12 months (coverage gap)
WITH covered AS (
  SELECT DISTINCT audit_type FROM dev_r_compliance_audits
  WHERE ts > now() - interval '12 months'
    AND result IN ('pass','partial')
)
SELECT unnest(ARRAY[
  'security','credential','infrastructure','eu_ai_act',
  'process','code','tooling','custom'
]) AS audit_type
EXCEPT SELECT audit_type FROM covered;
 
-- Forced rotation trend per quarter (parse secrets-rotation-log.md manually;
-- or if rotation log is in DB, query here)
SELECT date_trunc('quarter', ts) AS q, count(*) AS forced_rotations
FROM dev_r_compliance_audits
WHERE audit_type = 'credential' AND findings ILIKE '%exposure%'
GROUP BY 1 ORDER BY 1 DESC;

External source: docs/secrets-rotation-log.md — grep exposure rows, count per quarter. Rising ratio of exposure vs scheduled rotations is the primary red flag.


Domain 12 — Infrastructure Expansion & Capacity

Risk: HIGH | Audit type: infrastructure

Tracks whether the infrastructure is growing sustainably and proactively identifies capacity ceilings before they cause outages.

Audit scopeFrequencyTrigger for immediate audit
RAM headroom: each server peak RAM < 80% total — prevents OOM kills and dispatcher RAM gateMonthly (Prometheus alert)OOM alert fires or RAM gate triggers
Disk headroom: each server disk usage < 75% (/, /var/lib/docker, data mounts)Monthly (Prometheus alert)Disk alert fires (bms-1 is known exception)
MongoDB rs0 disk growth: data volume fits 6-month runway at current growth rateMonthlyGrowth rate doubles vs prior month
Worker queue throughput: avg queue depth and job completion time within SLAMonthly (Grafana)Queue depth > 20 sustained for >1h
New server justification: every server/VM added since last audit has a GH issue with cost + capacity rationaleQuarterly (90d)New server appears in fleet without issue
dev_r_services growth: number of registered services per quarter — track sprawlQuarterly (90d)Service count grows >20% in a quarter
Capacity plan freshness: docs/strategia-p24-infra-2026.md capacity plan reviewed, timeline still validQuarterly (90d)Major infra change or milestone passed
bms-1 EOL resolution: Ubuntu 20.04 upgrade or replacement plan has an active GH issue with target date — the committed date lives in bms1-ubuntu-2204-upgrade.md §Committed target date & SLA and must match the Target date block on #1756; current commitment 2026-09-30MonthlyUpgrade stalls >30 days without update, or the committed target date passes un-met (escalate to a P0 row in priorities.md + new gap issue)
Claude Max seat utilization: both seats actively used; over-provisioned seats flagged for cost reviewQuarterly (90d)Seat appears idle >30 days

Key Grafana metrics:

  • node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes — per server
  • node_filesystem_avail_bytes{mountpoint="/"} — per server
  • mongodb_dbstats_dataSize — Pinbox24 DB size 30d trend
  • Worker queue: agent_tasks depth + P95 completion time panel

Playbooks: ionos-api-server-management.md · oom-auto-remediation.md · bms1-ubuntu-2204-upgrade.md · queue-dispatcher-ram-gate-exhaustion.md


Audit Calendar Summary

FrequencyDomains
Continuous / automatedSupabase backup freshness · Wasabi freshness · overdue rotations · MongoDB RS health · worker queue health · triage health · repeat alert pattern (D11)
WeeklyMongoDB replication lag (D10b) · n8n Supabase credentials validity (D7)
MonthlySOPS integrity · Docker image CVE · dev_r_services completeness · vps-h1 protection · SSH authorized_keys · Mezmo log coverage · Agent session anomaly · n8n workflow completeness · Claude runner OAuth · Backup restore test · Wasabi growth · Monthly cost · Supabase slow queries (D10a) · MongoDB slow ops (D10b) · MongoDB oplog window · RAM/disk headroom (D12) · MongoDB data growth runway (D12) · Worker queue throughput (D12) · bms-1 EOL status (D12)
Quarterly (90d)Credential completeness · Rotation log integrity · Key isolation · SOPS coverage · Server OS/network audit · Cloudflare tokens · GitHub collaborators · Supabase RLS · Agent session patterns · Node.js/Python deps · Worker MCP inventory · Playbook accuracy · CLAUDE.md audit · Memory hygiene · Supabase RPC p95 (D10a) · pg_stat_statements baseline (D10a) · MongoDB collection bloat (D10b) · MongoDB index health (D10b) · MongoDB profiling config (D10b) · Forced rotation rate (D11) · Rotation clusters (D11) · Incident-to-playbook ratio (D11) · Time-to-rotation SLA (D11) · New server justification (D12) · dev_r_services sprawl (D12) · Capacity plan freshness (D12) · Claude seat utilization (D12)
Semi-annual (6 months)Ansible drift · Container runtime · EU AI Act review · AI system registry · Audit log coverage (D11) · This policy review (D11)
AnnualEU AI Act incident reporting capability

Immediate Audit Triggers (Out-of-Cycle)

These events require an unscheduled audit regardless of the calendar:

EventAudit required
Secret value appears in chat/logs/gitDomain 1 (Credentials) — full rotation audit within 1 hour
Server compromise suspectedDomain 4 (Security) + Domain 1 immediately
Production data loss or corruptionDomain 3 (Backup & DR) + Domain 2 (Infra) immediately
New service deployed to productionDomain 2 (dev_r_services completeness) + Domain 9 (onboarding checklist)
Tool/service decommissionedDomain 4 (Tooling inventory) + Domain 9 (CLAUDE.md audit)
Staff access changeDomain 4 (SSH + GitHub access) within 24h
CRITICAL CVE in any running imageDomain 5 (CVE) within 48h
Regulatory change (EU AI Act update)Domain 6 within 30 days
P1 incidentDomain 9 (post-mortem + playbook) within 48h

Audit Execution Process

Step 1 — Identify the audit to run

Check which domain is due (daily cron issue, next_audit_due <= today in DB, or out-of-cycle trigger):

-- Run via psql on bms-4 or Supabase Management API
SELECT DISTINCT ON (scope) scope, audit_type, next_audit_due, result
FROM dev_r_compliance_audits
ORDER BY scope, ts DESC;

If no prior record exists for a domain, it is overdue — run it now.

Step 2 — Run the checks

Follow the SQL/CLI commands listed in the domain section of this policy.
Never print secret values — run queries as-is; log only aggregate counts and rates, not key values.

For each check in the domain:

  • PASS — finding matches expected state
  • FAIL — finding violates policy (must produce a GH issue — see Step 4)
  • PARTIAL — finding partially meets policy (document exactly what is missing)

Step 3 — Register the audit in the database

Every audit produces exactly one row. Run via psql on bms-4 or Supabase Management API:

INSERT INTO dev_r_compliance_audits (
    ts, audit_type, scope, ordered_by, conducted_by,
    result, summary, findings, action_taken, doc_url, gh_issue, next_audit_due
) VALUES (
    now(),
    'credential',                                     -- audit_type from domain table
    'Describe exactly what was audited (scope)',      -- max 200 chars
    'radieu',                                         -- who ordered it
    'claude',                                         -- who conducted it
    'pass',                                           -- pass | fail | partial | deferred | in_progress
    'One-sentence summary visible in dashboards.',    -- required
    'Detailed findings (no secret values).',          -- optional
    'Action taken or "No action required."',          -- optional
    'docs/playbooks/relevant-playbook.md',            -- or GH PR URL
    NULL,                                             -- gh_issue: number of remediation issue, if any
    (now() + interval '90 days')::date                -- next_audit_due from frequency table
);

Minimum required fields: audit_type, scope, ordered_by, conducted_by, result, summary.
next_audit_due is mandatory if result IN ('pass', 'partial') — omitting it removes the domain from the due-date monitor.

Step 4 — Create a GH issue for each gap found

Every FAIL or PARTIAL finding must produce a GH issue before closing the audit session:

gh issue create \
  --repo radieu/p24-infra \
  --title "[Compliance Gap] <audit_type>/<scope-short>: <one-line finding>" \
  --label "compliance-gap,<audit_type>" \
  --body "## Compliance Gap
 
**Audit date:** YYYY-MM-DD
**Domain:** Domain N — <name> (compliance-audit-policy.md)
**Risk level:** CRITICAL | HIGH | MEDIUM | LOW
**Finding:** [describe the gap without secret values]
 
## Impact
 
[What could go wrong if this gap persists]
 
## Remediation
 
- [ ] [Step 1]
- [ ] [Step 2]
- [ ] Register corrective action in \`dev_r_compliance_audits\` (update \`action_taken\` on the original audit row)
 
## References
 
- Audit row ID: [uuid from dev_r_compliance_audits]
- Policy: docs/playbooks/compliance-audit-policy.md §Domain N
- Playbook: docs/playbooks/<relevant>.md"

Add the resulting issue number to the gh_issue field of the audit row:

UPDATE dev_r_compliance_audits
SET gh_issue = <issue_number>, result = 'fail'
WHERE id = '<uuid-from-step-3>';

Step 5 — Verify the due-date monitor picks up the next audit

After insertion, confirm next_audit_due is set and the domain will appear in the cron check:

SELECT scope, next_audit_due, result
FROM dev_r_compliance_audits
WHERE next_audit_due IS NOT NULL
ORDER BY next_audit_due;

Recording Audits

Every completed audit must produce a row in public.dev_r_compliance_audits:

-- Run on bms-4 via psql (Supabase Postgres connection string from SOPS SUPABASE_DB_URL)
INSERT INTO dev_r_compliance_audits (
    ts, audit_type, scope, ordered_by, conducted_by,
    result, summary, findings, action_taken, doc_url, gh_issue, next_audit_due
) VALUES (
    now(),
    'credential',                                     -- audit_type from policy domain
    'SUPABASE_SERVICE_ROLE_KEY isolation',            -- what was audited
    'radieu',                                         -- who ordered it
    'claude',                                         -- who conducted it
    'pass',                                           -- pass | fail | partial | deferred | in_progress
    'All 3 per-component keys isolated, old keys deleted, n8n credentials verified valid.',
    NULL,                                             -- detailed findings (no secrets)
    NULL,                                             -- action taken
    'docs/playbooks/supabase-service-key-rotation.md',
    NULL,                                             -- gh_issue number
    (now() + interval '90 days')::date               -- next_audit_due
);

Minimum fields always required: audit_type, scope, ordered_by, conducted_by, result, summary.