Skip to content

Derive the Deletion List from Hub-Hosted Files - #650

Merged
ptr727 merged 1 commit into
developfrom
add-hub-only-file-detector
Aug 10, 2026
Merged

Derive the Deletion List from Hub-Hosted Files#650
ptr727 merged 1 commit into
developfrom
add-hub-only-file-detector

Conversation

@ptr727

Copy link
Copy Markdown
Owner

Problem

Nothing detected a file a repo carries and should not. Every file check in spec/audit.py reads a path the manifest declares, so the manifest's complement was invisible. The only record of a deletion was a hand-written retire disposition in spec/divergences.json, surfaced through a manifest-gap pass that read a single reference adopter (Financial-Modeling).

That pass understated the work. It named six repos carrying repo-config/configure.sh. Fifteen do.

Approach

The detector is derived, not listed: the hub's git-tracked paths minus the spec/files.json baseline is what the hub hosts and no repo carries. A file dropped from the manifest starts being reported on the next run, with no retirement list to remember to edit.

  • spec/audit.py gains hub_only_paths() and reports each hub-hosted path the audited repo's ground-truth branch also holds, as a new carried-scope cross-cutting dimension (carried.hubonly.absent).
  • spec/fidelity_honesty.py's gap pass runs over the whole fleet instead of one adopter, and each gap names its carriers, so a disposition reads as a work list from live state rather than a hint.
  • git ls-files and the tree read move into audit.py as shared helpers, since both tools needed the same subtraction.

A hit is a candidate, not a verdict

The match is on path alone, and the first fleet run proved that matters twice:

PathReality
scripts/README.md (KiCadLibrary)Documents its own KiCad tooling (common.py, verify_library.py) beside the scripts it describes
.husky/pre-commit (7 repos)Each runs that repo's own formatters (CSharpier, dotnet format); the hub's runs the doc gates

Both were read before being dispositioned accepted. So only retire asserts a deletion, and AUDIT.md section 10 states that where the fix gets applied, because deleting an untriaged hit destroys work the hub never owned. TODO.md is accepted on the same basis: STANDUP.md section 2 names it as a destination for a repo's own backlog, so the manifest omits it deliberately.

Fleet result

repo-config/configure.sh (15) retire <- the real deletion
.github/workflows/publish-release.yml (17) investigate
.github/workflows/validate-task.yml (13) investigate
LICENSE (20) accepted
.husky/pre-commit (7) accepted <- path collision
pyproject.toml (3) investigate
TODO.md (2) accepted
scripts/README.md (1) accepted <- path collision

Every gap is triaged, so the report's untriaged section holds only the pre-existing verbatim-section rows. reports/divergences.md is regenerated (13 lines changed).

Verification

spec/audit.py --selftest gains three cases, each checked against a broken implementation rather than only against the working one, so they can fail:

CaseMutant it catches
Declared paths never leak into the deletion setDropping the subtraction leaks 25 declared paths
A malformed ledger row is dropped, not fatalDropping the shape guard raises KeyError
A truncated tree returns NoneIgnoring truncated returns a partial set that reads as "carries nothing"

Live: both target repos now report exactly one deletion each (configure.sh), confirmed present via the contents API rather than inferred. Gates: --selftest PASS, validate.py OK, prose_lint.py --diff HEAD clean, repo_gate.py --check eol clean, markdownlint-cli2 0 issues, ruff (repo rule set) clean, mypy clean.

One incidental observation, not addressed here: a transient TLS error mid-audit replaces all of a repo's findings with a single ERROR, so a flaky call costs that repo's whole result. It reports honestly and exits non-zero, so it is not a false clean, but it is worth a follow-up.

🤖 Generated with Claude Code

