AI Compliance - EU AI Act Article 12 (tamper-evident inference receipts)
PSIS / AtoM-AHG ships ahgAiCompliancePlugin to satisfy EU AI Act Article 12's
"automatic event logs across the lifecycle ... in a form that ensures
traceability and cannot be silently modified" obligation for AI inference calls.
Same byte format as the Heratio sibling, so receipts from either stack can be
cross-verified by any consumer of the standalone ahg/inference-receipts
Composer library.
Why
Plain database rows do not meet the Article 12 bar. The plugin layers three primitives on top of an append-only table:
- SHA-256 hash chain - each receipt embeds the hash of its predecessor, so modifying or deleting an entry breaks the chain at a detectable point.
- RFC 8785 JCS (JSON Canonicalization Scheme) - deterministic byte serialization, so any verifier agrees on what was hashed and signed.
- Ed25519 detached signatures - each entry is signed under a per-server
key. Auditors verify off-host using the public key served at
/.well-known/ai-inference-pubkey.
EU AI Act enforcement: 2 August 2026.
Architecture
+--------------------+ +-------------------------+
| AI service call | ---> | InferenceLogger::log() |
| (NER / HTR / LLM) | +-----------+-------------+
+--------------------+ |
v
+-----------------------------+
| ReceiptChain::append() |
| (ahg/inference-receipts) |
+-----------+-----------------+
|
v
+-----------------------------+
| PropelChainStore |
| -> ai_inference_log row |
+-----------+-----------------+
|
v
+-----------------------------+
| ai_inference_log (chain) |
| ai_inference_key (kids) |
+-----------------------------+
External auditor: curl /.well-known/ai-inference-pubkey
-> verifies offline against any exported receipt
Tables
ai_inference_log- one row per inference. Columns: seq (monotonic, UNIQUE), ts (millisecond precision), prev_hash, entry_hash, signature, kid, service, model_id, model_version, input_fingerprint (SHA-256 of input body), output_fingerprint, request_id, user_id, tenant_id, latency_ms, tokens_in/out, payload_json (canonical JSON used for hashing), payload_pruned_at.ai_inference_key- per-kid public key registry. Rotation appends a new row and demotes the old toactive=0withrotated_atstamped, so old receipts remain verifiable against their original key.
Both tables live alongside the existing ahg_* sidecar tables; no
AtoM/Qubit base tables are altered.
Operator workflow
# 1. Back up the database before schema changes
mysqldump archive > /var/backups/archive-before-ai-compliance.sql
# 2. Apply the schema
mysql archive < /usr/share/nginx/archive/atom-ahg-plugins/ahgAiCompliancePlugin/database/install.sql
# 3. Add the composer dep and install
( cd /usr/share/nginx/archive && composer require ahg/inference-receipts:^0.1 --no-scripts )
# 4. Generate the signing key
sudo -u www-data php symfony ai-compliance:install-key
# 5. Clear cache so the new module + route load
php symfony cc
# 6. Verify the public-key endpoint
curl -s https://psis.theahg.co.za/.well-known/ai-inference-pubkey | jq .
# 7. Schedule weekly verification (cron / systemd timer)
# php symfony ai-compliance:verify-inference-log --quiet-pass
# 8. Schedule the retention sweep (daily / weekly)
# php symfony ai-compliance:prune
Verifying a receipt off-host
Any external party can verify a receipt by:
- Pulling
/.well-known/ai-inference-pubkeyto obtain the public key matching the receipt'skid. - JCS-encoding the receipt's signing view (everything except
entry_hashandsignature). - SHA-256 over the JCS bytes - must equal the stored
entry_hash. - Ed25519 verify the
signatureagainst theentry_hashbytes under the public key from step 1.
Test vectors that pass against PSIS receipts must also pass against
Heratio receipts and against the nobulex reference - this is the
cross-verification contract.
Retention
Default 7 years (configurable via the AtoM setting
ai_compliance_retention_years or --years=N on ai-compliance:prune).
Prune nulls payload_json past the retention window while preserving seq /
prev_hash / entry_hash / signature, so the chain stays structurally
verifiable indefinitely even when the PII-bearing payload has been wiped.
Phase-2 follow-up
Wiring the actual AtoM AI service classes (ahgAIPlugin/lib/Services/*) to
call InferenceLogger::log() after each inference is a separate phase.
This phase ships:
- the plugin scaffold + tables
- the
ai-compliance:install-key,verify-inference-log,prunetasks - the
/.well-known/ai-inference-pubkeyendpoint - the reference / help docs
Once Phase 2 lands, the existing observability instrumentation in
ahgProvenancePlugin and ahgAIPlugin will route through the same chain.
See also
- Standalone library:
ahg/inference-receiptson Packagist - Heratio sibling:
packages/ahg-ai-compliance/ - Plugin README:
ahgAiCompliancePlugin/README.md