{
  "schema": "https://schemas.twoaye.com/protected-resource/threat-cases/v1.json",
  "schemaVersion": "1.0",
  "note": "The hostile suite a protected resource must survive. Each case names the test that asserts it against the reference platform, and passing those proves the platform refuses - it does not prove a resource refuses. The milestone is a resource refusing across a real process boundary, with its own database, key material, hostname and process. Eleven of these now run against the protected receiver over a real TLS socket - see runAgainstAResource - and what remains is deployment rather than design. A resource conforms when every case below is refused and the refusal carries the wire code stated in CONFORMANCE.md section 7 - never the reason.",
  "howToRun": "cd verid-platform && dotnet test --filter ThreatCaseTests (platform side) and --filter ReceiverThreatCaseTests (across a TLS socket into apps/protected-receiver)",
  "runAgainstAResource": "Eleven of these now run against the protected receiver over real TLS on a loopback port: its own Kestrel, its own certificate pinned by hash, its own ledger file and receipt key, reached by an ordinary HTTP client. The receiver verifies every grant locally and nothing in that suite asks the platform whether a grant is genuine. T8, T11 and T12 are asserted platform-side only, because that receiver is bound to one agent, one contract and one tenant by configuration - there is no second identity to present. What remains between this and the milestone is deployment: a separate host, a separate database, a separate operator.",
  "cases": [
    {
      "id": "T1",
      "test": "T1_a_changed_amount_is_refused_at_redemption",
      "name": "amount changed after issue",
      "attack": "The grant was issued for 13800.00. The request presents 99999.00 with the same grant.",
      "mustRefuseWith": "unauthorized",
      "why": "The grant binds a hash of the material parameters. A resource that checks the signature and then reads the body has verified that somebody was authorized to do something, not that they were authorized to do this."
    },
    {
      "id": "T2",
      "test": "T2_the_same_amount_with_a_different_scale_is_refused",
      "name": "amount scale normalised",
      "attack": "13800.00 presented as 13800. The same number.",
      "mustRefuseWith": "unauthorized",
      "why": "Amounts are hashed as text, so the scale is part of what was authorized. This is the case an implementation that parses to a decimal and re-renders fails - and it fails it on every legitimate request rather than on an attack, which is why it is listed separately."
    },
    {
      "id": "T3",
      "test": "T3_a_changed_counterparty_is_refused",
      "name": "counterparty changed",
      "attack": "The same amount, to a different recipient.",
      "mustRefuseWith": "unauthorized",
      "why": "The recipient is the parameter an attacker most wants to change and the one a limit-based control does not notice."
    },
    {
      "id": "T4",
      "test": "T4_a_changed_currency_is_refused",
      "name": "currency changed",
      "attack": "13800.00 USD authorized, 13800.00 EUR presented.",
      "mustRefuseWith": "unauthorized",
      "why": "A numeric limit compared without its currency is not a limit."
    },
    {
      "id": "T5",
      "test": "T5_a_changed_action_is_refused",
      "name": "action changed",
      "attack": "A grant for a renewal presented against a refund.",
      "mustRefuseWith": "unauthorized",
      "why": "The action is material. A grant that covers whichever operation it is presented against is a capability, not an authorization."
    },
    {
      "id": "T6",
      "test": "T6_a_changed_tool_is_refused",
      "name": "tool changed",
      "attack": "A grant for the billing tool presented to payroll.",
      "mustRefuseWith": "unauthorized",
      "why": "This is the audience check. A grant accepted by whichever resource receives it is a bearer token."
    },
    {
      "id": "T7",
      "test": "T7_a_changed_environment_is_refused",
      "name": "environment changed",
      "attack": "A production grant presented against development, or the reverse.",
      "mustRefuseWith": "unauthorized",
      "why": "The same action means different things in each, and a development grant that works in production is the shortest path from a test account to a real effect."
    },
    {
      "id": "T8",
      "test": "T8_another_agent_cannot_spend_the_grant",
      "name": "presented by another agent",
      "attack": "A valid grant, intact parameters, presented by an agent it does not name.",
      "mustRefuseWith": "unauthorized",
      "why": "A grant that any caller can spend is a credential, and credentials leak."
    },
    {
      "id": "T9",
      "test": "T9_a_grant_cannot_be_spent_twice",
      "name": "replay",
      "attack": "The same grant presented a second time after a successful execution.",
      "mustRefuseWith": "conflict",
      "why": "Single use. This is the one refusal a well-behaved caller needs to distinguish, because retrying is the wrong response - which is why it has its own wire code. The refusal must also be recorded: a replay that leaves no trace is the event most worth having and the easiest to drop."
    },
    {
      "id": "T10",
      "test": "T10_an_expired_grant_is_refused",
      "name": "expired",
      "attack": "A grant presented after its expiry.",
      "mustRefuseWith": "unauthorized",
      "why": "Grants live around sixty seconds. A resource that widens the window to absorb a broken clock widens the replay window by the same amount."
    },
    {
      "id": "T11",
      "test": "T11_a_grant_is_refused_after_its_contract_is_paused",
      "name": "authority withdrawn after issue",
      "attack": "A grant issued while the contract was active, presented after it was paused.",
      "mustRefuseWith": "unauthorized",
      "why": "Authority can be withdrawn in the seconds between issue and use, and that window is exactly when somebody who has noticed a problem acts. A resource that checks only the signature and the expiry will execute it."
    },
    {
      "id": "T12",
      "test": "T12_another_tenants_grant_is_not_reachable",
      "name": "cross-tenant",
      "attack": "A grant from one tenant presented to a resource serving another.",
      "mustRefuseWith": "unauthorized",
      "why": "Refused in both directions, and the refusal says nothing about whether the grant exists."
    },
    {
      "id": "T13",
      "test": "T13_many_simultaneous_redemptions_produce_exactly_one_execution",
      "name": "concurrent replay",
      "attack": "One hundred redemptions of a single grant, at once.",
      "mustRefuseWith": "conflict",
      "expect": "exactly 1 execution, 99 refusals",
      "why": "The case that decides whether single use is a property or a hope. Every one of these would pass verification, because verification is local and they are all the same valid grant - only an atomic compare-and-swap decides a winner. A resource that redeems with a read, a decision and a write satisfies the interface and loses this test. It is the strongest case in this file, and across a real network and process boundary it is the one worth publishing a result for."
    }
  ],
  "notCoveredHere": [
    "Receiver admission bypass, and the authority-epoch refusal, are enforced in the executor chain rather than at redemption. They have their own tests and belong in this file once a resource implements them.",
    "A forged or re-signed grant is covered by vectors.v1.json rather than here: it is a verification case, and these are redemption cases.",
    "An altered approval display digest is refused by the device statement and has its own interop fixture. A resource does not see it."
  ]
}
