diff --git a/README.md b/README.md index 962b102..55b94b9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ Agent Action Stack is a thin orchestrator. It does not re-implement the libraries. It runs them in a fixed order so a visitor can see how they compose. +The testbench pass gates orchestration for a response fixture. It is not a +signed authorization over the rail's separately constructed synthetic proposal. +The same-case workflow below binds the rail outcome to its review and replay. + ![Reference workflow from policy evaluation through recourse-gated action and outcome verification, with an optional dispute evidence simulation. Policy failure stops execution.](.github/assets/project-overview.svg) On policy failure the stack stops. On a clean `settled` outcome, MandateBound is skipped unless you pass `--dispute`. @@ -37,9 +41,18 @@ Private repositories are never cloned or modified. ```bash npm run bootstrap -npm run demo +node ./bin/aas.mjs demo --fault duplicate --prove rail ``` +This primary demonstration compensates a duplicate synthetic refund, verifies +its rail receipt, and records a MandateBound review of those same bytes. Expect +`act_outcome: compensated`, `prove_mode: rail-review`, and +`flow: decide -> act -> prove`. A recorded handoff does not establish source +truth or legal effect. Follow the export/replay commands below to review it +without executing the action again. + +For a clean settlement that needs no review, run `npm run demo`. + Optional, only for the browser test suite: Playwright needs a Chromium binary. After `npm install`, run `npx playwright install chromium` once before `npm run test:browser`. @@ -69,10 +82,10 @@ Fail closed at decide: npm run demo:fail ``` -Force the dispute path via a compensated rail outcome: +Review a compensated rail outcome using the same case: ```bash -npm run demo:dispute +node ./bin/aas.mjs demo --fault duplicate --prove rail ``` Expected flow line: @@ -81,12 +94,16 @@ Expected flow line: flow: decide -> act -> prove ``` -Review the same case instead of simulating one: +The separate canned simulation remains available explicitly: ```bash -node ./bin/aas.mjs demo --fault duplicate --prove rail +node ./bin/aas.mjs demo --fault duplicate --prove simulate ``` +`npm run demo:dispute` retains this simulation behavior for compatibility. Its +MandateBound scenario is unrelated to the rail case and does not produce a +same-case handoff. CLI defaults are unchanged. + The rail-review path persists the act-stage rail bundle, verifies it with the rail's own verifier, and binds it into a MandateBound review record for the same action id and digests. The review records the rail's verdict without @@ -182,6 +199,9 @@ same-case rail review; every result and export stays tied to its run id. long-running process. The server binds only to `127.0.0.1` on port 8787 by default (`AAS_GUI_PORT` selects another loopback port), requires the exact loopback Host and same-origin boundary, and uses POST for a run. +Choose the **Duplicate compensation and review** preset for the +primary handoff workflow. Applying it only prepares the controls; **Run stack** +starts the synthetic action. ## Tests @@ -193,6 +213,9 @@ npm run check `npm test` is the unit suite (orchestrator and GUI models). `npm run integration` proves the pinned components from a clean checkout, and `npm run example:review-handoff` runs the integrator example. +Installation, bootstrap, tests, and replay can write dependencies, caches, or +temporary files. See the [local write targets](docs/architecture.md#local-write-targets) +before running them in an existing checkout. Real browser workflow tests drive the GUI through actual clicks, file selection, and asynchronous responses with Playwright (Chromium only, to diff --git a/bin/aas-gui.mjs b/bin/aas-gui.mjs index c6b7872..228a138 100644 --- a/bin/aas-gui.mjs +++ b/bin/aas-gui.mjs @@ -319,14 +319,14 @@ export function renderPage() { Agent Action Stack

Agent Action Stack

Run the local decide, act, and prove flow using the reviewed component lock.

-

Synthetic local demo only. No real account operations. Policy evaluation, rail receipt verification, and MandateBound recording remain separate authorities. Source truth is unknown; legal effect is not determined.

+

Synthetic local demo only. No real account operations. The testbench response check gates the run; it is not a signed authorization over the rail proposal. Rail receipt verification and MandateBound recording remain separate authorities. Source truth is unknown; legal effect is not determined.

Choose a scenario or configure the options below. Applying a scenario only changes controls.

