Skip to content

fix(handoff-index): a repo this run could not read IN FULL is not a repo it may DELETE - #1267

Merged
ZacxDev merged 10 commits into
mainfrom
fix/handoff-index-incomplete-read-authority
Sep 4, 2026
Merged

fix(handoff-index): a repo this run could not read IN FULL is not a repo it may DELETE#1267
ZacxDev merged 10 commits into
mainfrom
fix/handoff-index-incomplete-read-authority

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Sep 3, 2026

Copy link
Copy Markdown
Member

Closes the exposure rebuild_delete_labels' own docstring filed and deliberately
deferred in #1209: a repo that MEASURES successfully but whose docs git cannot
produce has its rows DELETED and zero re-inserted, at rc 0, with no PARTIAL
notice — on the timer's own argv.

The reproduction (re-measured here, not taken from the filing)

goodrepo (1 committed handoff doc) + badrepo (1 committed doc whose blob was
removed from .git/objects — a blobless or partially-fetched clone, an
incomplete object store):

badrepo   unmeasured=None  docs=0  unreadable=('claudedocs/handoff-zarfwidget-latch.md',)
partial_scope_warnings()  == ()
rebuild_refusal()         is None
rebuild_delete_labels()   == ('badrepo', 'goodrepo')

main(["--repo", good, "--repo", bad, "--rebuild", "--write"])
  -> rc 0
     DELETE params = [['badrepo', 'goodrepo']]
     INSERT count  = 2          (both goodrepo's)
     "wrote 2 section row(s) to initiatives.handoff_section (after DELETE of 2 repo label(s)…)"
     success line does NOT say PARTIAL

The pre-flight actively asserted the opposite: KEPT (configured but UNMEASURED): (none). The only signal anywhere in the run was one
⚠ UNREADABLE line on stderr.

The filing understated it

Same fixture with one blob of two removed — a run that looks healthier,
because it writes rows:

zarfrepo  unmeasured=None  docs=1  unreadable=1
rebuild_delete_labels() == ('zarfrepo',)
  -> rc 0, DELETE [['zarfrepo']], 2 of 4 sections re-inserted

The second doc's stored rows are deleted and never replaced. A guard scoped to
the filed symptom ("every doc unreadable") would have closed the fourth instance
and left the shape open — so the guard here is on any unreadable doc.

The four-spelling shape, and whether this closes it

The four are: an unpredicated TRUNCATE; a delete scope computed over the wrong
label set; a refusal that checked whether a repo could be read when the risk
was whether the config was wide; and this one, a scope keyed on unmeasured
("did the ref resolve") when the DELETE asks "did anything come back".

The common shape is not "someone forgot a field." Authority over the DELETE
was granted by a negative predicate — the absence of whichever failure had
already been seen — instead of by a positive demonstration that the run holds
a complete replacement for the rows it destroys. A blacklist re-opens the hole
for every failure nobody has met yet, which is why four rounds of naming one more
failure never converged. Spelling 3 is literally two sites disagreeing about
which question they were answering.

So this inverts the default in one place:

def incomplete_reason(d: RepoDerivation) -> str | None: ...
def may_replace_stored_rows(d: RepoDerivation) -> bool: ...

A label enters the delete scope only when this run read that repo in full;
any incompleteness, of any kind including kinds not yet invented, withholds it.
rebuild_delete_labels, partial_scope_warnings, rebuild_refusal and
rebuild_plan_lines now read that predicate instead of each re-deriving "is
this repo good" from whatever field was nearest.

That is a claim about the shape, so state its limit honestly: it closes the shape
for the rebuild delete scope. It does not make every future predicate in this
module positive by construction — what it does is leave exactly one place where
"may I destroy this repo's rows" is decided, so a fifth spelling has to be a
deliberate edit to that function rather than an omission at a new call site.

Behaviour changes, and which exit codes move

  • The DELETE scope shrinks. A repo carrying any unreadable doc is out of it.
    Its stored rows are preserved and every doc that did read is still upserted
    (ON CONFLICT … DO UPDATE refreshes without destroying).
  • unmeasured is deliberately NOT set for the unreadable case — a considered
    departure from the obvious fix, which would have been "reclassify it as
    UNMEASURED and let the existing machinery cover it". Two reasons:
    1. it would make unmeasured mean both "nothing came back" and "not
      everything came back"
      — the exact conflation this module has already been
      burned by three times (handoff_paths_in_ref's None-vs-(), DiskScan's
      three meanings of (), and docs == 0's two mechanisms);
    2. it would hide 39 readable docs from --offline search over one corrupt
      blob, because an UNMEASURED repo contributes zero rows.
  • handoff_search's rc 6 / rc 7 split does NOT move. The deferral note
    assumed it must ("the two have to move together"), and that coupling exists
    only for the UNMEASURED route. Under this design the search front end reads
    unmeasured and unreadable separately and already renders both correctly;
    all of its existing rc 6 / rc 7 tests pass unchanged. Neither front end now
    claims a repo measured completely when it did not.
  • --prune refuses (rc 4 RC_REFUSED) while any repo is incomplete, for
    the same reason it already refuses an unmeasured one: the run cannot replace
    what it would delete. Message opens came back UNMEASURED or INCOMPLETE — a
    disjunction rather than a conditional, so it is true in every state it fires
    in, with the per-repo reason in the detail. Interaction with
    prune_config_refusal is unchanged: that guard still runs first (config
    width is a precondition for --prune's whole premise) and the two messages
    keep their disjoint discriminating tokens, pinned by the existing matrix test.
  • NEW refusal arm, rc 4: not ONE of the N repo(s) was read COMPLETELY.
    Before this, "not every repo is unmeasured" implied "the delete scope is
    non-empty"; it no longer does, and PostgresSectionStore.write raises on
    an empty rebuild scope by design. A traceback out of a 6h timer is a worse
    report than a refusal that names the repos and a remedy. It is ordered after
    the zero-rows arm so rows is non-empty by construction — which is what makes
    its "re-run without --rebuild and it writes" remedy true. That is the exact
    sentence the all-unmeasured arm had to retract as false in its own state, so
    it is also asserted behaviourally, not just written down.
  • No legitimate case is newly refused. A healthy repo, a partially
    unmeasured config, and a complete --prune all behave exactly as before —
    each has a negative-control test. The only new refusals require a repo whose
    committed doc git cannot produce, which is a broken object store.
  • Prose / surfaces: the PARTIAL warning drops contributed NOTHING (false
    of a partially readable repo, which contributes rows in the very transaction
    that prints it); the pre-flight grows a third bucket, because a repo in
    neither list was invisible to the operator; the success line says not read COMPLETELY; --json publishes rebuildable and incomplete_reason so a
    machine consumer reads the same value the DELETE reads rather than re-deriving
    it.

What it costs, stated rather than discovered

A single corrupt blob freezes that repo's rows until the object store is
repaired. The rows are preserved, not lost, every readable doc is still
upserted, and the PARTIAL warning says so on every output path — the
conservative direction. If every configured repo is incomplete the rebuild
refuses (rc 4), which on the 6h timer means a failure toast; that state is a
broken machine, and the refusal names git fsck and re-fetch.

Tests — red before, green after

Base sha a36d3a40 (the merge base this work started from), new test file
run against the base library:

13 failed, 221 passed   @ a36d3a40
234 passed              @ daf87b4a  (this branch, merged with origin/main baa95854)

11 of the 13 are the new regression tests. The other 2 are the renamed
pre-flight line (DELETE (measured…DELETE (read in FULL…). Six further new
tests are negative controls / invariant guards and are green at both ends —
labelled as such, not counted as regression coverage.

The end-to-end test asserts the bound DELETE parameters through main, not
an exit code: the statement text, the row count and the exit code are identical
between the correct run and the destructive one, so the bound scope is the only
place the difference shows.

Fixture note: the first draft of _TRIAD reused DOC_FULL twice, and because
git addresses a blob by content hash the two docs were one object — deleting
"one" blob took out two documents and the partial case silently became the total
one. The three bodies are now distinct, and the counts (docs=2,
unreadable=1, total=3) are pairwise distinct and distinct from every
constant the assertions name.

Mutation sweep — fresh tree per mutant, PYTHONDONTWRITEBYTECODE=1

Mutating the narrowest expression that can be wrong (the unreadable clause):

mutant result
M1 if d.unreadable:if False: KILLED (11 tests)
M2 → if d.unreadable and not d.docs: (the "close the 4th instance only" mutant) KILLED (8 tests)
M3 scope reverts to d.unmeasured is None KILLED (5 tests)
M4 warning reverts to d.unmeasured KILLED (3 tests)
P positive control — scope → every derived label KILLED (12 tests)
N negative control — a true semantic no-op SURVIVED, 234 passed

Reachable, not merely breakable.
TestTheAuthorityPredicateIsREACHEDByTheDeleteScope builds a RepoDerivation
directly with unmeasured=None and unreadable non-empty, so the earlier check
provably does not always win — and M2 dies there, with this guard's own
assertion (assert None is not None, and a delete scope holding the extra
label), not with a different guard's error.

Verification limits

No test in this module reaches a live Postgres. The store sits behind
handoff_index.SectionStore; what is pinned is the SQL text and the bound
parameters, not that a server accepts them. Stated in the test file's own header
already, repeated here because this change is entirely about what a DELETE
binds.

…epo it may DELETE

REPRODUCED, not inferred. Two repos, one healthy and one whose committed
handoff-doc blob was removed from `.git/objects` (a blobless or partially
fetched clone, an incomplete object store):

    badrepo   unmeasured=None  docs=0  unreadable=('claudedocs/handoff-*.md',)
    partial_scope_warnings() == ()
    rebuild_refusal()        is None
    main([--repo good, --repo bad, --rebuild, --write])
      -> rc 0, DELETE params [['badrepo','goodrepo']], 2 INSERTs (both goodrepo)
         "wrote 2 section row(s)" with no PARTIAL notice

badrepo's stored rows were deleted and zero re-inserted, at rc 0, on the
timer's own argv. The only signal in the whole run was one `⚠ UNREADABLE`
line on stderr.

AND THE FILING UNDERSTATED IT. One bad blob of TWO does the same thing from a
run that looks HEALTHIER, because it writes rows:

    zarfrepo  unmeasured=None  docs=1  unreadable=1
    rebuild_delete_labels(...) == ('zarfrepo',)
    -> rc 0, DELETE [['zarfrepo']], 2 of 4 sections re-inserted

so the second doc's rows were deleted and never replaced. A fix scoped to the
filed symptom ("every doc unreadable") would have closed the fourth instance
and left the shape open.

THE COMMON SHAPE, ACROSS FOUR SPELLINGS. Authority over a DELETE was granted by
a NEGATIVE predicate — the absence of whichever failure had already been seen —
instead of by a POSITIVE demonstration that the run holds a complete
replacement. An unpredicated TRUNCATE, a scope over the wrong label set, a
refusal that checked "could it be READ" when the risk was "is the config WIDE",
and now a scope keyed on `unmeasured` ("did the ref resolve") when the DELETE
asks "did anything come back". A blacklist re-opens the hole for every failure
nobody has met yet, which is why four rounds of naming one more failure never
converged.

So the default is inverted, in ONE place: `incomplete_reason(d)` /
`may_replace_stored_rows(d)`. A label enters the delete scope only when the run
read that repo IN FULL; any incompleteness, of any kind, withholds it.
`rebuild_delete_labels`, `partial_scope_warnings`, `rebuild_refusal` and
`rebuild_plan_lines` now read that one predicate instead of each re-deriving
"is this repo good" from whatever field was nearest — which is what spelling 3
literally was.

BEHAVIOUR CHANGES

* the DELETE scope shrinks: a repo carrying ANY unreadable doc is out of it.
  Its rows are preserved, and every doc that DID read is still upserted (an
  ON CONFLICT insert refreshes without destroying).
* `unmeasured` is deliberately NOT set for the unreadable case. Calling a repo
  that read 39 of 40 docs "UNMEASURED" would make that field mean both "nothing
  came back" and "not everything came back" — the conflation this module has
  been burned by three times — and would hide 39 readable docs from
  `--offline` search over one corrupt blob. handoff_search's rc 6 / rc 7 split
  therefore does NOT move; that coupling only existed for the UNMEASURED route.
* `--prune` now refuses while any repo is incomplete (rc 4), for the same
  reason it already refuses an unmeasured one.
* NEW refusal arm, rc 4: "not ONE of the N repo(s) was read COMPLETELY". Before
  this, "not every repo is unmeasured" implied "the delete scope is non-empty";
  it no longer does, and `PostgresSectionStore.write` RAISES on an empty rebuild
  scope by design. Ordered AFTER the zero-rows arm so `rows` is non-empty by
  construction and its "re-run without --rebuild and it writes" remedy is true
  — the sentence the all-unmeasured arm had to retract in its own state.
* prose: the PARTIAL warning drops "contributed NOTHING" (false of a partially
  readable repo), the pre-flight grows a third bucket (a repo in NEITHER list
  was invisible), the success line says "not read COMPLETELY", and `--json`
  publishes `rebuildable` + `incomplete_reason` so a machine consumer reads the
  same value the DELETE reads rather than re-deriving it.

TESTS — red at base a36d3a4, green at HEAD

    13 failed, 221 passed  @ a36d3a4 (new test file over the base library)
    234 passed             @ HEAD

11 of the 13 are the new regression tests; the other 2 are the renamed
pre-flight line ("DELETE (measured…" -> "DELETE (read in FULL…"). Six further
new tests are negative controls / invariant guards and are green at BOTH ends,
labelled as such.

The end-to-end test asserts the BOUND DELETE parameters through `main`, not an
exit code: the statement text, the row count and the exit code are identical
between the correct run and the destructive one.

MUTATION SWEEP — fresh tree per mutant, PYTHONDONTWRITEBYTECODE=1

    M1  `if d.unreadable:` -> `if False:`              KILLED (11 tests)
    M2  -> `if d.unreadable and not d.docs:`           KILLED  (8 tests)
        (the "close the 4th instance only" mutant)
    M3  scope reverts to `d.unmeasured is None`        KILLED  (5 tests)
    M4  warning reverts to `d.unmeasured`              KILLED  (3 tests)
    P   positive control (scope -> every label)        KILLED (12 tests)
    N   negative control (true semantic no-op)         SURVIVED, 234 passed

REACHABLE, not merely breakable: `TestTheAuthorityPredicateIsREACHEDByTheDeleteScope`
builds a `RepoDerivation` with `unmeasured=None` and `unreadable` non-empty, so
the earlier check provably does not always win — and M2 dies there with this
guard's own assertion (`assert None is not None`, and a delete scope holding the
extra label), not with a different guard's error.

⚠ No test in this module reaches a live Postgres; the store sits behind
`SectionStore` and the SQL text is what is pinned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Gate verdicts — merged tree daf87b4a (= origin/main baa95854 + this commit)

Branch protection is off, so these are run by hand. Both tiers, named, with their
own RESULT: lines
— and neither tier is clean, so read the attribution below
rather than the headline.

Tier 2 — sandbox (nix build .#checks.x86_64-linux.…, ONE AT A TIME) — the tier Tekton runs

NIXBUILD_RC[pytests]=1
  devrc-pytests>   PASS  scripts/tests  (collected=12012 passed=12012 skipped=0 floor=10269)
  devrc-pytests>   FAIL  scripts/collector/keylog/tests  (collected=114 passed=113 failed=1)
  devrc-pytests>   TOTAL collected=21025  passed=21021  skipped=3  failed=1  (floor: 18404)
  devrc-pytests> RESULT: FAIL (exit=1)
  error: Cannot build '/nix/store/0jzg3llqm98zgy1n9rhmwk8x01pamf4x-devrc-pytests.drv'.

NIXBUILD_RC[nodetests]=0
  devrc-nodetests> RESULT: PASS (exit=0)

Built sequentially, and the store was uncontended (the run waited for two
sibling agents' nix builds to clear first), so the combined-invocation false-failure
mode does not apply here.

scripts/tests — the target that holds test_handoff_index.py — is PASS,
12012/12012
. The one failure is
scripts/collector/keylog/tests::test_recommend_terms_resolve_on_the_live_config,
which this diff cannot reach (it touches scripts/lib/handoff_index.py and
scripts/tests/test_handoff_index.py and nothing else).

Discriminating control, run rather than reasoned about: that test fails
identically on a pristine extraction of origin/main baa95854 with none of these
changes. It is pre-existing on main.

Tier 1 — dev host (scripts/gate.sh --tier both)

  FAIL  pytest  exit=1  verdict='RESULT: FAIL (exit=1)'
  PASS  node    exit=0  verdict='RESULT: PASS (exit=0)'
GATE: RESULT=FAIL exit=1

  pytest TOTAL collected=21025  passed=21013  skipped=3  failed=9  (floor: 18404)
  node   TOTAL suites=5 files=41 tests=1449 pass=1449 fail=0  (floor: 1367)

Nine failures, none in test_handoff_index.py, and all attributed by measurement:

  • 8 of 9 reproduce identically on pristine origin/main baa95854 — measured,
    not assumed: test_analyze_service_index_escrow_verify.py ×5,
    test_analyze_service_index_backup.py ×1, test_opencode_engine.py ×1,
    test_espanso_detect.py ×1. (Seven of these are dev-host-only — they do not
    reproduce in the sandbox tier, whose environment differs.)
  • The 9th is a load flake, by wall time, not by re-run.
    browser-bridge/tests::test_agent_without_any_tab_is_untouched died as a
    subprocess 300 s timeout (returncode: -9), not a failed assertion, at
    load average ≈ 20 with three sibling agents running their own suites and mutation
    sweeps on this box. The same file passes 54/54 on pristine origin/main
    (in 394 s — itself load-inflated, which is the tell).
  • GUARD 9 / GUARD 10 additionally reported cannot attribute rather than
    enforcing: live sibling sessions are sitting inside the shared clone, so the
    harness could not prove who wrote /home/zach/workspace/devrc/.git/config. That
    is the harness's own attribution arm, not a test result — its prevention half
    stayed in force.

So main is currently red in both tiers for reasons that predate this branch.
Stated plainly rather than folded into a pass: this PR does not make the gate green,
and it does not make it redder. What it can claim is that the target containing the
changed code is 12012/12012 green in the authoritative sandbox tier.

Not verified

No test in this module reaches a live Postgres — SectionStore is the seam, and what
is pinned is the SQL text and the bound parameters, never that a server accepts them.
The --rebuild --write path against the real initiatives.handoff_section table has
not been exercised by anything here.

…omplete-read-authority

Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Re-gated on the TRUE merged tree — both sandbox tiers now GREEN

main moved after the run above (baa9585479338677), and one of the commits
that landed, df02571f fix(espanso tests): retire the recommend-collision live guard, is the fix for the exact test that was the previous run's only sandbox
failure
. So the earlier verdict was a claim about a base that no longer exists.

Re-merged (f3122a73 = origin/main 79338677 + this work), pushed, and both
check derivations re-built one at a time, on an uncontended store:

NIXBUILD_RC[pytests]=0
  devrc-pytests>   PASS  scripts/tests  (collected=12050 passed=12050 skipped=0 floor=10269)
  devrc-pytests>   TOTAL collected=21062  passed=21059  skipped=3  failed=0  (floor: 18404)
  devrc-pytests> PASS 48   FAIL 0
  devrc-pytests> RESULT: PASS (exit=0)

NIXBUILD_RC[nodetests]=0
  devrc-nodetests>   TOTAL suites=5 files=41 tests=1449 pass=1449 fail=0 skipped=0  (floor: 1367)
  devrc-nodetests> RESULT: PASS (exit=0)

scripts/tests (the target holding test_handoff_index.py) is 12050/12050, and the
whole sandbox tier is failed=0. test_handoff_index.py alone re-run on the merged
tree: 234 passed.

The dev-host tier (gate.sh --tier both) was NOT re-run against this base — its 8
remaining failures were measured pre-existing on main and are environment-shaped
(they do not reproduce in the sandbox at all), plus one 300 s subprocess timeout
under load ≈ 20. That is an honest gap: the claim above is about the sandbox tier
on f3122a73, and nothing more.

Everything under "Not verified" in the previous comment still stands — no test in
this module reaches a live Postgres.

ZacxDev and others added 2 commits September 3, 2026 19:59
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
…d in a state home.nix calls SUPPORTED

F1 (the one that mattered). The fourth `rebuild_refusal` arm — "not ONE repo was
read COMPLETELY", rc 4 — fires on the unit's own argv whenever no configured repo
is replaceable, and three absent checkouts plus one corrupt blob is exactly that.
MEASURED on the timer's argv (`--rebuild --write`, unscoped), one handle present
with one corrupt blob of three docs, three absent:

    pre-widening : rc 0  DELETE + 18 INSERTs   index refreshed, PARTIAL printed
    PR head      : rc 4  SQL kinds []          nothing written at all
    here         : rc 0  no DELETE, 18 INSERTs index refreshed, PARTIAL printed

`nix/home.nix` states in as many words that a handle pointing at an absent
checkout is SUPPORTED and that the price is a standing warning, so that arm
turned a supported state into `OnFailure=notify-failure@%n.service` every 6h with
the index frozen — the permanently-red gate this same function's all-unmeasured
arm had to unwind once already. Replaced with `rebuild_downgrade_reason`: the run
takes the remedy that refusal printed as its OWN remedy — write, without the
DELETE. Strictly non-destructive (ON CONFLICT refreshes), and `main` computes the
delete scope only when the downgrade does not fire, so an empty scope can never
reach `write()`. home.nix's enumeration corrected: the prune condition widened
from unmeasured to INCOMPLETE, and the fourth state is documented as a downgrade.

F2 `rebuild_plan_lines` was the one consolidated site still re-deriving from raw
fields — two of its three buckets read `d.unmeasured`. They are now a partition on
a new `incomplete_kind`, and a kind the plan does not enumerate is REPORTED rather
than dropped out of all three buckets.

F3 three surviving mutants killed, two of which produced self-contradicting
operator reports; plus the seam the audit asked for, binding the plan's DELETE
line to the scope `DELETE_SQL` is actually bound with.

F4 `--prune`'s refusal kept, its stated reason fixed: an unreadable doc raises no
spelling ambiguity and its rows are already outside `measured`, so "nothing to put
back" was false. The real reason — a run that could not read its corpus cannot
vouch for it at all — is now what it says.

F5 "`partial_scope_warnings` says so on every output path" was false: it
short-circuited when EVERY repo was incomplete and deferred to a refusal `main`
only consults under `--rebuild`. MEASURED, `--write` without `--rebuild`: rc 0,
24 rows, no PARTIAL anywhere. The claim is now true rather than narrowed.

F6 authority is per DERIVATION and the DELETE binds LABELS, so a healthy twin
grants authority over a broken twin of the same name. Pre-existing and NOT fixed
here (its fix moves the delete scope, the stored key and handoff_search's scoping
together); what is fixed is that the run stopped saying "left untouched" and "kept
their old rows" about rows it had just deleted. Residual recorded in-code and
pinned by a test that fails the day it is fixed.

F7/F8 a disjunction note moved to the arm whose opener is one, and a stale
`--prune` bullet corrected — both pinned structurally.

Mutation sweep: 11 mutants, fresh tree each, PYTHONDONTWRITEBYTECODE=1, no-op
negative control SURVIVED and a known-caught positive control KILLED; all 9 real
mutants KILLED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round-1 audit: dispositions

Merged origin/main first (the two new commits touch only
claudedocs/handoff-signal-skill-eval.md and three browser-bridge/reference/sites/*
— disjoint from this diff). Every number below is a claim about the merged tree
952a88f8 and the fix commit on top of it, not about f3122a73.

# disposition where
F1a FIXED — behaviour changed. The fourth refusal arm is gone; the state is a downgrade, not a refusal rebuild_downgrade_reason (scripts/lib/handoff_index.py), main
F1b FIXED. nix/home.nix enumeration corrected + the fourth state documented nix/home.nix:3000-3018
F2 FIXED. All three plan buckets are a partition on a new incomplete_kind; an unenumerated kind is REPORTED rebuild_plan_lines
F3 FIXED. All three mutants killed, each by its own guard; seam test added TestTheReportsAndTheBOUNDDeleteCannotDisagree
F4 FIXED (message only, behaviour kept). rebuild_refusal's prune arm
F5 FIXED — the claim is now true. The all-bad short-circuit is gone partial_scope_warnings
F6 REPORT fixed; defect deliberately NOT fixed and recorded in-code + pinned by a test authority_label_collisions
F7 FIXED. Comment moved, placement pinned structurally TestTheCommentsSitWithTheCodeTheyDescribe
F8 FIXED. Stale bullet corrected, pinned both by words and by behaviour same class

F1 — the finding that mattered

I took the fallback rather than narrowing the refusal. MEASURED on the timer's own
argv
(--rebuild --write, unscoped), one handle pointing at a real checkout with
one corrupt blob of three docs and three handles absent, over three versions of
one file:

pre-widening 79338677 : rc 0   DELETE ['<label>'] + 18 INSERTs   index refreshed, PARTIAL printed
PR head (merged) …    : rc 4   SQL kinds []                      nothing written at all
this commit           : rc 0   no DELETE,          18 INSERTs    index refreshed, PARTIAL printed

The downgrade is strictly non-destructive (ON CONFLICT refreshes without
destroying), and it is the remedy that refusal printed as its own remedy
("Re-running WITHOUT --rebuild is a real remedy here") — a remedy only a human can
type is not a remedy for a unit. main computes the delete scope only when the
downgrade does not fire, so an empty scope can never reach write(); write()'s
ValueError is now the backstop rather than the mechanism, and that equivalence is
pinned as a seam over every reachable (scoped, prune) combination.

What the downgrade costs, stated rather than discovered: with no DELETE, a section
removed from a doc keeps its stale row until some repo reads in FULL again. The
run says so, on the success line.

F3 — the three mutants, dying

Fresh tree per mutant, PYTHONDONTWRITEBYTECODE=1, narrowest expression:

mutant verdict killed by
partial_scope_warnings.okd.unmeasured is None KILLED (4 failed) …::test_the_PARTIAL_warnings_ok_list_names_only_repos_read_in_FULL
rebuild_plan_lines.measuredd.unmeasured is None KILLED (5 failed) …::test_the_plan_DELETE_line_names_only_repos_read_in_FULL + the seam
zero-rows denominator → len(bad) KILLED (1 failed) …::test_the_zero_rows_arm_counts_repos_that_RESOLVED_a_ref

The last kills with exactly one failing test, which is also its reachability
proof: no earlier check wins and nothing else observes it.

The seam (test_the_planned_DELETE_IS_the_scope_that_gets_BOUND) parses the
DELETE line out of the operator's pre-flight and compares it to
conn.params_for("DELETE") — the parameters DELETE_SQL is actually bound with —
rather than restating either side.

Full sweep: 11 mutants, no-op negative control SURVIVED, a known-caught
positive control (may_replace_stored_rowsTrue) KILLED, and all 9 real
mutants KILLED.

What remains OPEN

  1. F6's structural residual is NOT fixed. Authority is demonstrated per
    DERIVATION and the DELETE binds per LABEL, so two checkouts named the same thing
    let a healthy twin grant authority over a broken twin's rows. MEASURED,
    DELETE [[['protorepo']]], 21 INSERTs all from the healthy twin, rc 0 —
    identical before this PR's widening. Its fix moves the delete scope, the stored
    key and handoff_search's --repo scoping together, so it is its own round.
    Recorded in rebuild_delete_labels' docstring and pinned by
    test_through_main_the_residual_is_recorded_and_the_report_is_true, which fails
    the day it is fixed
    — so it cannot become a stale "known issue" comment.
    What this PR changed is that the run stopped saying those rows were "left
    untouched" and "kept their old rows".
  2. Nothing here executes PostgresSectionStore against a real database. The
    whole suite is hermetic by design: the DELETE and the upsert are asserted as
    bound SQL against a recording connection. The downgrade's central claim — that a
    non-rebuild write REFRESHES rather than destroys — rests on ON CONFLICT DO UPDATE being what the SQL says, which is pinned as text and never as server
    behaviour. Unverified against live Postgres.

Gate — I am the gate (branch protection is off)

All on the merged tree, fix commit da13e698 (parent 952a88f8 = f3122a73 + origin/main):

tier verdict
subset test_handoff_index.py 262 passed
gate.sh --tier both (dev host) node RESULT: PASS (exit=0) (1449/1449); pytest RESULT: FAIL (exit=1)both failures environmental, see below
nix build .#checks.x86_64-linux.pytests (alone) RESULT: PASS (exit=0)PASS 48 FAIL 0, TOTAL collected=21090 passed=21087 skipped=3 failed=0
nix build .#checks.x86_64-linux.nodetests (alone) RESULT: PASS (exit=0)suites=5 files=41 tests=1449 pass=1449 fail=0

The two dev-host pytest failures, and why they are not this diff — the discriminating
control is that both pass in the sandbox tier, which ran the same suites:

  • test_clawgate_task_interview_guard.py::test_a_body_file_written_by_a_heredoc_on_the_same_line_is_read — a sibling agent's real /tmp/body.md (21,181 bytes, Sep 3 19:41) exists on the dev host and the guard reads it. Sandbox: passes.
  • test_browser_tab_ref.py::test_agent_refuses_a_frame_from_any_source[...]browser-agent's opencode warm step needs network + npm (browser-agent:355 says so in as many words); the subprocess hit its 300 s timeout. Sandbox: scripts/browser-bridge/tests (collected=890 passed=890 …). This diff touches only nix/home.nix, scripts/lib/handoff_index.py and scripts/tests/test_handoff_index.py — none reachable from scripts/browser-bridge/browser.

GUARD 10's ATTRIBUTION arm prints cannot attribute in the shared checkout throughout; noted, not chased.

@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round 1 audit claims for PR #1267, for the round-2 delta to check against the diff.

1. F1a — the fourth refusal arm claims to be DELETED and replaced by a DOWNGRADE to a plain upsert (`rebuild_downgrade_reason`): writes what read, deletes nothing, rc 0. `main` claims to compute the delete scope ONLY when the downgrade does not fire, so an empty scope is claimed to be structurally unable to reach `write()` — its `ValueError` claimed to be a backstop, not the mechanism. Measured three ways on the timer's own argv: pre-PR rc 0 with DELETE + 18 INSERTs; base rc 4 with nothing written; fix rc 0 with no DELETE and 18 INSERTs.
2. F1b — `nix/home.nix`'s refusal enumeration claims to be corrected (unmeasured -> INCOMPLETE) and the fourth state claims to be documented as a downgrade WITH its cost.
3. F2 — all three plan buckets claim to be a PARTITION over a new `incomplete_kind`, and an unenumerated kind claims to be REPORTED rather than silently dropped.
4. F3 — three previously-surviving mutants claim to be KILLED, plus a new SEAM test binding the plan's DELETE line to the scope actually BOUND. The zero-rows mutant claims to be killed by EXACTLY ONE test, offered as its own reachability proof.
5. F4 — the `--prune` refusal claims to be KEPT with its rationale replaced: an unreadable doc raises no spelling ambiguity and its rows are already outside `measured`. The guard is claimed to be word-level on one retracted sentence, with that limit stated in its own docstring.
6. F5 — the all-bad short-circuit claims to be REMOVED with a distinct total-variant warning, making the "says so on every output path" claim TRUE rather than narrowed.
7. F6 — `authority_label_collisions` claims to remove the FALSE report (warning, plan, success line) while deliberately leaving the structural defect. The residual claims to be recorded in `rebuild_delete_labels`' docstring and pinned by `test_through_main_the_residual_is_recorded_and_the_report_is_true`, a test claimed to FAIL the day the defect is fixed.
8. F7/F8 — the disjunction note claims to be moved to the prune arm and the stale `--prune` bullet corrected, both pinned by `TestTheCommentsSitWithTheCodeTheyDescribe`.
9. Test claims — 19 failed / 13 passed at merged base `952a88f8` -> 262 passed at `da13e698`. The 13 green-at-base are claimed to be negative controls plus the three F3 mutant guards, whose red is the mutant and not the base.
10. Mutation claims — 11 mutants, all 9 real ones KILLED, no-op negative control SURVIVED, positive control (`may_replace_stored_rows` -> `True`) KILLED, fresh tree per mutant under `PYTHONDONTWRITEBYTECODE=1`.
11. Gate claims — sandbox tier BOTH PASS on `da13e698`: pytests `collected=21090 passed=21087 skipped=3 failed=0`, nodetests `1449/1449`, each built alone. Dev-host pytest FAILS on two failures claimed ENVIRONMENTAL, with the discriminating control claimed to be that both pass in the sandbox tier.
12. Unverified-by-the-author — nothing touches live Postgres; the downgrade's central claim (a non-rebuild write REFRESHES rather than destroys) rests on `ON CONFLICT DO UPDATE` being what the SQL text says, pinned as text and never as server behaviour.

ZacxDev and others added 3 commits September 3, 2026 21:31
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
…reported as not having happened

Six findings from the round-2 delta audit of #1267, plus one the fix work
surfaced. The unifying defect in three of them is the same shape: a DECISION
taken on the truthiness of a RENDERED STRING rather than on the fact it renders.

F-A  `partial_scope_warnings` chose its variant with `if not ok:` over a joined
     label list. A label is `Path(r).name`, so `--repo .` derives "" — and a run
     whose only replaceable repo is that one joined to "", took the all-bad arm,
     and printed "NOTHING WAS DELETED" over a bound `DELETE … ANY([''])`. One run,
     three contradicting statements; the sentence also contradicted itself
     ("all 2 … were incomplete" then naming one). Now `nothing_was_read_completely`
     — ONE owner, read by both `rebuild_downgrade_reason` and this arm, so the two
     spellings that disagreed cannot. Swept the SHAPE, not the site: the plan's
     `', '.join(x) or '(none)'` printed "(none)" for a non-empty bucket, and
     `derive_repo`'s `label or root.name` silently discarded an explicit "" and
     substituted the directory name — a third site, found by a fixture that
     asserted itself.

F-B  `incomplete_reason`'s trailing `return` was an unguarded fall-through, and
     `incomplete_kind`'s docstring told a reader a third kind "has exactly one
     place to be added". Adding one there alone rendered `docs-unreadable (0 of 3)`
     for a repo with ZERO unreadable docs — the mechanism built to make an
     unenumerated kind LOUD explained it with a confident wrong reason, published
     beside the true kind in adjacent `--json` fields. Now a branch table with no
     fall-through; an unenumerated kind reports itself as one and still withholds
     authority. Its guard patched `incomplete_kind` AND `incomplete_reason`
     together — structurally blind, on a justification that was measurably false
     (`may_replace_stored_rows` is already False with the kind alone). It now
     patches only the kind.

F-C  Three mutants that survived a full green suite, two on code this round added:
     the plan header ignoring `downgraded` (parameter, header string and main's
     call-site argument were ALL deletable with the suite green), the `kept`
     bucket reverted to the raw field, and the downgrade message dropping its
     stated cost. Each now has a guard; each shown dying by that guard.

F-D  Two comments named guards that do not exist, so a reader who greps one finds
     nothing and concludes the invariant is unguarded. Fixed — and pinned:
     `TestEveryGuardThisModuleNamesByNameActuallyExists` fails the suite on a
     dangling citation. It immediately found a third, pre-existing one (a
     truncated real name) that the audit had not.

F-E  The downgrade's stated cost was an order of magnitude too small. "A section
     REMOVED from a doc" reads as trailing-ordinal drift; the DELETE is
     per-repo-LABEL and the key is (repo, slug, section, ordinal), so what
     persists is EVERY row not re-derived — whole DELETED docs, and both slugs of
     a RENAMED one, returned side by side. One constant, `DOWNGRADE_COST`, quoted
     verbatim by nix/home.nix and pinned as a whole normalised string, so
     correcting one surface and not the others is now a failing test.

F-F  `--json` publishes `rebuild_would_be_downgraded`, so a machine consumer no
     longer word-matches "NOTHING WAS READ COMPLETELY" out of prose — the
     guard-on-WORDS this module refuses everywhere else. A distinct exit code
     (drift-check's rc 16 idiom) was CONSIDERED AND NOT TAKEN: this file is
     git-delivered (the unit runs the working-tree copy) while SuccessExitStatus
     is switch-delivered, so between a pull and the next switch the unit would be
     RED in a state home.nix calls SUPPORTED — the regression the previous commit
     exists to undo, re-entered through the deploy gap. Reasoning and the named
     residual (a shell consumer reading neither prose nor --json still cannot see
     it) are recorded at the decision site.

Red→green at 6e818df: 13 tests red at the merge base, all green at HEAD.
Mutation sweep, fresh tree per mutant, PYTHONDONTWRITEBYTECODE=1: 9 mutants all
KILLED, no-op negative control SURVIVED (281 passed), positive control KILLED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round-2 delta audit — all six findings fixed, plus one the fix work surfaced

Head 455b7dec (was da13e698). origin/main merged twice — once at the start (14275969) and again mid-run when it moved (a7dac5bd, docs-only). Every number below is a claim about the merged tree 455b7dec, base 6e818dfa for the red→green matrix.

Per-finding disposition

# Disposition Where
F-A FIXED — variant now chosen by count, via one owner handoff_index.py:1849 (nothing_was_read_completely), consumed at :2033, :2296
F-B FIXED — branch table, no fall-through; guard de-blinded :1791 (reason), :1732 (docstring), test_handoff_index.py:4443
F-C FIXED — M8, M10, M11 all killed TestThePlanPlumbingThisRoundAddedIsGuarded
F-D FIXED — names corrected and pinned TestEveryGuardThisModuleNamesByNameActuallyExists
F-E FIXED — one constant, three surfaces, pinned by identity DOWNGRADE_COST (:1659)
F-F FIXED (json), exit code considered and declined derivation_json rebuild_would_be_downgraded

F-A — and the shape, not just the site

if not ok: branched on a rendered string. Reproduced at 6e818dfa end-to-end: a bound DELETE … ANY(['']) beside 🔴 NOTHING WAS READ COMPLETELY — all 2 configured repo(s) were incomplete … : varkelthornrepo — a sentence that denies the DELETE the same run issued and contradicts itself in isolation.

Consolidated into nothing_was_read_completely, read by both rebuild_downgrade_reason and partial_scope_warnings, so the two spellings that disagreed no longer exist. Swept the shape and found two more sites:

  • rebuild_plan_lines' ', '.join(x) or '(none)' → printed (none) for a non-empty bucket.
  • 🔴 derive_repo's name = label or root.name — silently discarded an explicitly empty label and substituted the directory name. Found because the new fixture asserted itself and failed; without that assertion every F-A test would have run against a repo labelled brindlemossrepo and passed vacuously. This is an authority key (delete scope, stored rows, handoff_search --repo).

F3's seam fixture widened as asked — test_the_planned_DELETE_IS_the_scope_that_gets_BOUND is joined by an empty-label e2e case asserting planned == bound == [""].

F-B — the guard was blind, and its justification was false

The old guard patched incomplete_kind and incomplete_reason, justified as "patching only the kind would leave the repo in the DELETE bucket". Measured false: incomplete_reason calls incomplete_kind, so may_replace_stored_rows is already False with the kind alone. The pair-patch supplied the output under test and hid the real defect — the real function answered a novel kind with docs-unreadable (0 of 3) for a repo with zero unreadable docs. Now patches only the kind; an unenumerated kind reports itself as unenumerated and still withholds authority (a string, not a raise — a raise would crash the unit).

F-D — the pin immediately earned itself

Fixed both dangling citations, then pinned the class. The new checker immediately found a third, pre-existing one the audit had not: test_every_canonical_prefix_has_a_section (:258) was a truncated real name (…_and_vice_versa). Stated blind spot, in source: it recognises test_…/Test… only, so a bare snake_case citation is not covered — and one of the two dead citations was exactly that shape.

F-F — why no new exit code

--json now publishes rebuild_would_be_downgraded (subjunctive because the function is pure over derivations and has no argv; under --rebuild "would be" is). The rc-16 idiom was considered and declined:

  • handoff_index.py is git-delivered (the unit's start command runs %h/workspace/devrc/…, the working tree) while SuccessExitStatus is switch-delivered. Between a pull and the next switch the unit would be RED in a state nix/home.nix calls SUPPORTED — the exact regression the previous commit exists to undo, re-entered through the deploy gap.
  • rc 0 is not a false claim: the run wrote every row it could and destroyed nothing.
  • Adding the code later is one commit; removing it once a caller branches on it is not.

Residual, recorded in source: a shell consumer reading neither prose nor --json still cannot distinguish a downgraded run from a clean rebuild.

Verification

Red→green, base 6e818dfa → HEAD 455b7dec: 13 tests red at base, all green at HEAD. Red reasons checked individually — the F-A e2e reproduces the bound-DELETE/denial contradiction, F-B reds on the literal docs-unreadable (0 of 3), the fixture reds on the discarded empty label. None red merely for a missing attribute.

Mutation sweep — fresh copytree per mutant (.git excluded), PYTHONDONTWRITEBYTECODE=1, narrowest expression:

mutant verdict killed by
NEG no-op control SURVIVED (281 passed)
POS control (measured bucket reverted) KILLED 6 tests
M8 header ignores downgraded KILLED …_HEADER_says_the_DELETE_is_SKIPPED_when_downgraded
M8b main drops the argument KILLED test_MAIN_passes_the_downgrade_through_to_the_plan_header
M10 kept reads the raw field KILLED …_UNMEASURED_bucket_is_keyed_on_the_KIND_not_the_raw_field
M11 downgrade drops its cost KILLED …_downgrade_states_its_FULL_cost_not_just_a_section
F-A / F-A2 / F-A3 / F-B reverts KILLED their own guards

M8's three sites (parameter, header string, call-site argument) are each independently killed. M4 not chased — equivalent under reachable states, as the audit said.

M10 is an INVARIANT guard, labelled as one, not regression coverage. Its discriminating state (a third kind claiming a repo whose unmeasured is set) is the module's own documented future, built with the same supported monkeypatch the pre-existing third-kind guard uses. The mutant dies; no live bug was reachable.

Gate — merged tree 455b7dec:

  • gate.sh --tier both (dev-host tier): node RESULT: PASS (exit=0) (1449 tests); pytest RESULT: FAIL (exit=1)TOTAL collected=21100 passed=21094 skipped=3 failed=3.
  • The 3 failures are the known environmental pair, attributed by a control at base 6e818dfa with my change absent, not by reasoning: test_browser_tab_ref.py (subprocess timeouts — needs network/npm) and test_clawgate_task_interview_guard.py::test_a_body_file_written_by_a_heredoc_on_the_same_line_is_read (a sibling's real /tmp/body.md). Same tests fail at base. My diff cannot reach either.
  • 🔴 The sandbox tier Tekton actually gates on — both green, built ONE AT A TIME:
    • nix build .#checks.x86_64-linux.pytestsNIXBUILD_RC=0, RESULT: PASS (exit=0), TOTAL collected=21100 passed=21097 skipped=3 failed=0. The three dev-host failures pass here, confirming the attribution directly.
    • nix build .#checks.x86_64-linux.nodetestsNIXBUILD_RC=0, RESULT: PASS (exit=0), TOTAL suites=5 files=41 tests=1449 pass=1449 fail=0 skipped=0.
  • Never a combined invocation (documented false failures), and each waited out concurrent sibling nix builds — observed in devrc-dlr-base and devrc-mention-rework, resolved by exec name plus /proc/<pid>/cmdline, never an -f pattern.

Still open

  • F6's structural residual — authority is per DERIVATION, the DELETE binds per LABEL. Unchanged this round and still the honest statement in rebuild_delete_labels/authority_label_collisions: a label one checkout read in FULL is a label this run will DELETE even where a different checkout spells itself the same and could not be read. Fixing it moves the delete scope, the stored key and handoff_search's scoping together.
  • An empty label renders as blank text in the plan and the ok list. The decision no longer depends on the rendering, and (none) vs blank are now distinguishable, but the real fix is rejecting or normalising an empty label at main — which is the same round as the label-identity work above.
  • The live-Postgres path is unexercised. Everything here is hermetic (recording connection over the production PostgresSectionStore); no run against the real database.
  • The shell-consumer residual under F-F, above.

@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round 2 audit claims for PR #1267, for the round-3 delta to check against the diff.

1. F-A — a new `nothing_was_read_completely` claims to be ONE owner read by both `rebuild_downgrade_reason` and `partial_scope_warnings`, so the two disagreeing spellings claim to no longer exist. The shape sweep claims to have found TWO MORE sites: the plan's `', '.join(x) or '(none)'`, and 🔴 `derive_repo`'s `name = label or root.name`, which claims to have silently discarded an explicit empty label and substituted the directory name — AN AUTHORITY KEY. That second one is claimed to have been findable only because the new fixture asserts itself, otherwise every F-A test would have passed vacuously.
2. F-B — `incomplete_reason` claims to be a branch table with NO fall-through; an unenumerated kind claims to report itself as unenumerated and still withhold authority, returning a string rather than raising (a raise would crash the unit). The guard claims to patch ONLY the kind now, and its old justification is claimed measurably false since `may_replace_stored_rows` is already False with the kind alone.
3. F-C — M8, M8b (the call site), M10 and M11 all claim to be KILLED, each by its own guard's assertion. M10 claims to be an INVARIANT GUARD, labelled as one, whose discriminating state is the module's documented future (a third kind) and through which no live bug was reachable via `derive_repo`.
4. F-D — both dangling guard names claim to be fixed and the class pinned by a checker; that checker claims to have immediately found a THIRD, pre-existing dangling citation the audit did not (`test_every_canonical_prefix_has_a_section`, a truncated name).
5. F-E — one `DOWNGRADE_COST` constant claims to be quoted verbatim by `nix/home.nix` and pinned by identity, replacing the narrower "a section REMOVED from a doc" wording.
6. F-F — `--json` claims to publish a run-level `rebuild_would_be_downgraded`. A distinct exit code was DECLINED, justified at the decision site: this file is git-delivered while `SuccessExitStatus` is switch-delivered, so a pull-without-switch would put the unit back to a 4x/day toast in a state `home.nix` calls SUPPORTED.
7. Test claims — 13 tests RED at base `6e818dfa`, all green at HEAD, 281 in the file. Red REASONS claim to be verified individually rather than merely counted: the F-A end-to-end claims to reproduce the audit's exact contradiction (a bound `DELETE ['']` beside "NOTHING WAS READ COMPLETELY … all 2 …"), and F-B claims to red on the literal `docs-unreadable (0 of 3)`. None is claimed red merely for a missing attribute.
8. Mutation claims — fresh tree per mutant under `PYTHONDONTWRITEBYTECODE=1`; no-op negative control SURVIVED at 281 passed; positive control KILLED; M8/M8b/M10/M11 killed plus four revert-mutants of the F-A/F-B fixes. M4 deliberately not chased as equivalent under reachable states.
9. Gate claims — sandbox BOTH PASS on merged tree `455b7dec`: pytests `collected=21100 passed=21097 failed=0`, nodetests `1449/1449`, each built alone while waiting out sibling builds. Dev-host pytest FAILS with `failed=3`, all three claimed attributed by a CONTROL RUN AT BASE with the change absent, and claimed to pass in the sandbox tier.
10. Open/unverified by the author — F6's structural residual unchanged; an empty label still RENDERS as blank text (the decision no longer depends on the rendering, but rejecting/normalising it at `main` is deferred); the live-Postgres path entirely unexercised; and a shell consumer reading neither prose nor `--json` still cannot see a downgrade.

ZacxDev and others added 3 commits September 4, 2026 01:36
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
…guarded, and its docstring cited a check the same commit deleted

Final fix round. Four findings, all small; every code change below is one
expression.

F1  `nothing_was_read_completely`'s `bool(derivations) and` conjunct had no
    test. The mutant that drops it — `return not any(...)` — SURVIVED a full
    281-test run, because every fixture in the file passes at least one
    derivation, so nothing ever reached the empty input. `any()` over an empty
    sequence is False, so the mutant returns the vacuous True the conjunct
    exists to refuse: `nothing_was_read_completely([])` flips False->True,
    dragging `rebuild_downgrade_reason([])` from None to a REASON and
    `derivation_json([])["rebuild_would_be_downgraded"]` to True. All three are
    `__all__` exports and `handoff_search` imports the module; `main` is
    insulated only because it returns RC_USAGE first. Now pinned on all three
    surfaces.
    (b) And the docstring is why it went unguarded: it said
    `rebuild_downgrade_reason` "kept" an `if not derivations: return None` of
    its own — the same commit had DELETED it, so `grep -n "if not derivations"`
    matched the docstring making the claim and nothing else. Retracted: the
    guard now lives here alone, and the docstring says so.

F2  The citation checker could not match ANY `test_` name containing a capital.
    `test_[a-z0-9_]+` cannot cross an uppercase letter and `\b` cannot fire
    mid-identifier, so such a name matched NOTHING — not a truncated prefix, no
    match. Three real citations were invisible, TWO written by the previous
    round; none dangled, so the checker was green and would have stayed green
    through any rename. Widened to `[A-Za-z0-9_]`: 10 names scanned -> 13, still
    zero dangling. The module's stated blind spot named only the non-`test_`
    shape and is widened to what the code actually missed, and the repo-wide
    dangling assertion grows a message naming the citation and the fix.

F3  The class's headline seam test built its empty-label fixture without the
    self-assertion its sibling helper carries. Under the `derive_repo`-revert
    mutant `empty.label` becomes `brindlemossrepo`, so cases[0] collapses into
    cases[2] and cases[1] into cases[6]: the 7-case matrix silently becomes 5
    and the "disagreeing input" the docstring names is gone, while the
    `verdicts == {True, False}` control still passes. MEASURED: the seam test
    passed ALONE under that mutant before this, and fails alone after.

F4  Six sites decided on the truthiness of a `str | None` whose owner spells the
    question `is None`. Not divergent today — but that is the same "they agree
    only while X is non-empty" argument that held for `if not ok:` until
    `--repo .` made X empty, and it is the fourth instance of the shape this
    branch has swept. Mechanical `is not None` at all of them
    (`incomplete_kind`, `authority_label_collisions`, `partial_scope_warnings`
    x2, `rebuild_delete_qualification`); no producer emits "" today, so no
    behaviour changes.

Recorded, not fixed:
  * the empty label renders blank on FOUR display surfaces, not the two the
    tests exercise; count corrected at `derive_repo`. The fix belongs at `main`
    (reject/normalise an empty --repo label), never in a renderer — a
    `label or "(unnamed)"` there would re-introduce the exact falsy-string shape
    just swept.
  * M10's guard is labelled an INVARIANT GUARD in as many words, and stays
    correctly out of the red-at-base matrix.
  * authority-per-derivation vs DELETE-per-label, and the live-Postgres write
    path, remain open and remain disclosed.

Mutation battery: fresh tree per mutant, PYTHONDONTWRITEBYTECODE=1, caches
cleared, `.git` detached from every copy. no-op control SURVIVED (283 passed);
inverted-authority positive control DIED (17 failures). F1's mutant now dies by
its own assertion (`assert True is False`); F2's dies on the narrow regex; F3's
seam test fails ALONE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
Claude-Session-Id: 90310ba1-6dc2-4779-a616-2ccfe89457d7
@ZacxDev

ZacxDev commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Round-3 delta audit — dispositions

Final fix round. Four findings fixed, three recorded. Every code change is one expression; the behaviour-bearing diff is 5 lines, all of them is not None.

Merged origin/main first (2882d2c7) — head was ahead 7 / behind 5 with nix/home.nix touched on both sides. Re-verified rather than trusting the prior audit: merge-tree --write-tree exit 0 (branching on the exit code, not a marker grep), and I read the merged let block — main's enableMainGreenDeadman and this branch's enableHandoffIndexSync are separate bindings, both intact; main's espanso edits are in a different attrset. nix-instantiate --parse clean. Merge commit bf3dfc71; fix commit fb73d583.

FIXED

F1 — the new guard was itself unguarded, and its docstring cited a check the same commit deleted.
(a) The mutant dropping bool(derivations) and survived at 281 passed. any() over an empty sequence is False, so the mutant returns the vacuous True the conjunct exists to refuse: nothing_was_read_completely([]) flips FalseTrue, dragging rebuild_downgrade_reason([]) from None to a REASON and derivation_json([])["rebuild_would_be_downgraded"] to True. All three are __all__ exports and handoff_search.py:160 does import handoff_index; main is insulated only because it returns RC_USAGE first. Now pinned on all three surfaces — handoff_index.py:1899 guarded by test_the_empty_derivation_list_is_not_an_all_bad_run.
(b) The docstring is why it went unguarded: it claimed rebuild_downgrade_reason "kept" an if not derivations: return None of its own, which that same commit had removedgrep -n "if not derivations" matched the sentence making the claim and nothing else. Retracted; the guard now lives here alone and the docstring says so.

F2 — the citation checker could not match any test_ name containing a capital.
test_[a-z0-9_]+ cannot cross an uppercase letter and \b cannot fire mid-identifier, so such a name matched nothing — not a truncated prefix, no match at all. Three real citations were invisible, two of them written by the previous round. None dangled, so the checker was green and would have stayed green through any rename. Widened to [A-Za-z0-9_]: scanned names go 10 → 13 on the tree as found (15 on the committed tree, which adds this round's own two citations), dangling still [] throughout. Also widened the module's stated blind spot (it named only the non-test_ shape) and gave test_no_test_name_cited_in_handoff_index_is_dangling a message naming the dangling citation and the fix — it is a repo-wide coupling any rename can turn red.

F3 — the headline seam test built its empty-label fixture without the self-assertion its sibling carries.
Under the derive_repo-revert mutant empty.label becomes brindlemossrepo, so cases[0] collapses into cases[2] and cases[1] into cases[6]: the 7-case matrix silently becomes 5, the "disagreeing input" the docstring names is gone, and the verdicts == {True, False} positive control still passes — nothing signals it. Added assert empty.label == "" with the reason.

F4 — truthiness on a str | None whose owner spells the question is None.
Swept to is not None at all sites: incomplete_kind, authority_label_collisions, partial_scope_warnings (×2, including d.unmeasured — same str | None shape), and rebuild_delete_qualification. No behaviour change today — verified the only three producers are the non-empty literals no-such-directory, no-mainline-ref, ls-tree-failed. This is the fourth instance of the shape that held for if not ok: right up until --repo . made X empty.

RECORDED, NOT FIXED

  • F5 — the empty label renders blank on four display surfaces, not the two the tests exercise. Count corrected at derive_repo, with each surface named and measured via main --repo . --rebuild. The fix belongs at main (reject or normalise an empty --repo label with RC_USAGE), never in a renderer — a label or "(unnamed)" there would re-introduce the exact falsy-string shape this branch just swept, in the layer read when the decisions are audited.
  • F6 — 5 of the 13 red-at-base tests were red on a missing attribute/key, which is the only possible red for an addition. The tests are fine; the claim that none did overstated it. No code change.
  • F7 — M10's guard is now labelled an invariant guard in as many words, and stays correctly out of the red-at-base matrix.

STILL OPEN (disclosed, not closed by this round)

  • Authority is demonstrated per DERIVATION and exercised per LABEL. Two checkouts sharing a label mean a healthy twin grants a DELETE over a broken twin's rows. authority_label_collisions fixes the report, not the structure; the real fix moves the delete scope, the stored key and handoff_search's scoping together.
  • F5's display residual — above.
  • The live-Postgres write path is unexercised. The DDL (initiatives.handoff_section, generated tsvector, GIN index) has been read and never executed: the gate runs in a nix sandbox with no cluster. enableHandoffIndexSync = false stays false until a supervised --rebuild --write has been watched to work.

Verification

Mutation battery: fresh tree per mutant, PYTHONDONTWRITEBYTECODE=1, __pycache__ cleared, and .git removed from every cp -a copy (a worktree's .git is a file — the copies would otherwise have shared the real git dir).

mutant expected result
no-op control (comment only) SURVIVE SURVIVED, 283 passed
positive control (may_replace_stored_rows inverted) DIE DIED, 17 failures
F1: drop bool(derivations) and die by the new assertion 1 failedassert True is False, the new test alone
F2: narrow NAME_RE back to [a-z0-9_] die 1 failed — the new test alone
F3: revert derive_repo to label or root.name seam test fails alone passed alone before / fails alone after

Gate

Base sha e92b4d45 (this branch merged with origin/main d8fe0bce). Both tiers named separately, because they are different tiers and not two spellings of one.

Dev-host tier (scripts/gate.sh --tier both, run on fb73d583 — the merge of origin/main 2882d2c7):

  • pytest RESULT: FAIL (exit=1)TOTAL collected=21142 passed=21138 skipped=3 failed=1. The single failure is test_clawgate_task_interview_guard.py::test_a_body_file_written_by_a_heredoc_on_the_same_line_is_read, one of the three documented known-environmental cases.
  • node RESULT: PASS (exit=0) — suites=5 files=41 tests=1449 pass=1449 fail=0.
  • My own target passed outright: PASS scripts/tests (collected=12139 passed=12139 skipped=0 floor=10269).

That failure is proven environmental, not diagnosed by plausibility. Cheap discriminating control: the same test was run on a pristine origin/main worktree carrying none of this branch's changes and failed identically (1 failed, 309 passed). /tmp/body.md exists on this box as a real 21 KB file written by a sibling agent at 19:41, which is exactly the documented cause. This diff touches only scripts/lib/handoff_index.py and scripts/tests/test_handoff_index.py and cannot reach that guard.

Sandbox tier (the one Tekton runs and the merge is gated on) — built one at a time, never combined:

  • nix build .#checks.x86_64-linux.pytestsRESULT: PASS (exit=0), TOTAL collected=21145 passed=21142 skipped=3 failed=0. Genuinely built (building '…devrc-pytests.drv'), not the cached case. Notably test_clawgate_task_interview_guard.py passes 310/310 here — the sandbox has no /tmp/body.md, which independently corroborates the diagnosis above.
  • nix build .#checks.x86_64-linux.nodetestsRESULT: PASS (exit=0), TOTAL suites=5 files=41 tests=1449 pass=1449 fail=0 skipped=0. Also genuinely built, not cached.

Both required contexts are green on the merged tree. Branch protection is currently off, so this run is the gate; the verdicts above are the runners' own RESULT: lines, read from nix log <drv>, not inferred from a piped exit code.

@ZacxDev
ZacxDev merged commit d86b4e4 into main Sep 4, 2026
0 of 2 checks passed
@ZacxDev
ZacxDev deleted the fix/handoff-index-incomplete-read-authority branch September 4, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant