Skip to content

backlog: five more instances for #1000, and the asymmetry rule for its controls - #218

Merged
wshallwshall merged 1 commit into
mainfrom
claude/backlog-1000-evidence
Aug 5, 2026
Merged

backlog: five more instances for #1000, and the asymmetry rule for its controls#218
wshallwshall merged 1 commit into
mainfrom
claude/backlog-1000-evidence

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Strengthens BACKLOG #1000 -- "Prove each required merge context can fail: negative controls for the gates that block merge" -- with five more measured instances and one design rule. Docs-only, one file.

No new number was allocated, deliberately. The conclusion that produced this change was that a near-duplicate sibling would dilute #1000 rather than add to it: #1000 already owns the class and was itself prompted by the backlog-hygiene gate. Verified: 120 items before, 120 after, zero new numbers.

The design rule, which is the part most likely to be skipped

A negative control must be ASYMMETRIC. A control that fails on everything when you neuter the rule cannot tell you which layer does the work -- so it cannot distinguish "the other cases are safe by design" from "safe by luck."

It is written with its measurement attached, including the uncomfortable half: the first draft of a comment credited new code with both ADS spellings when it covered one. That is an overstatement in the direction that flatters one's own code, which is the worst direction for it to be wrong in. A control failing on all eight inputs would have looked stronger, confirmed the overstatement, and taught nothing.

The five instances

Each is a check that was green while unable to see what it was trusted to report on:

  • a ValidatePattern that accepted a trailing newline -- "abc\n" matches ^[A-Za-z0-9._-]+$, because .NET's $ also matches before a final newline, so the guard did not enforce its own stated shape
  • a hijack test asserting a substring, which stayed green while the command it checked was unrunnable
  • a missing exit $LASTEXITCODE, without which pwsh reports 0 for a script that died at parameter binding
  • a control that failed on 1 of 8 spellings, which is what exposed the overstatement above
  • a drain loop that passed --arg to gh --jq; gh errored, printed usage, and the script read the empty stdout as "nothing eligible" while two PRs sat eligible

The last is stated as a pattern rather than an incident, because its remedy generalises: a probe must validate the SHAPE of its own output, not merely read it. Otherwise "looked and found nothing" is indistinguishable from "did not look". The fix is to assert the probe returned the expected type before branching on it.

Verification

Banner invariant checked with the repo's own parser rather than a hand-rolled scan: backlog_status_check.py reports 315 items, each declaring exactly one status. Off current origin/main, zero behind, intersection with main empty, merge-tree clean.

…s controls

#1000 ("prove each required merge context can fail") was filed 2026-08-03 with
four instances, all found by hand and none by CI. 2026-08-05 produced at least
five more in a single day across three sessions. The rate is the argument: this
is an ongoing yield, not a backlog of historical mistakes.

  - a ValidatePattern that did not enforce its own stated shape ("abc"+newline
    MATCHES ^[A-Za-z0-9._-]+$, because .NET's $ also matches before a final
    newline)
  - a test that rendered the broken output and asserted a substring of it, so it
    passed for the whole life of the defect it covered
  - an exit code that masked the failure being asserted: pwsh -File returns 0
    when the script inside died at parameter binding, so every execution
    assertion without an explicit `exit $LASTEXITCODE` is vacuously green
  - a probe that could not tell "found nothing" from "did not look": a gh call
    errored, printed usage, and its EMPTY output was read as "nothing eligible"
    while two PRs sat eligible
  - a control too uniform to locate the layer doing the work

Also records the design rule the last of those produced, because it is the part
most likely to be skipped by whoever starts #1000: A NEGATIVE CONTROL MUST BE
ASYMMETRIC. It must fail for exactly the shapes its rule covers and KEEP PASSING
for shapes another layer catches, or it cannot say which layer does the work.

Measured: a rule-1b fix believed to cover two ADS spellings had its eight-case
control fail on ONE when reverted -- the ::$DATA forms were already refused by an
extension backstop, so the new code was load-bearing for a single shape. The
first draft of its comment credited it with both, an overstatement in the
direction that flatters one's own code. A control failing on all eight would have
looked stronger, confirmed the overstatement, and taught nothing.

