') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Promote the Sync Entrypoint, Deletion Detector, and Host Contract by ptr727 · Pull Request #655 · ptr727/ProjectTemplate · GitHub
Skip to content

Promote the Sync Entrypoint, Deletion Detector, and Host Contract - #655

Merged
ptr727 merged 7 commits into
mainfrom
develop
Aug 10, 2026
Merged

Promote the Sync Entrypoint, Deletion Detector, and Host Contract#655
ptr727 merged 7 commits into
mainfrom
develop

Conversation

@ptr727

Copy link
Copy Markdown
Owner

Promotion of six squashes. Merge with a merge commit, never a squash, and never with --delete-branch — this PR's head isdevelop.

PRWhat it fixed
#649AUDIT.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.
#650The 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.
#651RESYNC.md, the third entry point, routed from the byte-locked AGENTS.md "Fleet Bootstrap".
#652A 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.
#653Two spec/ enumerations that went stale when the host contract landed there.
#654The 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:

  • 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

ptr727and others added 6 commits August 10, 2026 09:58
## Problem
[AUDIT.md](../blob/develop/AUDIT.md) section 6's ruleset comparison
projected `bypass_actors` on both sides and sorted it with
`sort_by(.actor_id)`. No committed payload declares that key, so the
committed side did not mis-compare, it failed outright:
```
jq: error (at repo-config/main.json:58): Cannot iterate over null (null)
```
Process substitution then hands `diff` an empty left side, so the whole
live payload reads as an addition and the `|| echo DRIFT` branch fires.
**Every ruleset on every repo reported drift**, from the snippet rather
than from the repo. Reported by the agent resyncing ESPHome-Config.
Pre-fix, against Blog, which is in sync:
```
0a1,68
> {
> "bypass_actors": [ { "actor_id": 5, "actor_type": "RepositoryRole", "bypass_mode": "always" } ],
...
develop: DRIFT
main: DRIFT
```
## Why this field
`spec/audit.py`'s `RULESET_SUBSET` already excludes it, and [the comment
above it](../blob/develop/spec/audit.py#L42) records that including it
once made every repo report a ruleset `DEFECT`.
`repo-config/configure.sh` is the authority both must agree with:
`apply` reads the live list and writes it back unchanged, `check`
reports it and asserts nothing, because who may bypass a ruleset is a
human decision taken in the UI. The mechanized path was corrected and
the prose snippet beside it was not.
## Change
Three surfaces stated the old policy, so the class is swept rather than
the instance:
- `AUDIT.md` section 6 compares `name`, `target`, `enforcement`,
`conditions`, `rules`, matching `RULESET_SUBSET`, and states why the
field is outside it.
- `docs/repo-config-carry.md` payload regeneration no longer captures
`bypass_actors`. Capturing it baked one repo's bypass list into the
canonical every other repo diffs against.
- `repo-config/README.md` no longer lists the field as part of the
compared subset.
## Verification
The snippet is extracted from the shipped `AUDIT.md` bytes rather than
retyped, placeholders substituted, and run against three repos including
two operational ones (exercising the `operational/develop.json`
selection):
```
=== Blog === develop: in sync main: in sync
=== ESPHome-Config === develop: in sync main: in sync
=== HomeAutomation-Config === develop: in sync main: in sync
```
Gates: `prose_lint.py --diff HEAD` clean, `repo_gate.py --check eol` and
`--check eol-coverage` clean, `markdownlint-cli2` 0 issues on all three
files.
Note the committed downstream copies of these payloads are separately
stale against the hub canonical (`reports/divergences.md`), which is a
re-vendor drift. This PR does not touch that: the live rulesets match
the hub's payloads, so enforcement was never affected.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## 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:
| Path | Reality |
|---|---|
| `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:
| Case | Mutant it catches |
|---|---|
| Declared paths never leak into the deletion set | Dropping the
subtraction leaks 25 declared paths |
| A malformed ledger row is dropped, not fatal | Dropping the shape
guard raises `KeyError` |
| A truncated tree returns `None` | Ignoring `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](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
> **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>
Your call, per the decision to accept the fleet-wide finding. Floor set
to **1.7** per your follow-up.
## The jq gap
`jq` is used by the ruleset normalizer in `repo-config/configure.sh`,
the ruleset diff in `AUDIT.md` section 6, and payload regeneration in
`docs/repo-config-carry.md`. It was declared in **neither** the contract
table nor `spec/host-tools.json`. A host without it cannot apply, check,
or audit a repo's branch configuration, and nothing said so.
## The floor is 1.7, and it is a target
I did not pick a number and justify it after. Measuring first said **no
floor was warranted at all**: nothing in the tree uses a post-1.5 jq
feature (checked `walk`, `@base64d`, `--args`, `$__loc__`, `pick`,
`toarray`, `abs`, `getpath`, `ltrimstr`, `splits`, `limit`, `$ENV` — the
only hits were shell variables and Actions expressions, not jq filters).
So the floor is a **target**, the same kind as `python3`, and the entry
says so rather than implying a defect nobody found.
1.7 is anchored to what the platform provides: **current Debian stable
packages 1.7.1**, so a distribution install satisfies the floor and no
host needs a manual build to meet it, while an older release sits below
and is the case the floor exists for. That anchor is what makes it
actionable. Two rejected alternatives, for the record:
- **1.8** (the newest, installed here) would fail a current apt host
that runs every documented filter correctly. A floor set to whatever
happens to be installed is a host failure nobody can act on, which is
the hazard this file's own note names.
- **1.6** would encode the `walk/1` boundary, which is a fact about the
filters rather than something a host operator can act on.
Facts kept in the entry because a reader needs them: on jq 1.5 `walk/1`
does not degrade, it **fails to compile**, so a diff built on it reports
drift on every ruleset it never compared. And the claim that
`keys_unsorted` also needs 1.6 stays **disproved** per the
`.github/copilot-instructions.md` record, measured on
`jq-1.5-1-a5b5cbe`.
## Consequences swept, not left
The floor now sits **above** 1.6, so `walk/1` is available — which made
false the comments in `AUDIT.md` and `repo-config/configure.sh` that
justified hand-defining a recursion by walk's absence. Both now give the
reason that survives: the recursion costs nothing and compiles below the
floor as well. This is the same class of defect as #649 (a rule changed,
its prose left asserting the old one), so it was swept by term rather
than by instance.
## host-tools.json becomes a carried file
A repo now states the tools its **own** procedures need beyond the fleet
declaration, so the tighten-only layering lives somewhere a reader finds
rather than has to know to look for. A repo with nothing to add carries
the stub with an empty `tools` list — the footing `OPERATIONS.md`
already set — and this repo's new root file is that worked example, with
a `note` distinguishing it from `spec/host-tools.json`.
**Measured before landing: none of the 22 cataloged repos carries one**,
so this adds exactly one `LETTER` per repo. This PR satisfies the hub's
own.
## The gate joins the procedures it was written for
`scripts/host_gate.py` existed and **no procedure ran it**. `STANDUP.md`
section 0 and `AUDIT.md` now do, alongside `RESYNC.md` in #651. Each
passes `--repo`, because the gate reads the target's declaration
relative to that flag and a bare run layers the hub's instead while
printing the same healthy digest either way.
## Verification
`scripts/test_host_gate.py` **failed first**, which is the tests
working: `test_the_declared_floors_are_the_ones_with_a_stated_reason`
asserts the floor set exactly, so a new floor cannot land unnoticed. I
updated it and generalized
`test_a_target_floor_says_so_rather_than_implying_a_defect` from the
single `python3` entry to the **set** of target floors, so a third one
added without the two-kinds wording fails rather than reading as
measured.
The boundary is checked through the gate's own `compare`/`parse_version`
rather than a reimplementation of them:
```
jq 1.5 -> FAILS jq 1.7 -> meets
jq 1.6 -> FAILS jq 1.7.1 -> meets
jq 1.6.1 -> FAILS jq 1.8.2 -> meets
```
- 552 script tests OK, `--selftest` PASS, `validate.py` OK
- `host_gate.py`: 7 tools, `jq 1.8.2 meets the 1.7 floor`, and it reads
the hub's own stub (`host-tools.json layered 0 local entry(s)`)
- `prose_lint`, `repo_gate --check eol`, `markdownlint-cli2`, `cspell`
on the gated files: all clean
- **`editorconfig-checker` caught a real defect every other gate
passed**: a `sed -i` I used wrote one bare LF into `AUDIT.md`. Fixed and
re-verified byte-wise; the diff stayed at 9 lines rather than a
whole-file rewrite.
## One bookkeeping item left for you
The `keys_unsorted` entry in `.github/copilot-instructions.md` carries
**Delete when** - "nothing this check runs on carries a jq older than
1.6", and an enforced 1.7 floor arguably satisfies that. I left it in
place rather than deleting it unilaterally, because it exists to answer
a repeat reviewer finding and its measured proof is cited from the new
`jq` entry. Your call whether it retires.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two changes from one source: a stale enumeration a peer agent found, and
a backlog entry for the method that found it. TODO disposition: **New
entry**, per `TODO.md` rule 8.
## 1. The stale enumeration
Two enumerations of `spec/` list its contents and stop before the host
tool contract, which landed there in #652 along with a stub at the
repository root:
- `GOVERNANCE.md` "Repository Layout"
- `README.md`'s `spec/` bullet
Both were accurate when written. Neither is now, and a reader looking
for where the tool floors live would search both and find nothing.
### Provenance, and two claims that did not reproduce
Raised by the **ESPHome-Config agent** over peer messaging, not by a
gate. It hit the same class in its own `Repository Layout` while
planning its `AGENTS.md` -> `AGENTS.md` + `GOVERNANCE.md` split, and
named it as the trap the `Documentation Style Conventions` maintenance
rule exists for: prose no linter can see, going stale under a change
that never mentioned it.
I checked the hub for the same shape and for two adjacent claims from
the same report. Recorded because a future reader will wonder:
| Claim | Hub result |
|---|---|
| Carried docs still link `AGENTS.md#<section>` for sections that moved
to `GOVERNANCE.md` | **Not present.** Zero `AGENTS.md#` anchors
tree-wide, so the hub's anchors were rewritten with the split.
Downstream-only, for repos still carrying pre-split docs. |
| Other `gh api ... --jq '{...}'` projections feeding a diff share the
gojq key-sorting exposure fixed in #649 | **Not exposed.** The only
other one is the settings diff, where both sides pass through `jq -S .`
over a flat boolean object, so key order cannot reach the comparison.
Verified by running it both ways, `gh api --jq` and raw `gh api` piped
to real `jq`: byte-identical. |
`repo-config/` keeps its "apply script" wording, still true of the hub,
which hosts the script it no longer carries downstream.
## 2. The backlog entry
A `decision` cluster, because the mechanism needs no build and the only
open question is **which document may carry rules that bind a downstream
agent**. `GOVERNANCE.md` reaches those agents and costs a fleet-wide
re-vendor plus the two manifest edits `spec/section-model.md` requires
of any new section. A hub-only `docs/` file costs nothing and leaves the
rules unreachable from the repositories that would apply them, which is
the failure `AGENTS.md` "Fleet Bootstrap" exists to prevent.
Settled and recorded so it is not re-derived:
- **Cross-host does not work, by construction rather than
configuration.** A peer address is a Unix domain socket under
`/run/user/1000/cc-socks/`, which cannot cross a machine boundary. Cloud
sessions and Remote Control sessions are the documented cross-host paths
and neither appears in a listing on this host, so both are unverified
rather than absent.
- **The addressing has a guardrail worth keeping.** A bare peer name was
refused and the transport demanded the `[ref]` a listing prints, which
is what stops a message reaching the wrong repository's agent.
- **The method earns its place on evidence.** One exchange produced the
causal commit for the section 6 defect (`90e3255`), which this session
had not identified from the symptom; a one-line reproduction of the gojq
behavior that made an earlier fix pass for the wrong reason; and four
procedure gaps no gate reports.
- **A peer's finding is checked rather than adopted.** Two of those four
did not reproduce here, one did and is part 1 of this PR.
- **The boundary that matters is permission, not politeness.** A peer
cannot widen what the asking session may do, so blocked work goes back
to the maintainer rather than sideways to another agent.
## Verification
`prose_lint --diff HEAD`, `repo_gate --check eol`,
`editorconfig-checker`, `markdownlint-cli2` on `TODO.md`, and `cspell`
on the gated files: all clean. Both `[files]` and `[section-model]`
reference names already resolve in `TODO.md`. `GOVERNANCE.md`
"Repository Layout" is `intent` fidelity per `spec/section-model.md`, so
part 1 forces no fleet-wide re-vendor.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## 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>
CopilotAI lite review requested due to automatic review settings August 10, 2026 19:47

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

