aboutsummaryrefslogtreecommitdiff
path: root/.ai
diff options
context:
space:
mode:
Diffstat (limited to '.ai')
-rw-r--r--.ai/sessions/2026-07-29-06-36-adversarial-review-flow-and-telegram-fixes.org514
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.