Derive the Deletion List from Hub-Hosted Files - #650
Merged
Conversation
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>
Contributor
There was a problem hiding this comment.
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()fromgit ls-filesminusspec/files.json, and report downstream hits ashub-only:findings inspec/audit.py. - Expand
spec/fidelity_honesty.pymanifest-gap pass to run fleet-wide and include carrier lists, updating the generatedreports/divergences.md. - Document and catalog the new cross-cutting carried-scope check, and extend
spec/divergences.jsongap 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
| File | Description |
|---|---|
| spec/project-types.json | Adds the cross-cutting carried-scope dimension and carried.hubonly.absent check definition. |
| spec/fidelity_honesty.py | Makes manifest-gap detection fleet-wide and reports carriers; updates report rendering accordingly. |
| spec/divergences.json | Updates the manifest-gap ledger to include additional triaged gaps (accepted collisions + retire). |
| spec/audit.py | Implements hub-tracked path derivation, repo tree helper, hub-only findings, and selftests. |
| reports/divergences.md | Regenerates the burn-down report to include carrier lists for manifest gaps. |
| AUDIT.md | Documents 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.
Uh oh!
There was an error while loading. Please reload this page.
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Nothing detected a file a repo carries and should not. Every file check in
spec/audit.pyreads a path the manifest declares, so the manifest's complement was invisible. The only record of a deletion was a hand-writtenretiredisposition inspec/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.jsonbaseline 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.pygainshub_only_paths()and reports each hub-hosted path the audited repo's ground-truth branch also holds, as a newcarried-scopecross-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-filesand the tree read move intoaudit.pyas 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:
scripts/README.md(KiCadLibrary)common.py,verify_library.py) beside the scripts it describes.husky/pre-commit(7 repos)dotnet format); the hub's runs the doc gatesBoth were read before being dispositioned
accepted. So onlyretireasserts a deletion, andAUDIT.mdsection 10 states that where the fix gets applied, because deleting an untriaged hit destroys work the hub never owned.TODO.mdis accepted on the same basis:STANDUP.mdsection 2 names it as a destination for a repo's own backlog, so the manifest omits it deliberately.Fleet result
Every gap is triaged, so the report's untriaged section holds only the pre-existing verbatim-section rows.
reports/divergences.mdis regenerated (13 lines changed).Verification
spec/audit.py --selftestgains three cases, each checked against a broken implementation rather than only against the working one, so they can fail:KeyErrorNonetruncatedreturns 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:--selftestPASS,validate.pyOK,prose_lint.py --diff HEADclean,repo_gate.py --check eolclean,markdownlint-cli20 issues,ruff(repo rule set) clean,mypyclean.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