Promotes a coordinated set of hub changes that (1) add RESYNC.md as the third fleet entrypoint, (2) formalize host-tool verification (including a jq floor and a carried host-tools.json overlay contract), and (3) improve conformance auditing by deriving “hub-only” deletions from the manifest complement.

Changes:

  • Add RESYNC.md and route existing entrypoints/docs to it for the “already stood up, now stale” case.
  • Extend the host-tool contract: declare jq (>= 1.7 target), ship and baseline a repo-local host-tools.json overlay + schema, and wire/clarify scripts/host_gate.py output formatting.
  • Implement and document the hub-only deletion detector (hub git-tracked paths minus spec/files.json), and widen the manifest-gap reporting to fleet-wide carriers.

Reviewed changes

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

Show a summary per file
FileDescription
TODO.mdAdds a decision/backlog entry documenting peer messaging as a method and its governance placement question.
STANDUP.mdAdds host tool verification guidance (including when/why to omit vs pass --repo), and notes host-tools.json in the baseline carry list.
spec/section-model.mdUpdates hub-only (“not carried”) enumeration to include RESYNC.md alongside other hub machinery.
spec/project-types.jsonIntroduces the carried-scope dimension/check describing hub-only file detection/remediation semantics.
spec/host-tools.jsonAdds jq as a required tool with a 1.7 target floor and supporting rationale/source guidance.
spec/host-tools-local.schema.jsonAdds a dedicated schema for repo-local host-tools.json overlays.
spec/files.jsonAdds host-tools.json to the carried baseline manifest.
spec/fidelity_honesty.pyMakes manifest-gap detection fleet-wide, names carriers per gap, and reports unreadable trees instead of silently undercounting.
spec/divergences.jsonExpands gaps ledger with accepted collisions and improves configure.sh retirement reasoning (no hand-kept carrier list).
spec/audit.pyAdds hub-only path derivation + repo tree reading helpers and audits hub-only carried files with ledger-based dispositions.
scripts/test_host_gate.pyAdds tests enforcing overlay/schema correctness and extends floor/target-floor assertions to include jq.
scripts/host_gate.pyImproves failure message formatting (multi-line WHY/INSTALL) and prints all continuation lines with proper indentation.
RESYNC.mdNew ordered resync procedure covering routing, host verification, measurement, ordered remedies, and deletion/inbound-reference sweep guidance.
reports/divergences.mdUpdates generated divergence report format to include carriers per manifest-gap entry.
repo-config/README.mdRemoves bypass_actors from the asserted subset and documents why it is writable-but-unmanaged.
repo-config/configure.shUpdates comments to reflect declared jq floor and rationale for retaining portable recursion.
README.mdAdds resync entrypoint (RESYNC.md) and updates “spec/” description to mention the host tool contract/floors.
host-tools.jsonNew repo-local overlay stub (empty tools) pointing at the new overlay schema and documenting layering semantics.
host-setup/agent-safety/claude-md-fleet.mdUpdates routing guidance to point to RESYNC.md for already-stood-up repos.
GOVERNANCE.mdNotes RESYNC.md as the procedure for already-stood-up repos that fell behind; updates spec/ description to include host tool contract/floors.
docs/repo-config-carry.mdRemoves bypass_actors from the regeneration projection and documents why it is excluded.
docs/host-setup.mdAdds jq to the host tool table and adds a missing [audit] reference for the new link.
cspell.jsonAdds resync / resynced to the project dictionary.
AUDIT.mdClarifies that AUDIT.md measures (does not sequence), adds host verification guidance, documents carried-scope/hub-only semantics, and updates the ruleset diff snippet to match the managed subset.
AGENTS.mdUpdates Fleet Bootstrap routing to use RESYNC.md for the “instruction set present, current or stale” case.

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

