import io

# --- 1. the hazard entry -------------------------------------------------------------------
hp = r"C:\Users\decid\Documents\projects\spt-claude-code\docs\KNOWN-HAZARDS.md"
h = io.open(hp, encoding="utf-8").read()
assert "REQ-HAZARD-COMMUNE-DROP-REARM" not in h
h = h.rstrip("\n") + """

<!-- [doc->REQ-HAZARD-COMMUNE-DROP-REARM] -->
## 9. Commune-drop detection (a trigger that is a state, not an event)

### 9.1 Trading an event trigger for a state trigger re-fires it until something remembers

- **Failure shape:** the across-commune boundary used to be triggered by a **Write tool call** —
  `tool == "Write" && file_path.ends_with("<id>-commune.md")` — which is an event: it happens once,
  and being observed once is a property of the event itself, not of anything we wrote. Keying the
  trigger on the **drop file** instead (so that a commune written with Bash, `Edit`, python or
  anything else fires the boundary at all — `REQ-COMMUNE-DROP-DETECT`) replaces that event with a
  **state**: the drop sits on disk until spt-core's watcher ingests it, which the operator measures
  at >15 seconds and many tool calls. A state-shaped trigger re-fires on every hook that reads it.
- **What re-firing costs:** a second wake-ARM self-send, a rewritten clearing latch, and ultimately
  two post-clear sequences — which **blend** rather than fail loudly (§ the v0.14.1 boundary-rename
  lesson: never emit two post-clear sequences). Nothing errors; the agent just gets a mangled
  boundary.
- **The path that can double-fire *through* the fix rather than around it:** our own `>>commune<<`
  shortcut writes the drop and arms in the same breath, so its own write is a live trigger for the
  detector on the very next hook. It has to stamp what it wrote.
- **Invariant (`REQ-HAZARD-COMMUNE-DROP-REARM`):** `state/commune-seen/<eid>.stamp` records the
  **last examined** drop identity (`<mtime_ms>:<len>`). An unchanged identity is a no-op that does
  **not read the file**; a changed or missing one triggers read → scan → arm → restamp; the drop's
  absence clears the stamp, so a stamp never outlives the file it describes.
- **Why *examined* and not *armed*:** a commune with no wake marker is the common case. Recording
  only arms would leave a 20–100KB file being re-read on every tool call for the whole ingest window
  while never arming — correct, and a cost regression nothing downstream would ever notice. Both
  halves are tested, including one test that asserts the file is stat'd and not read.
- **Lane:** adapter-side, and self-inflicted by the fix above — the price of making the detector ask
  about the commune instead of about the tool call.
"""
io.open(hp, "w", encoding="utf-8", newline="\n").write(h + "\n")

# --- 2. activate the two requirements -----------------------------------------------------
tp = r"C:\Users\decid\Documents\projects\spt-claude-code\traceable-reqs.toml"
t = io.open(tp, encoding="utf-8").read()


def sub(old, new):
    global t
    assert t.count(old) == 1, t.count(old)
    t = t.replace(old, new)


sub(
    '''required_stages = []  # PLANNED 2026-09-06 (COMMUNE-DROP-DETECT-PLAN.md) - ACTIVATE to ["impl", "unit"] at the FIRST IMPL COMMIT, not before''',
    '''required_stages = ["impl", "unit"]  # ACTIVATED 2026-09-06 at the impl commit (COMMUNE-DROP-DETECT-PLAN.md, claude-spt-bs#25). impl: hook.rs `commune_drop_path` / `hook_cwd` / `examine_commune_drop` / `arm_from_commune_drop`, called from handle_post_tool_use (Write matcher kept as the fastest leg), handle_pre_tool_use (mid-turn) and handle_stop (backstop, ordered ahead of the quiet-window check); `is_commune_write` DELETED with its guard test. unit: a_commune_drop_arms_whichever_tool_wrote_it (five instruments, the defect itself), the_pretool_leg_arms_a_drop_no_posttooluse_ever_saw, the_stop_leg_arms_and_then_holds_the_quiet_window_in_one_pass, a_drop_without_the_wake_marker_never_arms_and_is_read_once, commune_drop_path_is_per_endpoint_and_under_the_watched_dir, an_empty_endpoint_id_or_cwd_examines_nothing, the_fixture_path_is_the_one_production_builds (the fixture-reaches-the-code guard). Mutation-verified: restoring the instrument key, and deleting either the PreToolUse or the Stop leg, each fail their own test and only theirs. WAS PLANNED with stages held empty until this commit, not before''',
)
sub(
    '''required_stages = []  # PLANNED 2026-09-06 - ACTIVATE to ["impl", "unit"] with its sibling at the first impl commit.''',
    '''required_stages = ["impl", "unit"]  # ACTIVATED 2026-09-06 with its sibling. impl: `commune_seen_rel` + the examined-stamp read/restamp/clear in `examine_commune_drop`, `stamp_commune_drop`, and the shortcut's stamp of its own write in `scan_and_dispatch`. unit: one_drop_arms_exactly_once_across_every_leg_and_every_hook, an_unchanged_drop_is_stat_but_never_re_read (the COST half), a_second_different_drop_at_the_same_path_arms_again (the stamp is a memory, not a latch), an_ingested_drop_forgets_its_stamp, the_commune_shortcut_stamps_its_own_write_so_the_detector_does_not_arm_twice. Mutation-verified four ways: removing the stamp guard, stamping ARMED instead of EXAMINED, dropping the shortcut's stamp, and letting the stamp outlive the file each fail their own tests. docs/KNOWN-HAZARDS.md § 9.1. WAS PLANNED with stages held empty until this commit.''',
)
io.open(tp, "w", encoding="utf-8", newline="\n").write(t)
print("ok")
