import io


def rw(path, pairs):
    s = io.open(path, 'r', encoding='utf-8', newline='').read()
    out = s
    for old, new in pairs:
        assert old in out, 'anchor missing in %s: %r' % (path, old[:70])
        out = out.replace(old, new, 1)
    assert out != s
    io.open(path, 'w', encoding='utf-8', newline='').write(out)
    print('patched', path)


# ---- 1. removal_check takes a TAG so the pre and post readings stay distinct ----
OLD_SIG = """removal_check() { # -> sets REMOVAL_CHECK
  local out rc absent present unread readings ctl_stores ctl_ok active_present persist_present psha
  REMOVAL_CHECK=UNREADABLE
  out="$R/rule-probe-post.out\""""
NEW_SIG = """removal_check() { # TAG -> sets REMOVAL_CHECK
  # TAGGED, because this now runs TWICE and the two readings are different facts: one BEFORE any
  # teardown is asked for (is there anything to remove at all?) and one AFTER (did the removal
  # happen?). A single un-tagged transcript would let a reader take one for the other.
  local tag="${1:-post}" out rc absent present unread readings ctl_stores ctl_ok active_present persist_present psha
  REMOVAL_CHECK=UNREADABLE
  out="$R/rule-probe-$tag.out\""""

OLD_EXIT = '  exits "rule_probe_post_EXIT=$rc"'
NEW_EXIT = '  exits "rule_probe_${tag}_EXIT=$rc"'

OLD_REC = '  record "REMOVAL_PROBE readings='
NEW_REC = '  record "REMOVAL_PROBE($tag) readings='

# ---- 2. the pre-teardown gate: nothing is removed on a MARKER alone ----
OLD_GATE = """    # VERIFIED A SECOND TIME, IMMEDIATELY BEFORE USE (doyle 2026-09-13). The pre-GO reading
    # was about the file as it stood then; these are the bytes about to be handed to an
    # elevated leg, and only a reading taken here can speak for them.
    if ! teardown_pin_verify pre-dispatch; then"""

NEW_GATE = """    # NOTHING IS REMOVED ON A MARKER ALONE (doyle GUS4EENB). fw_mutation_may_have_occurred is
    # set BEFORE the setup is dispatched, deliberately, so that a REFUSED setup still tears
    # down. It is therefore a statement about what this run INTENDED, never about what the host
    # holds -- the same confusion that made IR-124: in run 20260913T041410Z the setup was
    # DECLINED, nothing was ever created, and the marker still drove a command.
    #
    # THE MARKER DECIDES WHETHER THE QUESTION IS ASKED. THE MEASUREMENT DECIDES WHETHER A
    # COMMAND IS REQUESTED. Same shape as the listener gate above, and the same three answers:
    #   ABSENT (CONFIRMED)  -- there is nothing to remove: NO dispatch. The disposition is
    #                          NOTHING_TO_REMOVE, which is a measurement, not a removal.
    #   UNREADABLE          -- attribution could not be established: NO dispatch, PENDING/review.
    #                          An unreadable host is not an empty one and is not a licence either.
    #   anything present    -- there IS something to remove: verify the pin and dispatch.
    removal_check pre-teardown
    REMOVAL_PRE="$REMOVAL_CHECK"
    if [ "$REMOVAL_PRE" = CONFIRMED ]; then
      TEARDOWN_DISPATCH=NOT_NEEDED_MEASURED_ABSENT
      CLEANUP_VERDICT=NOTHING_TO_REMOVE
      record "TEARDOWN_NOT_NEEDED - both owned names measured ABSENT in both stores before any teardown was asked for, with each store's controls alive in the same sample. NO command is dispatched: asking an elevated leg to remove what is not there is how residue gets manufactured."
    elif [ "${REMOVAL_PRE#UNREADABLE}" != "$REMOVAL_PRE" ]; then
      TEARDOWN_DISPATCH=WITHHELD_UNREADABLE_STATE
      CLEANUP_VERDICT=PENDING
      record "TEARDOWN_WITHHELD state=$REMOVAL_PRE - the owned rules could not be read either way before teardown. NO command is dispatched and nothing is spawned; disposition PENDING/review."
      cfail "teardown withheld: the pre-teardown reading was $REMOVAL_PRE. This is a REFUSAL TO ACT on an unreadable measurement, not a failed action."
    # VERIFIED A SECOND TIME, IMMEDIATELY BEFORE USE (doyle 2026-09-13). The pre-GO reading
    # was about the file as it stood then; these are the bytes about to be handed to an
    # elevated leg, and only a reading taken here can speak for them.
    elif ! teardown_pin_verify pre-dispatch; then"""

# ---- 3. the post reading keeps its own tag ----
OLD_POST = """      removal_check
      record "TEARDOWN_CHECK receipt_exit="""
NEW_POST = """      removal_check post-teardown
      record "TEARDOWN_CHECK receipt_exit="""

# ---- 4. the final verdict rule learns the measured-absent terminal state ----
OLD_FINAL = """  if [ "$CLEANUP_VERDICT" != VERIFIED ] || [ "$RESIDUE_COUNT" != 0 ] || [ "${LISTENER_STATE:-UNMEASURED}" != ABSENT ]; then
    CLEANUP_VERDICT=PENDING
  fi"""
NEW_FINAL = """  # NOTHING_TO_REMOVE IS A TERMINAL MEASURED STATE, NOT A WEAK 'VERIFIED' (doyle GUS4EENB).
  # A run whose setup was declined created nothing, so no removal could be measured and VERIFIED
  # is not available to it -- but neither is PENDING/review the truth, because there is nothing
  # outstanding. It still has to clear the SAME two other facts: no residual process of this
  # home, and no listener still bound. Either of those forces PENDING exactly as before.
  if { [ "$CLEANUP_VERDICT" != VERIFIED ] && [ "$CLEANUP_VERDICT" != NOTHING_TO_REMOVE ]; } \\
     || [ "$RESIDUE_COUNT" != 0 ] || [ "${LISTENER_STATE:-UNMEASURED}" != ABSENT ]; then
    CLEANUP_VERDICT=PENDING
  fi"""

rw('_part_cleanup.sh', [(OLD_SIG, NEW_SIG), (OLD_EXIT, NEW_EXIT), (OLD_REC, NEW_REC),
                        (OLD_GATE, NEW_GATE), (OLD_POST, NEW_POST), (OLD_FINAL, NEW_FINAL)])

# REMOVAL_PRE joins the reported state
rw('_part_cleanup.sh', [("REMOVAL_CHECK=NOT_REACHED        # the INDEPENDENT post-removal reading, by name and by store",
                         "REMOVAL_CHECK=NOT_REACHED        # the INDEPENDENT reading, by name and by store\nREMOVAL_PRE=NOT_REACHED          # the same reading taken BEFORE any teardown was asked for")])
