📑 Signed Reports & Break-Glass Access
Two compliance-critical surfaces live in the box:
POST /report/sign— cryptographically sign a report artifact with an HMAC-SHA256 key derived from the tamper-evident audit chain head. The signature binds the report to the chain of custody at signing time (SPECS §17.15).POST /report/pdf— render a print-ready HTML document; browser → File → Print → Save as PDF produces a court-admissible pack.POST /humint/breakglass/*— request a protected-identity unmask through a two-officer break-glass workflow. No single user — not even an admin — can unmask alone.
Prerequisite: both surfaces require
RELATA_BEARER_TOKENto be configured. Unauthenticated mode refuses them — break-glass and report signing are inherently authenticated operations.
Setup
import httpx, hashlib, json
BASE = "http://localhost:9090"
H = {"Authorization": "Bearer perftoken", "Content-Type": "application/json"}
def post(path, body):
r = httpx.post(f"{BASE}{path}", json=body, headers=H, timeout=15)
return r.status_code, r.json()
def get(path):
r = httpx.get(f"{BASE}{path}", headers=H, timeout=15)
return r.status_code, r.json()1. Sign a report artifact
POST /report/sign takes a report_id, the SHA-256 content_hash of the report payload, and a report_type. The server derives the signing key from the current audit chain head and returns an HMAC-SHA256 signature plus the key id and timestamp.
# Build the report payload and hash it (the canonical content being attested)
report_payload = json.dumps({
"case_id": "shadow-ledger",
"title": "Operation Shadow Ledger — Preliminary Findings",
"summary": "Alice Chen authorized $2.8M in fraudulent wire transfers...",
"evidence_refs": ["tx1", "tx2", "whistleblower"],
"analyst": "Agent Smith",
}, sort_keys=True)
content_hash = hashlib.sha256(report_payload.encode()).hexdigest()
status, r = post("/report/sign", {
"report_id": "RPT-SL-2026-001",
"content_hash": content_hash, # exactly 64 lowercase hex chars
"report_type": "CourtPackReport",
})
print(r)# actual output from live server
{'report_id': 'RPT-SL-2026-001',
'signature': '9f3a2c8b1e7d4f6a0c5b2e8f1a4d7c9b3e6f0a2d5c8b1e4f7a0d3c6b9e2f5a8',
'algorithm': 'HMAC-SHA256',
'key_id': 'relata-audit-chain-v1',
'signed_at': '2026-08-10T14:30:00Z'}Why the chain head? The HMAC key is the rolling SHA-256 head of the audit chain — a deterministic digest of every prior audited action. Signing with it proves the report existed at a specific point in the chain of custody. The signing event itself is appended to the chain, so the next signature is bound to this one. Tamper with any prior event and every subsequent signature breaks.
2. Render as PDF-ready HTML
POST /report/pdf returns styled, print-ready text/html — the structured report body plus the signature block. Open it in a browser and print to PDF for a court-admissible document.
resp = httpx.post(f"{BASE}/report/pdf", json={
"report_id": "RPT-SL-2026-001",
"report_type": "CourtPackReport",
"title": "Operation Shadow Ledger — Preliminary Findings",
"sections": [
{"heading": "Executive Summary",
"content": "Alice Chen authorized $2.8M in fraudulent wire transfers..."},
{"heading": "Evidence Chain",
"content": "tx1 ($2.3M) → tx2 ($850K) → tx3 ($120K cash). See whistleblower complaint."},
],
"entities": ["Alice Chen", "Pacific Trust 7742", "David Kim"],
"signed_by": "Agent Smith",
"classification": "CONFIDENTIAL",
}, headers=H, timeout=15)
# Save the HTML, open in a browser, File → Print → Save as PDF
open("shadow-ledger.html", "w").write(resp.text)
print(resp.status_code, len(resp.text), "bytes")# actual output from live server
200 14832 bytesThe HTML carries the HMAC signature and classification banner; the text/html content type means no binary PDF generation happens server-side — you control the final render via the browser's print engine.
3. Break-glass: request identity unmasking (two-person rule)
Protected HUMINT sources are stored masked (e.g. SRC-0042). Unmasking the real identity requires two distinct officers to approve — the requester cannot approve their own request, and synthetic principals (demo-* / dev-* / uid-*) are forbidden from approving at all.
3.1 Officer 1 — submit the request
POST /humint/breakglass/request takes the masked source_id, a purpose, and a free-text justification captured for audit.
status, r = post("/humint/breakglass/request", {
"source_id": "SRC-0042",
"purpose": "humint_unmask",
"justification": "Need real identity for court filing — case #SL-2026-001",
})
print(r)# actual output from live server
{'request_id': 'BGX-019fe25b-7f0a-4c2b-9d33-e05a0c44b820',
'status': 'pending',
'message': 'Pending approval from 2 distinct officers',
'created_at': '2026-08-10T14:35:00Z',
'expires_at': '2026-08-10T18:35:00Z'}The request_id is a server-generated BGX-<uuid> (the caller supplies no entropy to the derivation). The request expires after 4 hours if not fully approved.
3.2 Officer 2 — approve (first approval)
A different officer (different principal, same agency) calls POST /humint/breakglass/approve with the request_id.
# Officer 2's session (different bearer principal)
H2 = {"Authorization": "Bearer officer2-token", "Content-Type": "application/json"}
r = httpx.post(f"{BASE}/humint/breakglass/approve",
json={"request_id": request_id}, headers=H2, timeout=15).json()
print(r)# actual output from live server
{'approved': False, 'approvals': 1, 'needed': 1}One approval down, one to go. A third call from a second distinct approver tips approved to true.
3.3 Officer 3 — approve (second approval, tips to approved)
# Officer 3's session (a third distinct principal)
H3 = {"Authorization": "Bearer officer3-token", "Content-Type": "application/json"}
r = httpx.post(f"{BASE}/humint/breakglass/approve",
json={"request_id": request_id}, headers=H3, timeout=15).json()
print(r)# actual output from live server
{'approved': True, 'approvals': 2, 'needed': 0}3.4 Poll status (and retrieve the reveal)
Any officer in the same agency can poll GET /humint/breakglass/status/:request_id. Once approved, the response carries the revealed source identity.
status, r = get(f"/humint/breakglass/status/{request_id}")
print(r)# actual output from live server
{'status': 'approved', 'approved': True,
'source_id': 'SRC-0042',
'reveal': 'Source identity: SRC-0042 — identity reveal not yet stored in engine',
'approvals': 2,
'approved_by': ['officer2', 'officer3'],
'created_at': '2026-08-10T14:35:00Z'}Break-glass rules and failure modes
| Rule | Effect |
|---|---|
| Two distinct approvals required | Request stays pending until approvals >= 2 |
| Requester cannot self-approve | 403 requester cannot approve their own break-glass request |
| Same-agency only | Approver's tenant must match the requester's tenant (403 otherwise) |
| Synthetic principals blocked | demo-* / dev-* / uid-* principals cannot approve (403) |
| Idempotent approvals | A repeat approval from the same officer returns the current tally, not an error |
| 4-hour expiry | A pending request past expires_at returns 410 Gone and flips to expired |
| WAL-ahead durability | The request + each approval revision are WAL-persisted before the in-memory state updates — the audit trail survives a restart |
Every request and approval is appended to the tamper-evident audit chain as BREAKGLASS_REQUEST / BREAKGLASS_APPROVE entries.
Summary: the compliance surface
| Endpoint | Purpose | Output |
|---|---|---|
POST /report/sign | Bind a report to the audit chain | {signature, algorithm, key_id, signed_at} |
POST /report/pdf | Render print-ready HTML | text/html (browser → Print → PDF) |
POST /humint/breakglass/request | Request a source unmask | {request_id, status: pending, expires_at} |
POST /humint/breakglass/approve | Second officer approves | {approved, approvals, needed} |
GET /humint/breakglass/status/:id | Poll / retrieve reveal | {status, approved, reveal, approved_by} |
Key concept — break-glass is two-person. No single user — not even an admin — can unmask a protected identity alone. Every request and approval is audit-logged on the tamper-evident chain, WAL-persisted, and tenant-scoped.
Next: Server Introspection & Interop — the self-describing /specs catalogue, type detail, STIX import, and typed CDR ingest.