bd-audit-ids: deleted means a deletion git will record

improvise-cota, found in the delta review of #244. The tdfd fix skipped an
absent tracked path whenever `ls-files -d` listed it. `ls-files -d` also
lists an absent --assume-unchanged path. `status` hides that path, and
`commit -a` never records the deletion, so its citations stayed in HEAD
unread and the audit exited 0.

The deletion oracle is now `diff --name-only --diff-filter=D`. It honours
both index flags and still lists a plain unstaged delete.

The new case in bd-audit-ids-test.sh was red at 33d563a9. A rival that
treats every absent path as unread fails only the deliberate
deleted-file control.

Stacked on #244 (tooling/lqs0-audit-exits).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Edward Langley
2026-09-23 17:06:23 -07:00
parent 33d563a902
commit 97a9379f79
3 changed files with 46 additions and 5 deletions
+29
View File
@@ -919,3 +919,32 @@ scanned" alone, so the deleted-file case is what stops the fix from
over-refusing. The rival ran from an ignored copy inside the worktree. A copy
under `/tmp` fails "the real tree does not pass against a silent bd" for a
reason unrelated to the rival: there is no repository there.
### Deleted means git will record it — `scripts/bd-audit-ids-test.sh` (improvise-cota)
**What broke.** The tdfd fix above asked `git ls-files -d` whether an absent path
was deleted. `ls-files -d` also lists an absent path marked
`--assume-unchanged`. But `git status` shows nothing for that path, and
`commit -a` would never record the deletion, so its citations stay in `HEAD`.
The audit skipped the file and exited 0. This is the tdfd shape again: absent,
not a deletion git will record, and never read. It was found in the delta
review of PR #244 at `33d563a9`.
**Fix, the reviewer's oracle.** A path counts as deleted only when
`git diff --name-only --diff-filter=D` lists it. That compares the index
with the working tree and honours both index flags: it hides a
skip-worktree path and an assume-unchanged path alike, and lists a plain
unstaged delete. A staged `git rm` never reaches the question, because the path
has already left the index that `ls-files --cached` enumerates.
**Red first.** "an assume-unchanged file missing from the tree is exit 4, and
named" exited 0 against `33d563a9`, reporting "all 1 cited ids resolve".
**Rivals.**
- The `ls-files -d` oracle, which is the red state above.
- Every absent path reported as unread (`elif true`). It fails "a tracked file
deleted from the working tree is not scanned" and nothing else, so the
deliberate skip of a real deletion survives the stricter oracle.
This rival was edited in place in the worktree script, not copied to `/tmp`, for
the reason the tdfd entry gives.
+5 -5
View File
@@ -13,8 +13,8 @@ exit 0 every cited id is a ticket bd lists
exit 1 at least one cited id is not; each is printed
exit 3 bd could not answer: missing, failing, or listing nothing
exit 4 usage error, not inside a git repository, or a cited file
could not be read or is absent (only a file `git ls-files -d`
reports as deleted is skipped)
could not be read or is absent (only a file `git diff
--diff-filter=D` reports as deleted is skipped)
The comparison is POSITIVE: a cited id passes only by appearing in what
`bd list --all` returns. The recipe this replaced looked for bd's "not found"
@@ -43,13 +43,13 @@ top=$(git rev-parse --show-toplevel 2>/dev/null) || {
exit 4
}
cd "$top"
# A deleted tracked file is skipped ON PURPOSE: its ids are leaving the tree.
# Only `ls-files -d` says deleted; any other absent path is exit 4 (lqs0, tdfd).
# A deleted tracked file is skipped ON PURPOSE: its ids are leaving the tree. Only
# `diff --diff-filter=D` says deleted; any other absent path is exit 4 (tdfd, cota).
: > "$work/absent"
git ls-files -z --cached --others --exclude-standard -- "${ROOTS[@]}" |
while IFS= read -r -d '' f; do
if [ -e "$f" ] || [ -L "$f" ]; then printf '%s\0' "$f"
elif [ -z "$(git --literal-pathspecs ls-files -d -- "$f")" ]; then
elif [ -z "$(git --literal-pathspecs diff --name-only --diff-filter=D -- "$f")" ]; then
printf '%s\n' "$f" >> "$work/absent"
fi
done > "$work/files"
+12
View File
@@ -132,6 +132,18 @@ git -C "$r2" update-index --no-skip-worktree context/b.md
&& ok "a skip-worktree file missing from the tree is exit 4, and named" \
|| bad "a skip-worktree file missing from the tree is exit 4, and named" "exit $got: $(head -c 200 "$work/out")"
# ls-files -d DOES list an absent assume-unchanged path, yet status hides it and
# commit -a never records the deletion (improvise-cota).
git -C "$r2" update-index --assume-unchanged context/b.md
mv "$r2/context/b.md" "$work/b.md.away"
(cd "$r2" && "$AUDIT") > "$work/out" 2>&1
got=$?
mv "$work/b.md.away" "$r2/context/b.md"
git -C "$r2" update-index --no-assume-unchanged context/b.md
[ "$got" = 4 ] && grep -q 'context/b.md' "$work/out" \
&& ok "an assume-unchanged file missing from the tree is exit 4, and named" \
|| bad "an assume-unchanged file missing from the tree is exit 4, and named" "exit $got: $(head -c 200 "$work/out")"
mkdir -p "$work/not-a-repo"
(cd "$work/not-a-repo" && GIT_CEILING_DIRECTORIES="$work" "$AUDIT") > "$work/out" 2>&1
got=$?