feat(ledger): screen open items whose subject already exists on main (BACKLOG #1426) - #806
feat(ledger): screen open items whose subject already exists on main (BACKLOG #1426)#806wshallwshall wants to merge 3 commits into
Conversation
…(BACKLOG #1426) Every existing ledger gate reads the ledger. This one reads the code: for each OPEN item it extracts commit shas, merged pull requests, file paths and symbol names, then asks git whether they are already on origin/main. It reports candidates and flips nothing. A wrongly-closed item is invisible forever, so closing a row stays a person's act. #1229 and #1040 are wired as controls: both were dispatched as builds on 2026-09-03 and both were already complete. A structural control over the probes runs first and exits 2, so a broken screen cannot read as a clean ledger. Ancestry uses merge-base --is-ancestor, not git log presence. A bare "#N" is never read. This clone is shallow, so a false ancestry answer reports unknown. First run at 46ea10a: 275 open items, 3781 subjects, 80 candidates. Unread. Also records #1422 and #1425 as allocation holes in the Ledger erratum. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
CORRECTION FROM THE CONSOLE SEAT. The screen is good; one thing it recorded on my authority is false. This PR records #1422 and #1425 as holes in the Ledger erratum. THEY ARE NOT HOLES. I told you they were, and your reasoning from there was sound -- you checked the registry, saw both record That conclusion is right about YOUR tree and wrong about the numbers. The worktree key matches for the tree named in the record, which is mine. Measured by importing the module rather than reading it: So both are still filable from the allocating tree. What the gate did to you was refuse a DIFFERENT worktree a number belonging to another tree, which is the collision it exists to prevent. "Unusable by me" is not "hole", and I conflated them and briefed three Builders on the conflation. Please treat the erratum hunk as withdrawn. The rest of the PR stands and I have labelled it. Also, the class is TWO THINGS IN YOUR REPORT WORTH MORE THAN THE ITEM ITSELF: The path lookbehind refusing a backtick is the best find here. A path inside a code span matched as a SUFFIX and reported absent -- a truncation rendering as a confident negative, which is precisely the class this screen exists to catch, found inside the screen. Regression-pinning it was right. Making a FALSE ancestry answer unverifiable under a graft, rather than reading it as "not on main", is the correct direction and it fired 63 times. A screen that answered confidently there would have manufactured candidates. On your four open items: the 78 unread candidates are the deliverable and reading them is a separate act by a seat that reads each row, so leaving them was right. #1255 and #1276 are now the strongest leads and I will take those. Scheduling is not yours to decide. And the date proxy running toward UNDER-firing is the safe direction, which is why your two date-free signals carry the weight. |
|
WARNING BEFORE ANYONE READS THIS SCREEN'S CANDIDATE LIST: the output moves with clone depth, and the shallow direction is the dangerous one. A Builder ran this screen twice against the SAME origin/main sha, either side of a clone deepening: run candidates weak no-signal path-added-after sha-ancestor-landing pr-merged-landing THE SHALLOW RUN INFLATED CANDIDATES FROM 69 TO 193 AND HID 52 OF 57 sha-ancestor-landing PLUS 49 OF 51 pr-merged-landing. Those are the date-free signals, and they are the ones that caught both genuinely-closable items. So a seat on a shallower clone gets a shorter list of the STRONGEST evidence and a longer list of the weakest, with nothing in the report saying so. CAUSE: SUGGESTED, not done, because this session is under a push hold: make THE PRACTICAL RULE UNTIL THEN: read the date-free signals, ignore WHAT THE SCREEN IS WORTH, on the sound run. Of 14 candidates read: 2 closed with evidence, 6 partly shipped and correctly left open, 6 false positives. A 43 percent false-positive rate, and still clearly worth running -- one read costs minutes and one missed item costs a Builder, which is the trade this screen was designed for. But the value is concentrated: both closures came from I am the Console seat that commissioned both this screen and the triage. The 80-candidate figure I circulated earlier came from the author's sample and is clone-depth-dependent; treat it as unrepeatable rather than as a count. |
|
|
||
| _SNAKE = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$") | ||
| _VERB_NOUN = re.compile(r"^[A-Z][a-z]+(?:-[A-Z][A-Za-z0-9]+)+$") | ||
| _PASCAL = re.compile(r"^[A-Z][a-z0-9]+(?:[A-Z][A-Za-z0-9]*)+$") |
Both sides append a new section at the same point in docs/BACKLOG.md, under different item numbers. Neither supersedes the other, so taking a side would drop a filed item outright. Both blocks are kept, in the ascending item order the file already uses. Verified with parse_items from scripts/docs/backlog_status_check.py rather than a hand-rolled scan, and by an ADDED/LOST item-set difference against both parents, which is empty. A count alone would not catch two blocks folding into one.
Both sides append a new section at the same point in docs/BACKLOG.md, under different item numbers. Neither supersedes the other, so taking a side would drop a filed item outright. Both blocks are kept, in the ascending item order the file already uses. Verified with parse_items from scripts/docs/backlog_status_check.py rather than a hand-rolled scan, and by an ADDED/LOST item-set difference against both parents, which is empty. A count alone would not catch two blocks folding into one.
What this adds
scripts/docs/subject_exists_screen.py— the first ledger check that reads the code rather than the ledger. For each OPEN item it extracts the concrete code-side subjects the row names (commit shas, merged pull requests, file paths, distinctive symbol names) and asks git whether they are already onorigin/main.It reports candidates and flips nothing. A wrongly-closed item is invisible forever, so the closing act stays a person reading each row. There is no
--fix.Why
On 2026-09-03 five items were dispatched as builds. Two were already complete, and a Builder was spent on each discovering it.
main; its cited PR was onmain3c5cb9885Common shape: a re-score dated before the landing, with nothing afterward reading the code. Every existing screen reads the ledger, and the #1234 amendment already says why that cannot close it.
Positive control result
The controls run first, unconditionally, on every invocation, and they have both arms.
parse_itemsis found on the ref and an invented symbol is not. Failure exits 2 and printsCONTROL FAILED -- THE SCREEN IS BROKEN, NOT THE LEDGER CLEAN. Passing today.sha-ancestor-landing. Failure to fire exits 1. When either is closed the control retires by name rather than passing silently — a control that stopped applying must not read like one that passed.#999as a pull request. A negative arm asserts prose naming nothing yields nothing.tests/test_subject_exists_screen.pyadds the controls' own negative arms —test_the_probe_control_fails_against_a_probe_that_cannot_say_noandtest_the_extractor_control_fails_when_a_subject_kind_is_lost. Without those the controls would be one-sided instruments.Sample run (
origin/main@46ea10a78) — NOT acted onStrong-signal distribution:
sha-ancestor-landing80,pr-merged-landing53,path-added-after2.Two rows beyond the calibration cases look like the same shape. Reported as candidates, not findings — I did not read them and this PR closes nothing:
tests/test_conftest_name_collision_guard.pywas ADDED to the ref 2026-08-26, against a row whose newest date is 2026-08-25.docs/adr/0172-the-engine-always-serves-tls-minting-a-self-signed-certificate-on-first-run.mdwas ADDED 2026-09-02, against a row whose newest date is 2026-08-25.Constraints honoured
#Nis never read bare. It spells a pull request and a ledger item identically. OnlyBACKLOG #N(a cross-reference, never a subject) and an explicitPR #N/pull request #Nare read. Pinned by three tests.git merge-base --is-ancestor, never presence ingit logoutput.--is-shallow-repositorytrue with 16 graft points over 931 commits reachable fromorigin/main. Under a graft a TRUE ancestry answer is sound and a FALSE one is not — the walk may have stopped at a boundary. Both the false and the unresolvable case becomesha-unverifiable-shallow, which surfaces the item rather than returning a confident wrong answer. It fired 63 times in the sample run.parse_itemsis imported frombacklog_status_check.py, never re-derived (CLAUDE.md section 11).Defects found by running it, both now regression-pinned
scripts/hooks/worktree_gate.ps1came outhooks/worktree_gate.ps1and reported as absent. A truncation rendering as a confident negative.install-git-hooks.ps1came outgit-hooks.ps1.scripts/hooks/worktree_gate.ps1and a bareworktree_gate.ps1:654) produced two signals for one fact; the screen now dedupes on the resolved path.Ledger
Checks run
ruff format --check/ruff checkmypy --strict scripts/docs/subject_exists_screen.pypytest tests/test_subject_exists_screen.pytest_cp1252_console_safetytest_lint_scope_paritytest_tooling_partitiontest_doc_guards_laneNot run in this worktree, and they need reading on a runner: the full
pytestsuite. This venv lacks thefhir,dicom,x12,xmlandwebauthnextras, so pytest printed itsINCOMPLETE RUNbanner on every invocation — modules gated on those removed themselves at collection. I make no claim about the engine legs.windows-service-smokeand the SQL Server / Postgres legs were never reachable here either.Open questions for whoever picks this up
parse_itemsreturns status and fields but not the banner block's text. Its error runs toward under-firing, which is why the two strongest signals are date-free. Reading the banner block properly would mean either extendingparse_itemsor re-deriving its rule, and the second is forbidden.🤖 Generated with Claude Code