Skip to content

Estate: 132 repos track files their own .gitignore hides; template-sync sweep (92 repos) left a SILENT fake gate in 29 #678

Description

@hyperpolymath

Summary

Two estate-wide findings from one detector nobody has run:

  1. 132 repos have tracked files that their own .gitignore matches — the index and the
    ignore rules disagree about whether the file exists. Precedent: cerro-torre, where an
    unanchored .gitignore hid 1,458 lines that a later sweep then deleted.
  2. The template-sync sweep that replaced .tool-versions with .mise.toml across 92 repos
    produced two opposite failure modes from one deletion — and one of them is a
    silent fake gate replicated in 29 repos.

1. The detector

git ls-files | git check-ignore --stdin --no-index

Non-empty output = at least one tracked file is matched by a .gitignore pattern.
One line. Nothing in the estate runs it.

Index-free variant, for repos whose index is corrupt (reads HEAD instead):

git ls-tree -r HEAD --name-only | git check-ignore --stdin --no-index

Reach — established before the count

227 .git dirs in hyper-repos/; 1 is not a repository (bulk_update_oikos-private-farm
[ -d .git ] passes, git answers fatal: not a git repository). Real population 226.
0 worktrees (.git is a directory in every one).

instrument repos scanned with >=1 shadowed tracked path shadowed paths
index (ls-files) 185 107 (58 %) 1,866
HEAD (ls-tree) — the 40 index-corrupt repos 40 25 741

Do not add these. HEAD != index; a staged-but-uncommitted file appears in one and not
the other. They are two instruments, reported separately on purpose.

Top hits: gitbot-fleet-recovery-20260824 888 · project-wharf 211 · ambientops 184 ·
snifs 102 · aerie 65 · trigger 36 · polystack 26 · verisimdb-data 20 ·
panic-attack 16 · standards 13 · dotfiles 13 · trope-checker 12.

Most common shadowed basename: .tool-versions, 143 times (76 at repo root, 67 nested).

2. The template-sync sweep — footprint is EXACTLY 92, and the signature is uniform

Every affected repo carries the identical three-part fingerprint, with zero variance:

D .tool-versions   (deleted, UNSTAGED)
+ .mise.toml       (present, UNTRACKED)
+ .tool-versions   (added to .gitignore, exactly one line, anchored)
repos
index healthy 76
index corrupt (measured via HEAD) 16
TOTAL 92

The pin conversion itself is CORRECT: .tool-versions: idris2 0.7.0 becomes
.mise.toml: [tools] / idris2 = "0.7.0". Verified by hand on lithoglyph.

⚠ Three of the 92 (dotmatrix-fileprinter, supernorma, grim-repo) also have damaged
packfiles
— see #677. Do not remediate those by any route that rewrites objects.
palimpsest-license is in the 92 and is one of the three PMPL repos.

3. One deletion, two opposite failure modes

374 files across all 92 repos still reference .tool-versions; zero repos are clean.
But a reference is not a reader — prose in a README fails nothing. Narrowing to surfaces
that actually execute:

surface files
other 160
doc 97
config 67
script 38
workflow 9
just 3

Executable: 50 files across 35 repos — and 29 of the 50 are the same script.

Mode A — LOUD, and correct. Do not weaken this.

check-toolchain-pins.sh treats the missing pin as a fact to assert: exits rc=2, names
the missing artefact, and lists the six artefacts that still agree at 0.7.0. A rare,
well-built gate.

Convention collision to reconcile: it uses rc=2 = FAIL, while the corpus S1
contract reserves exit 2 = VOID (NO CHECK PERFORMED). Same code, opposite meanings.

Mode B — SILENT. A fake gate, in 29 repos.

.machine_readable/scripts/lifecycle/install-tools.sh treats the missing pin as a
branch to select, and exits 0.

#!/usr/bin/env bash
# install-tools.sh — Developer toolchain installer
# Detects and installs the required project toolchain (asdf, nix, or guix).
set -euo pipefail
echo "=== RSR Toolchain Installer ==="
if [ -f "flake.nix" ] && command -v nix &>/dev/null; then
    ...
elif [ -f ".tool-versions" ] && command -v asdf &>/dev/null; then
    ...
else
    echo "No standard toolchain (Nix/asdf) detected or installed."
    echo "Please refer to README.adoc for manual setup instructions."
fi
echo "Installer complete."

Run in an empty directory (no flake.nix, no .tool-versions):

=== RSR Toolchain Installer ===
No standard toolchain (Nix/asdf) detected or installed.
Please refer to README.adoc for manual setup instructions.
Installer complete.
SCRIPT_RC=0

The installer installs nothing and reports success. There is no mise branch, so after
the sweep this is the branch every one of the 29 repos now takes.

Hash distribution: a0b3846b… x25 (canonical) · 7d3b7536… x3 (cicd-squabbler,
hermeneia, knot-rider) · 191227c7… x1 (game-server-admin).

4. Why set -euo pipefail gave zero protection

errexit catches commands that FAIL. It cannot catch a BRANCH THAT IS NEVER ENTERED.

[ -f .tool-versions ] on a missing file does not error — it evaluates false, correctly,
and control proceeds to the success path. A guard whose absence is expressible as
"condition false" is invisible to errexit.

That is the whole explanation for the two opposite outcomes: same missing file, same repo
family — the script that asserts screams, the script that branches lies.

This generalises well beyond this sweep and is worth stating as estate doctrine.

5. A third, independent defect (predates the sweep)

install-tools.sh's own header claims it installs "asdf, nix, or guix". There is no guix
branch.
There is now no mise branch either. The docstring has been wrong since it was written.

Suggested action

Fix it once, at the RSR template source — not as 29 per-repo edits. This is the estate's
familiar fan-out shape and the policy-fanout precedent applies directly (~372 duplicated
policy copies; fixing one copy silently un-fixes nothing elsewhere). The per-repo copies
should be regenerated from the template, not hand-patched.

Minimum cure for install-tools.sh:

  • add a mise branch;
  • make the no-toolchain-found path exit non-zero;
  • fix the docstring to name what it actually supports.

Corpus units for proven-tests-and-benches

  • U1.gitignore shadows a tracked file. Detector above; fires on 132 live repos.
  • U2 — a toolchain-pin artefact is deleted while N other artefacts still declare the pin.
  • U3 — an installer selects a toolchain by branch and exits 0 when none matches.
    Canonical wrongness: remove every toolchain file; the installer MUST NOT exit 0.
    Silent fixture: a valid .mise.toml present. Strongest unit of the set — trivially
    constructible ground truth, fires on 29 live repos today.
  • U4 — a docstring claims a capability the body does not implement.

Full forensics, with raw data:
developer/.claude/forensics/estate-shadow-and-toolchain-sweep-2026-08-27.md

Related: #677 (damaged object databases, overlapping repo set).

Metadata

Metadata

Assignees

No one assigned

    Labels

    choreRoutine maintenance with no behaviour changescaffoldingRSR templates, repo init, instantiation, project skeletonsscope:estateAffects many or all repos across the estate

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions