diff --git a/interop/composition-test/README.md b/interop/composition-test/README.md index 1667266..fb52788 100644 --- a/interop/composition-test/README.md +++ b/interop/composition-test/README.md @@ -1,8 +1,8 @@ -# Composition Test: ScopeBlind + APS +# Composition Test: ASM + ScopeBlind + APS -**One tool call. Two governance evaluations. One verifier.** +**One service choice. One tool call. Two governance evaluations. One verifier.** -This test demonstrates the composition model for multi-engine agent governance: a single CrewAI tool call (`execute_api_call`) is independently evaluated by two different governance engines (ScopeBlind Cedar policy + APS delegation scope), both producing Ed25519-signed receipts in the IETF draft envelope format, both verifiable by the same offline tool. +This test demonstrates the composition model for service selection and multi-engine agent governance. ASM records why the fixture chooses one eligible API route before the call. A single CrewAI tool call (`execute_api_call`) is then independently evaluated by two different governance engines (ScopeBlind Cedar policy + APS delegation scope), both producing Ed25519-signed receipts in the IETF draft envelope format and both verifiable by the same offline tool. ## Scenario @@ -11,6 +11,11 @@ A CrewAI research agent calls `execute_api_call`: - **Spend:** $0.50 - **Model:** `anthropic/claude-sonnet-4-20250514` +### ASM selection (pre-call) +- Checks: cloud invocability, required chat/API functions, fixture price, and approval boundary +- Produces: an unsigned Selection Receipt that pins both candidate manifests by digest +- Authority: explains the choice only; it is not an authorization, execution, or payment receipt + ### APS evaluation (delegation + scope) - Checks: delegation scope includes `tools:api_call`, spend within $500 budget - Produces: delegation receipt with `extensions.aps` @@ -20,12 +25,13 @@ A CrewAI research agent calls `execute_api_call`: - Produces: policy evaluation receipt + execution receipt with `extensions.scopeblind` ### Correlation -Both governance evaluations reference the same `action_ref` -- a SHA-256 hash of the canonical tool invocation (`agent_id + tool_name + args`). A verifier links the evaluations by this hash without needing to understand either engine's internals. +Both governance evaluations reference the same `action_ref` -- a SHA-256 hash of the canonical tool invocation (`agent_id + tool_name + args`). ScopeBlind's signed policy and execution receipts also carry `extensions.asm.selection_receipt`, which references the exact pre-call Selection Receipt digest. A verifier can therefore link **why this route** to **was this call allowed** and **what executed** without making any receipt authoritative for another event. ## Receipts | File | Engine | Type | Chain | |------|--------|------|-------| +| `asm-selection-receipt.json` | ASM | Pre-call selection explanation (unsigned v0.1) | Pins the two fixture manifests | | `scopeblind-policy-eval.json` | ScopeBlind | Cedar policy evaluation | First in chain | | `scopeblind-execution.json` | ScopeBlind | Tool execution result | Links to policy eval | @@ -47,6 +53,16 @@ npx @veritasacta/verify@0.2.5 ../aps-test-vectors/receipt-commerce.json --key $A All four should return exit code 0 (VALID). +Validate the two scenario-local selection manifests independently: + +```bash +python -m pip install "asm-protocol==0.5.2" +asm-lint asm-openrouter-manifest.json --as-of 2026-08-17 --fail-on not-ready +asm-lint asm-direct-manifest.json --as-of 2026-08-17 --fail-on not-ready +``` + +These manifests deliberately describe fixed fixture values, not current provider pricing. + ## Verify all at once ```bash @@ -57,11 +73,13 @@ bash verify-all.sh 1. **Format convergence.** Two independent implementations (APS ProxyGateway + ScopeBlind protect-mcp) produce receipts that verify against the same tool, without coordination on the verification path. -2. **Extension isolation.** `extensions.scopeblind` carries Cedar policy results. `extensions.aps` carries delegation chains and spend tracking. Both are covered by the envelope signature (tamper-evident) but opaque to the other engine's verifier. +2. **Event authority stays separate.** The unsigned ASM receipt explains provider selection. ScopeBlind and APS remain authoritative for their own signed policy and execution events. Referencing the Selection Receipt digest does not turn ASM into an authorization or settlement proof. + +3. **Extension isolation.** `extensions.asm` carries only a Selection Receipt reference, `extensions.scopeblind` carries Cedar results, and `extensions.aps` carries delegation chains and spend tracking. Signed extensions are tamper-evident but remain semantically owned by their issuers. -3. **Composable governance.** An agent can be simultaneously governed by APS (delegation scope, spend limits) and ScopeBlind (Cedar policy, rate limits) without either system needing to know about the other. The `action_ref` is the only shared anchor. +4. **Composable governance.** An agent can be simultaneously selected by a local ASM policy and governed by APS and ScopeBlind without any engine reproducing another engine's facts. -4. **IETF draft as interop baseline.** Both systems reference `draft-farley-acta-signed-receipts-01`. The draft defines the envelope; extensions carry engine-specific data. +5. **IETF draft as interop baseline.** The governance systems reference `draft-farley-acta-signed-receipts-01`. The draft defines their signed envelope; the ASM Selection Receipt remains a separate, digest-linked pre-call artifact. ## Generate fresh receipts @@ -69,7 +87,7 @@ bash verify-all.sh node generate-receipts.mjs ``` -This generates new Ed25519 keys and fresh receipts for the scenario. The `action_ref` is deterministic for the same tool invocation. +This generates new Ed25519 keys and fresh governance receipts for the scenario. The `action_ref` and Selection Receipt digest are deterministic while their input files remain unchanged. ## Cedar policy used @@ -95,3 +113,4 @@ Policy digest included in every ScopeBlind receipt. If the policy changes, the d - [@veritasacta/verify](https://npmjs.com/package/@veritasacta/verify) (Apache-2.0) - [protect-mcp](https://npmjs.com/package/protect-mcp) (MIT) - [agent-passport-system](https://npmjs.com/package/agent-passport-system) +- [ASM Selection Receipt v0.1](https://github.com/YE-YI7/asm-spec/blob/main/docs/specs/selection-receipt.md) diff --git a/interop/composition-test/action-ref.txt b/interop/composition-test/action-ref.txt index 8b60790..279d973 100644 --- a/interop/composition-test/action-ref.txt +++ b/interop/composition-test/action-ref.txt @@ -1 +1 @@ -sha256:0985753552dfd9ca60c978d00309c844c512ab0ae20062f9bebeac826f00bbdf +31789af0e4b00d6c3f686ee56b5b361ba4f62055a37d52f6758d671d8ff60139 diff --git a/interop/composition-test/asm-direct-manifest.json b/interop/composition-test/asm-direct-manifest.json new file mode 100644 index 0000000..2322197 --- /dev/null +++ b/interop/composition-test/asm-direct-manifest.json @@ -0,0 +1,56 @@ +{ + "asm_version": "0.3", + "service_id": "scopeblind-fixture/direct-provider-route@1.0", + "taxonomy": "ai.llm.chat", + "display_name": "Direct provider route (composition fixture)", + "provenance": { + "source_url": "https://github.com/ScopeBlind/examples/tree/main/interop/composition-test", + "retrieved_at": "2026-08-17T00:00:00Z", + "last_verified_at": "2026-08-17T00:00:00Z", + "verification_status": "self_reported", + "notes": "Scenario-local alternative used only to make the selection decision reproducible." + }, + "capabilities": { + "description": "Alternative route for the same fixed chat-completion scenario.", + "functions": ["chat_completion", "paid_api_call"], + "input_modalities": ["text"], + "output_modalities": ["text"] + }, + "pricing": { + "billing_dimensions": [ + { + "dimension": "fixture_call", + "unit": "per_1", + "cost_per_unit": 0.8, + "currency": "USD" + } + ] + }, + "operational_constraints": { + "risk_class": "critical", + "side_effects": ["external_api_call", "network_access", "financial_charge"], + "approval": { + "required": "conditional", + "conditions": ["financial_charge"] + }, + "spend_caps": { + "per_call_usd": 0.8, + "currency": "USD" + }, + "receipt_required": true, + "policy_notes": "The $0.80 value belongs to this fixed composition scenario." + }, + "invocation": { + "interface": "rest_api", + "reach": "cloud", + "agent_operable": true, + "auth_to_invoke": "api_token", + "agent_completable_setup": false, + "setup_requires": ["account_creation", "api_key_request"], + "platforms": ["any"] + }, + "usage_terms": { + "automation_allowed": "conditional", + "notes": "Test fixture only; callers remain responsible for provider terms." + } +} diff --git a/interop/composition-test/asm-openrouter-manifest.json b/interop/composition-test/asm-openrouter-manifest.json new file mode 100644 index 0000000..da6a195 --- /dev/null +++ b/interop/composition-test/asm-openrouter-manifest.json @@ -0,0 +1,58 @@ +{ + "asm_version": "0.3", + "service_id": "scopeblind-fixture/openrouter-route@1.0", + "taxonomy": "ai.llm.chat", + "display_name": "OpenRouter route (composition fixture)", + "provenance": { + "source_url": "https://github.com/ScopeBlind/examples/tree/main/interop/composition-test", + "retrieved_at": "2026-08-17T00:00:00Z", + "last_verified_at": "2026-08-17T00:00:00Z", + "verification_status": "self_reported", + "notes": "Scenario-local fixture values, not a claim about current OpenRouter pricing or availability." + }, + "capabilities": { + "description": "Route the composition-test chat completion through the endpoint used by the scenario.", + "functions": ["chat_completion", "paid_api_call"], + "input_modalities": ["text"], + "output_modalities": ["text"] + }, + "pricing": { + "billing_dimensions": [ + { + "dimension": "fixture_call", + "unit": "per_1", + "cost_per_unit": 0.5, + "currency": "USD" + } + ] + }, + "operational_constraints": { + "risk_class": "critical", + "side_effects": ["external_api_call", "network_access", "financial_charge"], + "approval": { + "required": "conditional", + "conditions": ["financial_charge"], + "human_readable": "The composition fixture requires approval before the paid API call." + }, + "spend_caps": { + "per_call_usd": 0.5, + "currency": "USD" + }, + "receipt_required": true, + "policy_notes": "The $0.50 value belongs to this fixed composition scenario." + }, + "invocation": { + "interface": "rest_api", + "reach": "cloud", + "agent_operable": true, + "auth_to_invoke": "api_token", + "agent_completable_setup": false, + "setup_requires": ["account_creation", "api_key_request"], + "platforms": ["any"], + "docs_url": "https://openrouter.ai/docs/api-reference/overview" + }, + "usage_terms": { + "automation_allowed": "conditional", + "notes": "Test fixture only; callers remain responsible for provider terms." + } +} diff --git a/interop/composition-test/asm-selection-receipt-digest.txt b/interop/composition-test/asm-selection-receipt-digest.txt new file mode 100644 index 0000000..102a52c --- /dev/null +++ b/interop/composition-test/asm-selection-receipt-digest.txt @@ -0,0 +1 @@ +sha256:4c4a9ebb847ff07721862a0f7e214c64606704b1ac31cac5cd6451488e6632e0 diff --git a/interop/composition-test/asm-selection-receipt.json b/interop/composition-test/asm-selection-receipt.json new file mode 100644 index 0000000..b9ce853 --- /dev/null +++ b/interop/composition-test/asm-selection-receipt.json @@ -0,0 +1,47 @@ +{ + "receipt_type": "selection", + "receipt_version": "0.1", + "selection_id": "5d7e46e7-3997-4db3-90fb-507f4a6d2b62", + "issued_at": "2026-08-17T00:00:00Z", + "selector": { + "name": "asm-protocol/0.5.2", + "policy": "gate: cloud agent-operable route with chat_completion and paid_api_call; rank: fixture cost within the approved per-call cap" + }, + "request": { + "task": "execute the fixed paid chat-completion call used by the ScopeBlind composition test", + "taxonomy": "ai.llm.chat", + "agent_reach": "cloud", + "user_platform": "any", + "required_functions": ["chat_completion", "paid_api_call"], + "require_approval_for": ["financial_charge"], + "max_cost_usd": 0.5 + }, + "evidence": [ + { + "service_id": "scopeblind-fixture/openrouter-route@1.0", + "manifest_digest": "sha256:57d3680b15b19d675ba11e125be77fc8072f12ba6754725a25ebc98b08481c3a" + }, + { + "service_id": "scopeblind-fixture/direct-provider-route@1.0", + "manifest_digest": "sha256:3f1c9300151453bc66fb4b3b4fe7aaf9c87425cefc9b1458c479baf775bc160f" + } + ], + "selected": { + "service_id": "scopeblind-fixture/openrouter-route@1.0", + "display_name": "OpenRouter route (composition fixture)", + "interface": "rest_api", + "reach": "cloud" + }, + "selection_reason": "Both fixture routes are eligible; the OpenRouter route matches the approved $0.50 per-call cap while the $0.80 alternative does not.", + "risk_class": "critical", + "approval_required": true, + "side_effects": ["external_api_call", "network_access", "financial_charge"], + "alternatives": [], + "rejected": [ + { + "service": "Direct provider route (composition fixture)", + "service_id": "scopeblind-fixture/direct-provider-route@1.0", + "reason": "fixture cost $0.80 exceeds max_cost_usd $0.50" + } + ] +} diff --git a/interop/composition-test/generate-receipts.mjs b/interop/composition-test/generate-receipts.mjs old mode 100644 new mode 100755 index b23719e..059fd0f --- a/interop/composition-test/generate-receipts.mjs +++ b/interop/composition-test/generate-receipts.mjs @@ -16,7 +16,7 @@ import { createHash } from 'crypto'; import { generateKeyPairSync, sign } from 'crypto'; -import { writeFileSync } from 'fs'; +import { readFileSync, writeFileSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; @@ -54,6 +54,20 @@ function sha256hex(data) { return createHash('sha256').update(data).digest('hex'); } +// --- Pre-call selection artifact --- +// ASM owns only the choice record. ScopeBlind remains authoritative for its +// signed authorization and execution receipts, which reference this digest. +const selectionReceipt = JSON.parse( + readFileSync(join(__dirname, 'asm-selection-receipt.json'), 'utf8'), +); +const selectionReceiptDigest = 'sha256:' + sha256hex(canonicalize(selectionReceipt)); +const selectionReceiptRef = { + receipt_type: selectionReceipt.receipt_type, + receipt_version: selectionReceipt.receipt_version, + selection_id: selectionReceipt.selection_id, + digest: selectionReceiptDigest, +}; + // --- Shared scenario --- const AGENT_ID = 'crewai-agent-research-001'; const TOOL_NAME = 'execute_api_call'; @@ -66,12 +80,14 @@ const TOOL_ARGS = { }; const SPEND_USD = 0.50; -// --- action_ref: content hash anchoring both governance evaluations --- -function computeActionRef(agentId, toolName, args) { - const input = JSON.stringify({ agent_id: agentId, args, tool: toolName }); - return 'sha256:' + sha256hex(input); -} -const actionRef = computeActionRef(AGENT_ID, TOOL_NAME, TOOL_ARGS); +// --- action_ref: the existing APS composition anchor --- +// APS owns the canonical action identifier in this checked-in composition +// fixture. Reuse it byte-for-byte so the two independently signed receipt +// families actually correlate; do not mint a second hash for the same call. +const actionRef = readFileSync( + join(__dirname, 'aps-receipts', 'action-ref.txt'), + 'utf8', +).trim(); // --- Cedar policy --- const CEDAR_POLICY = `permit( @@ -112,6 +128,9 @@ const policyPayload = { }, }, extensions: { + asm: { + selection_receipt: selectionReceiptRef, + }, scopeblind: { cedar_decision: 'permit', cedar_diagnostics: [], @@ -163,6 +182,9 @@ const execPayload = { currency: 'usd', }, extensions: { + asm: { + selection_receipt: selectionReceiptRef, + }, scopeblind: { cedar_decision: 'permit', policy_digest: policyDigest, @@ -192,10 +214,12 @@ writeFileSync(join(__dirname, 'scopeblind-policy-eval.json'), JSON.stringify(pol writeFileSync(join(__dirname, 'scopeblind-execution.json'), JSON.stringify(execReceipt, null, 2) + '\n'); writeFileSync(join(__dirname, 'scopeblind-pubkey.txt'), pubKeyHex + '\n'); writeFileSync(join(__dirname, 'action-ref.txt'), actionRef + '\n'); +writeFileSync(join(__dirname, 'asm-selection-receipt-digest.txt'), selectionReceiptDigest + '\n'); writeFileSync(join(__dirname, 'cedar-policy.cedar'), CEDAR_POLICY + '\n'); console.log('Generated ScopeBlind composition test receipts:'); console.log(` action_ref: ${actionRef}`); +console.log(` selection: ${selectionReceiptDigest}`); console.log(` public key: ${pubKeyHex}`); console.log(` policy receipt: scopeblind-policy-eval.json`); console.log(` exec receipt: scopeblind-execution.json`); diff --git a/interop/composition-test/scopeblind-execution.json b/interop/composition-test/scopeblind-execution.json index b480256..e4a6f95 100644 --- a/interop/composition-test/scopeblind-execution.json +++ b/interop/composition-test/scopeblind-execution.json @@ -1,12 +1,12 @@ { "payload": { "spec": "draft-farley-acta-signed-receipts-01", - "receipt_id": "sha256:ace89c38ff7d7276b8245cc8d020a92150c6f4fd133d0b60b03d90e10e58d276", + "receipt_id": "sha256:1f4355395182edef4c3ecebcc8839a7dce6964c953e9ad39cfe19923818484bc", "type": "tool:execution", - "issued_at": "2026-04-08T16:10:01.974Z", - "issuer_id": "sb:issuer:91e4d1a4f0f0", - "previousReceiptHash": "sha256:178ab43edefbbe99a89e9f2249c2ff995d2ab749ef532dc4a9bf6893ed748f37", - "action_ref": "sha256:0985753552dfd9ca60c978d00309c844c512ab0ae20062f9bebeac826f00bbdf", + "issued_at": "2026-08-17T01:18:22.141Z", + "issuer_id": "sb:issuer:93a2146e3977", + "previousReceiptHash": "sha256:24fabc778fefb0c79ac250a405107339af3f6e6adeab52b90d87462adacb6e59", + "action_ref": "31789af0e4b00d6c3f686ee56b5b361ba4f62055a37d52f6758d671d8ff60139", "agentId": "crewai-agent-research-001", "action": { "tool": "execute_api_call", @@ -23,6 +23,14 @@ "currency": "usd" }, "extensions": { + "asm": { + "selection_receipt": { + "receipt_type": "selection", + "receipt_version": "0.1", + "selection_id": "5d7e46e7-3997-4db3-90fb-507f4a6d2b62", + "digest": "sha256:4c4a9ebb847ff07721862a0f7e214c64606704b1ac31cac5cd6451488e6632e0" + } + }, "scopeblind": { "cedar_decision": "permit", "policy_digest": "sha256:f381122b9014899cffcd2b9ead006ba8f2e2881121010dfef903f5a5db307b3f", @@ -36,7 +44,7 @@ }, "signature": { "alg": "EdDSA", - "kid": "sb:issuer:91e4d1a4f0f0", - "sig": "8159dc4e7cacfdf3e78706f50c3f837729e65c898fb458a2b252d531679064b23939431dcdb38d8a2024697c4672e9ce396947c4f5b6432c2570682cd368580a" + "kid": "sb:issuer:93a2146e3977", + "sig": "8a80fd24f32a5b60a74b598943351409848ff450de5ab736e150db385eebd63e28c7a6f2cc9d7adc716085072d6b3b748c06450c1ef4d667c5e3a4a67f4e3206" } } diff --git a/interop/composition-test/scopeblind-policy-eval.json b/interop/composition-test/scopeblind-policy-eval.json index d3c4046..f1949d4 100644 --- a/interop/composition-test/scopeblind-policy-eval.json +++ b/interop/composition-test/scopeblind-policy-eval.json @@ -1,12 +1,12 @@ { "payload": { "spec": "draft-farley-acta-signed-receipts-01", - "receipt_id": "sha256:4fbc5e083ff3578f854ba9e7018f26bf25f43c263fd2c128fe41575dc24298a8", + "receipt_id": "sha256:5358b13366d744a34c83e51919d193736a47565e1a4f1513bb5bc74d39afef21", "type": "policy:evaluation", - "issued_at": "2026-04-08T16:10:01.824Z", - "issuer_id": "sb:issuer:91e4d1a4f0f0", + "issued_at": "2026-08-17T01:18:21.991Z", + "issuer_id": "sb:issuer:93a2146e3977", "previousReceiptHash": null, - "action_ref": "sha256:0985753552dfd9ca60c978d00309c844c512ab0ae20062f9bebeac826f00bbdf", + "action_ref": "31789af0e4b00d6c3f686ee56b5b361ba4f62055a37d52f6758d671d8ff60139", "agentId": "crewai-agent-research-001", "action": { "tool": "execute_api_call", @@ -18,6 +18,14 @@ } }, "extensions": { + "asm": { + "selection_receipt": { + "receipt_type": "selection", + "receipt_version": "0.1", + "selection_id": "5d7e46e7-3997-4db3-90fb-507f4a6d2b62", + "digest": "sha256:4c4a9ebb847ff07721862a0f7e214c64606704b1ac31cac5cd6451488e6632e0" + } + }, "scopeblind": { "cedar_decision": "permit", "cedar_diagnostics": [], @@ -30,7 +38,7 @@ }, "signature": { "alg": "EdDSA", - "kid": "sb:issuer:91e4d1a4f0f0", - "sig": "bac12f42fa4cbffa4990ac37722582edc39e02836b45f6eda8d8a01fd8935239c35a959849fc8ff75b7c729490814d275296e2260dc0d0e4ef860b6ed56d290d" + "kid": "sb:issuer:93a2146e3977", + "sig": "5b59ddfbd1f5b23ac7e988b099c7a0872badf8842529311598a5ceb5b46d54f2ee174f53665e674f5ce2eeb4dc5222d945c2c1152f0c0495f2f0a21d5808330b" } } diff --git a/interop/composition-test/scopeblind-pubkey.txt b/interop/composition-test/scopeblind-pubkey.txt index 3775f19..e06449f 100644 --- a/interop/composition-test/scopeblind-pubkey.txt +++ b/interop/composition-test/scopeblind-pubkey.txt @@ -1 +1 @@ -91e4d1a4f0f09c748b4d837a4ba692d7bc74d16c334a20731158ad3fa373940e +93a2146e397775bb4e1a1f847b42871c1ff02ec9452fd0a81db3e74cde7568b7 diff --git a/interop/composition-test/verify-all.sh b/interop/composition-test/verify-all.sh index 4eb655a..2d20ae4 100755 --- a/interop/composition-test/verify-all.sh +++ b/interop/composition-test/verify-all.sh @@ -1,6 +1,5 @@ #!/bin/bash -# Composition Test: verify all receipts from both governance engines -# One tool call, two engines, five receipts for the same action_ref +# Composition Test: verify the pre-call selection link and signed governance receipts set -e @@ -9,6 +8,50 @@ SB_KEY=$(cat "$DIR/scopeblind-pubkey.txt") APS_COMP_KEY=$(cat "$DIR/aps-receipts/gateway-pubkey.txt") APS_VEC_KEY=$(cat "$DIR/../aps-test-vectors/gateway-pubkey.txt") +echo "=== ASM pre-call selection link ===" +python3 - "$DIR" <<'PY' +import hashlib +import json +import pathlib +import sys + +root = pathlib.Path(sys.argv[1]) + +def load(name): + return json.loads((root / name).read_text(encoding="utf-8")) + +def digest(value): + canonical = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest() + +selection = load("asm-selection-receipt.json") +selection_digest = digest(selection) +expected = (root / "asm-selection-receipt-digest.txt").read_text(encoding="utf-8").strip() +assert selection_digest == expected, "Selection Receipt digest file does not match" + +manifests = { + item["service_id"]: item["manifest_digest"] + for item in selection["evidence"] +} +for name in ("asm-openrouter-manifest.json", "asm-direct-manifest.json"): + manifest = load(name) + assert manifests[manifest["service_id"]] == digest(manifest), f"manifest digest mismatch: {name}" + +for name in ("scopeblind-policy-eval.json", "scopeblind-execution.json"): + receipt = load(name) + reference = receipt["payload"]["extensions"]["asm"]["selection_receipt"] + assert reference["digest"] == selection_digest, f"Selection Receipt reference mismatch: {name}" + assert reference["selection_id"] == selection["selection_id"], f"selection_id mismatch: {name}" + +scopeblind_action = load("scopeblind-policy-eval.json")["payload"]["action_ref"] +aps_action = (root / "aps-receipts" / "action-ref.txt").read_text(encoding="utf-8").strip() +assert scopeblind_action == aps_action, "ScopeBlind and APS action_ref values do not match" + +print(f"Selection Receipt: {selection_digest}") +print("2 manifest digests, 2 signed receipt references, and shared action_ref: MATCH") +PY + +echo "" echo "=== ScopeBlind receipts (Cedar policy engine) ===" npx @veritasacta/verify@0.2.5 "$DIR/scopeblind-policy-eval.json" --key "$SB_KEY" npx @veritasacta/verify@0.2.5 "$DIR/scopeblind-execution.json" --key "$SB_KEY" @@ -30,4 +73,4 @@ echo "=== action_ref anchors ===" echo "ScopeBlind: $(cat "$DIR/action-ref.txt")" echo "APS composition: $(cat "$DIR/aps-receipts/action-ref.txt")" echo "" -echo "8 receipts, 2 engines, 2 scenarios, 1 verifier. All VALID." +echo "1 selection artifact, 8 signed receipts, 3 engines, 2 scenarios. All VALID."