{
  "protocol": "aml-guarded-tool-rehearsal/2",
  "provenance": "project-authored local Node simulation",
  "scope": "Fixture callbacks only. No message, human approval, provider call, or delivery evidence.",
  "cases": [
    {
      "id": "host_allow",
      "title": "Exact route, host allow rule",
      "change": "The host's exact rule allows this local callback without an approval request or one-use claim.",
      "host_move": "Reserve approval-free rules for deliberate host policy. Bind the actual destination to the rule and apply host authentication, audit, and operation idempotency where needed.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "allow",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "dispatched",
          "reason": "host_dispatch_returned",
          "host_hook_events": [
            "Fixture callback entered",
            "Fixture callback returned"
          ],
          "result": {
            "simulated": true,
            "would_send_to": "team@example.test"
          }
        }
      ],
      "approval_requests": 0,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 1,
      "fixture_state_changes": 0
    },
    {
      "id": "approved_route",
      "title": "Exact route, simulated approval",
      "change": "The host fixture approves the exact proposal and consumes its one-use claim.",
      "host_move": "Replace the fixture hooks with an authenticated approval, a durable one-use claim, and the actual host service call.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "dispatched",
          "reason": "host_dispatch_returned",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim consumed",
            "Fixture callback entered",
            "Fixture callback returned"
          ],
          "result": {
            "simulated": true,
            "would_send_to": "team@example.test"
          }
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 1,
      "claims_consumed": 1,
      "callback_count": 1,
      "fixture_state_changes": 0
    },
    {
      "id": "changed_destination",
      "title": "Changed destination",
      "change": "The requested destination is outside the one exact host rule.",
      "host_move": "Derive the policy resource from the exact provider destination and keep the rule narrow.",
      "destination": "other@example.test",
      "attempts": [
        {
          "policy_decision": "deny",
          "proposal_sha256": "31b00be0f1d6df1499933e54d3cebee5570468ee2549a27a9c90a2ca8385b01e",
          "execution_status": "blocked",
          "reason": "no_exact_rule",
          "host_hook_events": []
        }
      ],
      "approval_requests": 0,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "mapper_mutated",
      "title": "Host mapper mutates the proposal",
      "change": "The host destination mapper changes its canonical argument snapshot. The adapter throws before producing a plan or dispatch receipt.",
      "host_move": "Keep resourceFor synchronous and read-only. Treat mapper failures as host errors before planning; never present them as blocked dispatch receipts.",
      "destination": "team@example.test",
      "attempts": [
        {
          "adapter_error": "resourceFor must not mutate arguments",
          "host_hook_events": [
            "Host mapper changed its argument snapshot"
          ]
        }
      ],
      "approval_requests": 0,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "mapper_write_error",
      "title": "Host mapper writes, then throws",
      "change": "The host destination mapper writes an in-memory fixture marker, then throws before an action plan or dispatch receipt exists.",
      "host_move": "Keep resourceFor read-only. If a mapper fails after changing host state, reconcile that state in the host error path; no receipt does not mean no local effect.",
      "destination": "team@example.test",
      "attempts": [
        {
          "adapter_error": "simulated_mapper_error",
          "host_hook_events": [
            "In-memory mapper fixture marker written",
            "Host mapper threw"
          ]
        }
      ],
      "approval_requests": 0,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 1
    },
    {
      "id": "invalid_arguments",
      "title": "Non-finite tool arguments",
      "change": "The caller supplies a non-finite number. Canonical capture rejects it, returning a blocked receipt with no proposal digest before the host mapper runs.",
      "host_move": "Validate tool arguments at the host boundary. Preserve the blocked input receipt and do not claim it has a proposal digest or that a provider call was attempted.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "deny",
          "proposal_sha256": null,
          "execution_status": "blocked",
          "reason": "AML_CANONICAL_JSON_NON_FINITE_NUMBER",
          "host_hook_events": []
        }
      ],
      "approval_requests": 0,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "approval_unavailable",
      "title": "No approval hook",
      "change": "The host supplies no way to request approval.",
      "host_move": "Supply an authenticated approval callback for approval-required rules, bound to this exact proposal.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "approval_unavailable",
          "host_hook_events": []
        }
      ],
      "approval_requests": 0,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "approval_declined",
      "title": "Approval request declined",
      "change": "The approval fixture refuses the exact proposal. No one-use claim or tool callback runs.",
      "host_move": "Treat a refusal as final for this attempt. A later attempt needs a new authenticated approval decision; never turn a refusal into an implicit grant.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "approval_missing_or_mismatched",
          "host_hook_events": [
            "Approval requested",
            "Fixture approval declined"
          ]
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "approval_hook_error",
      "title": "Approval hook throws after a local write",
      "change": "The approval fixture writes an in-memory marker, then throws before returning a decision. No one-use claim or tool callback runs.",
      "host_move": "Reconcile host authorization records after an approval hook error. A blocked tool receipt does not prove that the approval system recorded nothing; require a fresh verified decision for another attempt.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "approval_error",
          "host_hook_events": [
            "Approval requested",
            "In-memory approval fixture marker written",
            "Approval hook threw"
          ]
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 1
    },
    {
      "id": "approval_mismatch",
      "title": "Approval for another proposal",
      "change": "The approval fixture returns a different proposal digest.",
      "host_move": "Compare the approved digest with the frozen proposal before any claim or callback.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "approval_missing_or_mismatched",
          "host_hook_events": [
            "Approval requested",
            "Different digest returned"
          ]
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "approval_reused",
      "title": "One-use approval claimed twice",
      "change": "The same proposal is attempted twice; the host consumes the claim once.",
      "host_move": "Use an atomic, durable one-use grant store so a replay cannot invoke the callback again.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "dispatched",
          "reason": "host_dispatch_returned",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim consumed",
            "Fixture callback entered",
            "Fixture callback returned"
          ],
          "result": {
            "simulated": true,
            "would_send_to": "team@example.test"
          }
        },
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "approval_reused_or_expired",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim refused"
          ]
        }
      ],
      "approval_requests": 2,
      "claim_attempts": 2,
      "claims_consumed": 1,
      "callback_count": 1,
      "fixture_state_changes": 0
    },
    {
      "id": "policy_revoked",
      "title": "Rule revoked during approval",
      "change": "The host removes the rule while the approval callback is pending.",
      "host_move": "Recheck current host policy after asynchronous approval and before consuming a claim.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "policy_changed",
          "host_hook_events": [
            "Approval requested",
            "Host rule revoked",
            "Matching digest returned"
          ]
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 0,
      "claims_consumed": 0,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "arguments_mutated",
      "title": "Caller changes the destination during approval",
      "change": "The caller changes its original arguments after the proposal snapshot. The fixture callback still receives the approved destination.",
      "host_move": "Capture canonical arguments once, then map and dispatch from that frozen proposal.",
      "destination": "team@example.test",
      "caller_destination_after_approval": "other@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "dispatched",
          "reason": "host_dispatch_returned",
          "host_hook_events": [
            "Approval requested",
            "Caller arguments changed after snapshot",
            "Matching digest returned",
            "One-use claim consumed",
            "Fixture callback entered",
            "Fixture callback returned"
          ],
          "result": {
            "simulated": true,
            "would_send_to": "team@example.test"
          }
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 1,
      "claims_consumed": 1,
      "callback_count": 1,
      "fixture_state_changes": 0
    },
    {
      "id": "claim_revoked",
      "title": "Rule revoked during the one-use claim",
      "change": "The host consumes the claim, then revokes the rule before dispatch. The callback stays untouched and the claim remains spent.",
      "host_move": "Recheck policy after the claim; keep the consumed grant spent and require a fresh decision for another attempt.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "policy_changed",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim consumed",
            "Host rule revoked after claim"
          ]
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 1,
      "claims_consumed": 1,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "claim_hook_unknown",
      "title": "Claim hook throws after consumption",
      "change": "The fixture spends the one-use claim, then its claim hook throws before returning. Dispatch blocks without entering the tool callback.",
      "host_move": "Reconcile the host grant store after a claim error. A blocked receipt does not prove that the one-use grant is still available.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "approval_consume_error",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim consumed",
            "Claim hook threw after consumption"
          ]
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 1,
      "claims_consumed": 1,
      "callback_count": 0,
      "fixture_state_changes": 0
    },
    {
      "id": "callback_unknown",
      "title": "Callback throws after entry",
      "change": "The local callback writes an in-memory fixture marker, then throws. The wrapper cannot claim that no side effect occurred.",
      "host_move": "Record the uncertain result and use provider idempotency and reconciliation; never retry automatically from this receipt.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "unknown",
          "reason": "host_dispatch_error",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim consumed",
            "Fixture callback entered",
            "In-memory fixture marker written",
            "Fixture callback threw"
          ]
        }
      ],
      "approval_requests": 1,
      "claim_attempts": 1,
      "claims_consumed": 1,
      "callback_count": 1,
      "fixture_state_changes": 1
    },
    {
      "id": "unknown_then_replay",
      "title": "Unknown result, replay claim refused",
      "change": "The callback writes one in-memory fixture marker, then throws. A second attempt using the same one-use fixture claim state blocks before callback entry.",
      "host_move": "Reconcile the uncertain provider outcome before any new authorization. A spent grant cannot justify a retry; use host idempotency for the actual operation.",
      "destination": "team@example.test",
      "attempts": [
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "unknown",
          "reason": "host_dispatch_error",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim consumed",
            "Fixture callback entered",
            "In-memory fixture marker written",
            "Fixture callback threw"
          ]
        },
        {
          "policy_decision": "requires_approval",
          "proposal_sha256": "f416748534da5982a029d03d6db48adf00b4138e0785d38857f0b1734923776c",
          "execution_status": "blocked",
          "reason": "approval_reused_or_expired",
          "host_hook_events": [
            "Approval requested",
            "Matching digest returned",
            "One-use claim refused"
          ]
        }
      ],
      "approval_requests": 2,
      "claim_attempts": 2,
      "claims_consumed": 1,
      "callback_count": 1,
      "fixture_state_changes": 1
    }
  ]
}
