Add Resync as the Third Entry Point - #651
Conversation
A request to sync a repository with the hub had no procedure to resolve to. STANDUP.md takes a repository from nothing to operational and AUDIT.md measures one, so a repository that is stood up already and has fallen behind routed to the audit, which reports findings and deliberately states no order for applying them. Three things were therefore undocumented rather than merely unwritten. The order matters and was nowhere. The rules govern every file authored against them, so a verbatim rule section lands before the files it judges, the same closing-window shape as STANDUP.md section 1A. A deletion lands before the re-vendors, since doing it after means refreshing a copy that is about to go. Deletion had no entry in any procedure. It is the one remedy that removes content, so acting on a wrong finding loses work rather than adding noise, and the detector matches on path alone. RESYNC.md section 4 says only a retire disposition authorizes it and an untriaged hit is read first, naming the two path collisions the first fleet run produced. What a resync cannot see is now stated rather than implied. A carried file at intent fidelity is checked for presence alone, so a hub revision inside CODESTYLE.md, WORKFLOW.md, .editorconfig, cspell.json or version.json raises no finding anywhere, and a clean run does not mean a current file. Host verification joins the procedure, which no procedure invoked before. scripts/host_gate.py and spec/host-tools.json existed and neither STANDUP.md nor AUDIT.md ran them. The documented invocation passes --repo, because the flag is what makes the target's own floors count: a bare run layers the hub's own declaration and prints the same healthy digest either way. The byte-locked AGENTS.md Fleet Bootstrap section changes, so downstream copies go stale. Two repositories carry it, Blog and HomeAutomation-Config, and both are already on the re-vendor list, so the edit adds no repository to it. host-setup/agent-safety/claude-md-fleet.md carries the same routing and changes with it, which reaches each machine when the installer is next run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds RESYNC.md as the third hub entry point alongside STANDUP.md and AUDIT.md, documenting an ordered procedure for bringing already-stood-up repositories back into conformance and wiring that procedure into the hub's main routing surfaces.
Changes:
- Add new hub-only
RESYNC.mdprocedure covering routing, host verification, ordered remedies (including deletions), and blind spots. - Update entry-point routing references across
AGENTS.md,README.md,AUDIT.md, andGOVERNANCE.mdto point "already stood up and stale" cases atRESYNC.md. - Update supporting docs to classify the new procedure as hub machinery (not fleet law) and keep the host-installed fleet block in sync.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/section-model.md | Treat RESYNC.md as hub-only machinery alongside other hub procedures. |
| RESYNC.md | New ordered resync procedure: routing, host gate invocation, remedy sequencing, and audit blind spots. |
| README.md | Add "Resync" to Getting Started and vocabulary, and link to RESYNC.md. |
| host-setup/agent-safety/claude-md-fleet.md | Update host-installed fleet routing to send "stale instruction set" to RESYNC.md. |
| GOVERNANCE.md | Mention RESYNC.md as the procedure for resyncing already-stood-up repos. |
| AUDIT.md | Clarify audit is measurement only and point sequencing to RESYNC.md; add reference link. |
| AGENTS.md | Route "instruction set present" cases to RESYNC.md instead of audit-only. |
💡 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.
Uh oh!
There was an error while loading. Please reload this page.
Two review findings on RESYNC.md. The hub-checkout rule dropped the "that" the canonical wording in AGENTS.md carries, leaving "in one another task is using". And section 2 called the mechanized subset one command directly above a block holding three, so name what each of the three does instead: the first measures, the second renders that run, and the third is a different tool over the fleet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI's cspell step gates README.md and HISTORY.md, and the README gained "resynced". The local check ran against RESYNC.md, which that step does not read, so the one file under the gate was the one not checked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
RESYNC.md:35
python3 spec/audit.py --issue <Repo>still runs a live audit (audit_repo(...)) and is not just a different rendering of the previous run. The current wording implies it avoids re-measuring, which is inaccurate and could mislead readers about the cost/behavior of running both commands.
Run [`AUDIT.md`][audit] end to end. Three commands cover the mechanized part, and they are not interchangeable: the first produces the findings, the second renders that same run as a filable issue rather than measuring again, and the third refreshes the fleet-wide ledger, which is a different tool over a different question. The rest of that file is the half no tool evaluates.
The wording had --issue rendering the previous run "rather than measuring again". It calls audit_repo itself, so it is a second live measurement and the two can disagree if the repository moves between them. Say that, since the inaccuracy understated both the cost and the freshness question. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
RESYNC.md:39
- In section 2's command block, the
--issueinvocation is documented as producing "the same findings".spec/audit.py --issuere-runsaudit_repo(...)(it does not reuse the previousspec/audit.py <Repo>run), so the comment should reflect that it re-measures and that results can differ if the repo changes between runs.
python3 spec/audit.py <Repo> # the deterministic findings, read at the registry groundTruthBranch
python3 spec/audit.py --issue <Repo> # the same findings as a ready-to-file convergence issue
python3 spec/fidelity_honesty.py --report # regenerate reports/divergences.md before using it as a work list
The prose in section 2 was fixed and the inline comment on the command itself still read "the same findings", which is the same false claim one line lower. Swept the term tree-wide rather than the instance: no other file claims the renderer reuses a previous run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
RESYNC.md:30
- The
--repoexplanation implies that runningscripts/host_gate.pywith/without--repoproduces “the same healthy digest either way”, butscripts/host_gate.pychanges both the declared-tool count and emits anote:when it successfully layers a repo’shost-tools.json(and those differences are exactly how you can tell the target’s floors were applied). Reword to avoid claiming the output is identical and instead state that omitting--reposkips reading the target’shost-tools.jsonentirely.
**Pass `--repo`, because the flag is what makes the target's own floors count.** A repository may declare a root `host-tools.json` layering over the hub's, tighten-only, per [`scripts/README.md`][scripts], and 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, silently passes on the floors the target adds, and prints the same healthy digest either way. A target carrying no local file is the common case and is silent by design, so the absence of a layering note is not evidence the flag was unnecessary.
The paragraph claimed a bare run prints the same healthy digest either way. It does not: a successful layering changes the declared-tool count and emits a note, and those differences are how a reader can tell the target's floors were applied at all. The accurate statement is also the one that matters. Omitting --repo does not read the target's declaration, so every floor that repository adds goes unapplied, and the run reports nothing about the omission because it cannot know a target was intended. A healthy digest from a bare run is a statement about the fleet floors alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 10, 2026
Addressed the suppressed finding on head in 12664a5. It was a real overclaim. A successful layering does change the output: the declared-tool count moves and a Reworded to state what is skipped rather than what looks identical:
The same wording appeared in |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (2)
RESYNC.md:39
- Spelling: prefer "fileable" over "filable" in this inline comment for consistency/readability.
python3 spec/audit.py --issue <Repo> # audits again and renders that run as a filable issue, so it can differ from the line above
RESYNC.md:35
- Spelling: "filable" is uncommon in US English and may read like a typo; "fileable" is the standard form for an issue that can be filed.
This issue also appears on line 39 of the same file.
Run [`AUDIT.md`][audit] end to end. Three commands cover the mechanized part, and they are not interchangeable. The first reports the findings. The second **runs its own audit** and renders those findings as a filable issue, so it is a second measurement rather than a view over the first, and the two can disagree if the repository moves between them. The third is a different tool answering a fleet-wide question. The rest of that file is the half no tool evaluates.
Both "filable" and "fileable" are defensible, neither is settled US usage, and the file's own gates cannot arbitrate: cspell reads README and HISTORY only, so this file is outside it, and the prose spelling rule covers British against US pairs rather than a coinage. An issue ready to file needs no adjective at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 10, 2026
Spelling finding answered in 31cc663, by removing the word rather than choosing between the two spellings. Both Both lines were changed together, since the finding named line 39 and noted it also appears on line 35. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
RESYNC.md:38
- In the audit command list, the comment says
spec/audit.py <Repo>reads from the registrygroundTruthBranch. However, AUDIT.md section 1 states ground truth ismainand that a registrygroundTruthBranchofdevelopcontradicts the procedure. To avoid mixed guidance, clarify thatgroundTruthBranchis expected to bemain(and that--branchis the supported override when you intentionally want to audit in-flight work).
python3 spec/audit.py <Repo> # the deterministic findings, read at the registry groundTruthBranch
The command comment cited the registry groundTruthBranch as though the value were open, where AUDIT.md section 1 says main is ground truth for both workflow models and a field naming develop contradicts the procedure rather than configuring it. Read together the two files gave mixed guidance. The line now says main, and the paragraph beside it states that the field is not a choice a resync makes. Measured rather than asserted: 18 cataloged repositories declare main and the other 4 omit the field and take it as the default, so none names anything else. --branch was missing from this file entirely, and a resync is where it earns its place. Convergence lands on a feature branch and reaches main only after the maintainer merges, so auditing ground truth mid-resync measures the state being replaced rather than the work replacing it. The invocation is verified as written rather than transcribed from the help text, including the claim about its stamp: the run prints "branch override develop" beside the hub sha and reads the repository at develop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 10, 2026
Addressed in 287ffec, and it was two problems rather than one. The mixed guidance was real. My comment cited the registry Measured before rewording rather than asserting it: 18 cataloged repositories declare The Verified as written rather than transcribed from the help text, including the claim about the stamp: |
Uh oh!
There was an error while loading. Please reload this page.
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>
## 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>
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)
Problem
"Sync this repository with the hub" had no procedure to resolve to.
STANDUP.mdtakes a repository from nothing to operational,AUDIT.mdmeasures 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:STANDUP.mdsection 1A. And a deletion lands before the re-vendors, or you refresh a file that is about to go.intentfidelity is presence-checked only, so a clean run does not mean a current file.Change
RESYNC.md, hub-only likeSTANDUP.mdfor 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:AGENTS.md"Fleet Bootstrap"AUDIT.mdhost-setup/agent-safety/claude-md-fleet.mdREADME.mdAUDIT.mdGOVERNANCE.md"Repository Onboarding and Conformance"spec/section-model.mdThe 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.pyandspec/host-tools.jsonboth existed and neitherSTANDUP.mdnorAUDIT.mdever invoked them.STANDUP.mdsection 0 checks identity and signing only.The documented invocation passes
--repo, and that is not incidental:A repository may declare a root
host-tools.jsonlayering 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 isverbatimfleet-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.mdis the source of the installed~/.claude/CLAUDE.mdfleet block, so each machine picks the new routing up whenhost-setup/agent-safety/install.pyis 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, andconfigure.sh's signature checked against its own usage.Gates:
prose_lint.py --diff HEADclean,repo_gate.py --check eolclean,markdownlint-cli20 issues over all 7 files,cspell0 issues onRESYNC.md, CRLF confirmed byte-wise on every file (the new file was written LF and converted).Deliberately not in this PR
Declaring
host-tools.jsonin thespec/files.jsonbaseline. 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