From 10308f6e598f459ab87478731cf7c79aa34bc5ce Mon Sep 17 00:00:00 2001 From: Sankalp Gilda Date: Wed, 30 Sep 2026 20:41:15 -0400 Subject: [PATCH 1/2] Add observed-effect references integration Five signed TRACE Trust Records cite observer-signed in-toto statements through a references entry with rel observed-effect. The tests verify each record with released agentrust-trace and recompute the relying-party verdict from the committed bytes; CI also replays the published vectors-observed-effect corpus at v0.15.0. Signed-off-by: Sankalp Gilda --- .../probityai-observed-effect-conformance.yml | 56 +++++ README.md | 1 + .../probityai-observed-effect/README.md | 21 ++ .../examples/01-observation-verified.json | 43 ++++ .../02-observation-altered-after-issue.json | 43 ++++ .../03-observer-and-observed-disagree.json | 43 ++++ .../examples/04-reference-unresolvable.json | 43 ++++ .../05-observer-key-not-configured.json | 43 ++++ .../examples/README.md | 98 ++++++++ .../examples/effect-store-altered.json | 35 +++ .../examples/effect-store.json | 35 +++ .../examples/expected.json | 68 ++++++ .../examples/gen_observed_effect_vectors.py | 216 ++++++++++++++++ .../examples/source/v1c6fdd82db5229e4.json | 10 + .../examples/source/v620e7755ba36aa0a.json | 10 + .../integration.yaml | 25 ++ .../probityai-observed-effect/pyproject.toml | 22 ++ .../tests/test_observed_effect.py | 231 ++++++++++++++++++ marketplace/catalog.json | 26 +- 19 files changed, 1068 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/probityai-observed-effect-conformance.yml create mode 100644 integrations/probityai-observed-effect/README.md create mode 100644 integrations/probityai-observed-effect/examples/01-observation-verified.json create mode 100644 integrations/probityai-observed-effect/examples/02-observation-altered-after-issue.json create mode 100644 integrations/probityai-observed-effect/examples/03-observer-and-observed-disagree.json create mode 100644 integrations/probityai-observed-effect/examples/04-reference-unresolvable.json create mode 100644 integrations/probityai-observed-effect/examples/05-observer-key-not-configured.json create mode 100644 integrations/probityai-observed-effect/examples/README.md create mode 100644 integrations/probityai-observed-effect/examples/effect-store-altered.json create mode 100644 integrations/probityai-observed-effect/examples/effect-store.json create mode 100644 integrations/probityai-observed-effect/examples/expected.json create mode 100644 integrations/probityai-observed-effect/examples/gen_observed_effect_vectors.py create mode 100644 integrations/probityai-observed-effect/examples/source/v1c6fdd82db5229e4.json create mode 100644 integrations/probityai-observed-effect/examples/source/v620e7755ba36aa0a.json create mode 100644 integrations/probityai-observed-effect/integration.yaml create mode 100644 integrations/probityai-observed-effect/pyproject.toml create mode 100644 integrations/probityai-observed-effect/tests/test_observed_effect.py diff --git a/.github/workflows/probityai-observed-effect-conformance.yml b/.github/workflows/probityai-observed-effect-conformance.yml new file mode 100644 index 0000000..4417f49 --- /dev/null +++ b/.github/workflows/probityai-observed-effect-conformance.yml @@ -0,0 +1,56 @@ +# Observed-effect references workflow. +# +# floating installs the latest released agentrust-trace, unpinned on purpose, +# as drift detection. +# fixed installs the agentrust-trace version named in integration.yaml +# tested_against, so that claim cannot move underneath itself. +# Both replay the published vectors-observed-effect corpus pinned to one release. +# This integration is an external-evidence-source and asserts no TRACE level. +name: probityai-observed-effect conformance +on: + push: + paths: + - "integrations/probityai-observed-effect/**" + - ".github/workflows/probityai-observed-effect-conformance.yml" + pull_request: + paths: + - "integrations/probityai-observed-effect/**" + - ".github/workflows/probityai-observed-effect-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", "3.14"] + mode: [floating, fixed] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.python }} + - name: Install agentrust-trace (latest release) + if: matrix.mode == 'floating' + run: pip install agentrust-trace + - name: Install agentrust-trace (tested_against) + if: matrix.mode == 'fixed' + run: | + v=$(sed -n 's/^ agentrust-trace: "\(.*\)"$/\1/p' integrations/probityai-observed-effect/integration.yaml) + test -n "$v" + pip install "agentrust-trace==$v" + - name: Install this integration + run: pip install -e "integrations/probityai-observed-effect[test]" + - name: Integration tests + run: pytest integrations/probityai-observed-effect/tests -q + - name: Replay the published observed-effect corpus + run: | + pip install "agent-evidence-vectors==0.15.0" + agent-evidence-vectors --corpus vectors-observed-effect diff --git a/README.md b/README.md index 98d6993..4bb35bf 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ TRACE only works as a standard if it is genuinely neutral. Integrations are list | [OpenAI Agents SDK](integrations/openai-agents/) | agentrust-io | trace | verified | | [OpenShell TRACE Adapter](integrations/openshell/) | agentrust-io | trace | community | | [OpenTelemetry GenAI](integrations/otel-genai/) | agentrust-io | trace | community | +| [Observed-effect references](integrations/probityai-observed-effect/) | probityai | trace | community | | [ramen-ai cMCP Adapter](integrations/ramen-ai-cmcp/) | ramen-ai | cmcp, trace | verified | | [SAGE AgenTrust Bridge](integrations/sage-agenttrust/) | SAGE | cmcp, trace | community | | [Agent Sentinel](integrations/sentinel/) | a1k7 | trace | community | diff --git a/integrations/probityai-observed-effect/README.md b/integrations/probityai-observed-effect/README.md new file mode 100644 index 0000000..6786685 --- /dev/null +++ b/integrations/probityai-observed-effect/README.md @@ -0,0 +1,21 @@ +# Observed-effect references + +A TRACE Trust Record says what an agent was and what it ran under. This integration adds what an observer outside the agent saw change while it ran: each record carries one `references` entry with `rel: "observed-effect"`, and the entry's `digest` pins an in-toto statement the observer signed over the state before and after the interval. + +## What it does + +- `examples/` holds five signed Trust Records and the effect store their references resolve against. The two observer statements in `examples/source/` are copied byte for byte from [vectors-observed-effect at v0.15.0](https://github.com/probityai/agent-evidence-vectors/tree/v0.15.0/vectors-observed-effect); everything else regenerates from one published seed. +- `tests/` verifies every record with released `agentrust-trace`, then recomputes the relying party's verdict for each case from the committed bytes: verified, digest mismatch, unresolved, and observer key not configured. +- CI also replays the whole published corpus the statements come from, pinned to one release. + +## Run it + +``` +pip install -e "integrations/probityai-observed-effect[test]" +python -m pytest integrations/probityai-observed-effect/tests +uvx agent-evidence-vectors==0.15.0 --corpus vectors-observed-effect +``` + +## What it does NOT claim + +A verified reference establishes that the resolved bytes are the cited bytes and that the named observer signed them. It does not establish that the change the statement reports occurred, and it never changes whether the Trust Record itself verifies (trace-v0.2 section 3.1.2 rule 3). The `observed-effect` value is proposed for the references registry in [trace-spec#403](https://github.com/agentrust-io/trace-spec/pull/403) and is not registered yet; the v0.2 schema leaves `rel` open. No TRACE conformance level is claimed. diff --git a/integrations/probityai-observed-effect/examples/01-observation-verified.json b/integrations/probityai-observed-effect/examples/01-observation-verified.json new file mode 100644 index 0000000..a65d241 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/01-observation-verified.json @@ -0,0 +1,43 @@ +{ + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1789776010, + "subject": "spiffe://trust.example.org/agent/build-bot", + "model": { + "provider": "example", + "model_id": "example-model" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "enforcement_mode": "enforce" + }, + "data_class": "internal", + "build_provenance": { + "slsa_level": 1, + "digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + }, + "appraisal": { + "status": "none", + "verifier": "https://verifier.example.org" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "ysSfGiHZFwj77FqkuI2D7bc9K0sNHVbPcoDjroN3aeA" + } + }, + "references": [ + { + "rel": "observed-effect", + "id": "interval/1", + "resolver": "https://observer.example.org/intervals", + "digest": "sha256:00bd35730a2e8fa462a5fbf0a30aac71302fdbd02b5217d6c3950a66531a9121", + "retention": "P1Y" + } + ], + "signature": "QieV5TjRrAypQcw-2fNAUxvtnYj2PKBH54SjK2WOlP4fF-4AwbuGtwnRar9WUsdZgcAR5TlWtSSsuYFN_WK8Ag" +} diff --git a/integrations/probityai-observed-effect/examples/02-observation-altered-after-issue.json b/integrations/probityai-observed-effect/examples/02-observation-altered-after-issue.json new file mode 100644 index 0000000..40bfd35 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/02-observation-altered-after-issue.json @@ -0,0 +1,43 @@ +{ + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1789776010, + "subject": "spiffe://trust.example.org/agent/build-bot", + "model": { + "provider": "example", + "model_id": "example-model" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "enforcement_mode": "enforce" + }, + "data_class": "internal", + "build_provenance": { + "slsa_level": 1, + "digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + }, + "appraisal": { + "status": "none", + "verifier": "https://verifier.example.org" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "ysSfGiHZFwj77FqkuI2D7bc9K0sNHVbPcoDjroN3aeA" + } + }, + "references": [ + { + "rel": "observed-effect", + "id": "interval/2", + "resolver": "https://observer.example.org/intervals", + "digest": "sha256:69aab598cc1ea76cce6325742406dc7db49640527d686348a9e73353894dd3f8", + "retention": "P1Y" + } + ], + "signature": "hgIkXMRuWkIzc3DS-da6CfurMNDQtoxLaTSYoeZii3gOTdPUaWG0b3Cr2U-9UckF7ChOxwXWwMXzCyMKAqHABA" +} diff --git a/integrations/probityai-observed-effect/examples/03-observer-and-observed-disagree.json b/integrations/probityai-observed-effect/examples/03-observer-and-observed-disagree.json new file mode 100644 index 0000000..40bfd35 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/03-observer-and-observed-disagree.json @@ -0,0 +1,43 @@ +{ + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1789776010, + "subject": "spiffe://trust.example.org/agent/build-bot", + "model": { + "provider": "example", + "model_id": "example-model" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "enforcement_mode": "enforce" + }, + "data_class": "internal", + "build_provenance": { + "slsa_level": 1, + "digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + }, + "appraisal": { + "status": "none", + "verifier": "https://verifier.example.org" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "ysSfGiHZFwj77FqkuI2D7bc9K0sNHVbPcoDjroN3aeA" + } + }, + "references": [ + { + "rel": "observed-effect", + "id": "interval/2", + "resolver": "https://observer.example.org/intervals", + "digest": "sha256:69aab598cc1ea76cce6325742406dc7db49640527d686348a9e73353894dd3f8", + "retention": "P1Y" + } + ], + "signature": "hgIkXMRuWkIzc3DS-da6CfurMNDQtoxLaTSYoeZii3gOTdPUaWG0b3Cr2U-9UckF7ChOxwXWwMXzCyMKAqHABA" +} diff --git a/integrations/probityai-observed-effect/examples/04-reference-unresolvable.json b/integrations/probityai-observed-effect/examples/04-reference-unresolvable.json new file mode 100644 index 0000000..d17b9a3 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/04-reference-unresolvable.json @@ -0,0 +1,43 @@ +{ + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1789776010, + "subject": "spiffe://trust.example.org/agent/build-bot", + "model": { + "provider": "example", + "model_id": "example-model" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "enforcement_mode": "enforce" + }, + "data_class": "internal", + "build_provenance": { + "slsa_level": 1, + "digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + }, + "appraisal": { + "status": "none", + "verifier": "https://verifier.example.org" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "ysSfGiHZFwj77FqkuI2D7bc9K0sNHVbPcoDjroN3aeA" + } + }, + "references": [ + { + "rel": "observed-effect", + "id": "interval/9", + "resolver": "https://observer.example.org/intervals", + "digest": "sha256:5bff9cfbe71b95959bf6a22101688acb936c00323399089f0cb9b93255fbc874", + "retention": "P1Y" + } + ], + "signature": "JAOx0cH1jIw0KBUrArsmOT-Evlx0dgWQzsJgi-on-0wfGLLqXgSVGWpsg-Z1e53eyHJi2szlp0OeThLrPlFgDQ" +} diff --git a/integrations/probityai-observed-effect/examples/05-observer-key-not-configured.json b/integrations/probityai-observed-effect/examples/05-observer-key-not-configured.json new file mode 100644 index 0000000..2a37d77 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/05-observer-key-not-configured.json @@ -0,0 +1,43 @@ +{ + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1789776010, + "subject": "spiffe://trust.example.org/agent/build-bot", + "model": { + "provider": "example", + "model_id": "example-model" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy": { + "bundle_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "enforcement_mode": "enforce" + }, + "data_class": "internal", + "build_provenance": { + "slsa_level": 1, + "digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + }, + "appraisal": { + "status": "none", + "verifier": "https://verifier.example.org" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "ysSfGiHZFwj77FqkuI2D7bc9K0sNHVbPcoDjroN3aeA" + } + }, + "references": [ + { + "rel": "observed-effect", + "id": "interval/3", + "resolver": "https://observer.example.org/intervals", + "digest": "sha256:dcfe876e7184675bd9877675a6841292c3802d150b8c5550041be2e05cdf8f13", + "retention": "P1Y" + } + ], + "signature": "flpGZJRQ9Hf_EtszJyklnCwhe_bZaqauIbQoO3MRQ4OY6W_SBVdPzvsoGACDLWHQhhJgIDNfCMM5hgdA0Wy4BQ" +} diff --git a/integrations/probityai-observed-effect/examples/README.md b/integrations/probityai-observed-effect/examples/README.md new file mode 100644 index 0000000..e149e20 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/README.md @@ -0,0 +1,98 @@ +# An observed mutation interval as an `observed-effect` reference + +A TRACE Trust Record can point at what an observer outside the agent saw change while +the agent ran: the state before an interval, the state after it, the paths the +observation covered, and the authority change ran under. These fixtures show that +composition. Each record carries one `references` entry with `rel: "observed-effect"`, +and its `digest` is the SHA-256 of the RFC 8785 canonical form of the DSSE envelope as +the resolver retains it. + +The relation is proposed for the TRACE references registry in [trace-spec#403](https://github.com/agentrust-io/trace-spec/pull/403). The v0.2 schema leaves `rel` open, so released `agentrust-trace` verifies these records today. +What a relying party may establish from a resolved reference is bounded to the object: +that the resolved bytes are the cited bytes, and, under an observer key it holds, that +the named observer signed them. Neither reaches the record: under §3.1.2 rule 3 it +verifies the same whether the reference resolves or not, and nothing in the object +becomes attested evidence. An interval in which the observer and the observed party +agree is not attested evidence that the agent's report was true, and one in which they +disagree is not a finding against the record. Cases `01` and `03` are the same record +shape citing an agreeing and a disagreeing interval, and they verify identically. + +## Where the objects come from + +The two statements in [`source/`](source/) are copied byte for byte from the published +observed-effect conformance corpus, [`vectors-observed-effect/` at `b916489`](https://github.com/probityai/agent-evidence-vectors/tree/b91648940b2042ff1cc34d11ed2ac1d97b3e42d4/vectors-observed-effect), where each is a member that corpus's own verifier +accepts: `v1c6fdd82db5229e4` is an authoritative interval in which both sides agree, and +`v620e7755ba36aa0a` is one in which they disagree on the write count and the after-state +root. A member's identifier is the first 16 hex characters of SHA-256 over its file, so +a copy that drifted fails the generator. The corpus publishes the observer's public key +in its manifest, and that key is the one this relying party holds. + +Everything else derives from one published seed through +[`gen_observed_effect_vectors.py`](gen_observed_effect_vectors.py): the Trust Record +producer key, a second observer key that re-signs the agreeing statement for case `05`, +and the altered store. The set regenerates byte for byte. +[`tests/test_observed_effect.py`](../tests/test_observed_effect.py) +recomputes every verdict from the committed bytes rather than reading it from +`expected.json`, and re-runs the generator against the committed files. + +## The referenced object + +`effect-store.json` holds three DSSE envelopes by identifier. Each one carries an in-toto +Statement whose `predicateType` is +`https://probityai.github.io/agent-evidence-vectors/predicate/v1/observed-effect`: + +| Member | What it is | +|---|---| +| `predicate.interval` | `beforeRoot`, `afterRoot`, `openedAt`, `sealedAt` | +| `predicate.pathScope`, `predicate.observation.coverage` | The paths observed, and whether any part of them went unseen | +| `predicate.authorityDigest` | The digest of the grant under which change was permitted | +| `predicate.dualValues` | Facts both sides report, with both values and whether they agree | +| `subject[0].digest.sha256` | Equal to `afterRoot` | +| `signatures[0]` | Ed25519 over the DSSE pre-authentication encoding, under the observer's `keyid` | + +This example checks the envelope, not the predicate's own rules. Those belong to the +predicate's conformance corpus, which is where both source statements come from. + +## Fixture cases + +Expected results are machine-readable in [`expected.json`](expected.json). Every record +verifies as a TRACE record; what differs is what the reference resolves to. Resolution, +digest match and signature verification are three separate findings, each reported in +its own column and its own field. The verdict names the state of the referenced +observation, not the effect it reports: a verified observation does not establish that +the reported change occurred, and a digest mismatch does not establish that it did not. + +| Record | Store | Reference | Digest | Observer key held | Envelope signature | Dual values | Verdict | +|---|---|---|---|---|---|---|---| +| `01-observation-verified.json` | `effect-store.json` | resolves | matches | yes | verifies | agree | observation verified | +| `02-observation-altered-after-issue.json` | `effect-store-altered.json` | resolves | **differs** | yes | **fails** | agree | observation digest mismatch | +| `03-observer-and-observed-disagree.json` | `effect-store.json` | resolves | matches | yes | verifies | **disagree** | observation verified | +| `04-reference-unresolvable.json` | `effect-store.json` | **no such entry** | n/a | n/a | n/a | n/a | observation unresolved | +| `05-observer-key-not-configured.json` | `effect-store.json` | resolves | matches | **no** | not checked | agree | observation unverified | + +`02` is `interval/2`, issued with two disagreeing rows, rewritten in the stored copy so +that both rows agree with the observed party's report, with the signature left as +issued. Both checks catch it independently: the record's digest no longer matches, and +the observer's signature no longer verifies over the rewritten payload. Both findings +are about the stored copy; neither says anything about whether the interval's change +happened. + +`03` cites the same `interval/2` from the unaltered store. The observer and the observed +party disagree, the record verifies exactly as `01` does, and the relying party reports +the disagreement without promoting it in either direction. + +`04` is what §3.1.2 rule 3 requires: a verifier must not reject a record because a +reference cannot be resolved. The record verifies, and the observation it points at is +reported as unresolved, which is a different answer from "nothing changed". + +`05` cites the agreeing statement re-signed by an observer whose key this relying party +does not hold. The rule §3.3.2 gives receipts applies: unverified, not invalid. + +## Running the checks + +``` +python -m pytest integrations/probityai-observed-effect/tests +``` + +To regenerate, run the generator with no arguments. It is deterministic, so the +committed files only change when the generator or a source statement does. diff --git a/integrations/probityai-observed-effect/examples/effect-store-altered.json b/integrations/probityai-observed-effect/examples/effect-store-altered.json new file mode 100644 index 0000000..7ce1a16 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/effect-store-altered.json @@ -0,0 +1,35 @@ +{ + "resolver": "https://observer.example.org/intervals", + "effects": { + "interval/1": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjIiLCJyZXBvcnRlZFZhbHVlIjoiMiJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwMSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiM2NiMDJjODk3ZWViN2VhY2JiNTJmYzdmOTJkODcwZTk2NzgxYTRjN2VjNmMzYTljMDNhMGNmN2VlZDQ2YzQzMyIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiMmQxNmFmOGU2ZWZjY2RiYjFkOWFjZjA3MTAzNWVhNWUyNDJhY2JiMTY2NjNhM2QwNTI2MWEwNmNmOTkyZjljZGFhY2IyM2UzM2I5MDQ0ZjFiNGUyZWMyNDQ0ZGY4ZjQzNTI0MDI1MWUwNmNiMmE3MTI2ZWRhNGU4ZjJkZGI3MDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDEifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "f3cb9b6750737ef6789a72b71d8305f2", + "sig": "rW8A8so/7gwe3AcKwNkq1qSxKLjNETKKhOdJIyUSvo0pJrd8VGhtiI3AT6pWV+3yz4FL7Z6zvbqbHQp2gC6OAQ==" + } + ] + }, + "interval/2": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjEiLCJyZXBvcnRlZFZhbHVlIjoiMSJ9LHsiYWdyZWVtZW50IjoiYWdyZWUiLCJmYWN0IjoiaW50ZXJ2YWwuYWZ0ZXJSb290Iiwib2JzZXJ2ZWRWYWx1ZSI6ImM0MDkxOWNkYjhlNjZjOTM1ZDM5MDczNTI0MzZiOWU2ZmU3OTYyNzg5NTVhZDY2Y2JmYzFkOTVmN2FmOTk2ZjEiLCJyZXBvcnRlZFZhbHVlIjoiYzQwOTE5Y2RiOGU2NmM5MzVkMzkwNzM1MjQzNmI5ZTZmZTc5NjI3ODk1NWFkNjZjYmZjMWQ5NWY3YWY5OTZmMSJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwNSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiYmM5OTc5OGFmYmYxYTRhY2Y2MGUyOGM4OTEzYmQ0NjJlNTU1ODk5YzAyZGJkMTBiZGVkYWQzMzViZjRjYzBjYiIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiNzI4ZmJhN2ViZjJkY2RlMzVhNTk2YWEzOGQ3NTZiNjIwM2Y2ZDIxM2JkYThmMzM3NzUyYzRkZTBmYjdhNTZkM2I2ZDQ2ZDlkOTJhMTVlMzY3YzNhMDk1MTczNGVhZjc4MDYzYTNiN2U5OTc5OGE2ODIyMjI3NGM0NWZlMTdkMDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDUifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "f3cb9b6750737ef6789a72b71d8305f2", + "sig": "4MbIcwmjIjbpak6tDi+F1mlfLu8AV28k0Zg6+x/C8tXax2emMFWuEX8uQ0sXISgiW2UQn9DyxecijLsgRcXrDA==" + } + ] + }, + "interval/3": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjIiLCJyZXBvcnRlZFZhbHVlIjoiMiJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwMSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiM2NiMDJjODk3ZWViN2VhY2JiNTJmYzdmOTJkODcwZTk2NzgxYTRjN2VjNmMzYTljMDNhMGNmN2VlZDQ2YzQzMyIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiMmQxNmFmOGU2ZWZjY2RiYjFkOWFjZjA3MTAzNWVhNWUyNDJhY2JiMTY2NjNhM2QwNTI2MWEwNmNmOTkyZjljZGFhY2IyM2UzM2I5MDQ0ZjFiNGUyZWMyNDQ0ZGY4ZjQzNTI0MDI1MWUwNmNiMmE3MTI2ZWRhNGU4ZjJkZGI3MDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDEifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "0f345495d0bdf85dcfda52b921aa265d", + "sig": "bkfApm1Zu5VvVdiJn2uMfiyJ/gfmdpnkHgmxPtI2j3kSrw+Ck5XVRHji4/Kc0vBdkldxuvwnMXPFBX9YpfvACQ==" + } + ] + } + } +} diff --git a/integrations/probityai-observed-effect/examples/effect-store.json b/integrations/probityai-observed-effect/examples/effect-store.json new file mode 100644 index 0000000..319dba9 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/effect-store.json @@ -0,0 +1,35 @@ +{ + "resolver": "https://observer.example.org/intervals", + "effects": { + "interval/1": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjIiLCJyZXBvcnRlZFZhbHVlIjoiMiJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwMSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiM2NiMDJjODk3ZWViN2VhY2JiNTJmYzdmOTJkODcwZTk2NzgxYTRjN2VjNmMzYTljMDNhMGNmN2VlZDQ2YzQzMyIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiMmQxNmFmOGU2ZWZjY2RiYjFkOWFjZjA3MTAzNWVhNWUyNDJhY2JiMTY2NjNhM2QwNTI2MWEwNmNmOTkyZjljZGFhY2IyM2UzM2I5MDQ0ZjFiNGUyZWMyNDQ0ZGY4ZjQzNTI0MDI1MWUwNmNiMmE3MTI2ZWRhNGU4ZjJkZGI3MDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDEifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "f3cb9b6750737ef6789a72b71d8305f2", + "sig": "rW8A8so/7gwe3AcKwNkq1qSxKLjNETKKhOdJIyUSvo0pJrd8VGhtiI3AT6pWV+3yz4FL7Z6zvbqbHQp2gC6OAQ==" + } + ] + }, + "interval/2": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiZGlzYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjIiLCJyZXBvcnRlZFZhbHVlIjoiMSJ9LHsiYWdyZWVtZW50IjoiZGlzYWdyZWUiLCJmYWN0IjoiaW50ZXJ2YWwuYWZ0ZXJSb290Iiwib2JzZXJ2ZWRWYWx1ZSI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJyZXBvcnRlZFZhbHVlIjoiYzQwOTE5Y2RiOGU2NmM5MzVkMzkwNzM1MjQzNmI5ZTZmZTc5NjI3ODk1NWFkNjZjYmZjMWQ5NWY3YWY5OTZmMSJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwNSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiYmM5OTc5OGFmYmYxYTRhY2Y2MGUyOGM4OTEzYmQ0NjJlNTU1ODk5YzAyZGJkMTBiZGVkYWQzMzViZjRjYzBjYiIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiNzI4ZmJhN2ViZjJkY2RlMzVhNTk2YWEzOGQ3NTZiNjIwM2Y2ZDIxM2JkYThmMzM3NzUyYzRkZTBmYjdhNTZkM2I2ZDQ2ZDlkOTJhMTVlMzY3YzNhMDk1MTczNGVhZjc4MDYzYTNiN2U5OTc5OGE2ODIyMjI3NGM0NWZlMTdkMDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDUifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "f3cb9b6750737ef6789a72b71d8305f2", + "sig": "4MbIcwmjIjbpak6tDi+F1mlfLu8AV28k0Zg6+x/C8tXax2emMFWuEX8uQ0sXISgiW2UQn9DyxecijLsgRcXrDA==" + } + ] + }, + "interval/3": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjIiLCJyZXBvcnRlZFZhbHVlIjoiMiJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwMSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiM2NiMDJjODk3ZWViN2VhY2JiNTJmYzdmOTJkODcwZTk2NzgxYTRjN2VjNmMzYTljMDNhMGNmN2VlZDQ2YzQzMyIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiMmQxNmFmOGU2ZWZjY2RiYjFkOWFjZjA3MTAzNWVhNWUyNDJhY2JiMTY2NjNhM2QwNTI2MWEwNmNmOTkyZjljZGFhY2IyM2UzM2I5MDQ0ZjFiNGUyZWMyNDQ0ZGY4ZjQzNTI0MDI1MWUwNmNiMmE3MTI2ZWRhNGU4ZjJkZGI3MDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDEifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "0f345495d0bdf85dcfda52b921aa265d", + "sig": "bkfApm1Zu5VvVdiJn2uMfiyJ/gfmdpnkHgmxPtI2j3kSrw+Ck5XVRHji4/Kc0vBdkldxuvwnMXPFBX9YpfvACQ==" + } + ] + } + } +} diff --git a/integrations/probityai-observed-effect/examples/expected.json b/integrations/probityai-observed-effect/examples/expected.json new file mode 100644 index 0000000..661512c --- /dev/null +++ b/integrations/probityai-observed-effect/examples/expected.json @@ -0,0 +1,68 @@ +{ + "trace_signer_jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "ysSfGiHZFwj77FqkuI2D7bc9K0sNHVbPcoDjroN3aeA" + }, + "resolver": "https://observer.example.org/intervals", + "predicate_type": "https://probityai.github.io/agent-evidence-vectors/predicate/v1/observed-effect", + "observer_keys": { + "f3cb9b6750737ef6789a72b71d8305f2": { + "kty": "OKP", + "crv": "Ed25519", + "x": "TypZ7cg2fetABHzoPuf1znEaV9k6u9qdHOhYjFajzog" + } + }, + "cases": { + "01-observation-verified.json": { + "store": "effect-store.json", + "trace_record_verifies": true, + "reference_resolves": true, + "digest_matches": true, + "observer_key_configured": true, + "envelope_verifies": true, + "dual_values": "agree", + "verdict": "observation-verified" + }, + "02-observation-altered-after-issue.json": { + "store": "effect-store-altered.json", + "trace_record_verifies": true, + "reference_resolves": true, + "digest_matches": false, + "observer_key_configured": true, + "envelope_verifies": false, + "dual_values": "agree", + "verdict": "observation-digest-mismatch" + }, + "03-observer-and-observed-disagree.json": { + "store": "effect-store.json", + "trace_record_verifies": true, + "reference_resolves": true, + "digest_matches": true, + "observer_key_configured": true, + "envelope_verifies": true, + "dual_values": "disagree", + "verdict": "observation-verified" + }, + "04-reference-unresolvable.json": { + "store": "effect-store.json", + "trace_record_verifies": true, + "reference_resolves": false, + "digest_matches": null, + "observer_key_configured": null, + "envelope_verifies": null, + "dual_values": null, + "verdict": "observation-unresolved" + }, + "05-observer-key-not-configured.json": { + "store": "effect-store.json", + "trace_record_verifies": true, + "reference_resolves": true, + "digest_matches": true, + "observer_key_configured": false, + "envelope_verifies": null, + "dual_values": "agree", + "verdict": "observation-unverified" + } + } +} diff --git a/integrations/probityai-observed-effect/examples/gen_observed_effect_vectors.py b/integrations/probityai-observed-effect/examples/gen_observed_effect_vectors.py new file mode 100644 index 0000000..4019512 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/gen_observed_effect_vectors.py @@ -0,0 +1,216 @@ +"""Generate examples/observed-effect: five signed Trust Records, each citing an +`observed-effect` reference, against an effect store and an altered copy of it. + +The two statements in `source/` are copied byte for byte from the published +observed-effect conformance corpus, so the referenced objects here are ones a second +implementation already resolves and verifies. Everything this script adds (the Trust +Record producer key, the second observer key, the store and the altered store) derives +from one published seed, so the set regenerates byte for byte and +tests/test_observed_effect_fixtures.py holds the committed files to this script. +Nothing here is a production record. + +Usage: python integrations/probityai-observed-effect/examples/gen_observed_effect_vectors.py [--out DIR] +""" + +from __future__ import annotations + +import argparse +import base64 +import copy +import hashlib +import json +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +import rfc8785 +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat + +from agentrust_trace import key_to_jwk, sign_record + +SEED = b"trace-spec examples/observed-effect 2026-09-23" +HERE = Path(__file__).resolve().parent + +PREDICATE_TYPE = "https://probityai.github.io/agent-evidence-vectors/predicate/v1/observed-effect" +PAYLOAD_TYPE = "application/vnd.in-toto+json" +RESOLVER = "https://observer.example.org/intervals" +IAT = int(datetime(2026, 9, 19, 0, 0, 10, tzinfo=UTC).timestamp()) + +# The corpus observer key, as its manifest publishes it. The corpus derives it from a +# published seed too, so anyone can rebuild the two source statements. +CORPUS_OBSERVER_PUBLIC = bytes.fromhex( + "4f2a59edc8367deb40047ce83ee7f5ce711a57d93abbda9d1ce8588c56a3ce88" +) +# An authoritative interval in which the observer and the observed party agree. +AGREE = "v1c6fdd82db5229e4" +# The same shape of interval in which they disagree on two facts, which the predicate +# records rather than rejects. +DISAGREE = "v620e7755ba36aa0a" + + +def b64u(raw: bytes) -> str: + return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") + + +def jcs_sha256(value: Any) -> str: + return "sha256:" + hashlib.sha256(rfc8785.dumps(value)).hexdigest() + + +def key(label: str) -> Ed25519PrivateKey: + return Ed25519PrivateKey.from_private_bytes( + hashlib.sha256(SEED + b"|" + label.encode()).digest() + ) + + +def raw_public(k: Ed25519PrivateKey) -> bytes: + return k.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw) + + +def keyid(raw: bytes) -> str: + """The corpus rule: the first 32 hex characters of SHA-256 over the raw public key.""" + return hashlib.sha256(raw).hexdigest()[:32] + + +def okp_jwk(raw: bytes) -> dict[str, str]: + return {"kty": "OKP", "crv": "Ed25519", "x": b64u(raw)} + + +def pae(payload_type: str, payload: bytes) -> bytes: + return b"DSSEv1 %d %s %d %s" % (len(payload_type), payload_type.encode(), len(payload), payload) + + +def source(member: str) -> dict[str, Any]: + raw = (HERE / "source" / f"{member}.json").read_bytes() + # A corpus member's identifier is the first 16 hex characters of SHA-256 over its + # file, so a copy that drifted from the corpus fails here rather than downstream. + assert "v" + hashlib.sha256(raw).hexdigest()[:16] == member, member + return json.loads(raw) + + +def resign(envelope: dict[str, Any], signer: Ed25519PrivateKey) -> dict[str, Any]: + """The same payload bytes under a different observer's signature.""" + payload = base64.b64decode(envelope["payload"]) + sig = signer.sign(pae(envelope["payloadType"], payload)) + return { + "payload": envelope["payload"], + "payloadType": envelope["payloadType"], + "signatures": [{"keyid": keyid(raw_public(signer)), "sig": base64.b64encode(sig).decode()}], + } + + +def erase_disagreement(envelope: dict[str, Any]) -> dict[str, Any]: + """The stored copy as a relying party later finds it: every disagreeing row + rewritten to agree with the observed party's report, signature left as issued.""" + altered = copy.deepcopy(envelope) + statement = json.loads(base64.b64decode(altered["payload"])) + for row in statement["predicate"]["dualValues"]: + row["observedValue"] = row["reportedValue"] + row["agreement"] = "agree" + altered["payload"] = base64.b64encode(rfc8785.dumps(statement)).decode() + return altered + + +def record(producer: Ed25519PrivateKey, reference: dict[str, Any]) -> dict[str, Any]: + unsigned = { + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": IAT, + "subject": "spiffe://trust.example.org/agent/build-bot", + "model": {"provider": "example", "model_id": "example-model"}, + "runtime": {"platform": "software-only", "measurement": "sha256:" + "0" * 64}, + "policy": {"bundle_hash": "sha256:" + "b" * 64, "enforcement_mode": "enforce"}, + "data_class": "internal", + "build_provenance": {"slsa_level": 1, "digest": "sha256:" + "e" * 64}, + "appraisal": {"status": "none", "verifier": "https://verifier.example.org"}, + "cnf": {"jwk": key_to_jwk(producer)}, + "references": [reference], + } + return sign_record(unsigned, producer) + + +def reference(id_: str, digest: str) -> dict[str, Any]: + return { + "rel": "observed-effect", "id": id_, "resolver": RESOLVER, + "digest": digest, "retention": "P1Y", + } + + +def case(store: str, resolves: bool, matches: bool | None, held: bool | None, + verifies: bool | None, agreement: str | None, verdict: str) -> dict[str, Any]: + return { + "store": store, "trace_record_verifies": True, "reference_resolves": resolves, + "digest_matches": matches, "observer_key_configured": held, + "envelope_verifies": verifies, "dual_values": agreement, "verdict": verdict, + } + + +def build() -> dict[str, Any]: + producer, other_observer = key("producer"), key("other-observer") + agree, disagree = source(AGREE), source(DISAGREE) + + store = { + "resolver": RESOLVER, + "effects": { + "interval/1": agree, + "interval/2": disagree, + "interval/3": resign(agree, other_observer), + }, + } + altered = copy.deepcopy(store) + altered["effects"]["interval/2"] = erase_disagreement(disagree) + + cite = lambda id_: reference(id_, jcs_sha256(store["effects"][id_])) # noqa: E731 + unresolvable = "sha256:" + hashlib.sha256(SEED + b"|digest|interval/9").hexdigest() + records = { + "01-observation-verified.json": record(producer, cite("interval/1")), + "02-observation-altered-after-issue.json": record(producer, cite("interval/2")), + "03-observer-and-observed-disagree.json": record(producer, cite("interval/2")), + "04-reference-unresolvable.json": record(producer, reference("interval/9", unresolvable)), + "05-observer-key-not-configured.json": record(producer, cite("interval/3")), + } + + expected = { + "trace_signer_jwk": key_to_jwk(producer), + "resolver": RESOLVER, + "predicate_type": PREDICATE_TYPE, + # The keys this relying party holds, by DSSE keyid. The second observer's key is + # deliberately absent: spec section 3.3.2 says a receipt whose issuer key is + # unknown to the verifier is unverified, not invalid. + "observer_keys": {keyid(CORPUS_OBSERVER_PUBLIC): okp_jwk(CORPUS_OBSERVER_PUBLIC)}, + "cases": { + "01-observation-verified.json": case( + "effect-store.json", True, True, True, True, "agree", "observation-verified"), + "02-observation-altered-after-issue.json": case( + "effect-store-altered.json", True, False, True, False, "agree", + "observation-digest-mismatch"), + "03-observer-and-observed-disagree.json": case( + "effect-store.json", True, True, True, True, "disagree", "observation-verified"), + "04-reference-unresolvable.json": case( + "effect-store.json", False, None, None, None, None, "observation-unresolved"), + "05-observer-key-not-configured.json": case( + "effect-store.json", True, True, False, None, "agree", "observation-unverified"), + }, + } + + return { + **records, + "effect-store.json": store, + "effect-store-altered.json": altered, + "expected.json": expected, + } + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--out", type=Path, default=HERE) + out = parser.parse_args().out + out.mkdir(parents=True, exist_ok=True) + files = build() + for name, value in files.items(): + text = json.dumps(value, indent=2, ensure_ascii=False) + "\n" + (out / name).write_bytes(text.encode("utf-8")) + print(f"{len(files)} files written to {out}") + + +if __name__ == "__main__": + main() diff --git a/integrations/probityai-observed-effect/examples/source/v1c6fdd82db5229e4.json b/integrations/probityai-observed-effect/examples/source/v1c6fdd82db5229e4.json new file mode 100644 index 0000000..daaac2a --- /dev/null +++ b/integrations/probityai-observed-effect/examples/source/v1c6fdd82db5229e4.json @@ -0,0 +1,10 @@ +{ + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjIiLCJyZXBvcnRlZFZhbHVlIjoiMiJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwMSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiM2NiMDJjODk3ZWViN2VhY2JiNTJmYzdmOTJkODcwZTk2NzgxYTRjN2VjNmMzYTljMDNhMGNmN2VlZDQ2YzQzMyIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiMmQxNmFmOGU2ZWZjY2RiYjFkOWFjZjA3MTAzNWVhNWUyNDJhY2JiMTY2NjNhM2QwNTI2MWEwNmNmOTkyZjljZGFhY2IyM2UzM2I5MDQ0ZjFiNGUyZWMyNDQ0ZGY4ZjQzNTI0MDI1MWUwNmNiMmE3MTI2ZWRhNGU4ZjJkZGI3MDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDEifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "f3cb9b6750737ef6789a72b71d8305f2", + "sig": "rW8A8so/7gwe3AcKwNkq1qSxKLjNETKKhOdJIyUSvo0pJrd8VGhtiI3AT6pWV+3yz4FL7Z6zvbqbHQp2gC6OAQ==" + } + ] +} diff --git a/integrations/probityai-observed-effect/examples/source/v620e7755ba36aa0a.json b/integrations/probityai-observed-effect/examples/source/v620e7755ba36aa0a.json new file mode 100644 index 0000000..cbcfae8 --- /dev/null +++ b/integrations/probityai-observed-effect/examples/source/v620e7755ba36aa0a.json @@ -0,0 +1,10 @@ +{ + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiYXV0aG9yaXR5RGlnZXN0IjoiNmQ4ODBjYTc4MmUyZTRkYjU1ZjFlODBjYzU1NGQyOTBiNGY0NjIyMGI3NTI0NWI3ZDFkZThhZmJiMDE3MTlhYiIsImRvZXNOb3RBc3NlcnQiOlsidGhhdCB0aGUgb2JzZXJ2ZWQgcGFydHkgcGVyZm9ybWVkIG5vIGFjdGlvbiBvdXRzaWRlIHBhdGhTY29wZSIsInRoYXQgdGhlIGF1dGhvcml0eSBkb2N1bWVudCBwZXJtaXRzIHdoYXQgdGhlIHdyaXRlcyBkaWQiXSwiZHVhbFZhbHVlcyI6W3siYWdyZWVtZW50IjoiZGlzYWdyZWUiLCJmYWN0Ijoid3JpdGVzLmNvdW50Iiwib2JzZXJ2ZWRWYWx1ZSI6IjIiLCJyZXBvcnRlZFZhbHVlIjoiMSJ9LHsiYWdyZWVtZW50IjoiZGlzYWdyZWUiLCJmYWN0IjoiaW50ZXJ2YWwuYWZ0ZXJSb290Iiwib2JzZXJ2ZWRWYWx1ZSI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJyZXBvcnRlZFZhbHVlIjoiYzQwOTE5Y2RiOGU2NmM5MzVkMzkwNzM1MjQzNmI5ZTZmZTc5NjI3ODk1NWFkNjZjYmZjMWQ5NWY3YWY5OTZmMSJ9XSwiaGFzaEFsZ29yaXRobSI6InNoYTI1NiIsImludGVydmFsIjp7ImFmdGVyUm9vdCI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEiLCJiYXNlUmVzb2x1dGlvbiI6InN1cHBsaWVkIiwiYmVmb3JlUm9vdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEiLCJvcGVuZWRBdCI6IjIwMjYtMDktMTlUMDA6MDA6MDBaIiwic2VhbGVkQXQiOiIyMDI2LTA5LTE5VDAwOjAwOjA0WiJ9LCJpbnRlcnZhbElkIjoiaXYtMDAwNSIsImlzc3VlZEF0IjoiMjAyNi0wOS0xOVQwMDowMDowNVoiLCJtdXRhdGlvbiI6Im9ic2VydmVkIiwib2JzZXJ2YXRpb24iOnsiY292ZXJhZ2UiOnsiZ2FwcyI6W10sInNjb3BlQ29tcGxldGUiOnRydWV9LCJvYnNlcnZlZFNpZ25lcnMiOlsiOTNjNGU1YjY0MGU4M2JhMGVkODdlZmZlOWE4NmYwNWUiXSwib3JpZ2luIjoiZmlyc3QtaGFuZCIsInByaW9yQ29tbWl0bWVudCI6eyJjb21taXRtZW50RGlnZXN0IjoiYmM5OTc5OGFmYmYxYTRhY2Y2MGUyOGM4OTEzYmQ0NjJlNTU1ODk5YzAyZGJkMTBiZGVkYWQzMzViZjRjYzBjYiIsImNvbW1pdHRlZEF0IjoiMjAyNi0wOS0xOFQyMzo1OTo1OFoiLCJrZXlpZCI6ImYzY2I5YjY3NTA3MzdlZjY3ODlhNzJiNzFkODMwNWYyIiwic2lnIjoiNzI4ZmJhN2ViZjJkY2RlMzVhNTk2YWEzOGQ3NTZiNjIwM2Y2ZDIxM2JkYThmMzM3NzUyYzRkZTBmYjdhNTZkM2I2ZDQ2ZDlkOTJhMTVlMzY3YzNhMDk1MTczNGVhZjc4MDYzYTNiN2U5OTc5OGE2ODIyMjI3NGM0NWZlMTdkMDQiLCJ3aXRuZXNzTm9uY2UiOiI2YWEyNGVhYjg5NzM0OGMzNmZiNjMyMTI3MDk1OWFkYmVjOWNjNGViNTUwZDYyMjVkNDU4MzNlYzBkNjNiN2U5In0sInZhbnRhZ2UiOiJiZWxvdy1vYnNlcnZlZCJ9LCJwYXRoU2NvcGUiOlsiL3Nydi9hcHAvIl0sInJlYWRzIjpbeyJibG9iRGlnZXN0IjoiODNjOWY0NjRmOTFkZjQ1NGEwMDJmODkwMTcwY2FjOWYzMTUyYmMzNjgxM2M0NzdiMGVlOGFiYmU3OWEzNjlhYyIsImJ5dGVSYW5nZSI6eyJlbmQiOjY0LCJzdGFydCI6MH0sInBhdGgiOiIvc3J2L2FwcC9jb25maWcueWFtbCIsInByZVN0YXRlRGlnZXN0IjoiZmU4MTYzMzhhZDllZmE3Mjc0YjUwMDk5NGYwMjIxNDk1OGFmNzg4NjZjNTZmYzQwNGYxZTcyMGYyYzg3ZThjMSIsInJhbmdlRGlnZXN0IjoiY2IwZWM2MDQxNTZiNjFlMjZjM2Q4OThhYmUxOTEzZGMwOGZlNTNkNTYzMTU5NTgzZWQ0NWM1YzJhMzU5MjM0NiIsInJlYWRTdGF0ZSI6ImJ5dGVzLXJlYWQifV0sInRpZXIiOiJhdXRob3JpdGF0aXZlIiwid3JpdGVzIjpbeyJpblNjb3BlIjp0cnVlLCJwYXRoIjoiL3Nydi9hcHAvbWFpbi5weSIsInBvc3RTdGF0ZURpZ2VzdCI6IjcwNzRlM2EzNzEyZmE2OTQ5YjQzNThjYTkzNjU1N2U4MWU1ZGUxNDE5MjhkMTcyNWIwZTQ2MDJiMWExYWNiZDMiLCJwcmVTdGF0ZURpZ2VzdCI6ImZlODE2MzM4YWQ5ZWZhNzI3NGI1MDA5OTRmMDIyMTQ5NThhZjc4ODY2YzU2ZmM0MDRmMWU3MjBmMmM4N2U4YzEifSx7ImluU2NvcGUiOnRydWUsInBhdGgiOiIvc3J2L2FwcC9oYW5kbGVyLnB5IiwicG9zdFN0YXRlRGlnZXN0IjoiZTcyYTQ0MWRkNTIxYjVjZDFkNjYxYmUxZjY1NjllMGY0M2I2MTdjMzQ3NjYwMTRjNTgwMGVjZWM0MGNjMDFkMSIsInByZVN0YXRlRGlnZXN0IjoiNzA3NGUzYTM3MTJmYTY5NDliNDM1OGNhOTM2NTU3ZTgxZTVkZTE0MTkyOGQxNzI1YjBlNDYwMmIxYTFhY2JkMyJ9XX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Byb2JpdHlhaS5naXRodWIuaW8vYWdlbnQtZXZpZGVuY2UtdmVjdG9ycy9wcmVkaWNhdGUvdjEvb2JzZXJ2ZWQtZWZmZWN0Iiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6ImU3MmE0NDFkZDUyMWI1Y2QxZDY2MWJlMWY2NTY5ZTBmNDNiNjE3YzM0NzY2MDE0YzU4MDBlY2VjNDBjYzAxZDEifSwibmFtZSI6Iml2LTAwMDUifV19", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "keyid": "f3cb9b6750737ef6789a72b71d8305f2", + "sig": "4MbIcwmjIjbpak6tDi+F1mlfLu8AV28k0Zg6+x/C8tXax2emMFWuEX8uQ0sXISgiW2UQn9DyxecijLsgRcXrDA==" + } + ] +} diff --git a/integrations/probityai-observed-effect/integration.yaml b/integrations/probityai-observed-effect/integration.yaml new file mode 100644 index 0000000..dc37e6d --- /dev/null +++ b/integrations/probityai-observed-effect/integration.yaml @@ -0,0 +1,25 @@ +name: Observed-effect references +vendor: probityai +integrates_with: + - trace +description: >- + TRACE Trust Records citing an observer-signed in-toto statement of what changed + while the agent ran, checked for digest match and observer signature. +maintainer: + github: astrogilda +repository: https://github.com/probityai/agent-evidence-vectors +license: Apache-2.0 +tier: community +marketplace: + category: Evidence & receipts + mark: OE + keywords: [observed-effect, references, in-toto, dsse, conformance] +# Role. The referenced objects are another party's signed evidence: in-toto +# statements an observer outside the agent signed, copied byte for byte from the +# published vectors-observed-effect corpus. The five Trust Records that cite them +# are fixtures signed by a demo key derived from a published seed, so this +# integration issues no Trust Record of its own and declares no conformance level. +trace_roles: + - external-evidence-source +tested_against: + agentrust-trace: "0.11.0" diff --git a/integrations/probityai-observed-effect/pyproject.toml b/integrations/probityai-observed-effect/pyproject.toml new file mode 100644 index 0000000..116885c --- /dev/null +++ b/integrations/probityai-observed-effect/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "probityai-observed-effect-integration" +version = "0.1.0" +description = "TRACE Trust Records citing observer-signed in-toto statements through an observed-effect reference" +requires-python = ">=3.11" +license = "Apache-2.0" +dependencies = [ + # 0.11.0 verifies a record carrying a references entry with an open rel value. + "agentrust-trace>=0.11.0", + "rfc8785", + "cryptography", +] + +[project.optional-dependencies] +test = ["pytest"] + +[tool.setuptools] +py-modules = [] diff --git a/integrations/probityai-observed-effect/tests/test_observed_effect.py b/integrations/probityai-observed-effect/tests/test_observed_effect.py new file mode 100644 index 0000000..adcef08 --- /dev/null +++ b/integrations/probityai-observed-effect/tests/test_observed_effect.py @@ -0,0 +1,231 @@ +"""An observed mutation interval as a TRACE `observed-effect` reference. + +Re-verifies examples/ from the committed bytes. The generator wrote +expected.json alongside the vectors, and a generator's own summary of its output is a +claim, so every outcome here is recomputed with this repository's dependencies and +compared with expected.json, never read from it. The generator is deterministic, and +the last test re-runs it against the committed files. +""" + +from __future__ import annotations + +import base64 +import copy +import hashlib +import json +import subprocess +import sys +from pathlib import Path + +import pytest +import rfc8785 +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + +from agentrust_trace import verify_record + +DIR = Path(__file__).resolve().parents[1] / "examples" +GENERATOR = DIR / "gen_observed_effect_vectors.py" +EXPECTED = json.loads((DIR / "expected.json").read_text(encoding="utf-8")) +CASES = sorted(EXPECTED["cases"]) +OUTCOME_KEYS = ( + "reference_resolves", "digest_matches", "observer_key_configured", + "envelope_verifies", "dual_values", "verdict", +) + + +def _load(name: str) -> dict: + return json.loads((DIR / name).read_text(encoding="utf-8")) + + +def _jcs_sha256(obj: object) -> str: + return "sha256:" + hashlib.sha256(rfc8785.dumps(obj)).hexdigest() + + +def _b64u_decode(text: str) -> bytes: + return base64.urlsafe_b64decode(text + "=" * (-len(text) % 4)) + + +def _pae(payload_type: str, payload: bytes) -> bytes: + return b"DSSEv1 %d %s %d %s" % (len(payload_type), payload_type.encode(), len(payload), payload) + + +def _statement(envelope: dict) -> dict: + return json.loads(base64.b64decode(envelope["payload"])) + + +def _envelope_verifies(envelope: dict, jwk: dict) -> bool: + """The object's own check: Ed25519 over the DSSE pre-authentication encoding.""" + (signature,) = envelope["signatures"] + message = _pae(envelope["payloadType"], base64.b64decode(envelope["payload"])) + try: + Ed25519PublicKey.from_public_bytes(_b64u_decode(jwk["x"])).verify( + base64.b64decode(signature["sig"]), message + ) + except InvalidSignature: + return False + return True + + +def _dual_values(envelope: dict) -> str: + rows = _statement(envelope)["predicate"].get("dualValues", []) + return "disagree" if any(r["agreement"] == "disagree" for r in rows) else "agree" + + +def _assess(record: dict, store: dict, observer_keys: dict) -> dict: + """What a relying party concludes about the object a record points at. + + Three separable findings: whether the reference resolves, whether the resolved bytes + are the cited bytes, and whether the envelope verifies under its observer's key when + the relying party holds that key. Each is reported in its own field. The verdict + names the state of the referenced observation, never the effect it reports: a + verified observation does not establish that the change occurred, and a digest + mismatch does not establish that it did not. What the statement reports is carried + as it states it and decides nothing here: section 3.1.2 rule 3 makes identity the + ceiling. + """ + (reference,) = record["references"] + assert reference["rel"] == "observed-effect" + assert reference["resolver"] == store["resolver"] == EXPECTED["resolver"] + envelope = store["effects"].get(reference["id"]) + if envelope is None: + return {"reference_resolves": False, "digest_matches": None, + "observer_key_configured": None, "envelope_verifies": None, + "dual_values": None, "verdict": "observation-unresolved"} + digest_matches = _jcs_sha256(envelope) == reference["digest"] + (signature,) = envelope["signatures"] + jwk = observer_keys.get(signature["keyid"]) + configured = jwk is not None + verifies = _envelope_verifies(envelope, jwk) if configured else None + if not digest_matches: + verdict = "observation-digest-mismatch" + elif not configured: + verdict = "observation-unverified" + elif not verifies: + verdict = "observation-signature-invalid" + else: + verdict = "observation-verified" + return {"reference_resolves": True, "digest_matches": digest_matches, + "observer_key_configured": configured, "envelope_verifies": verifies, + "dual_values": _dual_values(envelope), "verdict": verdict} + + +def test_the_committed_records_are_exactly_the_declared_cases() -> None: + committed = {p.name for p in DIR.glob("0*.json")} + assert committed == set(CASES) + for case in EXPECTED["cases"].values(): + assert (DIR / case["store"]).is_file() + + +@pytest.mark.parametrize("name", CASES) +def test_every_case_verifies_as_a_trust_record(name: str) -> None: + # Section 3.1.2 rule 3: what a reference resolves to never decides whether the + # record verifies, so the mismatched, unverified and unresolved cases verify too. + verify_record(_load(name), EXPECTED["trace_signer_jwk"], max_age_seconds=None) + assert EXPECTED["cases"][name]["trace_record_verifies"] is True + + +@pytest.mark.parametrize("name", CASES) +def test_the_outcome_is_recomputed_from_the_committed_bytes(name: str) -> None: + case = EXPECTED["cases"][name] + observed = _assess(_load(name), _load(case["store"]), EXPECTED["observer_keys"]) + assert observed == {k: case[k] for k in OUTCOME_KEYS} + + +def test_every_resolved_object_carries_the_registered_predicate_type() -> None: + for store in ("effect-store.json", "effect-store-altered.json"): + for envelope in _load(store)["effects"].values(): + assert envelope["payloadType"] == "application/vnd.in-toto+json" + assert _statement(envelope)["predicateType"] == EXPECTED["predicate_type"] + + +def test_the_subject_digest_is_the_interval_after_root() -> None: + """The statement binds its single subject to the state the interval ended in, so + the reference identifies a state change, not only a document about one.""" + for envelope in _load("effect-store.json")["effects"].values(): + statement = _statement(envelope) + (subject,) = statement["subject"] + assert subject["digest"]["sha256"] == statement["predicate"]["interval"]["afterRoot"] + + +def test_an_agreement_and_a_disagreement_verify_identically() -> None: + """What the statement reports never reaches validity. `01` cites an interval where + the observer and the observed party agree and `03` one where they disagree; the two + records differ only in the reference they carry, both verify, and the relying + party's assessment differs only in what it reports.""" + store = _load("effect-store.json") + agree = _assess(_load("01-observation-verified.json"), store, EXPECTED["observer_keys"]) + disagree = _assess( + _load("03-observer-and-observed-disagree.json"), store, EXPECTED["observer_keys"] + ) + assert (agree["dual_values"], disagree["dual_values"]) == ("agree", "disagree") + assert {k: v for k, v in agree.items() if k != "dual_values"} == \ + {k: v for k, v in disagree.items() if k != "dual_values"} + strip = lambda r: { # noqa: E731 + k: v for k, v in r.items() if k not in ("references", "signature") + } + assert strip(_load("01-observation-verified.json")) == \ + strip(_load("03-observer-and-observed-disagree.json")) + + +def test_a_bad_signature_over_matching_bytes_is_its_own_verdict() -> None: + """A corrupted observer signature, with the reference digest recomputed over the + corrupted envelope, matches on digest and fails on signature. The verdict says so and + does not fold it into a digest mismatch.""" + store = copy.deepcopy(_load("effect-store.json")) + envelope = store["effects"]["interval/1"] + (signature,) = envelope["signatures"] + raw = base64.b64decode(signature["sig"]) + signature["sig"] = base64.b64encode(bytes([raw[0] ^ 1]) + raw[1:]).decode() + record = copy.deepcopy(_load("01-observation-verified.json")) + record["references"][0]["digest"] = _jcs_sha256(envelope) + observed = _assess(record, store, EXPECTED["observer_keys"]) + assert observed["digest_matches"] is True + assert observed["envelope_verifies"] is False + assert observed["verdict"] == "observation-signature-invalid" + + +def test_the_record_signature_covers_the_reference() -> None: + record = copy.deepcopy(_load("01-observation-verified.json")) + digest = record["references"][0]["digest"] + record["references"][0]["digest"] = digest[:-1] + ("0" if digest[-1] != "0" else "1") + with pytest.raises(InvalidSignature): + verify_record(record, EXPECTED["trace_signer_jwk"], max_age_seconds=None) + + +def test_every_observer_key_id_is_derived_from_the_key_it_names() -> None: + for kid, jwk in EXPECTED["observer_keys"].items(): + assert kid == hashlib.sha256(_b64u_decode(jwk["x"])).hexdigest()[:32] + held = set(EXPECTED["observer_keys"]) + named = { + envelope["signatures"][0]["keyid"] + for envelope in _load("effect-store.json")["effects"].values() + } + assert named - held, "no case exercises an observer whose key the relying party does not hold" + + +def test_the_altered_store_differs_in_one_payload_only() -> None: + original, altered = _load("effect-store.json"), _load("effect-store-altered.json") + assert original["resolver"] == altered["resolver"] + assert set(original["effects"]) == set(altered["effects"]) + changed = [id_ for id_ in original["effects"] + if original["effects"][id_] != altered["effects"][id_]] + assert changed == ["interval/2"] + before, after = original["effects"]["interval/2"], altered["effects"]["interval/2"] + assert before["signatures"] == after["signatures"] + assert (_dual_values(before), _dual_values(after)) == ("disagree", "agree") + + +def test_the_source_statements_are_the_corpus_members_they_name() -> None: + for path in (DIR / "source").glob("*.json"): + assert "v" + hashlib.sha256(path.read_bytes()).hexdigest()[:16] == path.stem + + +def test_the_generator_reproduces_the_committed_bytes(tmp_path: Path) -> None: + subprocess.run([sys.executable, str(GENERATOR), "--out", str(tmp_path)], check=True, + capture_output=True) + produced = {p.name for p in tmp_path.iterdir()} + committed = {p.name for p in DIR.iterdir() if p.suffix == ".json"} + assert produced == committed + for name in committed: + assert (tmp_path / name).read_bytes() == (DIR / name).read_bytes(), name diff --git a/marketplace/catalog.json b/marketplace/catalog.json index 2d5b540..1ebd60d 100644 --- a/marketplace/catalog.json +++ b/marketplace/catalog.json @@ -1,6 +1,6 @@ { "catalog_version": 1, - "count": 41, + "count": 42, "integrations": [ { "name": "Claude Code", @@ -591,6 +591,30 @@ "confidential-computing" ] }, + { + "name": "Observed-effect references", + "package_name": "Observed-effect references", + "vendor": "probityai", + "description": "TRACE Trust Records citing an observer-signed in-toto statement of what changed while the agent ran, checked for digest match and observer signature.", + "path": "integrations/probityai-observed-effect", + "url": "https://github.com/agentrust-io/integrations/tree/main/integrations/probityai-observed-effect", + "homepage": null, + "repository": "https://github.com/probityai/agent-evidence-vectors", + "tier": "community", + "stack": [ + "TRACE" + ], + "category": "Evidence & receipts", + "mark": "OE", + "featured": null, + "keywords": [ + "observed-effect", + "references", + "in-toto", + "dsse", + "conformance" + ] + }, { "name": "OntoGuard Decision Authorization", "package_name": "OntoGuard Decision Authorization", From b9daa44cc07f71dcbf107864c35a7eea24f94303 Mon Sep 17 00:00:00 2001 From: Sankalp Gilda Date: Fri, 2 Oct 2026 08:05:50 -0400 Subject: [PATCH 2/2] Run the corpus replay under Python 3.13 on every matrix leg Signed-off-by: Sankalp Gilda --- .../probityai-observed-effect-conformance.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/probityai-observed-effect-conformance.yml b/.github/workflows/probityai-observed-effect-conformance.yml index 4417f49..c4c451f 100644 --- a/.github/workflows/probityai-observed-effect-conformance.yml +++ b/.github/workflows/probityai-observed-effect-conformance.yml @@ -50,7 +50,15 @@ jobs: run: pip install -e "integrations/probityai-observed-effect[test]" - name: Integration tests run: pytest integrations/probityai-observed-effect/tests -q + # The corpus runner requires Python 3.13 or later, so it runs in its own + # interpreter and the replay happens on every matrix leg. + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + id: replay-python + with: + python-version: "3.13" + update-environment: false - name: Replay the published observed-effect corpus run: | - pip install "agent-evidence-vectors==0.15.0" - agent-evidence-vectors --corpus vectors-observed-effect + "${{ steps.replay-python.outputs.python-path }}" -m venv "$RUNNER_TEMP/replay" + "$RUNNER_TEMP/replay/bin/pip" install "agent-evidence-vectors==0.15.0" + "$RUNNER_TEMP/replay/bin/agent-evidence-vectors" --corpus vectors-observed-effect