Compliance Jobs.
A FastAPI service running four always-on schedulers that publish hourly Merkle-chained roots over audit databases, enforce per-class retention with legal holds, cascade GDPR erasure across stores, and generate Ed25519-signed regulatory reports.
Install
Executed from a clean container against this repository:
git clone https://github.com/bulletproofsoftware-ai/bulletproof-compliance-jobs.git cd bulletproof-compliance-jobs pip install -r requirements.txt cp .env.example .env

bulletproof-compliance-jobs/docs/media/1Problem Statement
Compliance programs fail at audit time because evidence is assembled retrospectively. Someone exports logs, reconciles them by hand, and asserts that nothing was altered — an assertion the logs themselves cannot support.
Three specific gaps recur. Audit trails have no tamper-evidence, so there is no cryptographic reason to believe a record was not edited. Retention is a policy document rather than an enforced behaviour, so data outlives its lawful basis. And erasure requests are handled in one system while copies persist in vector stores, archives, and workflow histories.
This service makes the evidence continuous and mechanical. Every module maps to numbered requirements — Merkle publishing, Ed25519 signing, retention and legal hold, GDPR cascade, annual attestations, and NAIC adverse-action population — and runs on a schedule rather than on request.
2Architecture
app/main.py is the entrypoint. A lifespan context manager calls ensure_dirs() and db.init(), pre-warms the signing key, then launches four asyncio tasks that are cancelled cleanly on shutdown.
Merkle chain
publish_hourly_root() reads the closed hour's audit events, SHA-256 hashes each canonicalized event into leaves, folds them pairwise (duplicating odd nodes), and links to the previous root as parent_root_hash. Roots are written write-once and chmod 0o444.
Ed25519 signing
app/signing.py uses PyNaCl. canonical_json() is the single canonicalisation source of truth; resolve_public_key() checks the current key then an archive directory by key id, so signatures over historical artifacts stay verifiable across rotation.
Declarative retention
retention.py enforces an archive-then-delete cycle over every declared audit store. Retention periods are configuration rather than hard-coded policy — AUDIT_RETENTION_YEARS and EVIDENCE_RETENTION_YEARS both default to 7 — and a sweep runs every RETENTION_INTERVAL_HOURS (default 24). Records are written to gzipped JSONL before any deletion, and active legal holds suspend deletion for the named scope.
DSR cascade
submit_dsr() records a request with a deadline; execute_erasure() fans out to Qdrant purge, audit reference counting, and file-artifact purge, then writes a deletion-confirmation artifact. Six GDPR request types are accepted.
Report generation
REPORT_TYPES maps four report types — SOX attestation, NY DFS Part 500, EU AI Act Annex VI, and NAIC adverse action — to titles and named control lists. Evidence is gathered from audit counts, chain state, retention runs, and DSR cascades, then signed.
Scheduler Behaviour
- Merkle loop — fires at minute :05 and always targets the closed hour, so a root is never published over a still-open window.
- Retention loop — archives to gzipped JSONL before deleting, and honours active legal holds that pause deletion for named scopes.
- Annual loop —
_annual_due()implements "keep at least one report covering last year", firing onnever_run,stale_>365d,unparseable_last_run, orno_run_for_target_year. - NAIC listener — scans governance and security audit databases for adverse-action event types and generates one artifact per event, with
INSERT OR IGNOREidempotency keyed onevent_id. - Audit databases are opened read-only, trying
immutable=1first and falling back tomode=ro, so read-only mounts work correctly. - Schema migrations are additive and idempotent, applied by introspecting
PRAGMA table_infoat startup.
3Requirements
Requirement identifiers follow the REQ-RCA series referenced throughout the module docstrings.
| ID | Requirement |
|---|---|
| REQ-RCA-006 | Publish hourly Merkle roots hash-linked to the previous root. |
| REQ-RCA-013 | Sign compliance artifacts with Ed25519 and expose public-key verification. |
| REQ-RCA-016 | Enforce per-class retention periods across all declared storage kinds. |
| REQ-RCA-017 | Archive records to compressed JSONL before deletion. |
| REQ-RCA-018 | Honour legal holds that suspend deletion for a named scope. |
| REQ-RCA-021 | Cascade GDPR erasure across vector, audit, and file stores with confirmation artifacts. |
| REQ-RCA-029 | Generate SOX attestation reports over named ITGC and AI controls. |
| REQ-RCA-030 | Generate NY DFS Part 500 and EU AI Act Annex VI reports. |
| REQ-RCA-031 | Auto-fire annual attestations covering the prior calendar year. |
| REQ-RCA-032 | Populate NAIC adverse-action artifacts idempotently from audit events. |
4Interfaces
A REST surface over the four job domains, plus signing and dashboard endpoints. Every scheduled job also has a run-now trigger for operators.
| Endpoint | Purpose |
|---|---|
| POST /api/merkle/publish-now | Publish a root immediately |
| GET /api/merkle/roots | List published roots |
| GET /api/merkle/verify-chain | Walk and verify the hash chain |
| POST /api/retention/run-now | Trigger a retention sweep |
| POST /api/retention/holds | Place a legal hold |
| DELETE /api/retention/holds/{hold_id} | Release a legal hold |
| POST /api/dsr/submit | Submit a data subject request |
| POST /api/dsr/{request_id}/execute | Execute an erasure cascade |
| POST /api/reports/generate | Generate a regulatory report |
| GET /signing/public-key | Retrieve the current public key |
| POST /signing/verify | Verify a signature over an artifact |
| GET /api/dashboard | Aggregate status across all job domains |
5Integration Points
The service reads from upstream audit stores and writes evidence outward.
- Governance and runtime-security audit databases — mounted read-only and scanned for events; the default
AUDIT_BUS_DBSnames a governanceaudit.dband a securityaudit_bus.sqlite. - Qdrant — targeted by the DSR erasure cascade and covered by a retention class.
- Compliance portal — the human-facing surface for the evidence this service produces.
- External anchoring —
MERKLE_PUBLISH_URLoptionally POSTs each root to a third party, so chain integrity can be corroborated off-system. - Postgres and n8n execution history appear as declared retention targets in policy.
6Repository
Application modules are organised one per compliance domain, with policy separated from orchestration.
| Path | Purpose |
|---|---|
| app/main.py | FastAPI app, all routes, four schedulers, NAIC scanners |
| app/merkle.py | Root computation, write-once persistence, chain verification |
| app/signing.py | Ed25519 keys, canonical JSON, rotation-aware verification |
| app/retention.py | Archive-then-delete orchestration and legal holds |
| app/config.py | Environment-driven settings — retention horizons, intervals, paths |
| app/dsr_cascade.py | GDPR request handling and erasure fan-out |
| app/reports.py | Four report generators and control mappings |
| app/db.py | SQLite schema and additive migrations |
| tests/ | Annual scheduler and signing test suites |
7Implementation Notes
Deployment constraints and one documentation discrepancy worth flagging.
- The README describes Postgres, but the code is SQLite.
config.pyexposesSQLITE_PATHwith no Postgres DSN anddb.pyis puresqlite3. Treat SQLite as the actual storage engine. - Docker runs with
--workers 1, which is required — the schedulers are in-process asyncio tasks and additional workers would duplicate every job. - Audit database mounts are expected read-only; where the security bus is read-only, retention archives but reports
cannot_delete=truerather than failing. - Retention sweeps only the
audit_events/forensic_eventstables of the mounted audit databases. Merkle roots are held in separate write-once files (chmod 0o444, never overwritten), so a retention sweep cannot sever chain continuity. - Signing keys are generated on first run into
SIGNING_KEY_DIR. There is no external KMS integration. - NAIC deduplication depends on a non-empty
event_idon the source event. - The seven-year retention horizon is set in whole years via
AUDIT_RETENTION_YEARSandEVIDENCE_RETENTION_YEARS, both defaulting to7, so it can be shortened per deployment without a code change.