Policy Portability
Anti-lock-in is a trust moat, not a weakness. If you can take your Adjudon policies and re-run them anywhere, you trust Adjudon more — exactly because you could leave.
Live today: Policy bundle export (.tar.gz), SHA-256 content-hash integrity, ADL + JSON v1 + Cedar policy artefacts, transcript records with policyVersionHash + cedarRequestHash, adjudon-replay Node CLI for offline verification against cedar-wasm.
Gated on founder action (architecture wired, signatures currently mock):
- D-Trust QTSP RFC 3161 qualified timestamp —
services/eidasTimestampService.jsreturns deterministic mock untilEIDAS_TSA_PRODenv-var is set with the D-Trust endpoint (commercial contract pending). - Sigstore Cosign keyless OIDC + Rekor v2 inclusion proof —
services/policyBundleSigner.jsreturns structured stub until the GitHub Actions OIDC pipeline is wired (FOUNDER_ACTION_CHECKLISTgate 3.1). - Pre-built signed standalone replay binaries —
backend/scripts/build-replay-binary.shexists; binaries publish in Phase 3.1.
Until each gate fires, the bundle's signature blocks contain mock tokens that preserve chain integrity but do not carry the eIDAS Art. 41(2) statutory presumption. We document this honestly so procurement does not arrive expecting a live qualified timestamp that does not exist yet.
What you get
When you export a Policy bundle from /dashboard/policies/export, you receive a single adjudon-policy-bundle.tar.gz containing:
manifest.json ← bundle index + SHA-256 fileHashes
policies/
policy_<id>.json ← Policy doc (current state)
policy_<id>_v<n>.meta.json ← PolicyVersion metadata + canonicalContentHash
policy_<id>_v<n>.adl.json ← ADL representation (per LD-2)
policy_<id>_v<n>.cedar ← Cedar source (post production-cutover)
transcripts/
transcripts_<from>_<to>.jsonl ← per-evaluation records (PII-scrubbed)
verify/
verify.sh ← POSIX hash-verification script
README.md
signatures (inside manifest.json):
sigstoreCosign ← keyless cert + Rekor v2 inclusion proof
qtspTimestamp ← D-Trust GmbH RFC 3161 token
The dual-anchor
Two independent layers attest to bundle integrity:
Sigstore Cosign + Rekor v2 (software-supply-chain transparency)
Adjudon's GitHub Actions workflow issues an OIDC token to Fulcio → ephemeral cert → keyless DSSE signature → Rekor v2 inclusion proof. The Rekor entry is publicly searchable and tamper-evident — Adjudon cannot retroactively change the bundle without breaking the Rekor leaf.
What this protects against: Adjudon-side tampering with the bundle after issuance.
What it does NOT do alone: Rekor is a transparency log operator (Linux Foundation), NOT a Qualified Trust Service Provider under eIDAS. A Rekor timestamp alone does not get the §371a Abs. 3 ZPO + eIDAS Art. 41(2) statutory presumption.
D-Trust GmbH RFC 3161 qualified timestamp (eIDAS Art. 41)
D-Trust is Bundesnetzagentur-supervised, on the EU Trusted List, TÜViT-confirmed eIDAS conformity. The bundle's SHA-256 hash is submitted via RFC 3161 → qualified TimeStampToken → stored in manifest.signatures.qtspTimestamp.
What this unlocks: Under §371a Abs. 3 ZPO, a qualified electronic timestamp is treated as an öffentliches elektronisches Dokument with statutory presumption of accuracy + integrity in German civil court. eIDAS Art. 41(2) extends this presumption across the EU.
Failover: GlobalSign nv-sa (Belgian FPS Economy) is the secondary QTSP.
Why both layers
- Rekor alone: software-supply-chain transparency without statutory evidentiary value.
- QTSP alone: German court-admissible but without independent-transparency-log redundancy (D-Trust is one entity; can it be coerced? With a single QTSP, you trust D-Trust).
- Both: even if Adjudon AND D-Trust were both coerced, the Rekor log shows the bundle was published with the QTSP token at a specific time. The two anchors are operated by independent legal entities in independent jurisdictions.
How to verify a bundle offline
- Receive
adjudon-policy-bundle.tar.gz(e.g., via email, USB stick, or a regulator hand-over). - Extract:
tar -xzf adjudon-policy-bundle.tar.gz - Verify hashes:
This checks SHA-256 of every file against
cd <extracted-dir>
sh verify/verify.shmanifest.fileHashes. - Verify Sigstore signature against the published Adjudon Fulcio identity (Phase 3.1 — wraps the verify script):
# Step 1: extract the signature + cert from the manifest JSON
jq -r '.signatures.sigstoreCosign.signature' manifest.json > bundle.sig
jq -r '.signatures.sigstoreCosign.certificate' manifest.json > bundle.pem
# Step 2: verify with cosign (signature + certificate as file paths)
cosign verify-blob \
--certificate-identity-regexp "^https://github.com/adjudon/.*$" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--signature bundle.sig \
--certificate bundle.pem \
adjudon-policy-bundle.tar.gz - Verify QTSP token:
- Extract the DER-encoded TimeStampToken from
manifest.signatures.qtspTimestamp.token - Verify the CMS signature against the D-Trust public certificate
- Cross-check the D-Trust signing cert against the Bundesnetzagentur Trusted List at
tl.bundesnetzagentur.deand the EU LOTL Browser ateidas.ec.europa.eu/efda/tl-browser/ - Assert
TSTInfo.messageImprint.hashedMessageequals the bundle hash
- Extract the DER-encoded TimeStampToken from
- Replay every recorded decision via the bundled
adjudon-replayCLI:This re-runs every transcript against the bundled policy versions using Cedar WASM, asserts byte-for-byte agreement with the stored decision.node adjudon-replay.js adjudon-policy-bundle.tar.gz
Cardinal Rules applied
- Rule #1 (org isolation): the bundle contains data for a single org; export endpoint filters
organizationIdfrom the authenticated user, never from the request body. - Rule #3 (EU residency): D-Trust + GlobalSign are EU Trusted List QTSPs. Sigstore Fulcio + Rekor v2 are global but only the SHA-256 hash leaves EU per EDPB Guidelines 02/2025.
- Rule #4 (PII scrub): transcripts in the bundle inherit the scrubbed form from write-time (per the Track D
policyTranscriptService.emitscrub stage); no raw PII surfaces in the bundle. - Rule #5 (audit immutability): the bundle is a snapshot — Policy and PolicyVersion + PolicyTranscript records are append-only on the live database, and the bundle is a frozen view of that chain at a specific moment.
What's NOT in the bundle (and why)
- Customer identity beyond
organizationId: no user emails, no API keys, no IP addresses. - Trace input payloads: only PII-scrubbed
traceValuestrings from the firedConditions list. The raw input that the customer's agent processed is NOT in the bundle. - The Cedar WASM runtime binary: the
adjudon-replayCLI bundles cedar-wasm internally; the customer doesn't need to install Cedar separately.
Phase 3.1 deliverables (founder action gated)
- Sigstore Cosign integration requires GitHub Actions OIDC setup (one-time DevOps; see
FOUNDER_ACTION_CHECKLIST3.1). - D-Trust QTSP requires commercial procurement (see
FOUNDER_ACTION_CHECKLIST2.1). - Pre-signed S3 download URL for the bundle (vs the current in-memory generation).
- Encrypted-at-rest bundle option with customer-held key (per Synthesis §8 caveat 9 — adversarial-evasion mitigation).