- +
Download run bundle
diff --git a/docs/architecture.md b/docs/architecture.md index 4c30220..51e4f89 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -28,6 +28,11 @@ The `runDemo` function in `bin/aas.mjs` is the orchestrator. It runs three stages in order. Each stage returns a child result captured from a spawned process, and the orchestrator records the stage status into the report. +The decide stage evaluates a response fixture. Its pass gates whether the act +stage runs, but it is not a signed authorization over the rail proposal. The +act stage constructs its own synthetic action inside the rail demo. Same-case +binding begins with that rail bundle and its subsequent review and replay. + ``` decide (Constitutional Agent Testbench, Python) | @@ -108,7 +113,24 @@ else: `test` (unit suite plus syntax check plus GUI smoke), `integration` or release step. It does not write to any registry, package index, or hosted target. `contents: read` is the only permission requested. -The same boundary holds locally: `npm test`, `npm run integration`, -`npm run example:review-handoff`, and `npm run test:browser` are read-only -with respect to anything outside `.out/`. The orchestrator writes only to -`.out/` for run bundles and to `.out/latest.json` for the latest pointer. \ No newline at end of file +## Local write targets + +Local verification does write files. Use a disposable checkout for integration +and browser tests. The commands do not publish, deploy, or operate real accounts. + +| Command | Local writes | +| --- | --- | +| `npm ci --ignore-scripts` | Root `node_modules/` and npm's configured cache/log directory | +| `npm run bootstrap` | Pinned public clones under `deps/`, MandateBound `node_modules/` and `dist/`, and npm cache/log files | +| `aas demo`, GUI **Run stack**, integrator examples | `.out/runs/`, `.out/latest.json`, temporary handoff directories under the OS temporary directory, and Python bytecode caches under `deps/constitutional-agent-testbench/src/constitutional_agent_testbench/__pycache__/` unless bytecode writing is disabled | +| `npm test`, `npm run gui:smoke` | Test fixtures, temporary case stores, and child-process scratch files under the OS temporary directory where needed | +| `npm run integration` | Root install, dependency bootstrap/build, the demo writes above (including Python bytecode), and temporary copied verifier runtimes; invokes npm and Git | +| Playwright install and `npm run test:browser` | Configured browser cache, `test-results/`, `playwright-report/`, temporary test case stores, and the GUI **Run stack** writes above (including `.out/` and Python bytecode) | +| `aas runs`, `cases`, `compare`, `inspect`, `latest` | Read saved cases only; shell redirection can write the printed report | +| `aas replay`, `verify`, GUI verification | Read the case store or import, write temporary verifier inputs, and remove scratch files afterward; do not execute an action or alter saved cases | +| `aas export --out` | Writes the named export; existing files require explicit `--overwrite` | +| `aas prune --keep` | Deletes eligible old runs under the selected output root; `--dry-run` previews without deletion | + +Saved-case commands honor `--root`. npm and Playwright honor their own cache +configuration, and temporary directories use the operating system's configured +temporary location. Interrupted processes can leave temporary files behind. diff --git a/docs/release-readiness.md b/docs/release-readiness.md index d2a1807..0d4574a 100644 --- a/docs/release-readiness.md +++ b/docs/release-readiness.md @@ -2,13 +2,20 @@ This checklist describes the evidence required before a public versioning update. +Current requirements: Node.js 22.12+ and Python 3.11+; the exact component pins +come from `stack-lock.json`. CI includes unit, full-stack integration, and real +Chromium browser workflows. The dated candidate records below are historical +evidence for their stated commits, not current runtime or test-count claims. +Local checks may install dependencies and write caches or temporary files; see +[local write targets](architecture.md#local-write-targets). + ## Source and dependency gates - [ ] Review the final raw tree and changed-file list. - [ ] Confirm `stack-lock.json` still contains the approved public URLs and commits: - Constitutional Agent Testbench: `16b2faa71b0f92b9afa15b13afad8c48da8132f4` - - Consequence Rail: `6c61e9fdcd1a4701afad1d2371abcb3f13bbab57` - - MandateBound: `e526c4c32ac61571757a98ca1a69189821c3dce7` + - Consequence Rail: `9f60ab3223970c22371c20d3584e8330674d997c` ([PR #67](https://github.com/EauDoon/consequence-rail/pull/67)) + - MandateBound: `b51fe137958afe26eee052c5a129e5481ccae560` ([PR #102](https://github.com/EauDoon/mandatebound/pull/102)) - [ ] Run bootstrap from a clean workspace and verify detached, clean, exact dependency checkouts. - [ ] Confirm no private repository, credential, or production endpoint is referenced. @@ -24,8 +31,8 @@ This checklist describes the evidence required before a public versioning update ## Integration evidence, 2026-09-06 candidate Candidate: this branch at the pin-update commit (`chore/update-stack-pins-202609`; -orchestrator base `a0e25144aaebff2885b67eb6b5b355c37a167f37`). Component pins -are the reviewed merge SHAs above: testbench PR #20 (`16b2faa7`), rail PR #20 +orchestrator base `a0e25144aaebff2885b67eb6b5b355c37a167f37`). That candidate's +component pins were testbench PR #20 (`16b2faa7`), rail PR #20 (`64cb30`, includes the unknown-recourse receipt-refusal fix), MandateBound PR #26 (`3682a24`, includes the fast-uri advisory fix). No component was upgraded past its reviewed merge; the rail SHA supersedes the earlier `7cf59e79` @@ -45,8 +52,8 @@ Clean-checkout proof (`/tmp/aas-clean`, fresh clone, no `deps/`, `dist/`, or modifications, HEAD equal to the pinned full SHA; remotes are the three public `EauDoon` repository URLs; MandateBound `npm ci` reports 0 vulnerabilities and its `tsc` build produces `dist/cli.js`. -- `npm test`: 51 passed, 0 failed (includes the lock-provenance fixture that - asserts the three pins above). +- `npm test`: 51 passed, 0 failed (includes the lock-provenance fixture for + that candidate's three pins). - `npm run gui:smoke`: passed. - `node ./bin/aas.mjs demo`: pass path, `decide passed`, `act settled`, `CLOSED`, `flow: decide -> act`; bundle records stage artifacts plus diff --git a/docs/stack-lock.md b/docs/stack-lock.md index 8a237ce..1b7d37b 100644 --- a/docs/stack-lock.md +++ b/docs/stack-lock.md @@ -61,6 +61,30 @@ is bumped only when the shape changes in a way that requires loader changes; existing tools keep reading older versions until the bump lands across all consumers. +The integration proof also runs `test/component-compatibility.test.mjs` against +the prepared components. It checks canonical bytes independently of producer +round trips, both currency validation boundaries, and refusal of re-signed +synthetic evidence whose currency contradicts the proposal. The historical +`fixtures/legacy-rail-review.json` was exported using Rail `6c61e9f` and +MandateBound `e526c4c`; it pins replay compatibility for an ordinary synthetic +refund. Its public demonstration signatures establish no real-world provenance. + +An older artifact with numeric-looking object keys may contain signatures made +with the former Rail canonical ordering. The current verifier does not try that +obsolete ordering after verification fails. Preserve the original artifact and +its recorded producer revision for historical inspection; do not rewrite its +signatures or describe a newly generated case as the same evidence. A successful +legacy fixture replay establishes compatibility for that fixture, not every +previously accepted artifact or an alternate canonical profile. + +The MandateBound pin also corrects U+2028/U+2029 bytes under its existing +RFC8785 profile. Legacy proofs containing those separators can fail current +integrity checks. Keep original bytes and the exact producer commit for +historical replay; version labels alone do not identify the affected behavior. +There is no alternate-byte verification or automatic migration. Follow the +[producer's compatibility guidance](https://github.com/EauDoon/mandatebound/blob/b51fe137958afe26eee052c5a129e5481ccae560/docs/PROTOCOL.md) +before reissuing affected artifacts. + ## Who can update The lock is owned by the Agent Action Stack maintainers. Updates land via @@ -79,4 +103,4 @@ or in this policy document. Real connectors, real secrets, real payment or merchant integrations; changes that would read or write private repositories; policy or rail rules that should live in their owning library. The lock pins reviewable -public artifacts. Anything else belongs in the sibling that owns it. \ No newline at end of file +public artifacts. Anything else belongs in the sibling that owns it. diff --git a/examples/README.md b/examples/README.md index ed30ad2..846caba 100644 --- a/examples/README.md +++ b/examples/README.md @@ -31,6 +31,10 @@ One journey across both synthetic domains (`--domain refund|inventory`): The pass path exits 0 after every binding verifies. The refusal path exits 1 after the policy refuses, before anything executes. +This is the primary same-case demonstration. The separate `--prove simulate` +mode runs an unrelated canned dispute scenario. The testbench pass is a gate +over a response fixture, not a signed authorization of the rail proposal. + What it establishes: the policy gate passed for this response, the rail produced this outcome for this action, recourse was reserved before the effect, the rail's verifier accepts the persisted bytes under the synthetic @@ -76,7 +80,7 @@ treats an observed effect as proof of external truth. connector's synthetic demo key. Verifier trust is supplied by the caller; nothing embedded in a bundle is trusted for its own integrity. - Caller-owned anchors (expected digests, expected action identity) are - separate from exported untrusted material. Rethem recomputation always runs + separate from exported untrusted material. Digest recomputation always runs over the actual bytes. - Source truth is never established. A recorded review proves the handoff and the digest binding, not the underlying external state. diff --git a/examples/connector-conformance.mjs b/examples/connector-conformance.mjs index 7bf698a..28afc7b 100644 --- a/examples/connector-conformance.mjs +++ b/examples/connector-conformance.mjs @@ -129,15 +129,21 @@ function main() { await inventory.execute(inventoryProposal, inventoryProposal.idempotency_key); const first = await inventory.remediate(inventoryProposal, { connector_commitment: reservation }, "remedy:a"); const onHandAfterFirst = inventory.inventory.get("sku_demo_1"); - const second = await inventory.remediate(inventoryProposal, { connector_commitment: reservation }, "remedy:b"); + const replay = await inventory.remediate(inventoryProposal, { connector_commitment: reservation }, "remedy:a"); + let secondError = null; + try { await inventory.remediate(inventoryProposal, { connector_commitment: reservation }, "remedy:b"); } + catch (error) { secondError = error.code; } out({ - first: first.status, second: second.status, + first: first.status, sameResult: JSON.stringify(first) === JSON.stringify(replay), + recourse: inventory.recourseStatus(reservation.reservation_token).status, + secondError, restoredOnce: inventory.inventory.get("sku_demo_1") === onHandAfterFirst, onHand: inventory.inventory.get("sku_demo_1"), }); `); check(RULES[4], - remedy.first === "remediated" && remedy.second === "failed" && remedy.restoredOnce === true, + remedy.first === "remediated" && remedy.sameResult === true && remedy.recourse === "consumed" + && remedy.secondError === "RECOURSE_NOT_ACTIVE" && remedy.restoredOnce === true, `the remedy must reverse once and refuse a second restoration (saw ${JSON.stringify(remedy)})`); const remedyStatus = run(` diff --git a/fixtures/legacy-rail-review.json b/fixtures/legacy-rail-review.json new file mode 100644 index 0000000..4b62f67 --- /dev/null +++ b/fixtures/legacy-rail-review.json @@ -0,0 +1,882 @@ +{ + "manifest": { + "schema_version": "agent-action-stack.run/v1", + "run_id": "2026-10-02T111712070Z-9a9cd7a9-49c", + "created_at": "2026-10-02T11:17:13.341Z", + "exit_code": 0, + "component_provenance": [ + { + "name": "constitutional-agent-testbench", + "repository": "https://github.com/EauDoon/constitutional-agent-testbench.git", + "commit": "16b2faa71b0f92b9afa15b13afad8c48da8132f4", + "origin": "https://github.com/EauDoon/constitutional-agent-testbench.git", + "detached": true, + "clean": true, + "entrypoints": [ + "pyproject.toml", + "src/constitutional_agent_testbench/cli.py" + ] + }, + { + "name": "consequence-rail", + "repository": "https://github.com/EauDoon/consequence-rail.git", + "commit": "6c61e9fdcd1a4701afad1d2371abcb3f13bbab57", + "origin": "https://github.com/EauDoon/consequence-rail.git", + "detached": true, + "clean": true, + "entrypoints": [ + "package.json", + "cmd/crctl.js" + ] + }, + { + "name": "mandatebound", + "repository": "https://github.com/EauDoon/mandatebound.git", + "commit": "e526c4c32ac61571757a98ca1a69189821c3dce7", + "origin": "https://github.com/EauDoon/mandatebound.git", + "detached": true, + "clean": true, + "entrypoints": [ + "package.json", + "package-lock.json", + "src/cli.ts", + "dist/cli.js" + ] + } + ], + "stages": { + "decide": { + "status": "passed", + "reason": null, + "code": null, + "stderr": null, + "artifact": "stages/decide.json" + }, + "act": { + "status": "passed", + "reason": null, + "code": null, + "stderr": null, + "artifact": "stages/act.json" + }, + "prove": { + "status": "passed", + "reason": null, + "code": null, + "stderr": null, + "artifact": "stages/prove.json" + } + }, + "report": "report.json" + }, + "report": { + "stack": "agent-action-stack", + "response": "pass", + "requested_options": { + "response": "pass", + "domain": "refund", + "fault": "duplicate", + "prove": "rail", + "dispute": false + }, + "domain": "refund", + "flow": "decide -> act -> prove", + "run_id": "2026-10-02T111712070Z-9a9cd7a9-49c", + "component_provenance": [ + { + "name": "constitutional-agent-testbench", + "repository": "https://github.com/EauDoon/constitutional-agent-testbench.git", + "commit": "16b2faa71b0f92b9afa15b13afad8c48da8132f4", + "origin": "https://github.com/EauDoon/constitutional-agent-testbench.git", + "detached": true, + "clean": true, + "entrypoints": [ + "pyproject.toml", + "src/constitutional_agent_testbench/cli.py" + ] + }, + { + "name": "consequence-rail", + "repository": "https://github.com/EauDoon/consequence-rail.git", + "commit": "6c61e9fdcd1a4701afad1d2371abcb3f13bbab57", + "origin": "https://github.com/EauDoon/consequence-rail.git", + "detached": true, + "clean": true, + "entrypoints": [ + "package.json", + "cmd/crctl.js" + ] + }, + { + "name": "mandatebound", + "repository": "https://github.com/EauDoon/mandatebound.git", + "commit": "e526c4c32ac61571757a98ca1a69189821c3dce7", + "origin": "https://github.com/EauDoon/mandatebound.git", + "detached": true, + "clean": true, + "entrypoints": [ + "package.json", + "package-lock.json", + "src/cli.ts", + "dist/cli.js" + ] + } + ], + "stages": { + "decide": { + "status": "passed", + "passed": true, + "policy_id": "aas-refund-gate-v1", + "rule_results": [ + { + "kind": "required_field", + "passed": true, + "path": "summary", + "reason_code": "RULE_SATISFIED", + "rule_id": "summary-present" + }, + { + "kind": "equals", + "passed": true, + "path": "decision", + "reason_code": "RULE_SATISFIED", + "rule_id": "decision-accept" + }, + { + "kind": "one_of", + "passed": true, + "path": "action_type", + "reason_code": "RULE_SATISFIED", + "rule_id": "action-is-refund" + }, + { + "kind": "one_of", + "passed": true, + "path": "risk_level", + "reason_code": "RULE_SATISFIED", + "rule_id": "risk-allowed" + }, + { + "kind": "false", + "passed": true, + "path": "blocked", + "reason_code": "RULE_SATISFIED", + "rule_id": "blocked-is-false" + }, + { + "kind": "equals", + "passed": true, + "path": "recourse_required", + "reason_code": "RULE_SATISFIED", + "rule_id": "recourse-required" + } + ], + "error": null + }, + "act": { + "status": "passed", + "outcome": "compensated", + "state": "CLOSED", + "fault": "duplicate", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "assurance_mode": "enforced", + "bundle_verification": "pass" + }, + "prove": { + "status": "passed", + "mode": "rail-review", + "scenario": null, + "triggered_by": "act_outcome=compensated", + "ok": true, + "result_keys": [ + "format", + "reviewId", + "source", + "actionId", + "evidenceDigest", + "receipt", + "upstream", + "verdict", + "legalEffect", + "reviewDigest" + ], + "review_verdict": "recorded", + "review_id": "review-b2d46deb1f051529", + "review_action_id": "act_x4ZgajdJZH1SM33KmKCU" + } + } + }, + "stages": { + "decide": { + "passed": true, + "policy_id": "aas-refund-gate-v1", + "rule_results": [ + { + "kind": "required_field", + "passed": true, + "path": "summary", + "reason_code": "RULE_SATISFIED", + "rule_id": "summary-present" + }, + { + "kind": "equals", + "passed": true, + "path": "decision", + "reason_code": "RULE_SATISFIED", + "rule_id": "decision-accept" + }, + { + "kind": "one_of", + "passed": true, + "path": "action_type", + "reason_code": "RULE_SATISFIED", + "rule_id": "action-is-refund" + }, + { + "kind": "one_of", + "passed": true, + "path": "risk_level", + "reason_code": "RULE_SATISFIED", + "rule_id": "risk-allowed" + }, + { + "kind": "false", + "passed": true, + "path": "blocked", + "reason_code": "RULE_SATISFIED", + "rule_id": "blocked-is-false" + }, + { + "kind": "equals", + "passed": true, + "path": "recourse_required", + "reason_code": "RULE_SATISFIED", + "rule_id": "recourse-required" + } + ] + }, + "act": { + "scenario": "synthetic-refund", + "fault": "duplicate", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "state": "CLOSED", + "outcome": "compensated", + "assurance_mode": "enforced", + "bypass_possible": false, + "execute_calls": 1, + "status_calls": 0, + "recourse_reservation_calls": 1, + "recourse_status_calls": 4, + "remedy_calls": 1, + "remedy_status_calls": 0, + "active_refunds": 1, + "bundle_verification": "pass", + "expected_rejection": null, + "tamper_detection": null, + "rail_bundle": { + "schema_version": "consequence-rail/settlement-bundle/v0.1", + "profile": "audit", + "action": { + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "action_type": "demo.refund.issue/v1", + "resource_id_digest": "RzuSKqG5Z5n2r39lMo3o_Q3upDg0UuqwGIyACB975X0", + "proposal": { + "schema_version": "consequence-rail/action-proposal/v0.1", + "action_type": "demo.refund.issue/v1", + "subject": { + "type": "service", + "id": "support-agent-demo" + }, + "target": { + "connector": "mock-refund-processor", + "resource_type": "order", + "resource_id": "ord_demo_42" + }, + "parameters": { + "amount_minor": 12000, + "currency": "USD" + }, + "idempotency_key": "refund:ord_demo_42:1", + "requested_at": "2035-01-01T00:00:00.000Z", + "expires_at": "2035-01-01T00:02:00.000Z", + "assurance_mode": "enforced", + "postcondition": { + "op": "all", + "clauses": [ + { + "path": "active_refund_count", + "op": "eq", + "value": 1 + }, + { + "path": "net_refunded_minor", + "op": "eq", + "value": 12000 + } + ] + }, + "evidence_plan": { + "source": "mock-refund-processor", + "max_age_seconds": 60 + } + } + }, + "recourse_reservation": { + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "kind": "reverse", + "connector": "mock-refund-processor", + "capability": "void-duplicate-refund", + "capability_reference_digest": "hQVGx_B4-JQBq8CvDw33swkg7Sil8DKE31TiKBc6bhM", + "expires_at": "2035-01-01T00:07:00.000Z", + "remedy_window_seconds": 120, + "max_attempts": 1, + "max_amount_minor": 12000, + "idempotency_key_digest": "8MW_Yc8R2uxM15pDeO2i6fmTmk5lp1kToF4Tqed7Ms0", + "connector_commitment": { + "schema_version": "consequence-rail/connector-recourse-commitment/v0.1", + "reservation_token": "rsv_eOwKgTlUqaC5314TKQk7qiO4", + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "connector": "mock-refund-processor", + "capability": "void-duplicate-refund", + "kind": "reverse", + "expires_at": "2035-01-01T00:07:00.000Z", + "max_attempts": 1, + "max_amount_minor": 12000, + "reserved_at": "2035-01-01T00:00:00.000Z", + "status": "active", + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-connector-ed25519-VcGmBF_daVE14fDB", + "value": "QESUU2rmDRGq9A15VkLaD_WZzJbIpI-LKCDgbESkRM85BJGrtm6xQhljESpygHrUZvOOFue0T25srv4QfgA6Dg" + } + }, + "schema_version": "consequence-rail/recourse-reservation/v0.1", + "reservation_id": "rr_H6KH_ELHUUE4I5UC_f_C", + "reserved_at": "2035-01-01T00:00:00.000Z", + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "vPNoUtQAhzt_HuXbP1EL7xf0JUdhKW_ZfT_QJWn_8S7sb25C37ciSMLx4eUtU7Qx1UoZSfdEH2mwcGXv8t22Dw" + } + }, + "action_permit": { + "schema_version": "consequence-rail/action-permit/v0.1", + "permit_id": "permit_x4ZgajdJZH1SM33KmKCU", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "recourse_reservation_digest": "ojziEUQo4xQCHkY7lbdO9sSeDHcsxqUnqAPJ0rr9VyI", + "assurance_mode": "enforced", + "bypass_possible": false, + "gated": true, + "jti": "jti_v28KExIQboomTjPD9KRb", + "issued_at": "2035-01-01T00:00:00.000Z", + "expires_at": "2035-01-01T00:02:00.000Z", + "max_uses": 1, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "_3zuGA-h5xkEw-n9jk12wp3Qj2AXjAUagLwhYUAQWQgQ6xZylb1cuhMxIB899EBSwvH3e-xDe4XJs1yH-fvNDg" + } + }, + "evidence_manifest": [ + "uXZ2KD-1U9iqXXXkuAxQEQzN3Q459BIrDQkQwdYUvVc", + "jhBWLNPHE0GF6YlPX43d5KC-t91CP0kIBVYl_Js8LvA" + ], + "outcome_evidence": [ + { + "schema_version": "consequence-rail/outcome-evidence/v0.1", + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "source": "mock-refund-processor", + "resource": { + "type": "order", + "id": "ord_demo_42" + }, + "observed_at": "2035-01-01T00:00:00.000Z", + "facts": { + "active_refund_count": 2, + "net_refunded_minor": 24000, + "currency": "USD" + }, + "evaluation": { + "satisfied": false, + "evaluations": [ + { + "path": "active_refund_count", + "operator": "eq", + "expected": 1, + "actual": 2, + "satisfied": false + }, + { + "path": "net_refunded_minor", + "operator": "eq", + "expected": 12000, + "actual": 24000, + "satisfied": false + } + ] + }, + "captured_by": "consequence-rail", + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "4cO7a5VweGgZ1qp2WbXgJKFSd_owyq8xKzU0MLhRAX7rVCuw2TpRC0huQf4tcnFCmYPaeoicLCkyuJcl1vIbBg" + } + }, + { + "schema_version": "consequence-rail/outcome-evidence/v0.1", + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "source": "mock-refund-processor", + "resource": { + "type": "order", + "id": "ord_demo_42" + }, + "observed_at": "2035-01-01T00:00:00.000Z", + "facts": { + "active_refund_count": 1, + "net_refunded_minor": 12000, + "currency": "USD" + }, + "evaluation": { + "satisfied": true, + "evaluations": [ + { + "path": "active_refund_count", + "operator": "eq", + "expected": 1, + "actual": 1, + "satisfied": true + }, + { + "path": "net_refunded_minor", + "operator": "eq", + "expected": 12000, + "actual": 12000, + "satisfied": true + } + ] + }, + "captured_by": "consequence-rail", + "phase": "post-remedy", + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "L7GGtxqWHn0nCI0OTC1Z5T2JQQ-HSl3L1CLA7_0PnqBAsQ58guggtMC5rl7dmtPSnGh58pj2fyyN034cnMzMDA" + } + } + ], + "settlement_receipt": { + "schema_version": "consequence-rail/settlement-receipt/v0.1", + "receipt_id": "receipt_w17NzfhBcfHs_sZgFZdE", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "recourse_reservation_digest": "ojziEUQo4xQCHkY7lbdO9sSeDHcsxqUnqAPJ0rr9VyI", + "connector_recourse_commitment_digest": "kjeRqSDyDGgcAWMuCP3Dmmhm8C6XYEZDGs9pvf0QI3s", + "recourse_final_status": "consumed", + "action_permit_digest": "c6kUT3coei2_Lg34MYo3KNj4rL0ya-sQZq00Ngmkm_4", + "assurance_mode": "enforced", + "bypass_possible": false, + "gated": true, + "outcome": "compensated", + "configured_postcondition_result": "satisfied", + "evidence_digests": [ + "uXZ2KD-1U9iqXXXkuAxQEQzN3Q459BIrDQkQwdYUvVc", + "jhBWLNPHE0GF6YlPX43d5KC-t91CP0kIBVYl_Js8LvA" + ], + "event_chain_head": "U66FzpJ0lvse2oBWgaTu12cA2Fi5KQ5medyZVIiZnnI", + "closed_at": "2035-01-01T00:00:00.000Z", + "technical_claim": "The configured postcondition was evaluated against declared evidence sources.", + "limitations": [ + "Signatures establish integrity and provenance, not truth or causality.", + "Settlement is a technical protocol status, not a legal or financial determination.", + "Recovery is not guaranteed." + ], + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "7hHdpEuObdjNNfE7fNsBJ0sVECEcJ5L6Dqao20cLe_tRf_1umif5bKjRNhdqKDrlHOHeBRdkOyfGGRL6Z-oCAg" + } + }, + "events": [ + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 0, + "previous_hash": null, + "event_type": "ACTION_PROPOSED", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "state": "PROPOSED", + "action_digest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU", + "action_type": "demo.refund.issue/v1", + "assurance_mode": "enforced" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "7as3K0MZrv4fpWN3R3nyrbTRYF5WnwBz4IDSfjLgsWm_ucD8Mq5NzBikX0wJ0D_95KnmsgZmXFfOixfX8IJ1AA" + }, + "event_hash": "c8kkxL_mxvj8Qxt1waCvuOMi0WkNZ56SRDspHBPPwf0" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 1, + "previous_hash": "c8kkxL_mxvj8Qxt1waCvuOMi0WkNZ56SRDspHBPPwf0", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "PROPOSED", + "to_state": "AUTHORIZED", + "reason_code": "POLICY_ALLOWED", + "details_digest": "oNBsBPliWxoVFsLOPSzwE52JkbrL2pgDjD_B1ddy6OY" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "ItPCtOjmdE34FCbCvg01Zd2xBsWgfZNFPgRjIuT25FUnvulzkuQto6Swfbp8AevcFhwR4hK3EZCT-J1hsb8tAg" + }, + "event_hash": "58cA0Qjpp-BLMZI3nKrwm3ME7qkQYqigf9_D89WU3S0" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 2, + "previous_hash": "58cA0Qjpp-BLMZI3nKrwm3ME7qkQYqigf9_D89WU3S0", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "AUTHORIZED", + "to_state": "RECOURSE_RESERVED", + "reason_code": "RECOURSE_VERIFIED", + "details_digest": "zw14OlIgSwKaos-G3u6YsQgrYd5axyVAmxkzWkEzCGA" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "bymOiTeQzQvSDs-a5Bf-CYqWzJs9blGUeBud7uw4A_p737ydXu9N9XJZs1jh2oceDF3Ho6Hc0hDX3GoYqEGVCA" + }, + "event_hash": "TKsli9-LQqXZ26n2wkvp26Lwo2OTMcWwcqKtAnNe2Nw" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 3, + "previous_hash": "TKsli9-LQqXZ26n2wkvp26Lwo2OTMcWwcqKtAnNe2Nw", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "RECOURSE_RESERVED", + "to_state": "PERMITTED", + "reason_code": "PERMIT_ISSUED", + "details_digest": "e5pi3G7SLacp-6T-ipXb1AH1eKafiETN9Z0Wyl8mj3Y" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "ovjoS3JQ3KQnt0z2OKfwAK7Uff0WlNZSIEjJ_ggOfLUkF61fsY2PZRFAWIzkAV7KKh_7VRtgyqFf65GIvR54DA" + }, + "event_hash": "hFKN-CzblvGyeQdO63gziebZrIMBcTAt-ypBNu5E2XY" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 4, + "previous_hash": "hFKN-CzblvGyeQdO63gziebZrIMBcTAt-ypBNu5E2XY", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "PERMITTED", + "to_state": "EXECUTING", + "reason_code": "PERMIT_CONSUMED", + "details_digest": "N2lEqgIZlJ52ptBl6zNzUmITVzbAmKgnBrt4pWXO-vI" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "_WgT-qSWJRYeALjaqK_ewAQg4L_dU3Uq5BNso6Fawa1XfBR7TxnQKPpfjrjvI6nV8Tmrhyl6Wr3wydRu5j30Dg" + }, + "event_hash": "AH9Ve1SCjAn7yj52o_GQZsKRFWBt7yQZMSlbNEI7MvY" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 5, + "previous_hash": "AH9Ve1SCjAn7yj52o_GQZsKRFWBt7yQZMSlbNEI7MvY", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "EXECUTING", + "to_state": "EXECUTED", + "reason_code": "CONNECTOR_EXECUTED", + "details_digest": "E2kUr3ci3-HOXtxe25v5te7p0XfdUyR_xVpd-nXHnvc" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "UBm0IE1dKhOviLlPHkubOWUCW5TRTFPV404Nd5JXvReYsx2msipBsVPB2W12qyzErPCROmNUKYfVW7aCAXxvCA" + }, + "event_hash": "SygONfLhvD1uqtC_TrSbOIixu941INoOQlmrSq9rXh4" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 6, + "previous_hash": "SygONfLhvD1uqtC_TrSbOIixu941INoOQlmrSq9rXh4", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "EXECUTED", + "to_state": "VERIFYING", + "reason_code": "OUTCOME_VERIFICATION_STARTED", + "details_digest": "RBNvo1WzZ4oRRq0W9-hknpT7T8If536DEMBg9hyq_4o" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "WHufpD9MNksqWaeU0RP7mzGFN0P3BTJoNBx-Or-2Exp6HVDG5rBsCDosb-xqx3nK0g0GRmIXt9rT_qUjzh8TBw" + }, + "event_hash": "zGdpssa-77pHPSlThZisnpg6sINvhUrldnSKHWG_7ng" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 7, + "previous_hash": "zGdpssa-77pHPSlThZisnpg6sINvhUrldnSKHWG_7ng", + "event_type": "EVIDENCE_ACCEPTED", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "evidence_digest": "uXZ2KD-1U9iqXXXkuAxQEQzN3Q459BIrDQkQwdYUvVc", + "source": "mock-refund-processor", + "satisfied": false + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "2Gcn7Wu1l3aqhbhqGEJlA0BjKAuNn_j6Qa-aNJjSa69NJn5M3Gk__-iP9ve9yOzdoIad5om6VnRfgvv2Wu82DA" + }, + "event_hash": "n_g4ugfUIcigCK1OkSYhBsyVY_c6welcAfrau2ZMbiM" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 8, + "previous_hash": "n_g4ugfUIcigCK1OkSYhBsyVY_c6welcAfrau2ZMbiM", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "VERIFYING", + "to_state": "BREACHED", + "reason_code": "POSTCONDITION_BREACHED", + "details_digest": "cwgEjCEKK8SrINHnhEnL82Aqi7WjwEMrrnJ0P33rF0U" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "A6NJpgUI8gWslvVFpV1BLwGF8dejjzYv6tmjmbyQSX84DUP3ullKzlmkJPXx3760BUP3NZjupQXkaZvmJagECw" + }, + "event_hash": "hcFsDmr5B0eatZAqKTMbICuaqtnp-eBPXkzBsPh-yps" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 9, + "previous_hash": "hcFsDmr5B0eatZAqKTMbICuaqtnp-eBPXkzBsPh-yps", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "BREACHED", + "to_state": "REMEDY_DUE", + "reason_code": "RESERVED_REMEDY_DUE", + "details_digest": "9LP6jxuN3Y5IZXnswd7WhEXM3ZOeWpA_mv-SYqICPUE" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "6OsePWwPc9BV3tpnKcCuzSN6q_34mTfjhErqtF_vidhIKnLE8vflxnEh4rNTWE6geYTScIQXoVN_85DCVDWzCw" + }, + "event_hash": "BOnMP7UZY3Oiab7mUzD-3mRjgQtZLo9KV02HyN88IeM" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 10, + "previous_hash": "BOnMP7UZY3Oiab7mUzD-3mRjgQtZLo9KV02HyN88IeM", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "REMEDY_DUE", + "to_state": "REMEDIATING", + "reason_code": "REMEDY_STARTED", + "details_digest": "7PYMy7IM31CXviM9pINmoFjH99vvtL2zIr9xpKjIi6E" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "kTmdQUNU-QVWrs5PyYKzDTQXtinfmm6a17ztex9Lfhz2xhcJJjkkKf1DMnBgvVyhrZG5ooJsrFOpKfwbXB7TCQ" + }, + "event_hash": "jSk6bUt2PwePfEMNtHm0a0VQlVG4nwNL5pMbE6hk2CQ" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 11, + "previous_hash": "jSk6bUt2PwePfEMNtHm0a0VQlVG4nwNL5pMbE6hk2CQ", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "REMEDIATING", + "to_state": "REMEDY_VERIFYING", + "reason_code": "REMEDY_EXECUTION_CONFIRMED", + "details_digest": "YhwN24jaK3cDaGIMFKXK1ifx5sjp8FIxfCks-TlWINI" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "IQ6zlvyfOj0Gl1oYC75QpynRwryXdSOb-IpsavZTCxP54aLdBhheeLYrZu8XsQF9WNcS-lZ7Os0ZkPhB7cESDQ" + }, + "event_hash": "0N0hs_XEsCLUt9QlB1UggF5a33D50RKVMIM1LRqCStw" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 12, + "previous_hash": "0N0hs_XEsCLUt9QlB1UggF5a33D50RKVMIM1LRqCStw", + "event_type": "REMEDY_EVIDENCE_ACCEPTED", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "evidence_digest": "jhBWLNPHE0GF6YlPX43d5KC-t91CP0kIBVYl_Js8LvA", + "satisfied": true + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "lwrTwkiKGgekRFHOlyV5RpJAclhEMf7PbbA28hzoNSAEgGkKXUX0fV0UelYj6Ff4zoi0fc_btp3WXHJjA25uBw" + }, + "event_hash": "jHp_K3Z2wBucMNf1LHe7KzhNTMoVJfRgFF6QNpuVuo0" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 13, + "previous_hash": "jHp_K3Z2wBucMNf1LHe7KzhNTMoVJfRgFF6QNpuVuo0", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "REMEDY_VERIFYING", + "to_state": "REMEDIATED", + "reason_code": "REMEDY_VERIFIED", + "details_digest": "WoJIp-Bfok15H4mS7vGeH_-vFXeoUGuCWrbFHYBy0Pw" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "DI3eX4WFaNxtnC9RbdKXg9zXSIMKBKSmgzxukcuU_pVn6EKfyH3MD3xoCR-hGlrZ8f7bi4i8GgZTvR8RJip5Aw" + }, + "event_hash": "JKS3RohYTDfFX68CsAc8PGy7G6aG1x1puNTgdZxYu8c" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 14, + "previous_hash": "JKS3RohYTDfFX68CsAc8PGy7G6aG1x1puNTgdZxYu8c", + "event_type": "RECOURSE_FINALIZED", + "actor": "connector", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "connector_commitment_digest": "kjeRqSDyDGgcAWMuCP3Dmmhm8C6XYEZDGs9pvf0QI3s", + "status": "consumed", + "reason": "SETTLEMENT_COMPENSATED" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "HYV4jTI2G4LhfJ4xW68HZa4lYUmT2fdF_kwObG-a4qNdAKLFnnP4AFoa2_J2CTl13l7B4sQDZuxpw1sVgydyAA" + }, + "event_hash": "eZ5ZaBfeVJXam10pm-joSk6jm8CJJWfax3pHjRNHCKA" + }, + { + "schema_version": "consequence-rail/event/v0.1", + "action_id": "act_x4ZgajdJZH1SM33KmKCU", + "sequence": 15, + "previous_hash": "eZ5ZaBfeVJXam10pm-joSk6jm8CJJWfax3pHjRNHCKA", + "event_type": "STATE_TRANSITION", + "actor": "rail", + "recorded_at": "2035-01-01T00:00:00.000Z", + "payload": { + "from_state": "REMEDIATED", + "to_state": "CLOSED", + "reason_code": "SETTLEMENT_COMPENSATED", + "details_digest": "aJIn0P4iVTpBbGIALYsfE7wTKWLhRbuVUNXTRy_1TlI" + }, + "signature": { + "algorithm": "Ed25519", + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "value": "9B0Raf3cqx4uMhc0ynj8Ag3JwCA_-nYseuJUjEt5ptfIK0RE0Yw0ltzmzCdoJqiP3IpSmYrWCXE5qOg93wIlBw" + }, + "event_hash": "U66FzpJ0lvse2oBWgaTu12cA2Fi5KQ5medyZVIiZnnI" + } + ], + "trust_hint": { + "rail": { + "key_id": "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAnRbxWLLLgUyGwA8UdJVAsb12PzYDjv5C3+wE5abhq5Y=\n-----END PUBLIC KEY-----\n" + }, + "connector": { + "key_id": "demo-connector-ed25519-VcGmBF_daVE14fDB", + "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAaT5lZhTYJhmk7hzLG+me9Zs4cARL14Wa3xx+G3a74+4=\n-----END PUBLIC KEY-----\n" + }, + "warning": "An embedded key is not trusted automatically." + } + } + }, + "prove": { + "ok": true, + "result": { + "format": "MandateBoundExternalEvidenceReview/v1", + "reviewId": "review-b2d46deb1f051529", + "source": { + "sourceId": "consequence-rail", + "eventClass": "settlement" + }, + "actionId": "act_x4ZgajdJZH1SM33KmKCU", + "evidenceDigest": "sha256:c69ab0f5f762135f71a05df331e80643bc43c763ff337abd95e08f977867b070", + "receipt": { + "receiptId": "receipt_w17NzfhBcfHs_sZgFZdE", + "outcome": "compensated", + "recourseStatus": "consumed", + "eventChainHead": "U66FzpJ0lvse2oBWgaTu12cA2Fi5KQ5medyZVIiZnnI", + "actionDigest": "x4ZgajdJZH1SM33KmKCUledwyCo3Wz6Hqn6H3AasaYU" + }, + "upstream": { + "verifier": "consequence-rail:bundle-verify", + "valid": true, + "actionId": "act_x4ZgajdJZH1SM33KmKCU", + "outcome": "compensated", + "trustedKeyIds": [ + "demo-rail-ed25519-aQs_UQHBF_Y4YXOx", + "demo-connector-ed25519-VcGmBF_daVE14fDB" + ] + }, + "verdict": "recorded", + "legalEffect": "not-determined", + "reviewDigest": "sha256:2c5556eac8112a5847fc40c1d0e4f138881d1da0f59e2d00f8e9896b6b8b255a" + } + } + } +} diff --git a/scripts/integration-check.mjs b/scripts/integration-check.mjs index 1bf0380..426d40f 100644 --- a/scripts/integration-check.mjs +++ b/scripts/integration-check.mjs @@ -159,6 +159,10 @@ async function main() { } } + const compatibility = run(process.execPath, ["--test", "test/component-compatibility.test.mjs"]); + check(compatibility.status === 0, + `component compatibility failed: ${compatibility.stdout.slice(-1800)}${compatibility.stderr.slice(-500)}`); + runDemo([]); { const { manifest } = latestBundle(); diff --git a/stack-lock.json b/stack-lock.json index 6efa3d8..c86d915 100644 --- a/stack-lock.json +++ b/stack-lock.json @@ -13,7 +13,7 @@ { "name": "consequence-rail", "repository": "https://github.com/EauDoon/consequence-rail.git", - "commit": "6c61e9fdcd1a4701afad1d2371abcb3f13bbab57", + "commit": "9f60ab3223970c22371c20d3584e8330674d997c", "expected_entrypoints": [ "package.json", "cmd/crctl.js" @@ -22,7 +22,7 @@ { "name": "mandatebound", "repository": "https://github.com/EauDoon/mandatebound.git", - "commit": "e526c4c32ac61571757a98ca1a69189821c3dce7", + "commit": "b51fe137958afe26eee052c5a129e5481ccae560", "expected_entrypoints": [ "package.json", "package-lock.json", diff --git a/test/component-compatibility.test.mjs b/test/component-compatibility.test.mjs new file mode 100644 index 0000000..575d16d --- /dev/null +++ b/test/component-compatibility.test.mjs @@ -0,0 +1,65 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; +import { replayBundle, runProveRail } from "../bin/aas.mjs"; +import { canonicalJson, digest } from "../deps/consequence-rail/src/canonical.js"; +import { createDemoRuntime, buildRefundProposal, runRefundDemo } from "../deps/consequence-rail/src/demo.js"; +import { validateSettlementBundle } from "../deps/consequence-rail/src/bundle-validation.js"; +import { MemoryEventStore } from "../deps/consequence-rail/src/event-store.js"; +import { createDemoSigner, signArtifact } from "../deps/consequence-rail/src/signing.js"; +import { canonicalize } from "../deps/mandatebound/dist/canonical.js"; + +// Run after bootstrap. These fixed expectations catch compatible-looking pins +// that bring back an upstream validation or canonicalization regression. +test("pinned producers retain independent canonical byte expectations", () => { + assert.equal(canonicalJson({ "2": 2, "10": 10 }), '{"10":10,"2":2}'); + for (const codepoint of [0x2028, 0x2029]) { + const separator = String.fromCodePoint(codepoint); + assert.equal(canonicalize(separator), `"${separator}"`); + assert.equal(canonicalize({ [separator]: separator }), `{"${separator}":"${separator}"}`); + } +}); + +test("pinned rail rejects array currency at both public input boundaries", async () => { + const runtime = createDemoRuntime(); + const proposal = buildRefundProposal(runtime.clock); + proposal.parameters.currency = ["USD"]; + assert.throws(() => runtime.rail.propose(proposal), { code: "SCHEMA_INVALID" }); + assert.equal(runtime.rail.actions.size, 0); + const { bundle } = await runRefundDemo(); + bundle.action.proposal.parameters.currency = ["USD"]; + assert.throws(() => validateSettlementBundle(bundle), { code: "BUNDLE_TAMPERED" }); +}); + +test("same-case handoff refuses re-signed evidence for another currency", async () => { + const { bundle } = await runRefundDemo(); + const signer = createDemoSigner(); + bundle.outcome_evidence[0].facts.currency = "EUR"; + bundle.outcome_evidence[0] = signArtifact(bundle.outcome_evidence[0], signer); + const changedDigest = digest(bundle.outcome_evidence[0]); + bundle.evidence_manifest = [changedDigest]; + for (const event of bundle.events) { + if (event.payload?.evidence_digest) event.payload.evidence_digest = changedDigest; + } + let recordedAt; + const events = new MemoryEventStore(signer, { now: () => recordedAt }); + bundle.events = bundle.events.map((event) => { + recordedAt = event.recorded_at; + return events.append(event.action_id, event.event_type, event.actor, event.payload); + }); + bundle.settlement_receipt.evidence_digests = [changedDigest]; + bundle.settlement_receipt.event_chain_head = bundle.events.at(-1).event_hash; + bundle.settlement_receipt = signArtifact(bundle.settlement_receipt, signer); + assert.throws(() => runProveRail(bundle), (error) => + error.stage === "prove" && JSON.parse(error.stderr).code === "SEMANTIC_INVALID"); +}); + +test("ordinary synthetic case exported by the previous pins still replays", () => { + const legacy = JSON.parse(readFileSync(new URL("../fixtures/legacy-rail-review.json", import.meta.url), "utf8")); + const previous = Object.fromEntries(legacy.manifest.component_provenance.map((entry) => [entry.name, entry.commit])); + assert.equal(previous["consequence-rail"], "6c61e9fdcd1a4701afad1d2371abcb3f13bbab57"); + assert.equal(previous.mandatebound, "e526c4c32ac61571757a98ca1a69189821c3dce7"); + const result = replayBundle(legacy); + assert.equal(result.ok, true, JSON.stringify(result)); + assert.ok(result.checks.every((check) => check.passed)); +}); diff --git a/test/gui.test.mjs b/test/gui.test.mjs index 07c3397..afca543 100644 --- a/test/gui.test.mjs +++ b/test/gui.test.mjs @@ -175,7 +175,7 @@ test("GUI run accepts a rail prove mode and rejects unknown modes", async () => test("GUI page exposes a prove-mode selector defaulting to simulation", () => { const page = renderPage(); assert.match(page, /