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
51 changes: 51 additions & 0 deletions .github/workflows/ramen-ai-cmcp-conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# ramen-ai cMCP adapter conformance workflow.
# Lives at repo root — GitHub Actions only discovers workflows here.
# Scoped to this integration via paths filter.
name: ramen-ai-cmcp conformance
on:
push:
paths:
- "integrations/ramen-ai-cmcp/**"
- ".github/workflows/ramen-ai-cmcp-conformance.yml"
pull_request:
paths:
- "integrations/ramen-ai-cmcp/**"
- ".github/workflows/ramen-ai-cmcp-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", "3.14"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python }}
- name: Install released agentrust-io packages
run: |
python -m pip install --upgrade pip
pip install agentrust-trace agentrust-trace-tests cmcp-runtime
- name: Install this integration
run: pip install -e "integrations/ramen-ai-cmcp[test]"
- name: Integration tests
run: pytest integrations/ramen-ai-cmcp/tests -q
- name: Emit a sample TRACE record
run: python integrations/ramen-ai-cmcp/examples/emit_record.py --out trust-record.jwt
- name: TRACE conformance level 0
run: trace-tests verify --record trust-record.jwt --level 0
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: conformance-${{ matrix.os }}-py${{ matrix.python }}
path: |
trust-record.jwt
trust-record.jwt.signed.json
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.egg-info/
59 changes: 59 additions & 0 deletions integrations/ramen-ai-cmcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ramen-ai cMCP Adapter integration with cMCP + TRACE