Nothing detected a file a repo carries and should not. Every file check reads
a path the manifest declares, so the manifest's complement was invisible, and
the only record of a deletion was a hand-written retire disposition surfaced
through a gap pass that read one reference adopter. That pass understated the
work: it named six repos carrying repo-config/configure.sh where fifteen do.
The detector is derived rather than listed. The hub's git-tracked paths minus
the spec/files.json baseline is what the hub hosts and no repo carries, so a
file dropped from the manifest starts being reported on the next run and there
is no second place to remember to edit. audit.py reports each one the audited
repo's ground-truth branch also holds, and fidelity_honesty.py runs the same
subtraction over the whole fleet so a disposition reads as a work list with
its carriers named from live state.
A hit is a candidate, not a verdict, because the match is on path alone. The
first fleet run proved it twice: KiCadLibrary's scripts/README.md documents its
own KiCad tooling, and seven .husky/pre-commit hooks each run their repo's own
formatters while the hub's runs the doc gates. Both were read before being
dispositioned accepted. Only retire asserts a deletion, and AUDIT.md section 10
says so where the fix is applied, since deleting an untriaged hit destroys work
the hub never owned.
TODO.md is accepted too: STANDUP.md section 2 names it as a destination for a
repo's own backlog, so the manifest omits it deliberately.
Three selftest cases cover it, each checked against a broken implementation
rather than only against the working one: a declared path leaking into the
deletion set, a malformed ledger row taking the run down, and a truncated tree
reading as a repo that carries nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 10, 2026 16:11

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a derived, hub-tracked "deletion detector" so the audit can flag hub-hosted (not carried) files that appear in downstream repos, and expands the fidelity-honesty manifest-gap report to cover the whole fleet with carrier lists.

Changes:

  • Derive hub_only_paths() from git ls-files minus spec/files.json, and report downstream hits as hub-only: findings in spec/audit.py.
  • Expand spec/fidelity_honesty.py manifest-gap pass to run fleet-wide and include carrier lists, updating the generated reports/divergences.md.
  • Document and catalog the new cross-cutting carried-scope check, and extend spec/divergences.json gap dispositions for known path-collision cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
