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
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# MUSUBI trace terms (musubi-trace-terms-v0) workflow.
#
# fixed installs the entry with its exact pins: agentrust-trace and
# agentrust-trace-tests as named in integration.yaml tested_against,
# and nenrin-verify 0.5.6.
# floating installs the latest agentrust-trace and agentrust-trace-tests
# releases instead, unpinned on purpose, as drift detection. The
# evaluator calls TRACE's own verify_bridge, so a release that
# changes a verdict shows up here.
# Both evaluate the 55 vendored vectors, check the vendored bytes, run the
# evaluator's CLI, and run trace-tests Level 0 on the two Trust Records the
# vectors consume. Nothing is fetched at test time.
name: horizonshield-musubi-trace-terms conformance
on:
push:
paths:
- "integrations/horizonshield-musubi-trace-terms/**"
- ".github/workflows/horizonshield-musubi-trace-terms-conformance.yml"
pull_request:
paths:
- "integrations/horizonshield-musubi-trace-terms/**"
- ".github/workflows/horizonshield-musubi-trace-terms-conformance.yml"
schedule:
- cron: "0 6 * * 1" # weekly: catch drift against the latest released packages
workflow_dispatch:

permissions:
contents: read

jobs:
check:
name: ${{ matrix.mode }} (py${{ matrix.python }})
strategy:
fail-fast: false
matrix:
python: ["3.11", "3.12", "3.13"]
mode: [fixed, floating]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python }}
- name: Install this integration with its pins (tested_against)
if: matrix.mode == 'fixed'
run: |
v=$(sed -n 's/^ agentrust-trace: "\(.*\)"$/\1/p' integrations/horizonshield-musubi-trace-terms/integration.yaml)
t=$(sed -n 's/^ agentrust-trace-tests: "\(.*\)"$/\1/p' integrations/horizonshield-musubi-trace-terms/integration.yaml)
test -n "$v" && test -n "$t"
pip install -e "integrations/horizonshield-musubi-trace-terms[test]" "agentrust-trace==$v" "agentrust-trace-tests==$t"
- name: Install the latest agentrust-trace releases
if: matrix.mode == 'floating'
run: pip install agentrust-trace agentrust-trace-tests "nenrin-verify==0.5.6" "pytest>=8"
- name: Record the versions under test
run: pip show agentrust-trace agentrust-trace-tests nenrin-verify | grep -E '^(Name|Version):'
- name: Integration tests
run: python -m pytest integrations/horizonshield-musubi-trace-terms/tests -q
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ TRACE only works as a standard if it is genuinely neutral. Integrations are list
| [EPI Recorder](integrations/epilabs-epi-recorder/) | EPI Labs | trace, wcm | verified |
| [EvidenceBound Core](integrations/evidencebound-core/) | EvidenceBound | trace | community |
| [Google ADK](integrations/google-adk/) | agentrust-io | trace | verified |
| [MUSUBI trace terms](integrations/horizonshield-musubi-trace-terms/) | The HORIZONs Co., Ltd. | trace | community |
| [LangChain](integrations/langchain/) | agentrust-io | trace | verified |
| [SOVP](integrations/litzki-systems-sovp/) | Litzki Systems | trace | verified |
| [LlamaIndex](integrations/llamaindex/) | agentrust-io | trace | verified |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Vendored bytes; the tests check them against their SHA-256.
vendor/** -text
21 changes: 21 additions & 0 deletions integrations/horizonshield-musubi-trace-terms/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 The HORIZONs Co., Ltd. (The HORIZ音s株式会社)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
15 changes: 15 additions & 0 deletions integrations/horizonshield-musubi-trace-terms/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
horizonshield-musubi-trace-terms: MIT, copyright (c) 2026 The HORIZONs Co., Ltd. See LICENSE.

vendor/trace-terms-v0/vectors/vectors.json carries, unchanged, two Trust Records
copied from agentrust-io/trace-registry at commit
58850e9d0f7113be2a4c89b3c2e7b03447f2dcce (staging/processed/), licensed
CC BY 4.0 by their authors:

summit-demo-record.json
producer verifiable-agent-summit-demo/0.1.0
sha256 of the registry file d9fbf07c7813b10da4cacdfda4260ba829178409a26829dbcdb4512eac08a51c
bernstein-3.20.0-20260920-165918-backend-99365805.json
producer bernstein/3.20.0 (the "trace" member of the registry file)
sha256 of the registry file bd39b2ecdea66b20e186663e24cd0217eab3f458256ef607ed2111a965481efb

License: https://creativecommons.org/licenses/by/4.0/
158 changes: 158 additions & 0 deletions integrations/horizonshield-musubi-trace-terms/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# MUSUBI trace terms

`musubi-trace-terms-v0` reads TRACE evidence about an agent's tool calls and
judges it against what two parties agreed. Its inputs are one bundle: an
`a2a-contract-v0` both parties signed, a terms annex both parties signed
(agreed authorizer keys, a map from TRACE tool names to contract actions,
per-action and total call counts, and optionally the runtime they agreed on),
the authorizer public keys, and one item per executed call. Each item holds an
act the contractor signed, the PIC/TRACE authorization
(`tag:agentrust-io.com,2026:pic-trace-bridge-v1`) with its tool call,
declaration and transcript, and optionally the Trust Record the act names. The
output is a verdict per call and overall: `within_terms`, `outside_terms`,
`undetermined` or `refused`.

TRACE checks one call against one authorization; this adds up all the calls
under one two-party contract.

The evaluator does not reimplement TRACE. Each authorization is checked by
`agentrust_trace.intent_bridge.verify_bridge` from the released
[agentrust-trace 0.11.0](https://pypi.org/project/agentrust-trace/0.11.0/), at
the time the contractor states the call ran. TRACE objects are read unchanged
and digested as TRACE digests them (RFC 8785).

The specification, the evaluator and the vector generator live in
[horizon-shield at 05b4924](https://github.com/ogasurfproject-jpg/horizon-shield/tree/05b4924254ebf3c7a5c4523bb2f2fd74ff22b61a/workers/hs-ledger/nenrin/trace-terms-v0)
([SPEC.md](https://github.com/ogasurfproject-jpg/horizon-shield/blob/05b4924254ebf3c7a5c4523bb2f2fd74ff22b61a/workers/hs-ledger/nenrin/trace-terms-v0/SPEC.md)
is the normative text).

## Pinned to agentrust-trace 0.11.0

The evaluator calls TRACE's own `verify_bridge`, so the agentrust-trace
release is part of every verdict, and the 55 vectors were built and are checked
against 0.11.0. This entry pins `agentrust-trace==0.11.0`. The evaluator also
records the version it ran against in its output and adds the finding
`agentrust_trace_not_the_pinned_version` under any other release.

The JSON Schema shipped in agentrust-trace 0.11.0 is older than the schema on
trace-spec main; this is tracked upstream in
[agentrust-io/trace-spec#591](https://github.com/agentrust-io/trace-spec/issues/591).
Every result in this directory is against the 0.11.0 release. None of it is a
statement about trace-spec main.

## Run it

Against released packages, from the repository root:

```bash
python3 -m venv .venv
. .venv/bin/activate
pip install -e "integrations/horizonshield-musubi-trace-terms[test]"
pytest integrations/horizonshield-musubi-trace-terms/tests -q
```

Expected: `75 passed`. Python 3.11 or later. No network access is needed after
installation.

The evaluator's own self-test and CLI, run directly:

```bash
MUSUBI_DIR="$(python -c 'from nenrin_verify import musubi; print(musubi.MUSUBI_DIR)')"
PYTHONPATH="$MUSUBI_DIR" python integrations/horizonshield-musubi-trace-terms/vendor/trace-terms-v0/trace_terms_v0.py --selftest
# === 55 / 55 (musubi-trace-terms-v0) ===
PYTHONPATH="$MUSUBI_DIR" python integrations/horizonshield-musubi-trace-terms/vendor/trace-terms-v0/trace_terms_v0.py check bundle.json
# exit 0 within_terms, 1 outside_terms, 2 refused, 3 undetermined
```

## What is in this directory

| Path | What it is |
|---|---|
| `vendor/trace-terms-v0/trace_terms_v0.py` | the evaluator, unchanged |
| `vendor/trace-terms-v0/vectors/vectors.json` | the 55 vectors, unchanged |
| `tests/test_trace_terms_vectors.py` | all 55 vectors against their expected results, and named cases |
| `tests/test_trace_terms_vendored.py` | the vendored files' SHA-256 against the source commit, the CLI self-test and exit codes, and `trace-tests verify --level 0` on the two consumed Trust Records |
| `NOTICE` | attribution for the two Trust Records the vectors carry |

The two vendored files are byte for byte the files at horizon-shield commit
`05b4924254ebf3c7a5c4523bb2f2fd74ff22b61a`:

| File | SHA-256 |
|---|---|
| `trace_terms_v0.py` | `5fbd7dd7a8d7313973673f2b0593c8b06f7f9f5c01381daf95c8faa81853e069` |
| `vectors/vectors.json` | `a636029b66d66768fbf37ea4e7a61ef0035548d6b0cc407b4c0830a43ac0d38a` |

The evaluator imports `contract_v0` (MUSUBI's contract verifier),
`agreement_verify` (the canonical form and Ed25519 check) and
`nenrin_verify.trace` (the Trust Record intake). They are not copied here: the
released [nenrin-verify 0.5.6](https://pypi.org/project/nenrin-verify/0.5.6/)
ships them byte for byte from the same repository, and the tests load them
with `nenrin_verify.musubi.load`. nenrin-verify is published by the same
vendor. The vector generator is not vendored, because it reads the source
records from horizon-shield; the horizon-shield workflow rebuilds the vectors
byte for byte.

## Where the evidence in the vectors comes from

Every authorization is signed by agentrust-trace's own `sign_bridge`. Two
Trust Records are real records copied unchanged from
[agentrust-io/trace-registry](https://github.com/agentrust-io/trace-registry)
(CC BY 4.0, see `NOTICE`): the summit demo record and a Bernstein 3.20.0
record. Both pass `trace-tests verify --level 0` with agentrust-trace-tests
0.6.2, which this entry's tests run. Other records are signed in the vectors by
agentrust-trace's `sign_record`. Every other key is a test key derived from a
fixed label.

## What the tests show

Each line is a case a per-call check cannot see, shown by a vector:

| Vector | Result |
|---|---|
| TT01 | three calls, each allowed by TRACE, inside the grant and the counts, on the agreed runtime: `within_terms` |
| TT02, TT03 | every call allowed, the third one more than the parties agreed: `calls_exceed_terms` |
| TT04 | the bridge scope includes the tool and TRACE allows it; the contract prohibits the action: `prohibited_action` |
| TT07 | TRACE verifies the bridge under the supplied key; the principal never agreed to that authorizer: `authorizer_not_agreed` |
| TT08 to TT10 | TRACE's `verify_bridge` refuses (deny, expired, mismatch): reported by kind, with `executed_without_valid_authorization` |
| TT12 | one authorization carried two calls: `authorization_reused` |
| TT15, TT16 | the real Bernstein record outside the runtime the parties agreed, and inside terms that agreed on exactly that runtime |
| TT11, TT36, TT43 | whoever assembles the bundle swaps evidence or withholds a key: `undetermined`, never `outside_terms` |
| TT25 | terms signed by one party only: `refused` |
| TT41 | no acts at all: `undetermined`, not `within_terms` |

The rule behind every code: an act is outside the terms only on what the
contractor signed and on signatures nobody else can forge.

## What a verdict does not establish

From SPEC.md section 8:

- Anything about acts that were not presented. An act the contractor never
signed, or signed and withheld, is not counted.
- That `executed_at` is true. The contractor states it, within the bridge's
window and the record's age.
- That a grant or an authorizer key was not revoked. Revocations are not read.
- That the runtime which ran the call is the one in the Trust Record the
contractor names, or that any Trust Record claim is true. Nothing here
appraises a hardware attestation.
- That the PIC intent and argument digests are what PIC would compute.
- Liability, fault, or that the contract is a legal agreement.

Clauses this profile does not judge (conditional grants, amount limits,
approval policies, parent contracts, evidence or recovery requirements) make
the bundle `refused`, not judged as if absent (TT47, TT48, TT52, TT53).

## What this entry does not claim

- It emits no Trust Record. TRACE Level 0 is claimed only in the sense stated
in `integration.yaml`: the Trust Records it consumes pass Level 0.
- The evaluator, its specification and its vectors have one author, the
maintainer of this entry. They are not an independent implementation.

## Conformance CI

`.github/workflows/horizonshield-musubi-trace-terms-conformance.yml` runs the
tests on Python 3.11, 3.12 and 3.13 in two modes: `fixed` installs the versions
in `tested_against` with this entry's pins, `floating` installs the latest
agentrust-trace and agentrust-trace-tests releases. It runs on changes to this
directory and weekly.
37 changes: 37 additions & 0 deletions integrations/horizonshield-musubi-trace-terms/integration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: MUSUBI trace terms
vendor: The HORIZONs Co., Ltd.
integrates_with:
- trace
description: >-
Judges PIC/TRACE authorizations and Trust Records, read unchanged, against a contract and
terms two parties signed, per call and overall: within, outside, undetermined or refused.
maintainer:
github: ogasurfproject-jpg
repository: https://github.com/ogasurfproject-jpg/horizon-shield
license: MIT
tier: community
marketplace:
category: Evidence & receipts
mark: MT
keywords: [contract, authorization, pic-trace-bridge, trust-record, terms, counts]
# Role. The evaluator consumes TRACE evidence: it checks each PIC/TRACE
# authorization with agentrust-trace's own intent_bridge.verify_bridge and
# reads each Trust Record the contractor names. It emits no Trust Record.
# Level 0 here means: the two Trust Records the vectors consume unchanged
# (copied from agentrust-io/trace-registry, see NOTICE) pass
# `trace-tests verify --level 0` (tests/test_trace_terms_vendored.py). No
# higher level is claimed.
trace_roles:
- record-consumer
trace_conformance_level: 0
tested_against:
agentrust-trace: "0.11.0"
agentrust-trace-tests: "0.6.2"
# The schema records agentrust-io package keys only. The fixed job in
# .github/workflows/horizonshield-musubi-trace-terms-conformance.yml also pins
# nenrin-verify 0.5.6, which supplies contract_v0.py, agreement_verify.py and
# nenrin_verify.trace byte for byte from horizon-shield.
# The JSON Schema in agentrust-trace 0.11.0 is older than the one on
# trace-spec main (agentrust-io/trace-spec#591). Every result here is against
# the 0.11.0 release, not trace-spec main.
# Run on Python 3.11, 3.12 and 3.13.
31 changes: 31 additions & 0 deletions integrations/horizonshield-musubi-trace-terms/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "horizonshield-musubi-trace-terms-integration"
version = "0.1.0"
description = "Runs musubi-trace-terms-v0 against released agentrust-trace"
requires-python = ">=3.11"
license = "MIT"
dependencies = [
# Exact pin. The evaluator calls TRACE's own verify_bridge, so the
# agentrust-trace release is part of every verdict; the vectors were built
# and are checked against 0.11.0.
"agentrust-trace==0.11.0",
# contract_v0.py, agreement_verify.py and nenrin_verify.trace, shipped byte
# for byte from horizon-shield.
"nenrin-verify==0.5.6",
]

[project.optional-dependencies]
test = [
"pytest>=8",
"agentrust-trace-tests==0.6.2",
]

[tool.setuptools]
py-modules = []

[tool.pytest.ini_options]
testpaths = ["tests"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Load the vendored evaluator against the released packages (shared by the tests).

trace_terms_v0.py imports contract_v0 and agreement_verify by bare name, and
nenrin_verify.trace as a package module. nenrin-verify ships all three byte for
byte from horizon-shield; nenrin_verify.musubi.load puts the directories of the
first two on sys.path.
"""

from __future__ import annotations

import importlib.util
import json
import sys
from pathlib import Path

from nenrin_verify import musubi

ENTRY = Path(__file__).resolve().parents[1]
VENDOR = ENTRY / "vendor" / "trace-terms-v0"
VECTORS_PATH = VENDOR / "vectors" / "vectors.json"

musubi.load("contract_v0")


def _load_evaluator():
if "trace_terms_v0" in sys.modules:
return sys.modules["trace_terms_v0"]
spec = importlib.util.spec_from_file_location("trace_terms_v0", VENDOR / "trace_terms_v0.py")
module = importlib.util.module_from_spec(spec)
sys.modules["trace_terms_v0"] = module
spec.loader.exec_module(module)
return module


evaluator = _load_evaluator()
_DOC = json.loads(VECTORS_PATH.read_text(encoding="utf-8"))
VECTORS = {v["id"]: v for v in _DOC["vectors"]}
MUSUBI_DIRS = [musubi.MUSUBI_DIR, musubi.AGREEMENT_DIR]


def evaluate(vector_id: str) -> dict:
return evaluator.evaluate(VECTORS[vector_id]["bundle"])


def signature(vector_id: str) -> dict:
"""What the evaluator's own --selftest compares: verdict, refusals, and per act index, status, codes."""
return evaluator._signature(evaluate(vector_id))


def acts(vector_id: str) -> list[tuple[str, list[str]]]:
return [(a["status"], a["codes"]) for a in signature(vector_id)["acts"]]
Loading
Loading