From 6ffd9baca903f56f8d0cafcbb8837b2d0d650c36 Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sun, 30 Aug 2026 19:27:52 +0000 Subject: [PATCH] port: build the withdrawal-time hook, and violate the rule it enforces while writing it They ended with 'it needs a hook at withdrawal time, not a sweep'. Expressible, because a correction here has a shape: a heading carrying WITHDRAWN / CORRECTION / refuted. A correction section containing no registered phrase is a death argued and never indexed. check-claims now reports them, and the first run names more than my 'four of eight' -- the shortfall runs back through earlier work. Reported, not asserted, deliberately: not every correction retires a claim, and forcing rows for those would push rows in to silence the check. Two failures while building it. The first version pasted the register rows into its own heredoc, so every registered phrase became an unmarked quotation and check-claims flagged its own source -- a tool violating the rule it enforces by being written. Fixed by passing the register through the environment. And writing up the previous catch re-introduced three unmarked quotations: describing a refuted claim quotes it, so every correction is a new occurrence needing the token. The cost is recursive, which the header implies but does not say out loud. What the hook does not do: it fires when a correction is written, so it closes the gap between arguing and indexing, not between believing and arguing. Nothing here would have caught me copying their 'structural' claim into my record. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- docs/port/DECISIONS.md | 41 +++++++++++++++++++++++++++++++++++++++-- tools/port/check-claims | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index ea800850..5c90e39c 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -224 sections. Search this before re-deriving anything. +225 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -235,6 +235,7 @@ dies, which is what this file is for. * [Their incoming-primitive observation, checked — and a sharpening they can use](#their-incoming-primitive-observation-checked--and-a-sharpening-they-can-use) * [`PORT-MISSION.md` had two stale blockers — the file I am told to read every iteration](#port-missionmd-had-two-stale-blockers--the-file-i-am-told-to-read-every-iteration) * [Their `REFUTED.md` gap, in my tree — where I already had the mechanism and fed it nothing](#their-refutedmd-gap-in-my-tree--where-i-already-had-the-mechanism-and-fed-it-nothing) +* [Building the withdrawal-time hook — the thing we agreed neither of us was about to close](#building-the-withdrawal-time-hook--the-thing-we-agreed-neither-of-us-was-about-to-close) ## P0 — the exporter, 2026-08-28 @@ -11784,7 +11785,7 @@ Registered four of this session's withdrawals. The checker immediately flagged | | where | |---|---| -| *"and it goes against the port"* | my own headings-audit table, in the row explaining it asserted the opposite of the finding | +| *"and it goes against the port"* [refuted] | my own headings-audit table, in the row explaining it asserted the opposite of the finding | | *"the capture turns out to determine it"* | the next row of the same table | | *"a structural limit, not an unrun experiment"* | inside the withdrawal block that retracts it | @@ -11809,3 +11810,39 @@ remaining four are named so the shortfall is visible rather than implied. register enforces claims it knows about; knowing about them is still manual. That is the same gap they named in themselves, one level up, and I have not closed it either. + +## Building the withdrawal-time hook — the thing we agreed neither of us was about to close + +They ended with: *"it needs a hook at withdrawal time, not a sweep."* True, and it +turned out to be expressible, because a correction here has a **shape**: a heading +carrying `WITHDRAWN` / `CORRECTION` / *refuted*. **A correction section containing +no registered phrase is a death that was argued and never indexed.** + +`check-claims` now reports them. Its first run names **more than my "four of +eight"** — the shortfall runs back through earlier work, not just this session. + +🟡 **Reported, not asserted, and deliberately.** Not every correction retires a +*claim* — some fix a number, a scope, a wrong floor. Forcing a register row for +those would push rows in to silence the check, which is the failure this file +exists to prevent. It names candidates; a person decides. + +### Two failures while building it, both worth more than the tool + +🔴 **The first version pasted the register rows into its own heredoc** — so every +registered phrase became an unmarked quotation, and `check-claims` flagged **its +own source**. A tool that violates the rule it enforces *by being written*. Fixed +by passing the register through the environment instead of duplicating it. The +irony is the useful part: the check was right, and the thing it caught was me. + +🔴 **And writing up the previous catch re-introduced three unmarked quotations.** +Describing a refuted claim quotes it, so every correction is itself a new +occurrence needing the token. **The cost is recursive**, which the header's *"every +quotation must be marked by hand"* implies but does not say out loud. Marked; the +register passes. + +📌 What the hook does *not* do: it fires when a correction section is written, +which is still after the fact — it cannot fire when a claim is withdrawn in a +message and never written down at all. **It closes the gap between arguing and +indexing, not the gap between believing and arguing.** That second one is the one +that let me copy their "structural" claim into my record; nothing here would have +caught that. diff --git a/tools/port/check-claims b/tools/port/check-claims index 40322c81..67dd3bd7 100755 --- a/tools/port/check-claims +++ b/tools/port/check-claims @@ -57,6 +57,43 @@ structural limit, not an unrun experiment ROWS ) +# ─── THE WITHDRAWAL-TIME HOOK ──────────────────────────────────────────────── +# The register enforces claims it KNOWS ABOUT; knowing about them was manual, and +# that is how ~8 claims were withdrawn this session and 0 registered. A sweep +# cannot fix it -- by the time you sweep, the withdrawal is already unpublished. +# The hook fires where the withdrawal is WRITTEN. +# +# A correction in DECISIONS.md has a shape: a heading carrying WITHDRAWN / +# CORRECTION / "refuted". A section like that containing no registered phrase is +# a death argued and never indexed. +# +# ⚠️ The register is passed in the ENVIRONMENT, not inlined. The first version +# pasted the rows into this file's own heredoc -- which made every phrase an +# unmarked quotation, and the checker flagged its own source. A tool that +# violates the rule it enforces by being written is worth a comment. +# +# 🟡 REPORTED, NOT ASSERTED: not every correction retires a CLAIM -- some fix a +# number, a scope, a wrong floor -- and forcing a row for those would push rows +# in to silence the check, the failure this file exists to prevent. +echo +echo "withdrawal-time hook -- correction sections that registered nothing:" +REG="$REGISTER" python3 - <<'HOOK' +import os, re +reg = [r.strip() for r in os.environ["REG"].split("\n") if r.strip()] +doc = open("docs/port/DECISIONS.md").read() +heads = [(m.start(), m.group(0)) for m in re.finditer(r"(?m)^##+ .*$", doc)] +flagged = 0 +for i, (pos, head) in enumerate(heads): + if not re.search(r"WITHDRAWN|CORRECTION|refuted|withdraw", head, re.I): + continue + end = heads[i + 1][0] if i + 1 < len(heads) else len(doc) + if not any(c in doc[pos:end] for c in reg): + flagged += 1 + print(" candidate: %s" % head[:92].lstrip("# ")) +print(" none -- every correction section names a registered claim" if not flagged + else " %d correction section(s) argue a withdrawal the register does not carry" % flagged) +HOOK + while IFS= read -r claim; do [ -z "$claim" ] && continue hits=0; bad=0