import io
def edit(p, pairs):
    s=io.open(p,encoding='utf-8',newline='').read(); nl='\r\n' if '\r\n' in s else '\n'
    for old,new in pairs:
        old=old.replace('\n',nl); new=new.replace('\n',nl)
        assert s.count(old)==1,(p,s.count(old),old[:70]); s=s.replace(old,new)
    io.open(p,'w',encoding='utf-8',newline='').write(s)
p='traceable-reqs.toml'
s=io.open(p,encoding='utf-8',newline='').read(); nl='\r\n'
i=s.index('id = "REQ-CI-PUSH-DOCS-ONLY-THIN"'); j=s.index(nl+nl,i)
blk=nl.join(['','[[requirements]]','id = "REQ-CI-CLASSIFY-PROBE-WITNESS"',
 'title = "IR-148 (hertz 2026-09-24, doyle-approved): the change classifier logs its own mechanism. Beside every CLASSIFY line it prints CLASSIFY_PROBE connected_at=<step0|deepen50|deepen500|none|not-probed> shallow=<true|false|n/a>, naming the step at which before..sha connected and whether the graph was shallow AT DECISION TIME. That shallow value is read ONCE, and the same value drives the before-undecidable/before-not-ancestor split, so a dead deepen cannot hide behind a correct-looking verdict on a warm workspace."',
 'required_stages = ["doc", "impl", "unit"]  # ACTIVATED #331 H4 (hertz 2026-09-24). doc = GOLDEN-CI Push-main docs-only classification (probe paragraph); impl = classify-changes.py CLASSIFY_PROBE; unit = classify-changes-selftest.py ProbeWitnessesTheMechanism (graft arm -> deepen50, pinned; one shallow read per push probe).'])
s=s[:j]+blk+s[j:]
g='  "REQ-CI-PUSH-DOCS-ONLY-THIN",'+nl
assert s.count(g)==1
s=s.replace(g,'  "REQ-CI-CLASSIFY-PROBE-WITNESS",'+nl+g)
io.open(p,'w',encoding='utf-8',newline='').write(s)
edit('docs/GOLDEN-CI.md',[('''`before-undecidable`.''','''`before-undecidable`.

<!-- [doc->REQ-CI-CLASSIFY-PROBE-WITNESS] -->

**The log witnesses the mechanism (IR-148).** Beside the verdict, the classifier
prints `CLASSIFY_PROBE connected_at=<step0|deepen50|deepen500|none|not-probed>
shallow=<true|false|n/a>`. `connected_at` names the step at which
`before..sha` connected. `shallow` is the ONE decision-time read that also
decides undecidable vs not-ancestor. Without it the verdict reads the same on a
warm workspace (`step0`) and on a graft the deepen had to fix (`deepen50`), so a
dead deepen would pass unseen. Rebuilt locally on real git: the #258 field
range and the IR-146 range both read `deepen50`, and #259's single-commit range
reads `step0`, each with an identical verdict line.''')])
edit('docs/INFRA-REGISTER.md',[("- **Status:** OPEN (hertz, 2026-09-24, filed at doyle's request; wanted built soon).",
"- **Status:** BUILT (hertz, 2026-09-24; lane `ci/ir148-classify-probe`, push HELD until W3 lands per doyle). `CLASSIFY_PROBE connected_at=<...> shallow=<...>` beside the verdict, with shallowness read once at decision time. `REQ-CI-CLASSIFY-PROBE-WITNESS`. Acceptance pending: a field push run whose probe matches a field-shaped local rebuild. Filed OPEN (hertz, 2026-09-24, at doyle's request).")])
print('ok')
