Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions integrations/comply54/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
144 changes: 144 additions & 0 deletions integrations/comply54/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# comply54 → TRACE Adapter

Converts a [comply54](https://github.com/comply54/comply54) `ComplianceResult` JSON into a **signed TRACE v0.1 JWT** (Ed25519).

comply54 evaluates AI agent actions against African regulatory frameworks (NDPA 2023, CBN Transaction Controls, KDPA 2019, POPIA, and 9 other jurisdictions). This adapter maps the compliance decision into the TRACE attestation format so the policy outcome becomes a cryptographically verifiable evidence record.

## Conformance Level

**Level 0 (software-only)** — No hardware TEE attestation. The JWT is signed with Ed25519 and carries all required TRACE envelope fields. Runtime fields use software-simulated placeholders; hardware fields are marked `not-attested`.

| Check | Status |
|-------|--------|
| `eat_profile` = `tag:agentrust.io,2026:trace-v0.1` | ✅ |
| `iat` (integer Unix timestamp) | ✅ |
| `subject` (SPIFFE URI) | ✅ |
| `cnf.jwk` with Ed25519 public key | ✅ |
| `policy.bundle_hash` (SHA-256 of sorted pack IDs) | ✅ |
| `appraisal.status` mapped from comply54 decision | ✅ |
| Ed25519 signature binding | ✅ |
| Hardware TEE measurement | ❌ Level 0 — placeholder |

## Decision → Appraisal Mapping

| comply54 `overall` | TRACE `appraisal.status` |
|--------------------|--------------------------|
| `allow` | `affirming` |
| `audit` | `advisory` |
| `escalate` | `warning` |
| `deny` | `contraindicated` |

## Usage

**1. Install dependencies**

```bash
pip install comply54 PyJWT cryptography
```

**2. Generate a comply54 ComplianceResult and save as JSON**

```python
from comply54 import NigeriaFintechCompliance
import json

compliance = NigeriaFintechCompliance()
result = compliance.check(
"transfer_funds",
{"amount": 15_000_000, "currency": "NGN"},
context={"kyc_tier": 3},
)
with open("result.json", "w") as f:
json.dump(result.model_dump(mode="json"), f, default=str)
```

**3. Convert to TRACE JWT**

```bash
python src/comply54_to_trace.py result.json \
--agent-id payments-agent \
--model anthropic/claude-sonnet-4-6
```

Output: `claim.jwt` (signed JWT, compact format) + printed to stdout.

**4. Inspect the JWT payload**

```bash
python -c "
import jwt
payload = jwt.decode(open('claim.jwt').read(), options={'verify_signature': False})
print('eat_profile:', payload['eat_profile'])
print('appraisal: ', payload['appraisal'])
print('comply54: ', payload['comply54'])
"
```

**5. Run tests**

```bash
pip install pytest
python -m pytest integrations/comply54/tests/ -v
```

All 20 tests should pass.

## Key Management

By default a fresh Ed25519 key is generated per run (suitable for testing and CI).

For persistent keys, set `TRACE_PRIVATE_KEY_PEM`:

```bash
# Generate a persistent key
python -c "
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
key = Ed25519PrivateKey.generate()
print(key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()).decode())
" > signing.pem

export TRACE_PRIVATE_KEY_PEM="$(cat signing.pem)"
python src/comply54_to_trace.py result.json
```

## What is verified

- `eat_profile` is exactly `tag:agentrust.io,2026:trace-v0.1`
- `policy.bundle_hash` is `sha256:` + hex(SHA-256(JSON-sorted pack IDs)) — reproducible from the same comply54 result
- `appraisal.status` matches the comply54 decision using the mapping table above
- `comply54.audit_id` matches the `audit_id` from the source ComplianceResult
- JWT is signed with Ed25519; public key is embedded in `cnf.jwk`

## Limitations

- Hardware attestation fields (`runtime.measurement`, `build_provenance.digest`, `model.weights_digest`) are software-simulated placeholders. Level 1/2 requires running comply54 inside a TEE (AMD SEV-SNP, Intel TDX, or equivalent).
- `transparency` is empty — no SCITT log anchor at Level 0.
- Model identity fields reflect what the caller passes via `--model`. comply54 evaluates policy against the agent's action; it does not independently verify which model ran.

## comply54 extension claims

The JWT carries a non-standard `comply54` object with:

```json
{
"comply54": {
"audit_id": "uuid",
"overall": "deny",
"jurisdictions": ["NG", "KE", "ZA"],
"packs_evaluated": ["nigeria/cbn", "nigeria/ndpa", "universal/pii-leakage"],
"violations": [
{
"pack": "nigeria/cbn",
"regulation": "CBN Transaction Controls",
"action": "deny",
"messages": ["CBN NIP cap exceeded: ₦15,000,000 > ₦10,000,000 limit"]
}
]
}
}
```

## Repository

[comply54/comply54](https://github.com/comply54/comply54)
17 changes: 17 additions & 0 deletions integrations/comply54/integration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: comply54
vendor: comply54
integrates_with:
- trace
description: >-
Converts a comply54 ComplianceResult into a signed TRACE v0.1 JWT,
mapping African regulatory policy decisions to verifiable attestation claims.
maintainer:
github: kingztech2019
email: oluwajuwon.falore@sagegreytech.com
repository: https://github.com/comply54/comply54
homepage: https://comply54.io
license: Apache-2.0
tier: community
trace_conformance_level: 0
tested_against:
agentrust-trace: "0.2.0"
2 changes: 2 additions & 0 deletions integrations/comply54/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PyJWT>=2.8.0
cryptography>=42.0.0
3 changes: 3 additions & 0 deletions integrations/comply54/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .comply54_to_trace import comply54_to_trace_payload, load_or_generate_key, private_key_to_jwk

__all__ = ["comply54_to_trace_payload", "load_or_generate_key", "private_key_to_jwk"]
175 changes: 175 additions & 0 deletions integrations/comply54/src/comply54_to_trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
comply54 -> TRACE v0.1 Adapter
Reads a comply54 ComplianceResult JSON and emits a signed TRACE v0.1 JWT (Ed25519).
Conforms to TRACE spec at Level 0 (software-only; no hardware TEE attestation).

Usage:
python comply54_to_trace.py result.json
python comply54_to_trace.py result.json --agent-id payments-agent --model anthropic/claude-sonnet-4-6

The JWT is written to claim.jwt and printed to stdout.
Set TRACE_PRIVATE_KEY_PEM to supply a persistent Ed25519 key; otherwise a
fresh key is generated per run (suitable for testing).
"""

import argparse
import base64
import hashlib
import json
import os
import sys
import time
from pathlib import Path

import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey


# ── comply54 appraisal → TRACE appraisal status ───────────────────────────────

_APPRAISAL_MAP = {
"allow": "affirming",
"audit": "advisory",
"escalate": "warning",
"deny": "contraindicated",
}


# ── Key helpers ───────────────────────────────────────────────────────────────

def load_or_generate_key() -> Ed25519PrivateKey:
pem = os.environ.get("TRACE_PRIVATE_KEY_PEM")
if pem:
return serialization.load_pem_private_key(pem.encode(), password=None)
return Ed25519PrivateKey.generate()


def private_key_to_jwk(key: Ed25519PrivateKey) -> dict:
raw = key.public_key().public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
return {
"kty": "OKP",
"crv": "Ed25519",
"x": base64.urlsafe_b64encode(raw).decode().rstrip("="),
}


# ── Mapping ───────────────────────────────────────────────────────────────────

def comply54_to_trace_payload(result: dict, agent_id: str, model: str) -> dict:
overall = result.get("overall", "deny")
audit_id = result.get("audit_id", "unknown")
decisions = result.get("decisions", [])

# Policy bundle hash: deterministic fingerprint of the pack IDs that ran
pack_ids = sorted({d.get("pack", "") for d in decisions if d.get("pack")})
bundle_input = json.dumps(pack_ids, sort_keys=True).encode()
bundle_hash = f"sha256:{hashlib.sha256(bundle_input).hexdigest()}"

# Jurisdictions covered by this evaluation
jurisdictions = sorted({d.get("jurisdiction", "") for d in decisions if d.get("jurisdiction")})

# Violations summary (embedded as a non-standard extension claim)
violations = [
{
"pack": d.get("pack"),
"regulation": d.get("regulation"),
"action": d.get("action"),
"messages": d.get("messages", [])[:1], # first message only for compactness
}
for d in decisions
if d.get("action") != "allow"
]

appraisal_status = _APPRAISAL_MAP.get(overall, "contraindicated")

provider, _, model_id = model.partition("/")

return {
# ── Required TRACE EAT envelope ──────────────────────────────────────
"eat_profile": "tag:agentrust.io,2026:trace-v0.1",
"iat": int(time.time()),
"subject": f"spiffe://comply54.io/agent/{agent_id}",

# ── Model identity (the agent being governed, not comply54) ──────────
"model": {
"provider": provider or "unknown",
"model_id": model_id or model,
"version": "unknown",
"weights_digest": "sha256:not-attested",
},

# ── Runtime (software-only — Level 0) ────────────────────────────────
"runtime": {
"platform": "software-only",
"measurement": "sha384:" + "0" * 96,
"rim_uri": "https://github.com/comply54/comply54",
},

# ── Policy evidence: comply54 pack bundle ────────────────────────────
"policy": {
"bundle_hash": bundle_hash,
"enforcement_mode": "enforce",
"version": "0.1.0",
},

# ── Data classification ───────────────────────────────────────────────
"data_class": "confidential",

# ── Build provenance (software-only) ─────────────────────────────────
"build_provenance": {
"slsa_level": 0,
"builder": "https://github.com/comply54/comply54",
"digest": "sha256:not-attested",
},

# ── Appraisal: the comply54 decision ─────────────────────────────────
"appraisal": {
"status": appraisal_status,
"verifier": "https://github.com/comply54/comply54",
"policy_ref": f"comply54-v0.1.0/{','.join(pack_ids)}",
},

# ── Transparency (no log anchor at Level 0) ───────────────────────────
"transparency": "",

# ── comply54-specific extension claims ───────────────────────────────
"comply54": {
"audit_id": audit_id,
"overall": overall,
"jurisdictions": jurisdictions,
"packs_evaluated": pack_ids,
"violations": violations,
},
}


# ── CLI ───────────────────────────────────────────────────────────────────────

def main() -> None:
parser = argparse.ArgumentParser(description="Convert comply54 ComplianceResult to TRACE v0.1 JWT")
parser.add_argument("result_json", help="Path to comply54 ComplianceResult JSON file")
parser.add_argument("--agent-id", default="fintech-agent", help="Agent SPIFFE identity suffix")
parser.add_argument("--model", default="unknown/unknown", help="Model in provider/model-id format")
parser.add_argument("--out", default="claim.jwt", help="Output JWT file (default: claim.jwt)")
args = parser.parse_args()

with open(args.result_json) as f:
result = json.load(f)

payload = comply54_to_trace_payload(result, args.agent_id, args.model)
key = load_or_generate_key()
payload["cnf"] = {"jwk": private_key_to_jwk(key)}

token = jwt.encode(payload, key, algorithm="EdDSA", headers={"alg": "EdDSA", "typ": "JWT"})

Path(args.out).write_text(token)
print(token)


if __name__ == "__main__":
main()
Empty file.
Loading
Loading