No new number allocated: this strengthens an existing item rather than adding a
sibling. The near-duplicate that was considered and declined is recorded in the
session log -- backlog-hygiene's "passed by not firing" on a PR with no BACKLOG
citation is correct behaviour by that gate, which logs "nothing to enforce" in
plain words, and #1000 already owns the general class.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 5, 2026 23:22
@wshallwshall
wshallwshall merged commit fd76396 into main Aug 5, 2026
31 checks passed
@wshallwshall
wshallwshall deleted the claude/backlog-1000-evidence branch August 5, 2026 23:24
wshallwshall added a commit that referenced this pull request Aug 6, 2026
PR #214 landed rule 3b's fixes, so main's worktree_gate.ps1 now carries
ConvertTo-WorktreeSlug where this branch carries Get-SafeForMessage. Both were inserted
immediately after Get-ComparablePath, which is the conflict two sessions predicted in
advance and agreed the resolution for.

RESOLVED KEEP BOTH. They are additive and independent: ConvertTo-WorktreeSlug maps a
verified refname to a legal worktree directory component for rule 3b's remediation;
Get-SafeForMessage folds a caller-supplied value before it reaches a deny reason for rules
1a/1b. Neither replaces the other and neither is a revert of the other. Kept in the
conflict's own order so main's trailing brace still closes main's function and the diff
against main stays minimal. $GateVersion stays 2026.08.05.2 by agreement -- the other
session ceded that line rather than re-bump it.

VERIFIED ON PRESENCE OF BOTH MARKERS, never on content equality against either branch. A
correct keep-both merge matches NEITHER parent byte-for-byte, so an equality check would
report failure precisely when the merge succeeded -- a check that cannot fire on success is
not a check. Measured on the merged file:

  ConvertTo-WorktreeSlug  2 occurrences (definition + rule 3b call site)
  Get-SafeForMessage      3 occurrences (definition + rules 1a and 1b call sites)
  0 parse errors, 0 non-ASCII bytes

239 passed across the 9 runnable gate files -- both suites on one file, up from 230 because
#214 added nine tests of its own. My 35-case decision probe unchanged at 35/35.
backlog_status_check OK at 316 items each declaring exactly one status, zero duplicate
numbers, and #1041 still present exactly once and open. docs/BACKLOG.md auto-merged: #218's
additions land inside an existing item rather than as new headings, so no heading collided.
wshallwshall added a commit that referenced this pull request Aug 6, 2026
…lision

PRs #222 and #223 landed since the last merge, so docs/BACKLOG.md collided at EOF again:
#223 appends #1051-#1055 where this branch appends #1041. One conflicted file, resolved
KEEP ALL SIDES, no renumbering and no re-sorting -- the file is not ordered by number and
nothing enforces one, so a re-sort would be a large invented diff over a file several
branches are appending to concurrently.

Resolved here rather than by the coordinator for the same entitlement reason as before: a
resolution that RE-INTRODUCES the `## 1041.` heading is a head-minus-base addition, so
ledger_check.py consults owns() for it (:340, :355), and the allocation record names this
worktree. No other worktree can commit it.

THE REVERT CHECK RAN FIRST, because the last merge is what taught me to. This branch
carried content main had since changed, and if my side won those lines the merge would
silently revert them while the diff looked ordinary. Built the merged tree and read it
rather than inferring from a clean-looking diff:

  headings on main but LOST here     NONE
  headings this branch adds vs main  ## 1041.  -- exactly one
  #1032 banner                       "SHIPPED 2026-08-05 - merged as PR #214" (#222's close
                                     survives; my side still carried the pre-#222 banner)
  #1051-#1055 from #223              5 of 5 present

"Lost nothing, gained exactly one" is the check that answers the revert question directly.
An item count cannot: it held steady at 121 across the previous merge because #218's
additions landed INSIDE item #1000 rather than as new headings. This time it grew 121 -> 126,
matching #223's five, which is consistent rather than coincidental.

Verified with the repo's own parser, never a hand-rolled scan of this file: zero conflict
markers, backlog_status_check OK at 321 items each declaring exactly one status, parse_items
reporting zero duplicate numbers, nothing in #1032-#1055 appearing more than once, and #1041
still open -- it lands open by design and is closed by the session building the rule 3d fix.

KEEP-BOTH RE-CHECKED ON THE GATE, by AST rather than by grep. A string search cannot tell a
definition from a call site or a comment, and the specific hazard here is that git left the
two helpers' shared closing brace outside an earlier conflict region -- a careless resolve
yields a file that parses cleanly with one function swallowed into the other's body. The
parser reports both ConvertTo-WorktreeSlug and Get-SafeForMessage as FunctionDefinitionAst,
8 functions total, 0 parse errors.
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