diff options
Diffstat (limited to '.ai/sessions')
| -rw-r--r-- | .ai/sessions/2026-07-29-06-36-adversarial-review-flow-and-telegram-fixes.org | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/.ai/sessions/2026-07-29-06-36-adversarial-review-flow-and-telegram-fixes.org b/.ai/sessions/2026-07-29-06-36-adversarial-review-flow-and-telegram-fixes.org new file mode 100644 index 0000000..12cbe3d --- /dev/null +++ b/.ai/sessions/2026-07-29-06-36-adversarial-review-flow-and-telegram-fixes.org @@ -0,0 +1,514 @@ +#+TITLE: Session Context — 2026-07-28 +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-28 + +* Summary + +** Active Goal + +Started as inbox triage on a telegram-plugin bug report and became two things: shipping the cross-project fixes that arrived overnight, then designing and dogfooding a mandatory isolated adversarial review before every commit — which immediately found real defects in its own design and in everything filed afterward. + +** Decisions + +- *Merge colliding fixes rather than sequence them.* The parked down-is-launch diff still carried the bad =loadChats= call and cited the segfault gotcha the other fix rewrites. Applying either alone would have shipped a file arguing against itself. +- *"Adversarial", not "hostile"* (Craig). An agent told to attack manufactures findings, so the stance carries a substantiation floor: a finding not substantiated against the diff is dropped. +- *Re-review until the reviewer approves* (Craig's addition, the thing I had missed). Same reviewer continued, not a fresh one — a fresh reviewer can't tell an addressed finding from one that never existed. Bounded at three rounds or first recurrence. +- *Dispatch on every commit*, with the reviewer's own Phase 0 ruling triviality. A floor written as "small" or "mechanical" puts the judgment back with the author, whose judgment is the thing being checked. +- *Pass the requirement source, withhold the rationale.* A ticket is not the author's model; it was written first and by someone else, so it's the only input that can contradict the author's claim. +- *=cycle=, not =pass=, for one sentry loop* (Craig). home proposed =pass=; =sentry.org= already uses it as a numbered noun for the eleven hygiene passes, so =Pass 11= would have collided with =pass 12=. +- *Drop the =references/= link rather than sync the directory.* The four calendar workflows already travel and already carry the recipes. +- *Strip the wrap-org-table task to Verified / Open questions* after its review loop bounded out. The measurements were never what failed. + +** Data Collected / Findings + +- *The telegram bug.* =(telega--loadChats 'main)= sends a bare symbol on the wire; =tdat_plist_value= (=telega-dat.c=) accepts only =(=, =[=, ="=, =-=, digit, =t=, =:=, =n= and calls =assert(false)= on =m=. Verified against telega's source at four points rather than trusting the handoff. Exposure was manual triage only — sentry excludes messengers, so home's eleven overnight cycles never loaded the plugin. +- *=wrap-org-table.el= splits logical rows*, and =lint-org= doesn't merely miss it — it *causes* it. =lint-org.el:424= calls the same broken predicate, so it reports the tool's own correct output as "missing rule between rows — wrap-org-table.el reflows it" when nothing is missing, then reports the corrupted result clean. Idempotence is broken: the tool corrupts its own output on a second run. +- *The isolated reviewer earned its keep on its first four uses*, finding: that withholding the ticket made my claim self-certifying; that my =subagents.md= override reaffirmed the Prompt Contract field that would destroy the isolation; a fourth verdict (=Needs Discussion=) I'd asserted didn't exist; and four successive wrong root-cause analyses on the table bug. +- *rulesets is itself exposed* to the table bug: =todo.org='s four-row attachment-sanitization table. Don't reflow until fixed. +- *Seven =../../= link sites* across four synced workflows resolve only in rulesets. =scripts/lint.sh='s =check_md_links= was built for that class and misses them because it matches markdown syntax only. + +** Files Modified + +Seven commits, all pushed, velox synced after each. =bff0138= merged telegram fixes. =43a4cf7= post-load liveness check. =614e3b1= removed finished working dirs. =ca508a1= filed three handoff findings. =f3f5bfd= the =fire= → =cycle= rename (72 sites plus three that had leaked outside =sentry.org=). =3a933a2= the =references/= analysis. =5999f88= dropped the dead link and deleted the stale file behind it. =ecd5d7b= filed the wrap-org-table bug. + +Rules changed: =publish/SKILL.md= Step 1 rewritten (dispatch contract, four defined verdicts, the loop, bounds), =review-code/SKILL.md= (two levels of dispatch, adversarial contract, re-review mode), =claude-rules/subagents.md= (Isolation Override; Prompt Contract field 2 inverts), and the three unattended callers taught to park. + +** Next Steps + +- *Three of the four items Craig queued are untouched*: the winvm =[#C]= lint defects, the context-engineering rightsizing (needs his four decisions), and the sentry spec deep read (two weeks old). +- *Rule gap found by using the rule*: =Needs Discussion= exits to the user, but nothing says what happens after the user answers — whether the round counter resets. I treated it as a fresh review; that judgment isn't written down. +- *Item 2 is now qualified*: its fix says "run =wrap-org-table.el=", and that tool has a live corruption bug. The specific table is safe, but verify the output rather than trust it. +- Four =[#B]= bugs filed tonight and unstarted: the table splitter, the =../../= links, plus the two carried in. + +KB: promoted 1 / consulted no + +* Session Log + +** 11:55 — Startup + +Ran startup. Rulesets already current, project repo clean and current, =make +install= had nothing new to link, =.ai/= synced from templates. No crash anchor +— previous session (context-engineering rightsizing, 2026-07-27 17:02) wrapped +cleanly. + +Findings: 6 tasks unreviewed >7 days; roam inbox holds 4 items; KB at 108 +=:agent:= nodes with nothing matching this project. Spec-sort and host-identity +probes silent. Language-bundle check silent. + +** 12:05 — Inbox: the telegram segfault root cause + +Four new inbox files from =.emacs.d=, two pairs: a 06:15 intro note + plugin +file, then a 07:21 correction + superseding plugin file. The correction retracts +one secondary claim from the 06:15 write-up (that the "19 of ~50 chats" reading +was truncation caused by the bug — it wasn't; 19 is the real account size, +measured by work at the wire level). Root cause and fix unchanged. + +The proposal: =triage-intake.telegram.org= Step 1 calls =(telega--loadChats +'main)=, and that bare symbol kills =telega-server= outright. + +I verified the whole chain against telega's own source rather than taking the +handoff's word for it (=elpa/telega-20260706.2147/=): + +- =telega--loadChats= (telega-tdlib.el:2190) drops its argument straight into + the request as =:chat_list= with no conversion. Confirmed. +- The C parser =tdat_plist_value= (server/telega-dat.c:466) accepts only =(=, + =[=, ="=, =-=, a digit, =t=, =:=, or =n= to start a value; anything else + prints "Unexpected char '%c' in plist value" and calls =assert(false)=. + =main= starts with =m=. Confirmed, and the accepted-char list in the handoff + is exactly right. +- telega's own callers all pass the object: telega.el:290, telega.el:295, + telega-tdlib-events.el:516. Confirmed. +- The symbol shorthand lives in a different layer — telega-filter.el:472 and + telega-folders.el:99 convert =(eq cl-fspec 'main)= to ='(:@type + "chatListMain")=. Confirmed. + +Every claim holds. Grepped all workflows for other bare-symbol arguments to +=telega--*= wrappers: only the two sites this diff already fixes. + +*The complication I found.* There is already a parked =[#B]= VERIFY (todo.org:136, +from =.emacs.d= 2026-07-24) proposing a different fix to this same file — the +down-is-launch fix, with a prepared diff in +=working/triage-telegram-down-launch/=. Its proposed file still carries the bad +=loadChats= call at its lines 52 and 122, and its third hunk adds prose citing +"tdlib segfaults in native mode (SEGFAULT gotcha below)" — pointing at the very +section this new handoff rewrites to say the crashes were our bug, not tdlib's. +Applying either one alone leaves the file half-right. They need merging, not +sequencing. + +Also: home and work both declare =telegram= in =:TRIAGE_SOURCES:=, so both have +been killing telega-server on every sweep. Work already patched locally and +verified at the wire level. Home has not been told. + +** 11:58 — Staged the batch, closed the loops + +The Stop hook counted the four files still sitting in =inbox/=. The triage was +done; what remained was Craig's merge decision, not mine. So I staged all four +into =working/triage-telegram-segfault-rootcause/= (the 07:21 pair as +=.proposed= + correction note, the 06:15 pair kept as superseded context — its +blast-radius and follow-up sections aren't repeated in the correction). Inbox +back to clean, =inbox-status= rc=0. + +Left =working/triage-telegram-down-launch/= untouched: the parked VERIFY's body +links its =proposed.diff= by path, and moving it would break the link before +Craig has decided anything. If he takes the merge, that consolidates then. + +Replied to =.emacs.d= — confirmed the verification, and told them the one thing +they couldn't see: their own 2026-07-24 parked diff collides with this, both in +the stale =loadChats= call it still carries and in the native-mode prose it adds +citing the gotcha this rewrites. Also declined their suggested bare-symbol lint: +one consumer file, two call sites both fixed, so the stated rule is cheaper than +a checker with a single input. + +Notified home directly with the mechanism and the two-site fix, told it to patch +locally rather than wait on the canonical, and carried the correction forward +explicitly so it doesn't inherit the retracted truncation story. + +Nothing applied to the canonical. That still waits on Craig. + +** 12:10 — Craig picked the merge. Applied and committed. + +Merged both fixes into one version rather than sequencing them. Base was the +segfault-corrected file, then the three parked hunks on top: the down-is-launch +directive, the SCAN-FAILED-only-after-launch-attempted rewording, and =(setq +telega-use-docker t)= restored to the Step 1 code block. + +The reconcile that made merging worth doing. The parked hunk's new comment said +"tdlib segfaults in native mode (SEGFAULT gotcha below)", pointing at the +section the other fix rewrites to say those deaths were our own bad argument. +Left alone the file would have argued against itself. I changed the Step 1 +comment to state plainly that the two are separate concerns (the deaths happened +*in* docker mode, so docker mode is neither a defense against the loadChats bug +nor evidence for itself), and reworded the Quick Reference line from "tdlib +segfaults outside docker mode" to "crashed in native mode (2026-06-09)" with the +same disambiguation. + +That reword also removed a host-identity violation I hadn't gone looking for. +The original asserted "Craig's daemon currently has telega-use-docker nil" — a +mutable machine fact stated as fixed in a synced doc. I checked the actual +default (=telega-customize.el:514=, =defcustom telega-use-docker nil=) and wrote +the durable claim instead. + +Verified: both live call sites use the TL object, the two remaining ='main= +occurrences are inside the gotcha prose describing the bug, lint-org clean on the +changed file, mirror synced, =make test= green before (exit 0) and after (exit +0). + +todo.org: closed the parked =**= VERIFY as =DONE= + =CLOSED:= per todo-format.md +with the merge rationale in the body. Promoted its =***= engine child (SCAN +FAILED must not advance the sentinel) to top-level =**= VERIFY so it doesn't get +buried under a DONE parent. Kept its =:LAST_REVIEWED: 2026-07-24= rather than +stamping today — I moved it and judged it separate, but nobody re-derived its +content, so the older date keeps it honest. + +Review: Approve, no Critical or Important. Two Minor, both surfaced rather than +fixed. The gotcha now advises a post-load =(process-live-p ...)= check that the +Step 1 recipe doesn't actually do, and adding it would extend the recipe past the +two fixes Craig approved. + +Committed =bff0138=. Not pushed — that's a separate confirmation. + +** 12:20 — home replied, and corrected my urgency read + +home accepted, patched both call sites locally, and re-verified the diagnosis +independently rather than trusting it. Useful correction back: home declares +telegram in =:TRIAGE_SOURCES:=, but sentry excludes messengers from triage +intake, so the plugin never loaded on a sentry fire. Eleven overnight fires ran +clean against the broken file. I had assumed the sweeps were affected; the real +exposure is manual triage intake only. + +Told home its stopgap won't be reverted into a broken state — the next rsync +replaces it with canonical content carrying the same fix. On this machine that +lands as soon as its next startup runs, since the rsync reads the local rulesets +working tree. velox needs the push. + +Inbox back to zero. + +** 14:10 — Pushed, and closed the velox gap + +Pushed =1675613..bff0138= to origin after the pre-push reconcile (still 1 ahead, +0 behind). + +=.emacs.d= replied while the push was in flight. It had already verified bff0138 +here, re-run its own workflows rsync, and confirmed the corrected form landed. +It withdrew the bare-symbol lint suggestion, conceded the home omission, and +flagged one real gap: bff0138 was committed but not pushed, so velox stayed +exposed. That was true when written and stale by the time it arrived. + +Checked velox over tailscale (this host is ratio, per =uname -n=). It was 1 +behind / 0 ahead and sync-safe, so I fast-forwarded it through the same +=git-worktree-gate sync-safe= check startup uses. Velox is now at bff0138 and its +workflow file carries the corrected call, with the only bare ='main= occurrences +inside the gotcha prose. Both daily drivers covered. + +Corrected read carried into both replies: the exposure was manual triage intake +only, not the automated sweeps, because sentry excludes messengers. + +** Open follow-ups (surfaced to Craig, not acted on) + +1. The gotcha tells callers to check =(process-live-p (telega-server--proc))= + after a load, but the Step 1 recipe doesn't do it. Now that the corrected call + shouldn't kill the server, that check is what would catch a regression. Left + out deliberately as scope creep past the two approved fixes. +2. Both =working/triage-telegram-*= dirs are completed-task artifacts and want + filing per working-files.md. Revised read after checking: delete both + outright. Every file is tracked (=b19d420= and =bff0138=), so git holds them + permanently and a copy in =assets/= would only duplicate history. Nothing + links to them. + +** 15:00 — Second inbox round: .emacs.d self-correction + winvm lint findings + +=.emacs.d= wrote back to say it had overcorrected on home: it accepted "home was +in the blast radius" and then recorded that home "had been killing telega-server +on every sweep too", which home's own sentry data refutes. It fixed its task +record rather than leaving it. I told it the pattern wasn't one-sided — I made +the same move this morning, estimating home's blast radius instead of measuring +it, and home's data is what corrected me. It also offered the emacs-side half of +a completed-vs-truncated signal, which it has filed as =[#C]=, if the +=process-live-p= recipe change lands. + +=winvm= sent a link-integrity pass with three findings, all reproduced against +the rulesets source rather than only its local copy. I verified all three: + +1. =protocols.org:273= links =references/calendar-reference.org=, but the rsync + set is only =protocols.org=, =workflows/=, =scripts/=. Dead link in every + consuming project. home and =.emacs.d= have no =.ai/references/= at all. +2. =retrospectives/PRINCIPLES.org:38= violates the org-table standard. + =lint-org= confirms, checker =org-table-standard=. +3. =protocols.org= lints 8 mechanical + 19 judgment =misplaced-heading=, all + from Markdown =**bold**= in an org file. 48 bold spans, 14 line-initial. + +The check winvm couldn't run: adding =references/= to the sync brings =--delete= +with it, which would destroy project-owned files there. work is the only project +with a =.ai/references/= and its copy is byte-identical to the source, so nothing +would be lost. That clears the path to sync-it over drop-the-link. + +One refinement back to winvm on finding 3: the 8 mechanical hits are would-fix, +and the 14 line-initial bold spans are exactly what =lint-org --fix= would +rewrite without knowing they're emphasis rather than headings. So that half is +not safe to run mechanically even though the count suggests it is. + +Filed as =[#B] :bug:= (the dead link, Minor x every-project-every-sync = P2) and +=[#C] :chore:= (the two cosmetic defects, P3), both stamped =:LAST_REVIEWED:= +today since I graded them today. Verified todo.org's lint counts are unchanged +from the pre-edit baseline (mechanical=2, judgment=12, both pre-existing and +already in =inbox/lint-followups.org=). + +Inbox back to zero. todo.org is left uncommitted deliberately — Craig has a +pending decision that will likely touch it again, so batching beats a churn +commit. + +** 18:45 — Craig took both follow-ups. Liveness check staged. + +Added the liveness check to the Step 1 load call. It now returns ='server-died= +instead of an unconditional ='loaded=, and new prose routes that into the +existing SCAN FAILED definition rather than inventing a category: a server that +dies during load leaves a thin hash, which reads exactly like a quiet account, +which is the same false all-clear the down/not-loaded rule already prevents. + +Self-review caught a real defect in my own first draft. I called +=(telega-server-live-p)= bare, but Step 0 guards the same predicate with +=fboundp= because telega may not be loaded. A launch that failed outright would +have signalled void-function instead of returning the clean contract. Added the +guard, matching Step 0's idiom. Verified the predicate is exactly the +=process-live-p= expression the gotcha names (=telega-server.el:221=). + +Verified: parens balance at depth 0, lint-org 0/0, mirror identical, =make test= +green (exit 0) on the final state. + +Not committed — waiting on the approval gate. + +** 18:48 — Third inbox round + +=.emacs.d= sent a closing FYI marked no-action, agreeing my framing of the shared +failure (blast radius estimated rather than measured) named the trigger rather +than the failure. Deleted without reply, since replying to "nothing owed back" +is noise. + +home proposed renaming sentry.org's noun-sense "fire" to "pass", after Craig read +its "nine fires" as nine emergencies: "I assume you mean nine crises, not nine +loop cycles and I begin to get scared." The problem is real, well-evidenced, and +reaches Craig directly through digest headings. + +But the proposed term is wrong, and the reason home gave for it is the +disqualifier. =sentry.org= already uses "pass" as a precise numbered noun — the +pass list, the Pass Runner, "eleven finding/hygiene passes", "pass 12". With +exactly eleven hygiene passes, home's proposed =** Pass 11= heading collides with +an existing referent. That trades a term Craig misreads as urgent for one that is +genuinely ambiguous. + +Counter-proposed *cycle*: zero occurrences in the file, and Craig's own word in +the quote home cited. Checked and rejected "sweep" (3 uses) and "run" (used as a +noun). Filed =[#C] :chore:= with the grading and the collision analysis; replied +to home with the counter-proposal. + +** 18:50 — Three commits, and the collision confirmed from live evidence + +Craig approved both follow-ups and the =cycle= term. Three commits: + +- =43a4cf7= the liveness check. +- =614e3b1= removed both telegram working dirs. Filing by deletion, since every + file was already in git via =b19d420= and =bff0138= and an =assets/= copy would + only duplicate history. +- =ca508a1= filed the three handoff findings with their gradings. + +home wrote back confirming the "pass" collision was real, and that it had already +walked into it: its anchor now carries =** Pass 11= meaning the eleventh cycle, +three lines from =pass 12 (solo-task implementation)= meaning the twelfth item in +the pass list. Same file, two referents, introduced by its own normalization an +hour earlier. It found that in live evidence faster than reading the file would +have caught it. + +It was blocked on Craig's confirmation and had written a memory saying "pass", so +I sent the confirmation immediately. The memory was the urgent half — a stale one +teaches every future home session the ambiguity, where the anchor is one file. + +Recording Craig's approval flipped the sentry task to =:solo:=. The term was the +only judgment it carried, and the completion check is objective, so it can ride a +backlog run rather than waiting for someone to touch =sentry.org=. + +Pushed =bff0138..ca508a1=, velox fast-forwarded to match. + +** 19:45 — The cycle rename, and the leak home's scope missed + +home did its side first: renormalized its anchor by restoring the +pre-normalization backup and re-running fire→cycle from clean, rather than +reverse-mapping pass→cycle. That was the right call — reverse-mapping would have +needed a judgment on every instance to separate its own conversions from genuine +pass-list references, where re-running from clean makes it structural. It also +corrected its memory and handed the canonical back. + +Did the canonical rename with a script rather than by eye: protect the verb sites +by explicit pattern, assert zero unclassified =-ed/-ing= forms survive, then +substitute. 72 noun instances converted, 4 verb sites untouched (=/loop= fires +again, two "fires on approval", "record of what fired"). + +*The leak home's scope missed.* The term wasn't confined to =sentry.org=. +=wrap-it-up.org= said "a crashed fire", and =todo-cleanup.el= and its test both +said "every sentry fire" — all three naming a sentry cycle. Renaming only +=sentry.org= would have split the vocabulary across files. Found by grepping +every file that mentions sentry, then re-grepping without a context window after +the first pass truncated short-line matches and hid them. + +Verified: exactly 3 verb instances left in sentry.org, no placeholder leaked, the +digest commit template now reads =<date> <time> cycle=, capitalized plurals +handled, lint 0/0 on sentry.org, suite green (exit 0 — load-bearing here, since +=todo-cleanup.el= and its test are under test). The two =wrap-it-up.org= lint +findings are pre-existing and identical at HEAD. + +Committed =f3f5bfd=, pushed, velox fast-forwarded and verified. Notified home +(with the leak it hadn't seen) and =.emacs.d=. Closed the task =DONE=. + +Four commits this session, all pushed, both daily drivers current, inbox at zero. + +** 20:00 — Roam inbox zero, then the adversarial-review design + +Roam scan: 3 items, 1 claimed (=rulesets:= prefix), 2 unowned gear links left +for Craig. Filed the claimed one, removed it from roam under capture-guard + +roam-write lock, triggered =roam-sync=. A local =.emacs.d= FYI also cleared. + +The claimed item: "code reviews must occur before every commit an agent does, +and they should be hostile reviews from a subagent without the agent's context." + +Craig's decisions: *adversarial* rather than hostile (he took my push-back that +an agent told to attack manufactures findings), plus a requirement I had missed — +a re-review loop that runs until the reviewer approves — and that the rules must +not contradict afterward. Unopposed recommendations I proceeded on: dispatch on +every commit with the reviewer's own gate deciding triviality, and the flow lands +in the =publish= skill. + +Wrote it into three files: =publish/SKILL.md= Step 1 (dispatch contract, +adversarial-with-substantiation, the loop, bounds), =review-code/SKILL.md= (the +two levels of dispatch, the adversarial contract, re-review mode), and +=claude-rules/subagents.md= (a new Isolation Override section, since three +separate size rules there said don't dispatch small work). + +** 20:30 — Dogfooded it, and the reviewer found nine things + +Ran the new flow on its own diff: dispatched an isolated adversarial reviewer +with the diff plus a one-line claim, withholding everything else. It returned +REQUEST CHANGES with seven Important and two Minor, every one substantiated. I +checked each against the files rather than accepting them, and all nine were +real: + +1. =Skipped= from Phase 0 is not =Approve=, so trivial diffs dead-ended at a gate + with no defined pass. +2. =no-approvals.org= line 73 (the actual execution step) still described the old + inline unbounded flow; I had only updated the preamble at line 49. +3. =work-the-backlog.org= and =sentry.org= — the unattended callers — had no + receiver for "stop and surface to the user". The speedrun routes to + work-the-backlog, not the file I updated. +4. The Step 2 exception still said the review runs "when it applies", which my + rewrite had made false. +5. *The sharpest one.* Withholding the ticket/plan makes the author's claim + self-certifying and strands =review-code='s Intent-vs-Delivery criterion — the + one aimed at exactly the inherited-scope error this gate exists to catch. A + ticket is not the author's model; it is the independent record of what was + asked. I had not considered this. +6. The loop turned on the verdict token, so a single Minor could burn all three + rounds and escalate. +7. My override said it "doesn't relax the Prompt Contract" while field 2 of that + contract says paste your context verbatim — which would destroy the isolation + the whole change is built on. +8. todo.org carried an unrelated =references/= rewrite, and the new task body + listed as open the questions the same commit answered. +9. =subagents.md= still says subagent output is a claim to verify, unreconciled + with "approval is the reviewer's to give". + +All nine fixed. The =references/= hunk is split out as =3a933a2=. Round 2 sent +back to the same reviewer, which is the loop working as designed. + +** 21:30 — The loop closed at three rounds + +Round 2 (three findings): =review-code='s adversarial contract still said two +inputs, so the round-1 fix landed in =publish= but not in the text the reviewer +reads — the two files disagreed at the one seam the change was about. A +count/list regression my own fix introduced ("exactly two things" over a +three-item list). And a fourth verdict I had missed entirely: =review-code= +emits =Needs Discussion=, which is exactly what an adversarial reviewer reaches +for on an architectural objection, and the flow had no handling for it. + +Fixing =Needs Discussion= reproduced round 1's finding 3 one level down, which I +caught myself: all four unattended parking clauses keyed on "the loop hits its +bound", and a first-round =Needs Discussion= is not a bound hit. Rewrote all four +to trigger on "the review can't reach approval" with the three causes named, and +asked the reviewer to verify that independently rather than take it from me. + +Round 3: APPROVE, with one Minor — a recurrence of the count/list mismatch, since +my fourth-verdict fix left the lead-in saying "three outcomes" above four +bullets. Fixed the numeral. The committed diff therefore differs from the +approved one by exactly that word, which the flow's own Minor-only rule permits +rather than spending a fourth round. + +The reviewer also verified things I had not asked about and would not have +checked: that =failed= is already a legal outcome slug in work-the-backlog's +metrics table, that the three workflow mirrors carry identical blob hashes rather +than merely similar text, and that no fifth verdict token exists anywhere in +=review-code=. It filed one follow-up correctly rather than fixing it in-diff — +=start-work.md= Phase 7 still summarizes the publish flow instead of pointing at +it, and was stale before today. Filed =[#C] :chore:solo:=. + +Convergence shape across the three rounds: nine findings, three, one Minor. The +residue in later rounds was integration error from the previous round's fixes +rather than new design problems, which is the shape the bound is calibrated for. + +Committed =8062460=, pushed, velox fast-forwarded and verified. + +Six commits this session, all pushed, both daily drivers current, inbox at zero, +tree clean. + +** 2026-07-29 — Walking the remaining items, and a long lesson + +*Item 1, the =references/= dead link* (=5999f88=). Craig picked drop-the-link. +The adversarial review returned =Needs Discussion= — the fourth verdict, on its +first real use — and widened the fix twice, both correctly. My replacement prose +said credentials "live in the rulesets repo" without naming a file, which would +have sent readers to =calendar-reference.org=, whose three paths had been dead +since May. Now names =mcp/README.org=. And the file itself was orphaned by the +link removal, so both copies and the empty =references/= dirs are gone. Round 2 +approved with three Low findings, all against the task record: my count was wrong +(seven sites, not five) and =scripts/lint.sh='s =check_md_links= already exists +for that class, missing them only because it matches markdown syntax. + +*Rule gap found by using the rule.* =Needs Discussion= exits to the user, but I +never wrote what happens after the user answers. Treated it as a fresh review on +the reasoning that Craig adjudicated and the scope changed. Flagged to Craig; not +yet written into the skill. + +*The wrap-org-table bug* (=ecd5d7b=). work reported that the tool splits a logical +row and lint passes the result. Everything I *measured* held. Everything I +*inferred* on top was refuted, four times: + +1. Root cause "absence of rules in the input" — refuted by the double-run repro + (the tool corrupts its own correct, rule-delimited output). +2. "Tested and killed the empty-cell hypothesis" — the fixture was confounded. + With no hlines the code short-circuits at =:184= before the predicate is + reached, so I varied the empty cell while the path that reads it was switched + off, got a negative, and wrote it down as settled. +3. "The reporter's row-below observation discriminates between the paths" — it + doesn't; both produce that signature. Claimed twice. +4. "A static scan can't see primary-path exposure, needs simulation" — wrong, and + worse, I sent it to work, who built on it. Then my *corrected* advice (run the + predicate over rule-delimited groups) was also wrong: work implemented it and + showed it can't discriminate at any threshold, with worked examples where the + same structural signature has opposite correct verdicts. + +Three corrections sent to work, plus a fourth acknowledging their disproof. The +review loop *bounded out* at three rounds — the first bound-out under the new +rule, working as designed. Craig adjudicated by stripping the task to Verified / +Two-fixes-that-work / Open-questions, which is the right shape: the measurements +were never the problem. + +The fix that survived is work's read, not mine: check idempotence (reflow twice, +diff) rather than build a detector. It's true by construction and needs nobody to +decide what a group means. + +*The lesson, stated plainly.* Every refutation across three rounds landed on +inference, never on a measurement. I ran experiments and then over-read them, +repeatedly, at full confidence. work — after I'd sent them three wrong analyses — +labelled their own uncertain number as a floor on a population they couldn't +cleanly define, unprompted. That discipline is the thing to copy. + +I also found the 2026-07-27 note where I'd already observed this exact failure +and left it in a session summary instead of filing it. work's framing: a correct +observation recorded and then read as fine is the same failure as a green check +on a corrupted table. |
