{
  "$comment": "The rogue scenarios with stable IDs (architecture v0.2 §7), so that \"n of how many\" has a denominator. `where` is demo or ci; `refuses` names the side that refuses; `expected` is the reason code and, from check 2 on, the ledger entry the refusal writes. `seller_status` says what agent-bench does today: `test` (a test exists), `partial`, or `pending` with what it waits on. The five attacks from the reference threat model that are documented as NOT TESTED are listed last; their source IDs are to be copied from the program's threat model when the contracts repo is created (question 10 of the Tuesday review).",
  "scenarios": [
    {
      "id": "R-01",
      "name": "Lookalike seller with a copied card",
      "where": "demo",
      "refuses": "buyer",
      "expected": {
        "code": "dane_mismatch|card_digest_mismatch|record_signature_invalid",
        "entry": "counterparty.verified (failed)"
      },
      "seller_status": "n/a (buyer-side); the lookalike domain is registered by the rogue owner after telling the ANS registry team"
    },
    {
      "id": "R-02",
      "name": "Same name, swapped endpoint",
      "where": "ci",
      "refuses": "buyer",
      "expected": {
        "code": "card_digest_mismatch|dane_mismatch"
      },
      "seller_status": "n/a"
    },
    {
      "id": "R-03",
      "name": "New certificate on the name",
      "where": "ci",
      "refuses": "buyer",
      "expected": {
        "code": "dane_mismatch"
      },
      "seller_status": "n/a; the seller's watcher catches the same drift from its side (dane_matches_served)"
    },
    {
      "id": "R-04",
      "name": "Mandate for another merchant",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "merchant_mismatch",
        "step": 6,
        "entry": "request.refused"
      },
      "seller_status": "test (mandate.test.mjs, checks 5 to 8) Since 2026-09-13 also in the AP2 v0.2 form: checkout.allowed_merchants without us, a payee that is not us, or an acceptable item that is not the quote's offer (ap2.test.mjs, ap2-vector.test.mjs)."
    },
    {
      "id": "R-05",
      "name": "Mandate without the matching key (cnf)",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "key_binding_mismatch",
        "step": 9,
        "entry": "request.refused"
      },
      "seller_status": "test: cnf.jkt against the envelope signer, act.sub against the envelope iss, and the quote against the key it was issued to. Settled 2026-09-12: a mandate with NO cnf at all is refused here too, as key_binding_mismatch at check 9, not as mandate_invalid at check 4; the schema makes cnf required, but the seller's check 4 verifies signature, issuer and the naming claims, and it is check 9 that reads cnf, so an absent cnf is a binding that fails. The buyer folds both cases into key_binding_mismatch for the same reason Since 2026-09-13 also in the AP2 v0.2 form: a hop signed by a key other than the root's cnf.jwk, a root bound to another key, a hop kid naming another key (ap2.test.mjs, ap2-vector.test.mjs)."
    },
    {
      "id": "R-06",
      "name": "Replay of a consumed mandate (jti)",
      "where": "demo",
      "refuses": "seller",
      "expected": {
        "code": "mandate_consumed",
        "step": 9,
        "entry": "request.refused"
      },
      "seller_status": "test on both paths; verified live 07/09 on the self-issued path Since 2026-09-13 also in the AP2 v0.2 form: the pair's shared jti spent once (ap2.test.mjs)."
    },
    {
      "id": "R-07",
      "name": "Body edited after signing",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "signature_invalid",
        "step": 2,
        "entry": "request.refused"
      },
      "seller_status": "test: body_sha256 in the envelope"
    },
    {
      "id": "R-08",
      "name": "Amount raised after the mandate (quote hash mismatch)",
      "where": "demo",
      "refuses": "seller",
      "expected": {
        "code": "quote_hash_mismatch",
        "step": 7,
        "entry": "request.refused"
      },
      "seller_status": "test: the mandate's quote_hash and a stated quote_hash are both held to the recomputation Since 2026-09-13 also in the AP2 v0.2 form: another checkout_jwt, another checkout_hash, another transaction_id, a raised amount on the open or the closed payment mandate (ap2.test.mjs, ap2-vector.test.mjs)."
    },
    {
      "id": "R-09",
      "name": "Currency or action changed",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "currency_mismatch|mandate_invalid",
        "step": 6,
        "entry": "request.refused"
      },
      "seller_status": "test (currency_mismatch; action other than purchase is mandate_invalid) Since 2026-09-13 also in the AP2 v0.2 form (ap2.test.mjs, ap2-vector.test.mjs)."
    },
    {
      "id": "R-10",
      "name": "Revoked mandate and revoked ANS identity, live",
      "where": "demo",
      "refuses": "seller",
      "expected": {
        "code": "mandate_revoked|identity_revoked",
        "step": 10,
        "entry": "request.refused"
      },
      "seller_status": "partial: mandate_revoked is a test (check 10 against GET <issuer>/mandates/<jti>); identity_revoked waits on the ANS revocation mechanics, to be confirmed with the ANS registry team Since 2026-09-13 also in the AP2 v0.2 form: the pair's jti revoked at the authority (ap2.test.mjs, check 10 unchanged)."
    },
    {
      "id": "R-12",
      "name": "Delegation attempt (act chain)",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "mandate_invalid",
        "step": 4,
        "entry": "request.refused"
      },
      "seller_status": "test: act.act is refused, not walked Since 2026-09-13 also in the AP2 v0.2 form: an intermediate kb+sd-jwt+kb hop, a third token in the chain, act.act in the open content, a terminal hop carrying cnf (ap2.test.mjs, ap2-vector.test.mjs)."
    },
    {
      "id": "R-13",
      "name": "Prompt injection planted in a product description",
      "where": "ci",
      "refuses": "seller (sanitizes), buyer (never treats text as instructions)",
      "expected": {
        "code": null
      },
      "seller_status": "test: third-party text reaches the buyer stripped and cut (app.test.mjs); the buyer's side is the buyer's"
    },
    {
      "id": "R-18",
      "name": "Authority unreachable",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "authority_unavailable",
        "step": 10,
        "entry": "request.refused",
        "invariant": "no PaymentIntent; the mandate stays spendable when the authority returns"
      },
      "seller_status": "test: fails closed after one retry; the jti claim is released"
    },
    {
      "id": "R-20",
      "name": "Oversized payload and rate abuse",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "request_too_large|rate_limited",
        "step": 1,
        "entry": "none; a counter"
      },
      "seller_status": "test (413/415/405 dropped, counted); rate_limited is the edge rule, not yet created"
    },
    {
      "id": "R-21",
      "name": "alg: none",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "header_forbidden",
        "step": 2,
        "entry": "request.refused"
      },
      "seller_status": "test on the authorization, the envelope and the mandate; vector in vectors/negative/R-21.json"
    },
    {
      "id": "R-22",
      "name": "alg: HS256 with the public key as the secret",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "header_forbidden",
        "step": 2,
        "entry": "request.refused"
      },
      "seller_status": "test; vector in vectors/negative/R-22.json"
    },
    {
      "id": "R-23",
      "name": "jku pointing at a rogue-controlled key set",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "header_forbidden",
        "step": 2,
        "entry": "request.refused"
      },
      "seller_status": "test; vector in vectors/negative/R-23.json"
    },
    {
      "id": "R-24",
      "name": "Refused requests carrying the legitimate run's correlation id",
      "where": "ci",
      "refuses": "verifier (join stays correct)",
      "expected": {
        "code": null,
        "entry": "request.refused entries from an unknown signer, counted as noise"
      },
      "seller_status": "partial: refusal entries carry source, actor, buyer and buyer_kid, so the verifier can tell them apart; the join is the verifier's"
    },
    {
      "id": "R-25",
      "name": "WhatsApp message from an unknown sender",
      "where": "ci",
      "refuses": "buyer",
      "expected": {
        "code": "sender_unknown",
        "entry": "none; a counter"
      },
      "seller_status": "n/a"
    },
    {
      "id": "R-26",
      "name": "The same jti fired twice in parallel",
      "where": "ci",
      "refuses": "seller",
      "expected": {
        "code": "mandate_consumed",
        "step": 9,
        "entry": "request.refused",
        "invariant": "exactly one PaymentIntent"
      },
      "seller_status": "test on both paths; verified live 07/09 and in the Durable Object runtime"
    },
    {
      "id": "R-27",
      "name": "Validly signed card declaring an endpoint outside the zone",
      "where": "ci",
      "refuses": "buyer",
      "expected": {
        "code": "endpoint_out_of_zone"
      },
      "seller_status": "n/a (POC profile rule on the buyer)"
    },
    {
      "id": "R-28",
      "name": "Equivocation: two divergent chains served to two verifiers",
      "where": "ci (verifier: two heads for one height, each registered at a log the keeper does not operate); live, the emulator since 2026-09-11 and a public transparency log beside it since 2026-09-13",
      "refuses": "verifier (sealed checkpoint)",
      "expected": {
        "code": null
      },
      "seller_status": "partial: checkpoints are signed at the end of every checkout and on a timer, sealed at WITNESS_URL (transparency service when confirmed; else the verifier's emulator, receipts marked emulator: true), published at GET /checkpoints; the emulator's log records two heads for one height as equivocation_with. Since 2026-09-13 each head is also registered, behind a flag, at a public transparency log the keeper does not operate (external-witness.md): a keeper that serves two chains has committed both heads to a log it cannot edit, and the verifier's test holds two checkpoints for one height, each with a receipt that verifies, and names the equivocation with both leaves. The two-verifier scene is not built"
    },
    {
      "id": "R-29",
      "name": "Envelope signed by a key its domain has revoked",
      "where": "ci; live as the identity half of the revocation rehearsal (F-07b)",
      "refuses": "seller",
      "expected": {
        "code": "identity_revoked",
        "step": 3,
        "entry": "request.refused"
      },
      "seller_status": "test (mandate.test.mjs, check 3): the domain's key-revocation+jwt read beside the attestations; refused after the signature verified, so the entry names the instance; a revocation the bundle did not sign is ignored. Live since 2026-09-13 (evening): the verifier times the publication to the first refusal on its own clock (revocation dimension, identity half)."
    }
  ],
  "also_tested": [
    {
      "id": "S-01",
      "name": "Envelope timestamp outside the 60 s window",
      "expected": {
        "code": "timestamp_out_of_window",
        "step": 2
      },
      "seller_status": "test"
    },
    {
      "id": "S-02",
      "name": "Nonce replayed under the same key",
      "expected": {
        "code": "nonce_replayed",
        "step": 2
      },
      "seller_status": "test; per signer"
    },
    {
      "id": "S-03",
      "name": "Envelope for another audience, method or path",
      "expected": {
        "code": "audience_mismatch|signature_invalid",
        "step": 2
      },
      "seller_status": "test"
    },
    {
      "id": "S-04",
      "name": "Key nobody attests; attestation by an issuer outside the bundle; expired attestation; attestation for another instance; trust domain not admitted",
      "expected": {
        "code": "signer_unknown",
        "step": 3
      },
      "seller_status": "test"
    },
    {
      "id": "S-05",
      "name": "X509-SVID chain: valid leaf for another workload; chain to a CA the bundle does not publish; chain pasted onto another key's signature; x5c anywhere but the attestation",
      "expected": {
        "code": "attestation_invalid|header_forbidden",
        "step": 3
      },
      "seller_status": "test, with openssl fixtures; X509Certificate confirmed in workerd on 07/09"
    },
    {
      "id": "S-06",
      "name": "Signed envelope checking out without a mandate; intent-stage mandate at checkout; expired or not-yet-valid mandate; mandate the authority does not know",
      "expected": {
        "code": "mandate_missing|mandate_invalid|mandate_expired|mandate_not_yet_valid",
        "step": "4, 6, 8, 10"
      },
      "seller_status": "test"
    }
  ],
  "not_tested": [
    {
      "source_id": "TBD",
      "name": "Token theft without the key",
      "reason": "The signed envelope binds every request to the stable key; a stolen mandate without the key fails check 9 (cnf.jkt). Documented, not exercised as a scenario; the binding itself is tested (R-05)."
    },
    {
      "source_id": "TBD",
      "name": "Credential forwarding",
      "reason": "Audience binding (check 5) and cnf (check 9) make a forwarded mandate useless; documented, not exercised."
    },
    {
      "source_id": "TBD",
      "name": "Ledger deletion",
      "reason": "Sealed checkpoints would show the gap. Seller and buyer chains are sealed at the verifier's emulator since 2026-09-11; the authority's chain is not. Documented as the defence, not exercised."
    },
    {
      "source_id": "TBD",
      "name": "Protocol downgrade",
      "reason": "One protocol version per interface, fixed in the card and the freeze manifest; no negotiation exists to downgrade."
    },
    {
      "source_id": "TBD",
      "name": "Malicious MCP tool",
      "reason": "The buyer never executes seller-provided tool definitions: it reads tools/list for schemas only, and R-13 covers the injection path."
    }
  ]
}