Intercepts tool calls at the [cMCP](https://github.com/agentrust-io/cmcp)
boundary, evaluates their semantic intent against configured compliance policies
via the [ramen-ai](https://ramenai.dev) API, and maps the resulting V5
Ed25519-signed receipt onto a TRACE Trust Record (EAT profile
`tag:agentrust.io,2026:trace-v0.1`).

Source: [ramen-ai-dev/ramen-ai-integrations — plugins/cmcp-python](https://github.com/ramen-ai-dev/ramen-ai-integrations/tree/master/plugins/cmcp-python)

## Run it

Against released packages (`agentrust-trace` 0.3.0, `cmcp-runtime` 0.3.0):

```bash
pip install agentrust-trace agentrust-trace-tests cmcp-runtime
pip install -e "integrations/ramen-ai-cmcp[test]"
pytest integrations/ramen-ai-cmcp/tests -q
python integrations/ramen-ai-cmcp/examples/emit_record.py --out trust-record.jwt
trace-tests verify --record trust-record.jwt --level 0
```

## What is verified

- `ramen_ai_trace.build_trace_record` maps a committed V5 fixture receipt onto
TRACE fields: `policy.bundle_hash` (`sha256:<payload_hash>`), `runtime.measurement`
(receipt UUID), `subject` (`spiffe://ramenai.dev/evaluation/<receipt_id>`),
`appraisal.status` (`affirming` / `denying`).
- `agentrust_trace.sign_record` signs the record with an ephemeral Ed25519 key
and `agentrust_trace.verify_record(..., allow_embedded_key=True)` verifies the
round-trip; `tests/` includes a tamper probe that must fail verification.
- `trace-tests verify --level 0` passes on the emitted record (8 checks).

## What it does NOT claim

See rules 2 and 4 in [CONTRIBUTING.md](../../CONTRIBUTING.md).

- **Level 0 carries a TR-SIG-005 UNVERIFIED finding.** The `agentrust-trace-tests`
loader rejects any plain record carrying a top-level `signature` field
(anti-downgrade), so the gradable record is the unsigned payload. The signed
form is written alongside it (`<out>.signed.json`) and verifies with
`agentrust_trace.verify_record`.
- The ephemeral signing key proves the sign/verify path works; it does **not**
chain to a trusted issuer.
- `runtime.platform` is `software-only`. No TEE, hardware root of trust, or
attested-execution claim is made.
- The ramen-ai evaluation API requires `RAMEN_API_KEY` and `OPENAI_API_KEY`
(BYOK on Starter/Professional tiers). The conformance workflow does not call
the live API — it maps a committed fixture receipt offline.
- V5 receipts bind policy UUIDs but not rule content (policies are mutable under
the same UUID). See `v5-conformance.md §6` in the ramen-ai-integrations repo.

## Conformance CI

The repository-root workflow
[`.github/workflows/ramen-ai-cmcp-conformance.yml`](../../.github/workflows/ramen-ai-cmcp-conformance.yml)
(path-scoped to this directory) installs the released agentrust-io packages,
runs the mapping tests, emits a record, and runs `trace-tests verify --level 0`
across Python 3.11–3.14. A clean matrix run is the basis for the Verified tier.
66 changes: 66 additions & 0 deletions integrations/ramen-ai-cmcp/examples/emit_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Emit a TRACE Trust Record from a ramen-ai V5 fixture receipt.

Loads ``examples/fixtures/vector1_allowed.json``, maps the receipt onto a TRACE
Trust Record via :func:`ramen_ai_trace.build_trace_record`, signs it with an
ephemeral Ed25519 key via ``agentrust_trace.sign_record``, verifies the
round-trip, and writes two files:

<out> Unsigned record for ``trace-tests verify``
<out>.signed.json Signed record, verifiable with
``agentrust_trace.verify_record(..., allow_embedded_key=True)``

Usage:
python examples/emit_record.py --out trust-record.jwt
"""
from __future__ import annotations

import argparse
import json
import sys
import time
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

import agentrust_trace
from ramen_ai_trace import build_trace_record

FIXTURE = Path(__file__).resolve().parent / "fixtures" / "vector1_allowed.json"


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", required=True, help="Path for the trace-tests-gradable record")
args = parser.parse_args()

fixture = json.loads(FIXTURE.read_text(encoding="utf-8"))
receipt: dict = fixture["receipt"]

key = agentrust_trace.generate_key()
jwk = agentrust_trace.key_to_jwk(key)
record = build_trace_record(receipt, iat=int(time.time()), jwk=jwk)

signed = agentrust_trace.sign_record(dict(record), key)
agentrust_trace.verify_record(signed, allow_embedded_key=True)

out = Path(args.out)
out.write_text(
json.dumps(record, sort_keys=True, separators=(",", ":")) + "\n",
encoding="utf-8",
)
signed_out = out.with_name(out.name + ".signed.json")
signed_out.write_text(
json.dumps(signed, sort_keys=True, separators=(",", ":")) + "\n",
encoding="utf-8",
)

print(f"subject: {record['subject']}")
print(f"appraisal.status: {record['appraisal']['status']}")
print(f"unsigned (for trace-tests): {out}")
print(f"signed (verify_record OK): {signed_out}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"_comment": "Vector 1 — Allowed (verdict 1). Source: v5-conformance.md §5.1. Signed by ramen_pk_ephemeral_test.",
"input": "What are the standard terms for a cash ISA?",
"receipt": {
"id": "11111111-1111-4111-8111-111111111111",
"schema_version": "5.0",
"kid": "ramen_pk_ephemeral_test",
"signature": "86rTO8547URmP0M-k0AEHbjSjz2ASRndoRrAFKrtrQJvsPbiAfn6rqEbuQrf4rtNFYq4klVhcHrXqtjRcoC2Ag",
"canonical_payload": "{\"schema_version\":\"5.0\",\"kid\":\"ramen_pk_ephemeral_test\",\"id\":\"11111111-1111-4111-8111-111111111111\",\"timestamp\":\"2026-06-20T09:00:00.000Z\",\"policy_ids\":[\"f47ac10b-58cc-4372-a567-0e02b2c3d479\"],\"payload_hash\":\"adb09112ff437c97a89b17e2dcba478b0c1ebbf2331fa4e5d216f10085eeff21\",\"verdict\":1,\"reasoning\":\"\",\"steering\":\"\",\"statutory_anchors\":[\"FCA COBS 4.2.1\"]}",
"statutory_anchors": ["FCA COBS 4.2.1"]
},
"expected": {
"valid": true,
"verdict": 1,
"reasoning": "",
"steering": "",
"payload_hash": "adb09112ff437c97a89b17e2dcba478b0c1ebbf2331fa4e5d216f10085eeff21"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"_comment": "Negative Vector N1 — tampered signature. Source: v5-conformance.md §5.4. Must verify as valid=false.",
"input": "What are the standard terms for a cash ISA?",
"receipt": {
"id": "11111111-1111-4111-8111-111111111111",
"schema_version": "5.0",
"kid": "ramen_pk_ephemeral_test",
"signature": "86rTO8547URmP0M-k0AEHbjSjz2ASRndoRrAFKrtrQJvsPbiAfn6rqEbuQrf4rtNFYq4klVhcHrXqtjRcoC2AA",
"canonical_payload": "{\"schema_version\":\"5.0\",\"kid\":\"ramen_pk_ephemeral_test\",\"id\":\"11111111-1111-4111-8111-111111111111\",\"timestamp\":\"2026-06-20T09:00:00.000Z\",\"policy_ids\":[\"f47ac10b-58cc-4372-a567-0e02b2c3d479\"],\"payload_hash\":\"adb09112ff437c97a89b17e2dcba478b0c1ebbf2331fa4e5d216f10085eeff21\",\"verdict\":1,\"reasoning\":\"\",\"steering\":\"\",\"statutory_anchors\":[\"FCA COBS 4.2.1\"]}",
"statutory_anchors": ["FCA COBS 4.2.1"]
},
"expected": {
"valid": false,
"reason_contains": "Signature does not verify"
}
}
22 changes: 22 additions & 0 deletions integrations/ramen-ai-cmcp/integration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: ramen-ai cMCP Adapter
vendor: ramen-ai
integrates_with:
- cmcp
- trace
description: >-
cMCP tool-call policy adapter that evaluates semantic intent via the ramen-ai
API and maps the V5 Ed25519-signed receipt onto a TRACE Trust Record.
maintainer:
github: ramen-noodle6
email: damian_smith442@aol.com
repository: https://github.com/ramen-ai-dev/ramen-ai-integrations
homepage: https://ramenai.dev
license: MIT
tier: community
# Level 0 passes with the TR-SIG-005 UNVERIFIED finding: trace-tests does not
# grade signatures on plain trace records — the signed form is written alongside
# the gradable record and verifies via agentrust_trace.verify_record.
trace_conformance_level: 0
tested_against:
agentrust-trace: "0.3.0"
cmcp-runtime: "0.3.0"
17 changes: 17 additions & 0 deletions integrations/ramen-ai-cmcp/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "ramen-ai-cmcp-integration"
version = "0.1.0"
description = "ramen-ai cMCP policy adapter and TRACE Trust Record mapper"
requires-python = ">=3.11"
license = "MIT"
dependencies = ["agentrust-trace"]

[project.optional-dependencies]
test = ["pytest"]

[tool.setuptools]
py-modules = ["ramen_ai_trace"]
124 changes: 124 additions & 0 deletions integrations/ramen-ai-cmcp/ramen_ai_trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""ramen_ai_trace — TRACE Trust Record mapper for ramen-ai V5 receipts.

Maps a ramen-ai V5 Ed25519 receipt onto a TRACE Trust Record dict
(EAT profile tag:agentrust.io,2026:trace-v0.1).

This is the self-contained copy for the agentrust-io/integrations submission.
The canonical implementation lives at:
https://github.com/ramen-ai-dev/ramen-ai-integrations/tree/master/plugins/cmcp-python

Field mapping
─────────────
TRACE field ← ramen-ai V5 source
──────────────────────────────────────────────────────────────
eat_profile constant "tag:agentrust.io,2026:trace-v0.1"
iat caller-supplied (int, Unix seconds)
subject "spiffe://ramenai.dev/evaluation/<receipt.id>"
cnf.jwk caller-supplied (public JWK for signing)
policy.bundle_hash "sha256:" + canonical_payload.payload_hash
policy.enforcement_mode "enforce"
policy.version canonical_payload.schema_version ("5.0")
runtime.measurement receipt.id
runtime.platform "software-only"
tool_transcript.call_count 1
tool_transcript.hash "sha256:" + canonical_payload.payload_hash
tool_transcript.transcript_uri "urn:ramen-ai:evaluation:<receipt.id>"
appraisal.policy_ref comma-joined canonical_payload.policy_ids
appraisal.status "affirming" if verdict==1 else "denying"
appraisal.timestamp iat
appraisal.verifier "ramen-ai-core"
appraisal.statutory_anchors canonical_payload.statutory_anchors (if non-empty)
appraisal.steering canonical_payload.steering (omitted when empty)
transparency "pending"
"""
from __future__ import annotations

import json
from typing import Any

EAT_PROFILE = "tag:agentrust.io,2026:trace-v0.1"
VERIFIER = "ramen-ai-core"


def build_trace_record(
receipt: dict[str, Any],
*,
iat: int,
jwk: dict[str, str],
) -> dict[str, Any]:
"""Map a ramen-ai V5 receipt dict onto an unsigned TRACE Trust Record dict.

Args:
receipt: The ``data.receipt`` sub-object from a ramen-ai evaluate
response. Must contain ``id``, ``schema_version`` (``"5.0"``),
``kid``, ``signature``, and ``canonical_payload``.
iat: Unix timestamp (int seconds) for the record issue time.
jwk: Public JWK dict embedded in ``cnf.jwk``; matches the private
key used to sign the record with ``agentrust_trace.sign_record``.

Returns:
Unsigned TRACE Trust Record dict.

Raises:
ValueError: if required fields are missing or schema_version != "5.0".
"""
_validate_receipt(receipt)
payload: dict[str, Any] = json.loads(receipt["canonical_payload"])

receipt_id: str = receipt["id"]
payload_hash: str = payload["payload_hash"]
verdict: int = payload["verdict"]
policy_ids: list[str] = payload.get("policy_ids", [])
statutory_anchors: list[str] = payload.get("statutory_anchors", [])
steering: str = payload.get("steering", "")

prefixed_hash = f"sha256:{payload_hash}"

appraisal: dict[str, Any] = {
"policy_ref": ", ".join(policy_ids),
"status": "affirming" if verdict == 1 else "denying",
"timestamp": iat,
"verifier": VERIFIER,
}
if statutory_anchors:
appraisal["statutory_anchors"] = statutory_anchors
if steering:
appraisal["steering"] = steering

return {
"eat_profile": EAT_PROFILE,
"iat": iat,
"subject": f"spiffe://ramenai.dev/evaluation/{receipt_id}",
"cnf": {"jwk": jwk},
"policy": {
"bundle_hash": prefixed_hash,
"enforcement_mode": "enforce",
"version": payload["schema_version"],
},
"runtime": {
"measurement": receipt_id,
"platform": "software-only",
},
"tool_transcript": {
"call_count": 1,
"hash": prefixed_hash,
"transcript_uri": f"urn:ramen-ai:evaluation:{receipt_id}",
},
"appraisal": appraisal,
"transparency": "pending",
}


def _validate_receipt(receipt: dict[str, Any]) -> None:
required = {"id", "schema_version", "kid", "signature", "canonical_payload"}
missing = required - receipt.keys()
if missing:
raise ValueError(f"Receipt missing required fields: {missing}")
if receipt["schema_version"] != "5.0":
raise ValueError(
f"Unsupported schema_version '{receipt['schema_version']}'; expected '5.0'"
)
try:
json.loads(receipt["canonical_payload"])
except json.JSONDecodeError as exc:
raise ValueError(f"canonical_payload is not valid JSON: {exc}") from exc
Loading
Loading