From e81dd3a0f07b5724b03f6e7271242cc4bcb3bd9c Mon Sep 17 00:00:00 2001 From: oluwajuwon omotayo Date: Tue, 4 Aug 2026 03:55:19 +0100 Subject: [PATCH] feat(comply54): add conformance CI to reach Verified tier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds what's needed to promote comply54 from tier: community to tier: verified, following the exact pattern already proven for the sibling spendguard integration (also Tier 0 in #40): - pyproject.toml: makes the integration pip-installable (comply54-agentrust-integration), src-layout aware (package-dir = {"" = "src"}), with a [test] extra bundling pytest, jsonschema, and agentrust-trace-tests. - examples/emit_record.py: emits a sample TRACE Trust Record from a committed comply54 ComplianceResult fixture (a denied ₦15M transfer exceeding the CBN NIP cap, the same scenario from the original PR #11 description), reusing comply54_to_trace_payload and load_or_generate_key unchanged. Writes an unsigned record (what trace-tests grades) plus a signed .jwt companion, with an immediate decode/verify round-trip assertion, matching spendguard's unsigned-for-grading / signed-for-proof convention. - .github/workflows/comply54-conformance.yml: installs the released agentrust-trace-tests package (not local/dev code), installs this integration, runs its existing test suite, emits the sample record, and verifies it with `trace-tests verify --level 0`. Matrix over Python 3.11-3.13. Verified locally end-to-end in a clean venv before pushing: 31/31 existing tests pass unchanged, emit_record.py produces a valid record, and `trace-tests verify --record ... --level 0` reports PASS (8 checks, 1 expected UNVERIFIED for the unsigned grading record, matching the sibling integration's documented Level 0 behavior). Signed-off-by: oluwajuwon omotayo --- .github/workflows/comply54-conformance.yml | 51 +++++++++++++ integrations/comply54/examples/emit_record.py | 74 +++++++++++++++++++ .../examples/fixtures/deny-cbn-nip-cap.json | 27 +++++++ integrations/comply54/pyproject.toml | 25 +++++++ 4 files changed, 177 insertions(+) create mode 100644 .github/workflows/comply54-conformance.yml create mode 100755 integrations/comply54/examples/emit_record.py create mode 100644 integrations/comply54/examples/fixtures/deny-cbn-nip-cap.json create mode 100644 integrations/comply54/pyproject.toml diff --git a/.github/workflows/comply54-conformance.yml b/.github/workflows/comply54-conformance.yml new file mode 100644 index 0000000..56d5ccf --- /dev/null +++ b/.github/workflows/comply54-conformance.yml @@ -0,0 +1,51 @@ +# agentrust-io conformance workflow for the comply54 integration. +# Lives in the repository root because GitHub Actions only discovers workflows +# in the root .github/workflows directory; scoped to this integration via paths. +name: comply54 conformance +on: + push: + paths: + - "integrations/comply54/**" + - ".github/workflows/comply54-conformance.yml" + pull_request: + paths: + - "integrations/comply54/**" + - ".github/workflows/comply54-conformance.yml" + schedule: + - cron: "0 6 * * 1" # weekly: catch drift against the latest released packages + workflow_dispatch: + +permissions: + contents: read + +jobs: + conformance: + strategy: + fail-fast: false + matrix: + python: ["3.11", "3.12", "3.13"] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.python }} + - name: Install released agentrust-io packages + run: | + python -m pip install --upgrade pip + pip install agentrust-trace-tests + - name: Install this integration + run: pip install -e "integrations/comply54[test]" + - name: Integration tests + run: pytest integrations/comply54/tests -q + - name: Emit a sample TRACE record + run: python integrations/comply54/examples/emit_record.py --out trust-record.jwt + - name: TRACE conformance + run: trace-tests verify --record trust-record.jwt --level 0 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: conformance-${{ matrix.os }}-py${{ matrix.python }} + path: | + trust-record.jwt + trust-record.jwt.signed.jwt diff --git a/integrations/comply54/examples/emit_record.py b/integrations/comply54/examples/emit_record.py new file mode 100755 index 0000000..9e63d8b --- /dev/null +++ b/integrations/comply54/examples/emit_record.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""Emit a TRACE Trust Record from a comply54 ComplianceResult. + +Maps the committed example fixture (a denied high-value transfer, exceeding +the CBN NIP transaction cap) onto a TRACE v0.2 Trust Record using the same +`comply54_to_trace_payload` mapping the adapter's own test suite already +verifies against agentrust-trace-tests Level 0. + +Two files are written: + + unsigned record for `trace-tests verify` — this is the + plain payload dict (includes `cnf.jwk`, no `signature` + field), the same shape the adapter's own + TestLevel0Conformance suite grades via the internal + trace_tests API. + .signed.jwt the same payload, compact-serialized and signed as an + Ed25519 JWT via PyJWT (comply54_to_trace.py's own + signing path), with an immediate decode-and-verify + round trip against the public key — proves the + sign/verify path is real, not just that the payload + shape is correct. + +The ephemeral key is generated per run and never persisted. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +import jwt as pyjwt + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) +from comply54_to_trace import ( + comply54_to_trace_payload, + load_or_generate_key, +) + +DEFAULT_RESULT = Path(__file__).resolve().parent / "fixtures" / "deny-cbn-nip-cap.json" + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--out", required=True, help="Path for the trace-tests-gradable record") + parser.add_argument("--result", default=str(DEFAULT_RESULT), help="comply54 ComplianceResult JSON path") + parser.add_argument("--agent-id", default="payments-agent", help="Agent SPIFFE identity suffix") + parser.add_argument("--model", default="anthropic/claude-sonnet-4-6", help="Model in provider/model-id format") + args = parser.parse_args() + + result = json.loads(Path(args.result).read_text(encoding="utf-8")) + + key = load_or_generate_key() + payload = comply54_to_trace_payload(result, args.agent_id, args.model, key=key) + + token = pyjwt.encode(payload, key, algorithm="EdDSA", headers={"alg": "EdDSA", "typ": "JWT"}) + decoded = pyjwt.decode(token, key.public_key(), algorithms=["EdDSA"]) + assert decoded["eat_profile"] == payload["eat_profile"], "sign/verify round trip mismatch" + + out = Path(args.out) + out.write_text(json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8") + signed_out = out.with_name(out.name + ".signed.jwt") + signed_out.write_text(token + "\n", encoding="utf-8") + + print(f"subject: {payload['subject']}") + print(f"appraisal: {payload['appraisal']['status']}") + print(f"unsigned (for trace-tests): {out}") + print(f"signed (decode/verify OK): {signed_out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/integrations/comply54/examples/fixtures/deny-cbn-nip-cap.json b/integrations/comply54/examples/fixtures/deny-cbn-nip-cap.json new file mode 100644 index 0000000..5609939 --- /dev/null +++ b/integrations/comply54/examples/fixtures/deny-cbn-nip-cap.json @@ -0,0 +1,27 @@ +{ + "overall": "deny", + "audit_id": "example-audit-001", + "decisions": [ + { + "pack": "nigeria/cbn", + "regulation": "CBN Transaction Controls", + "jurisdiction": "NG", + "action": "deny", + "messages": ["CBN NIP cap exceeded: ₦15,000,000 > ₦10,000,000 limit"] + }, + { + "pack": "nigeria/nfiu-aml", + "regulation": "NFIU AML Guidelines", + "jurisdiction": "NG", + "action": "deny", + "messages": ["Currency Transaction Report required for amounts above ₦5,000,000"] + }, + { + "pack": "universal/human-approval", + "regulation": "OWASP LLM09", + "jurisdiction": "UNIVERSAL", + "action": "deny", + "messages": ["High-value transfer requires human approval before execution"] + } + ] +} diff --git a/integrations/comply54/pyproject.toml b/integrations/comply54/pyproject.toml new file mode 100644 index 0000000..4b6420f --- /dev/null +++ b/integrations/comply54/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "comply54-agentrust-integration" +version = "0.1.0" +description = "Converts comply54 ComplianceResult decisions into signed TRACE Trust Records" +requires-python = ">=3.11" +license = "Apache-2.0" +dependencies = [ + "PyJWT>=2.8.0", + "cryptography>=42.0.0", +] + +[project.optional-dependencies] +test = [ + "pytest>=7.0.0", + "jsonschema>=4.0.0", + "agentrust-trace-tests>=0.4,<0.5", +] + +[tool.setuptools] +package-dir = {"" = "src"} +py-modules = ["comply54_to_trace"]