Follow-up to [#654](#654),
fixing a scoping defect in it.
## Problem
[#654](#654) put the
inbound-reference sweep **inside** the bullet for "A `hub-only:`
finding" in `AUDIT.md` section 10, and after three disposition-specific
bullets in `RESYNC.md` section 4 whose intro scopes to the hub-only
detector.
The case that produced the rule was not a hub-only finding. It was a
repository deleting **its own** file: no `spec/divergences.json` entry,
no `retire` disposition, no hub equivalent. So the rule its case
generalized was documented in the one place its case could not reach,
and an agent removing a repo-owned file finds nothing.
Nothing about the sweep depends on who owned the file. Only the
`retire`-confirmation step is hub-only-specific.
## Change
- `AUDIT.md` section 10: the sweep becomes its own bullet, stated as
governing **any** deletion. The `retire` confirmation stays with the
`hub-only:` bullet, where it belongs.
- `RESYNC.md` section 4: the sweep bullet says the three bullets above
it turn on a disposition and belong to the detector, while the sweep
turns on nothing.
- The repo-owned case is promoted from footnote to evidence, since it is
what demonstrates the rule is not hub-only.
## The second case is now measured, not predicted
[#654](#654) said an
orphaned definition "fails the no-unused-defs rule". That has since been
observed rather than argued: removing the inline link to a nested
`AGENTS.md` **tripped `MD053`** on the definition it orphaned, before
the author reached that edit. So the delete-the-file-only path fails CI
rather than merely breaking prose, which is a stronger claim than the
one shipped.
## Provenance
Raised by the **ESPHome-Config agent** over peer messaging. It verified
[#654](#654 merged text
against `ca3df85` rather than taking it on trust, and found the scoping
while executing the very deletion the rule came from. Its words: *"the
rule that my case generalized is documented in the one place my case
would not have reached."*
That is the second defect this loop has caught in my own merged work
today, both times by a reader doing the thing the doc describes rather
than reading it.
## Verification
`prose_lint --diff HEAD`, `markdownlint-cli2` on both files,
`editorconfig-checker`, `repo_gate --check eol`: all clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 10, 2026 21: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

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

@ptr727
ptr727 merged commit 69e638f into mainAug 10, 2026
7 checks passed
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