spec/project-types.jsonAdds the cross-cutting carried-scope dimension and carried.hubonly.absent check definition.
spec/fidelity_honesty.pyMakes manifest-gap detection fleet-wide and reports carriers; updates report rendering accordingly.
spec/divergences.jsonUpdates the manifest-gap ledger to include additional triaged gaps (accepted collisions + retire).
spec/audit.pyImplements hub-tracked path derivation, repo tree helper, hub-only findings, and selftests.
reports/divergences.mdRegenerates the burn-down report to include carrier lists for manifest gaps.
AUDIT.mdDocuments the carried-scope/hub-only detection and the deletion (not re-vendor) convergence model.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@ptr727
ptr727 merged commit d33428d into developAug 10, 2026
7 of 9 checks passed
@ptr727
ptr727 deleted the add-hub-only-file-detector branch August 10, 2026 16:58
ptr727 added a commit that referenced this pull request Aug 10, 2026
> **Merge after #650.** Section 4 documents the `hub-only:` finding that
PR lands. Merged first, this file describes a detector that does not
exist yet.
## Problem
"Sync this repository with the hub" had no procedure to resolve to.
`STANDUP.md` takes a repository from nothing to operational, `AUDIT.md`
measures one and deliberately changes nothing, so an already-stood-up
repository that has fallen behind routed to the audit and got a findings
list. A findings list states no order, and three things were
undocumented rather than merely unwritten:
1. **The order.** The rules govern every file judged against them, so a
verbatim rule section lands before those files, the same closing-window
shape as `STANDUP.md` section 1A. And a deletion lands before the
re-vendors, or you refresh a file that is about to go.
2. **Deletion.** The one remedy that removes content, so a wrong finding
loses work instead of adding noise.
3. **What the measurement cannot see.** A carried file at `intent`
fidelity is presence-checked only, so a clean run does not mean a
current file.
## Change
`RESYNC.md`, hub-only like `STANDUP.md` for the same reason (a
repository needing it cannot be relied on to hold a current copy), wired
into every surface that already advertises the other two:
| Surface | What it gains |
|---|---|
| `AGENTS.md` "Fleet Bootstrap" | The third route points here instead of
at `AUDIT.md` |
| `host-setup/agent-safety/claude-md-fleet.md` | The same routing, kept
in sync deliberately |
| `README.md` | A Getting Started row, a doc bullet, a **"Resync a
repository"** vocabulary entry, and the carry-the-rules paragraph |
| `AUDIT.md` | States that it measures and does not sequence |
| `GOVERNANCE.md` "Repository Onboarding and Conformance" | Names the
third procedure |
| `spec/section-model.md` | Lists it as hub machinery, not fleet law |
The vocabulary entry is the point of the whole PR: per that section of
the README, a request using one of those terms is "a complete
instruction rather than a starting point for interpretation." Saying
"resync with the hub" in a downstream repo now resolves to an ordered
procedure.
## Host verification joins a procedure for the first time
`scripts/host_gate.py` and `spec/host-tools.json` both existed and
**neither** `STANDUP.md` nor `AUDIT.md` ever invoked them. `STANDUP.md`
section 0 checks identity and signing only.
The documented invocation passes `--repo`, and that is not incidental:
```shell
python3 scripts/host_gate.py --repo <path-to-target-checkout>
```
A repository may declare a root `host-tools.json` layering over the
hub's (tighten-only). The gate reads that file relative to `--repo`,
which defaults to the working directory, so a bare run from a hub
checkout layers the **hub's own** declaration and silently passes on the
floors the target adds. Verified both ways: the bare run emits no
layering note, and a target carrying no local file is silent by design,
so a missing note is not evidence the flag was unnecessary. My first
draft of that line said to run it bare, which would have shipped a
command that skips what it claims to check.
## Blast radius of the byte-locked edit
The `AGENTS.md` "Fleet Bootstrap" section is `verbatim` fleet-wide, so
editing it makes downstream copies stale. Measured before writing it:
**two** repositories carry the section (Blog, HomeAutomation-Config) and
**both are already on the re-vendor list**, so this adds no repository
to it. The other 18 do not carry it at all, which is a standup gap
rather than drift.
`claude-md-fleet.md` is the source of the installed
`~/.claude/CLAUDE.md` fleet block, so each machine picks the new routing
up when `host-setup/agent-safety/install.py` is next run. **I have not
run the installer**, since the source is unmerged and installing from a
branch would put unreleased content on the host.
## Verification
Every command in the file was run as written, not transcribed:
`host_gate.py` (passes, confirming the Python 3.13 floor), `audit.py`,
`fidelity_honesty.py --report`, and `configure.sh`'s signature checked
against its own usage.
Gates: `prose_lint.py --diff HEAD` clean, `repo_gate.py --check eol`
clean, `markdownlint-cli2` 0 issues over all 7 files, `cspell` 0 issues
on `RESYNC.md`, CRLF confirmed byte-wise on every file (the new file was
written LF and converted).
## Deliberately not in this PR
Declaring `host-tools.json` in the `spec/files.json` baseline. That
makes every repository owe a file it does not have, which is a
fleet-wide new finding and a spec decision rather than a doc fix.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727 added a commit that referenced this pull request Aug 10, 2026
## Problem
#650 introduced the `hub-only:` finding, whose remedy removes a file
rather than updating one. #651 and #652 stated that remedy in `AUDIT.md`
section 10 and `RESYNC.md` section 4 as: delete the copy, reach the
hub's, and *"where a carried doc named the local path, point it at the
hub's."*
That covers one of three shapes and assumes a hub equivalent exists. The
two it missed are the ones that cost something.
**A runnable command citing the deleted path.** This is the expensive
shape, because a documented command *fails* where a dead link merely
disappoints. Measured rather than argued:
```
NxWitness repo-config/README.md
13: - [`configure.sh`](./configure.sh) - idempotent `gh api` script with two modes:
14: - `./repo-config/configure.sh check` - validate only, no writes...
17: - `./repo-config/configure.sh apply` - create-or-update the rulesets...
61: REPO=ptr727/NxWitness ./repo-config/configure.sh apply
62: REPO=ptr727/NxWitness ./repo-config/configure.sh check
```
Retiring that one file makes **five lines of a carried document wrong,
two of them commands a reader would run**. `configure.sh` is carried by
**15 repositories**, so this is queued work rather than a hypothetical.
**A mention with no hub equivalent.** It is removed rather than
re-pointed, and removing a reference-style link removes its definition
with it, per `GOVERNANCE.md` "Documentation Style Conventions", *because
an orphaned definition fails the no-unused-defs rule.* So the old remedy
could leave a **gate failure** behind while reading as complete.
## Provenance
Raised by the **ESPHome-Config agent** over peer messaging, from a case
of its own: deleting a nested `easystart/AGENTS.md` at Pieter's
instruction, it found three inbound references across two files, one of
them a reference definition that would have been left orphaned. It put
the scope question to Pieter rather than deciding it, and that is what
surfaced the generalization.
I measured the fleet-wide shape rather than adopting the specific case:
checked five repositories' `repo-config/README.md` for links (not merely
mentions) to `configure.sh`, which is how the NxWitness figures above
were obtained. Four mention it without linking; one links it and
documents four invocations.
## Change
Both statements of the remedy now name the sweep as part of the
deletion, with the three shapes and their different fixes. `RESYNC.md`
additionally carries the measured evidence, since it is the procedure an
agent follows step by step.
## Verification
`prose_lint --diff HEAD`, `repo_gate --check eol`, `markdownlint-cli2`
on both files, `editorconfig-checker`: all clean. Both files gained a
`governance-documentation-style` reference definition, and both
reference blocks re-verified as sorted by reference name.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727 added a commit that referenced this pull request Aug 10, 2026
Promotion of six squashes. **Merge with a merge commit, never a squash,
and never with `--delete-branch`** — this PR's head *is* `develop`.
| PR | What it fixed |
|---|---|
| [#649](#649) |
`AUDIT.md` section 6's ruleset diff projected `bypass_actors`, which no
payload declares, so jq exited 5 and **every ruleset on every repo
reported DRIFT**. Two further surfaces carried the same dead policy. |
| [#650](#650) | The
deletion detector: hub git-tracked paths minus the `spec/files.json`
baseline, so a retired file is derived rather than remembered. Corrected
`configure.sh` from 6 carriers to **15**. |
| [#651](#651) |
`RESYNC.md`, the third entry point, routed from the byte-locked
`AGENTS.md` "Fleet Bootstrap". |
| [#652](#652) | A `jq`
1.7 target floor, `host-tools.json` as a carried baseline file, and
`scripts/host_gate.py` wired into all three procedures for the first
time. |
| [#653](#653) | Two
`spec/` enumerations that went stale when the host contract landed
there. |
| [#654](#654) | The
inbound-reference sweep counts as part of a deletion, including the
runnable-command and orphaned-definition shapes. |
## Why this promotion matters more than most
Downstream repos read hub `main` as ground truth. While `main` sits at
[`0a86bca`](0a86bca):
- Every repo that runs the `AUDIT.md` section 6 snippet gets **false
ruleset drift on both branches**. The ESPHome-Config agent reproduced
this live and is holding its own fix stashed rather than diverging from
the hub.
- A repo resyncing now re-vendors to `main`'s revision and then again
after promotion. Verified against Blog, whose carried `AGENTS.md` "Fleet
Bootstrap" differs from both refs, so it is two re-vendors instead of
one.
- `RESYNC.md` does not exist on `main`, so a downstream agent asking
"how do I sync" still routes to `AUDIT.md`, which measures and
deliberately states no order.
## Fleet cost this carries, measured and accepted
`host-tools.json` becomes a carried baseline file, so it is one `LETTER`
on **22 of 22** cataloged repos, the hub included. That was measured
before landing and accepted by the maintainer. `RESYNC.md` section 0 was
amended so a letter wave of this shape reads as a file to carry inside a
resync rather than as evidence a repo was never stood up.
## Verification on the merged head
`spec/validate.py` OK, `spec/audit.py --selftest` PASS, 557 script tests
OK, `scripts/host_gate.py` clean over 7 declared tools, `repo_gate.py
--check eol` clean, `editorconfig-checker` clean, working tree clean.
Post-merge fleet audit: 22 repos, **zero errors**.
Two hub-side artifacts clear on this merge, both promotion-pending
rather than defects: the hub's own `AGENTS.md` "Fleet Bootstrap" reads
as stale against its `develop` canonical, and the hub reports its own
`host-tools.json` absent because the file exists on `develop` only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for freeto 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.

2 participants

@ptr727