Skip to content

Promote develop to main: fidelity verification system, agent write-safety, and doc reconciliation - #383

Merged
ptr727 merged 35 commits into
mainfrom
develop
Jul 22, 2026
Merged

Promote develop to main: fidelity verification system, agent write-safety, and doc reconciliation#383
ptr727 merged 35 commits into
mainfrom
develop

Conversation

@ptr727

@ptr727 ptr727 commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Promotes the accumulated develop work since the last promotion (merge-base #327). Merge with a merge commit, never squash; never --delete-branch (head is develop). A human merge does not auto-publish — release is a separate gated dispatch.

Verified clean: local trial merge is conflict-free, zero files deleted.

What's promoted (33 commits)

Fidelity verification system — the headline. Each carried unit now has a scope (appliesTo selector) and a fidelity (presence/intent/verbatim/interface):

Agent write-safety (#367) — repo-scope + never-fabricate-id + never-suppress-write rules, the Copilot-runbook fix, and a portable PreToolUse enforcement kit.

Doc + spec reconciliation from the Financial-Modeling adoption (#379 round: #380-#382) — MD033 now flags inline HTML elements (#378); source-only release model reconciled to the standalone inlined publisher across AGENTS.md/WORKFLOW.md/spec; repo-config/ manifest completed (README.md, configure.sh verbatim, settings.json intent).

Governance hardening — Verification Discipline section, prose/comment style rules, carried-files-carry-no-coordination-references rule + mechanized check, set -Eeuo pipefail everywhere, audit run-stamping, Dependabot ecosystem-coverage check, csharp+python repo shape, configure.sh model inference (#333-#362).

Operational-repo conformance — HomeAutomation, HomeAssistant, ESPHome rollouts (#335/#342/#348).

Merge checklist

  • Merge-commit method (main ruleset enforces it)
  • Required Check pull request workflow status job green
  • Copilot review clean, threads resolved
  • Held for explicit maintainer go-ahead

🤖 Generated with Claude Code

ptr727 and others added 30 commits July 16, 2026 10:17
… blessing (#333)

Incorporates the hub-side findings filed from the Vantage-Config
operational onboarding (Vantage-Config #9). Fixes #328, fixes #329,
fixes #331. (#330, the downstream carry-drift sweep, is handled by
per-repo PRs, not here.)

## AGENTS.md: every-tier bots (#328)

"Foundational Principles" claimed operational repos "run no bots and
commit directly to `develop`" - contradicting the owner-confirmed
every-tier merge-bot policy (and reality: Vantage-Config runs Dependabot
dual-target plus the App-signed merge-bot). Reworded: operational repos
run no **codegen or auto-publish** bots; Dependabot's dual-target sync
and the App-signed merge-bot run on **every** tier. A next operational
adoptee reading the old wording would have skipped the merge-bot
standup.

## Registry: Vantage-Config driftNotes (#329)

The entry recorded onboarding as "pending once content lands"; it
completed 2026-07-16 per Vantage-Config #9. The stale note is replaced
with the completed state (baseline docs, advisory lint CI, dispatch-only
publisher, repo-config operational carry verified in sync, Dependabot +
App merge-bot with the secret pair in both stores, adapted self-audit).
`spec/validate.py` passes.

## Downstream Carry: reference self-audit shape (#331)

"Adapted self-audit carry" required every downstream repo to carry
adapted `AUDIT.md` + `spec/secrets.json` but shipped no reference shape
- Vantage-Config had to invent the first one. Now:

- The Vantage-Config carry is blessed as the reference adaptation
(settings diff, normalized ruleset diff against the carried payloads,
names-only secrets check, all targeting the current repo); a `release`
repo adapts the same shape with its `develop.json` payload and its
publish mechanisms' secret names.
- `spec/files.json` letter-checks both carried files, so the fleet audit
surfaces repos that don't carry them yet (today: all but Vantage-Config;
the #330 sweep starts closing that).

## Verification

- `python3 spec/validate.py`: OK (21 cataloged, 0 backlog).
- markdownlint, cspell (CI scope), editorconfig-checker clean on the
changed files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #332 (core gap + the six bundled nits from the #330 sweep reviews;
two of the six live in the blessed downstream reference and propagate
via the Vantage-Config carry, not this repo - see below).

## configure.sh: model inference on downstream carries

In a carry the registry is absent, so the no-arg form defaulted to
`release` and then hit the intended fail-safe abort on operational
carries (missing `develop.json`) - arg 2 was effectively mandatory
there. Now the model is inferred from which `develop` payload is carried
(a carry holds exactly its own model's); ambiguous layouts (both or
neither - e.g. a partial copy) abort rather than guess. Also: the model
may be passed as the sole argument (`configure.sh operational`) -
previously arg 1 was always parsed as a repo name (bundled nit,
Utilities #417).

Matrix-tested with the apply path stubbed: infer release, infer
operational, both-present abort (exit 1), neither-present abort (exit
1), explicit arg-2 override, model-as-sole-arg, unknown-model abort. Hub
behavior unchanged (registry present -> registry lookup, as before).
shellcheck + bash -n clean.

## Ruleset snippet hardening (duplicate-name id corruption)

The `id=$(gh api ... select(.name==...).id)` pattern in the README regen
snippet and AUDIT.md section 6 produced a **multiline id** when
duplicate same-name rulesets exist, silently corrupting the follow-up
API path (bundled nit, Utilities #417 + LanguageTags #278). Both
snippets now fetch the list once and enforce **exactly one** ruleset per
name: the regen snippet fails loudly (never regen from a guess); the
audit snippet reports `found N (defect/drift)` and continues - matching
the declared drift semantics. Live-tested read-only against this repo
(develop + main in sync; duplicate simulation counts correctly).

## README wording

Documents the inference fallback and the model-only argument form
(bundled nit, Utilities #417).

## Bundled nits NOT in this PR (with rationale)

- `gh api --input -` portability (MediaTools #19): declined - fleet
standard is current gh; not worth temp-file churn.
- jq-derived secret name lists + `--paginate` in the secrets check
(MediaTools #19, LanguageTags #278, PlexCleaner #854): these live in the
**blessed downstream self-audit reference** (the Vantage-Config carry),
not in hub files - propagated there directly after this merges (the
hub's fleet audit reads `spec/secrets.json` programmatically already).

## Propagation caveat

This re-drifts the five existing carries (Vantage-Config + the four #330
sweep repos) until their next conformance touch; Vantage-Config is
updated immediately after this merges since it is the blessed reference
the queued operational standups (HomeAssistant-Config #16,
ESPHome-Config #46, HomeAutomation-Config #21) will adapt from.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconciles the HomeAutomation-Config registry entry after the #21
conformance carry landed on its develop (54637c4, 2026-07-16) - same
close-out pattern as #329 did for Vantage-Config.

- "Operational rollout pending" was stale: lint CI
(test-pull-request.yml feeding the required check), the dispatch-only
publisher (publish-release.yml + version.json), and
develop-as-ground-truth were already live; the carry added the
repo-config operational baseline (rulesets/settings verified in sync via
the carried self-audit), the adapted AUDIT.md + spec/secrets.json, and
the Dependabot merge-bot.
- The "strip the legacy Vantage/ subtree" instruction is updated to
record it as done (verified: no such tree on develop).
- The outstanding owner task (App secret pair in both stores; merge-bot
inert until then) is recorded so the entry reflects reality.

`spec/validate.py` passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…337)

Fixes #336: the branch-drift check derived "main carries content develop
lacks" from the three-dot compare alone, which lists files changed on
main since the merge-base and is blind to cherry-picked
(patch-equivalent) promotions. Any repo promoting via cherry-pick
branches re-triggered the finding on every audit - HomeAutomation-Config
#21's forward-sync task was authored and worked against exactly this
false positive.

## Fix

Post-filter the compare's `files[]` by **blob equality at the two
heads** (one `git/trees?recursive=1` call per head, as the issue
suggested): a path whose blob SHA matches at `develop` and `main` is
content develop already has, not content it lacks. Only the remainder
raises the DRIFT finding.

- The finding now **names the lacking files** (up to 8) - addressing the
issue's impact note that "N+ changed file(s)" gave a downstream reader
nothing to spot-check.
- If either tree comes back `truncated` (very large repos), the filter
is skipped and the unfiltered finding is kept, marked "tree too large to
blob-filter cherry-pick noise" - conservative, never silently
suppressing.
- Deletions on main still flag (path absent from main's blobs but
present on develop's is a blob mismatch), as do renames.

## Verification (live, read-only)

- **False positive resolved**: `python3 spec/audit.py
HomeAutomation-Config` -> clean (previously: `DRIFT branch: main carries
2+ changed file(s)...` from the cherry-picked cspell-scope promotions;
both files are blob-identical at the heads).
- **Genuine positives still fire, now with names**:
homeassistant-purpleair -> `main carries 2 file(s) develop lacks:
requirements-test.txt, requirements.txt`; DevKitCIoT -> `main carries 12
file(s) develop lacks: .editorconfig, ...` (first 8 shown).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile the stale `HomeAssistant-Config` registry `driftNotes` after
the [`#16`](ptr727/HomeAssistant-Config#16)
operational onboarding. Mirrors the `#329` reconciliation for
Vantage-Config.

## What changed

The prior driftNotes described work that is now complete and planned a
`groundTruthBranch` flip that is declined:

- "develop is behind main by 2 commits / fast-forward then flip
groundTruthBranch to develop" - develop is content-ahead; the flip is
**declined** (see below).
- "pending: lint CI" - `test-pull-request.yml` with the `Check pull
request workflow status job` required check is live.
- "pending: dispatch-only source-release scaffolding (version.json +
publish-release.yml)" - both present.

Replaced with a completed-onboarding summary: baseline carried
(repo-config operational, `AUDIT.md`, `spec/secrets.json`, App-signed
Dependabot merge-bot) and promoted `develop -> main` via
HomeAssistant-Config PR `#17`. Self-audit verified in sync (settings,
both rulesets, `CODEGEN_APP_*` present in both stores).

## groundTruthBranch stays `main` (deliberate)

Kept `groundTruthBranch: main`, **not** flipped to `develop`. develop is
the working branch (direct signed commits); main is the promoted stable
snapshot the audit should target. This is a deliberate divergence from
the other operational repos (Vantage-Config / ESPHome-Config /
HomeAutomation-Config use `develop`); the broader convention question is
raised in #340.

## Findings note (context, see #341)

The `#16` audit block was stale: the `CODEGEN_APP_*` secrets were
already present in both stores and develop was already ahead of main.
Only the `repo-config/... absent` LETTER finding was real, and it is now
cleared on `main` by PR `#17`.

Validation: `jq` parses clean, `spec/validate.py` -> "21 cataloged, 0
backlog repos classify cleanly."

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #339. Registers the fleet's first mixed-language shape
(PlexCleaner: a .NET console app plus a stdlib-only Python tooling
subtree) and codifies how the `csharp` and `python` baselines coexist.
Reference implementation: ptr727/PlexCleaner#855.

Per the issue, this is **not** a new `polyglot` type - a mixed repo is
expressed as `types: ["csharp","console","docker","python"]` and the
audit runs the union of the per-type checks. The work is: register the
shape, codify the coexistence rules, add a matrix row.

## Three decisions (settled with the owner before writing)

1. **Two python profiles, one type, split by a third-party runtime
import.** PROJECT = a PEP 621 uv project with runtime deps + committed
LF-pinned `uv.lock` (the existing Financial-Modeling shape). SCRIPTS =
stdlib-only utility scripts embedded in a non-Python repo, run via
`uvx`, no lockfile, `pyproject.toml` carrying only
`[tool.ruff]`/`[tool.mypy]`. Detected structurally from
`pyproject.toml`.
2. **Tool-version pinning for SCRIPTS is CI-only.** CI pins exact
versions in the `uvx` command (`uvx ruff@X`, bumpable there);
tasks/README run latest - a deliberate CI-vs-local gap so local never
silently falls behind. PROJECT keeps pinning through `uv.lock` + `uv
sync --frozen` (unchanged).
3. **Coverage N/A for SCRIPTS.** `python.coverage.codecov` is N/A for a
lint/type-only subtree (no pytest); `codecov.yml` presence stays
required by any co-present type with tests (the C# side here).

## Changes

| File | Change |
|---|---|
| `registry/repos.json` | PlexCleaner `types` += `python`; driftNote
records the SCRIPTS subtree + which checks go N/A |
| `spec/project-types.json` | `python` type: `profileNote` + new
`python.profile.detect`; SCRIPTS-profile N/A wording on
`pyright`/`coverage`/`uvlock`; new `python.scripts.uvx` check |
| `CODESTYLE.md` | "Two profiles" paragraph (project vs scripts: uvx,
tool-config-only pyproject, mypy, CI-pinned/local-latest, `.py` on repo
EOL default, coverage N/A) |
| `README.md` `## Rules` | Python bullet names the type-checker choice;
new "If Both C# and Python" both-apply subsection |
| `reports/conformance-matrix.md` | `csharp` + `python` shape row
(PlexCleaner) |
| `cspell.json` | `bumpable`, `stdlib`, `uvx` |

## Issue items mapped

- **(1) line endings** - CODESTYLE "Scripts" bullet states `.py`/`.toml`
follow the repo CRLF default (only a shebang-executed script is
LF-pinned), so nobody "fixes" `.py` to LF; `recurring.eol` already
covers the general rule.
- **(2) uvx-vs-uv-project** - `python.profile.detect` + CODESTYLE "Two
profiles".
- **(3) CI-only pinning** - `python.scripts.uvx` + CODESTYLE.
- **(4) coverage** - `python.coverage.codecov` N/A wording.
- **(5) tasks.json union** - confirmed, **no change needed**:
`spec/files.json` already carries separate `csharp` and `python`
`.vscode/tasks.json` references, so a mixed repo satisfies both by union
(that is the intended audit behavior).
- **(6) docs both-apply** - README "If Both C# and Python" + CODESTYLE
"Two profiles".

## Validation

- `python3 spec/validate.py` -> OK (21 cataloged, 0 backlog).
- markdownlint + editorconfig-checker clean on changed files; CI-scoped
cspell (README + HISTORY) clean.

Related: #306 (closed - source-only Python adaptation gaps), #310 (open
- the conformance-matrix row here is a `not-tested` entry pending that
issue's cold-start self-test).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ce rule (#344)

A file the fleet carries verbatim must name no sibling fleet repo and
link none. Raised by the ESPHome-Config adoption: the
`repo-config/README.md` "blessed reference" pointed a `[vantage-config]`
link at the **private** Vantage-Config, which **404s in every public
carrier** (PlexCleaner, LanguageTags, Utilities already carry it). Two
reasons, both general: any fleet repo may be private -> a cross-repo
link 404s in a public carrier; and a cross-repo reference couples the
repos and rots as they diverge.

## The rule (now codified in AGENTS.md)

A carried file (`AGENTS.md`, `CODESTYLE.md`, `WORKFLOW.md`, `README.md`,
`.github/copilot-instructions.md`, `repo-config/*`, `spec/*`, the
carried `AUDIT.md`) is **repo-agnostic**: it describes the shape/pattern
self-containedly and names a sibling repo **only where that repo is the
subject**. A current good example is named in the
**onboarding/conformance issue** (one-off, not carried) or the hub-only
**`reports/conformance-matrix.md`** (a fleet tracking table) - never a
carried doc. This pairs with the present-tense rule: state the current
shape, not its provenance.

## Changes

| File | Change |
|---|---|
| `repo-config/README.md` | Remove the `[vantage-config]` link +
External group; the self-audit-carry bullet describes the adapted
`AUDIT.md` shape self-containedly, pointing to the issue for a current
example |
| `AGENTS.md` | New rule under Documentation Style Conventions; the
mixed-consumer-config example keeps its Windows-editor/CRLF **context**
but drops the repo names |
| `CODESTYLE.md` | Logging-seam and Python "Two profiles" examples
describe the pattern, no repo names |
| `spec/project-types.json` | `python` profileNote genericized; new
`recurring.norepoxref` audit check |
| `spec/secrets.json` | Coverage-threshold note no longer names repos |

Hub-only files (`registry/repos.json`, `reports/conformance-matrix.md`)
keep repo names - naming repos **is** their subject and they are not
carried.

## Rollout

Hub only. The 6 repos that already carry the bad link (public:
PlexCleaner, LanguageTags, Utilities - live 404; private:
Vantage-Config, HomeAutomation-Config, MediaTools) correct on each
repo's **next conformance touch**. ESPHome-Config's in-flight adoption
carries the fixed hub `repo-config/README.md`, so it never inherits the
link.

## Validation

- `python3 spec/validate.py` -> OK; markdownlint + editorconfig-checker
clean.
- Grep confirms no residual illustrative cross-repo reference in any
carried file, and the Vantage URL is gone from all carried paths.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…346) (#347)

Fixes the audit-coverage half of #346. (The concrete ESPHome-Config
drift is filed as a separate downstream issue for that repo to fix.)

## The gap

`spec/audit.py` checked Dependabot **secret stores** but never that a
repo's `.github/dependabot.yml` declares the **ecosystems its tree
implies**. So ESPHome-Config - which ships `test-pull-request.yml` /
`publish-release.yml` / `merge-bot-pull-request.yml` with SHA-pinned
actions but tracks only `devcontainers` - passed the standup audit
clean, even though its action pins get no bump PRs and the merge-bot it
just stood up has no action-update PRs to act on.

## The check

New crossCutting check `setup.dependabot.ecosystems` (repo-setup
dimension): for each ecosystem the tree implies, `dependabot.yml` must
declare it (dual-target `main`+`develop` per the fleet norm):

- **`github-actions`** when `.github/workflows/` ships actions,
- **`devcontainers`** when a `.devcontainer` is present.

A missing implied ecosystem is a **DRIFT** finding. `dependabot.yml` is
YAML (audit.py is stdlib-only, no parser), so the declared
`package-ecosystem` values are scanned by regex - enough to assert
presence. The check only runs when the file exists (its absence is
already a file-presence LETTER). Language ecosystems
(`nuget`/`uv`/`npm`) are directory-scoped and left to inspection for now
(noted in AUDIT.md).

Codified in `AUDIT.md` section 6 (with a shell snippet) and
`spec/project-types.json`.

## Verification (live, read-only)

```
ESPHome-Config    -> DRIFT dependabot: github-actions ecosystem not declared though .github/workflows/ ships SHA-pinned actions
Vantage-Config    -> clean       HomeAutomation-Config -> clean
PlexCleaner       -> clean       Utilities -> clean       aiopurpleair -> clean
```

The `devcontainers` ecosystem ESPHome-Config *does* declare is correctly
not flagged. `spec/validate.py` passes; markdownlint +
editorconfig-checker clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…esolved (#348)

Reconciles the ESPHome-Config registry entry after its conformance
standup (ESPHome-Config#46) and the Dependabot ecosystem fix
(ESPHome-Config#49 / promotion #50) completed - same close-out pattern
as #329/#335.

The second `driftNote` recorded the operational rollout as pending and
Dependabot as `devcontainers`-only. Both are resolved and **verified
from the live repo** before dropping the note:

- esphome-config validation job feeds the required check; `version.json`
+ `publish-release.yml` present; **2.0.12** released 2026-07-18;
`develop` is ground truth.
- `.github/dependabot.yml` declares `github-actions` **dual-target** on
both `develop` and `main`.
- `python3 spec/audit.py ESPHome-Config` (with the #346 check now on
develop) -> **clean**; no `dependabot` DRIFT.

The first note (ESPHome device config consumed by the ESPHome-NonRoot
image) stays - still true. `spec/validate.py` passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…only doc (#345) (#349)

Fixes #345 (both findings). Surfaced when ESPHome-Config became the
first **public** repo to carry `repo-config/README.md`.

## Finding 1: carry-process narration shipped downstream

`repo-config/README.md` is carried verbatim by every fleet repo, but it
shipped carry-**process** guidance - `## Downstream Carry`, the ruleset
import/regen procedure, and the maintainer `## Brownfield Migration`.
That is meta-guidance for whoever performs the carry, not a current fact
about the repo the file lands in (it's also what hosted the private-repo
link #344 removed).

**Split** (the settled approach):
- **New hub-only `docs/repo-config-carry.md`** holds the process:
Downstream Carry, Applying the Config, Regenerating the Payloads,
Brownfield Migration. It's not in `spec/files.json` baseline, so it's
never carried; being hub-only it may name repos and link the hub freely.
- **`repo-config/README.md` trims to current facts** about a repo's own
config: file descriptions, what the rulesets enforce, the secrets each
mechanism needs, the general settings.
- **Cross-refs fixed** (carried docs can't link a hub-only doc):
`AGENTS.md` plain-text-mentions the hub doc for the full
procedure/brownfield and drops two stale anchors (`Rules / Rulesets`,
`Template - GitHub Setup`); `STANDUP.md` and `spec/files.json` (both
hub-only) link the new doc directly.

## Finding 2: repo-scoped secrets schema clarity

The rule for the adapted `spec/secrets.json` now lives in the hub doc: a
**source-only** repo whose publish targets all map to a null mechanism
carries just `baseline` (+ `note`) - it omits `targetMechanisms`, since
a lone routing map with no `mechanisms` reads as "the audit ignores the
mechanism list" (Copilot tripped on it 3x across the standup PRs). A
repo with a real mechanism carries `mechanisms` **and** its
`targetMechanisms` routing, which the audit picks up.

## Rollout

Hub only. Downstream repos' carried `repo-config/README.md` (and any
source-only `spec/secrets.json` still carrying an all-null
`targetMechanisms`) correct on each repo's next conformance touch -
consistent with the prior sweeps. ESPHome-Config already stripped both
in its standup.

## Validation

`spec/validate.py` OK; markdownlint + editorconfig-checker clean on all
changed files; CI-scoped cspell (README+HISTORY) clean; grep confirms no
dangling references to the moved sections.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #338 - but not as filed. Worth reading the first section before
the diff.

## The issue's premise was inverted

#338 states the fleet mandates `set -Eeuo pipefail` and that
`repo-config/configure.sh` violates it. Verified against the hub:

- `AGENTS.md` said **`set -euo pipefail`**, and `configure.sh` used
exactly that - **they already agreed**.
- The `-Eeuo` mandate exists only in **HomeAutomation-Config's carried
`AGENTS.md`**, which has drifted from the hub.
- There are **no `trap ... ERR` anywhere in the fleet**, so `-E` is
functionally inert today.

Taking the issue at face value would have changed the canonical hub
script to match a *drifted downstream copy*. Instead, per the owner's
call: adopt `-Eeuo` at the hub as the better default and let the carries
converge on it. (The divergence itself is more evidence for #305.)

## Changes

- **Swept all 26 occurrences** to `set -Eeuo pipefail`: the 4 hub
workflows, `repo-config/configure.sh`, the 7 catalog workflow snippets,
and both devcontainer `post-create.sh` scripts.
- **`AGENTS.md` rule prose** now states the rationale (`-E` lets an
`ERR` trap inherit into functions/subshells/command substitutions;
defense in depth since the fleet ships no trap yet) **and resolves the
scope ambiguity** #338's last paragraph raises - it covers "every bash
surface: a multi-line `run:` block and every committed `.sh` script
alike", so inline snippets are explicitly in scope.
- **`WORKFLOW.md`** style bullet and D9.3 match.

## Verification

- shellcheck + `bash -n` clean on all three scripts.
- actionlint clean on the hub workflows (snippet fragments show only the
pre-existing "reusable workflow not found" note).
- `configure.sh` re-run in place: still resolves `release` and
`operational` correctly.
- markdownlint, editorconfig-checker, `spec/validate.py`, CI-scoped
cspell: clean. CRLF/LF endings preserved (swept with `sed`, which leaves
line endings untouched).

Downstream carries pick this up on their next conformance touch, as
usual.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…) (#351)

Fixes #340 - and it turns out to be a **reconciliation, not a policy
change**.

## What I found

`AUDIT.md` section 1 already says, with **no model exception**:

> Read the target's **`main` branch** as ground truth: `main` is the
released, authoritative state.

But three operational entries in `registry/repos.json` named `develop` -
Vantage-Config, ESPHome-Config, HomeAutomation-Config. So the registry
was **contradicting the hub's own documented rule**; #340's proposal is
what the docs already required. (HomeAssistant-Config was correctly on
`main`.)

## Changes

- **`registry/repos.json`**: the three operational entries flip to
`groundTruthBranch: main`.
- **`AUDIT.md` section 1**: states the both-models rule explicitly,
since that is precisely where the ambiguity arose - an operational repo
commits directly to `develop`, but its ground truth is still `main`, the
promoted snapshot; `develop` is mid-flight by design, so conformance
work landed there but not yet promoted is *un-promoted work*, not a
defect. A `groundTruthBranch` naming `develop` contradicts the section,
for either model.

## Verification (the risk was manufacturing false findings)

Before flipping, I confirmed **all four conformance files**
(`repo-config/main.json`, `repo-config/operational/develop.json`,
`AUDIT.md`, `spec/secrets.json`) are **already present on `main`** in
all three repos - so measuring `main` surfaces nothing new.

```text
BEFORE (develop ground truth):  Vantage/ESPHome/HomeAutomation -> all clean
AFTER  (main ground truth):     all four operational repos      -> all clean, 0 findings
```

`spec/validate.py`, markdownlint, editorconfig-checker: clean.

## Out of scope here: four `release` repos also name `develop`

Checking the fleet surfaced that **KiCadLibrary, EspDinIoT,
PhotoCleaner, and HolidayLights** (all `release` model, all with a
`main` branch) also carry `groundTruthBranch: develop` - the same
contradiction. They are **deliberately not flipped in this PR**, because
unlike the operational three their `main` is stale, so the flip is not
mechanical:

| repo | baseline files present on `main` |
|---|---|
| KiCadLibrary | 4/4 |
| PhotoCleaner | 2/4 |
| HolidayLights | 1/4 |
| EspDinIoT | 0/4 |

Flipping them would surface real-but-voluminous findings (their released
state genuinely is non-conformant - which is arguably the correct
signal, but it is a scope and noise decision for the owner, not a
mechanical reconciliation). Raised for a separate call.

## Note on the counter-view

#340 raises deploy-from-develop as the one case for a `develop` ground
truth, and suggests a separate `deployBranch` field rather than
overloading `groundTruthBranch`. No fleet repo deploys from `develop`
today, so I have not added the field - cheap to add later if one
genuinely needs it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #341. Both halves are the same failure mode: **a point-in-time
snapshot presented as current state**, so an agent picking it up "fixes"
what is already fixed.

## 1. Freshness signal on every run

`spec/audit.py` now prints:

```text
audit run 2026-07-18T15:11:28Z | hub bd48caf

== HomeAutomation-Config (source-only; operational) @ main@6fadcbe ==
  clean (deterministic checks; the full operational verdict is AUDIT.md's)
...
Findings are a point-in-time snapshot: re-run this audit before acting on them, and quote the
run stamp above in any issue derived from it (AUDIT.md section 8).
```

- a **run stamp** (UTC + the hub commit the ground truth came from), and
- **per repo, the exact commit read** (`@ <branch>@<sha>`) - so a
finding is attributable to a specific state.

`AUDIT.md` section 8 now requires anything derived from a run - a
report, and **especially an onboarding/conformance issue** - to quote
the stamp, and requires the agent picking that issue up to **re-run the
audit and act on the live result, not the pasted findings**. Findings
are evidence for *why* the issue was filed, never a claim about current
state. That is exactly the #16 failure: two of three findings were
already resolved at pickup.

## 2. driftNotes flagged when they outlive the deviation

A `driftNote` records a **current** deviation; once resolved it is
deleted, not left describing finished work. `audit.py` now raises a
drift finding when a repo **audits clean** but a note still asserts
outstanding work (`pending`, `not yet`, `missing`, `behind`, `owed`,
`todo`, `still`, `absent`).

The marker list is deliberately narrow and the check only fires on an
otherwise-clean repo, so a note recording a *permanent* deviation ("no
get-version-task; relies on validate-task") never trips it. Codified as
`setup.driftnotes.current`.

## The check earned its keep immediately

It caught **a stale note I wrote myself** for HomeAutomation-Config
yesterday - `"OWNER TASK OPEN: CODEGEN_APP_CLIENT_ID +
CODEGEN_APP_PRIVATE_KEY not yet set in either secret store"` - which the
owner resolved (I verified the secrets and closed the downstream issue)
but I never reconciled in the registry. Verified both secrets present in
both stores, then removed the note entirely: with conformance complete
it recorded no deviation at all.

**Noise check:** run across the fleet, the heuristic flagged exactly
that one note.

## Validation

`spec/validate.py`, markdownlint, editorconfig-checker clean; `audit.py`
parses and runs; CRLF/LF preserved.

This lands before the fleet-wide audit sweep deliberately - it
determines whether the issues that sweep produces are accurate on
arrival.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tes (#354) (#355)

Fixes #354, taking **option 1** (the minimal, recommended one) plus the
generalization.

## The fix

A `main` pin push by an actor outside the allowlist now emits a
`::warning::` rather than falling through silently:

```sh
elif [[ "$REF" == "main" ]]; then
  echo "::warning::Pin push on main by unrecognized actor '$ACTOR'; not publishing. If this is the codegen App under a new identity, update the allowlist in publish-plan-task.yml."
fi
```

## Verification: the publish decision is unchanged

Simulated the patched logic across the full matrix - the gate behaves
**identically** for every legitimate case, and the warning fires only on
the silent-failure path:

```text
push               ptr727-codegen[bot]      main     -> publish=true
push               dependabot[bot]          main     -> publish=true
push               ptr727-codegen-v2[bot]   main     -> publish=false  + ::warning::   <- the renamed-App case
push               ptr727                   main     -> publish=false  + ::warning::   <- hand edit, worth seeing
push               ptr727-codegen[bot]      develop  -> publish=false
schedule           github                   main     -> publish=true
workflow_dispatch  ptr727                   develop  -> publish=true
```

## Generalization codified: WORKFLOW.md D8.4

> **An identity allowlist used as a gate fails loud.**

with the failure mode it prevents, and two judgment calls worth
recording:

- **Where an annotation is optional:** the merge-bot hard-codes the same
identities, but its failure is **self-announcing** - it stops merging
and bot PRs visibly pile up. The publish gate is the acute case
precisely because the weekly schedule *masks* it, leaving only a
timeliness symptom.
- **The escalation:** option 2 (mint an App token, resolve the identity
via `GET /app`) removes the hard-coded string entirely, and is the right
move only if an allowlist proves fragile in practice - noted rather than
adopted, per the issue.

## Validation

actionlint clean on the snippet; `spec/validate.py`, markdownlint,
editorconfig-checker clean; LF preserved on the workflow YAML.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nge (#356) (#358)

Fixes #356, including the amendment.

## What lands

**New `## Verification Discipline` section** in AGENTS.md, placed
between `Documentation Style Conventions` and `PR Review Etiquette` -
write it right, verify it, then review. It opens with the unifying
property: **every failure below is green**, so no linter, status check,
or review layer catches any of them.

The six from the proposal, plus the amendment's review companion:

1. A test must assert the mechanism it names.
2. Gates, filters, and gate-like watchers fail loud, never narrow
quietly.
3. Run the repo's whole lint gate before every push.
4. Editing CRLF files programmatically: `.` matches `\r`.
5. A green check is not evidence the work happened.
6. A workflow change is only fully exercised by CI.
7. A review flags an instance; fix the class.

**Rule 7 from the amendment** - search for prose asserting the old
behavior after changing it - goes under **`Documentation Style
Conventions`** per your placement note, directly beside the
present-tense rule as its maintenance counterpart: that rule governs
*how to phrase* a doc, this one *how to keep it true* when the behavior
underneath moves.

**Placement judgment on the companion:** you grouped "fix the class"
with rule 7 but did not place it. I put it in `Verification Discipline`
rather than the docs section, because it generalizes past stale prose to
any flagged defect class (a silent-narrowing pattern, a mis-worded
contract). Happy to move it if you'd rather keep the pair together.

## Complementing rather than duplicating

You named three adjacent rules; each generalization now
**cross-references its instance** instead of restating it:

- fail-loud -> `WORKFLOW.md` **D8.4** (the identity-allowlist gate,
landed yesterday via #354)
- whole-lint-gate -> "Running the Linters Locally" (which documents
*how* to invoke each, not that **all** run)
- green-check -> the `changes`-job note under Branching Model (its
instance for that one job)
- CRLF regex -> the Line Endings warning, whose mechanism it names

I also folded the mirror failure into rule 4: a **text-mode rewrite**
silently flattens CRLF to LF, the inverse of the `CRCRLF` case. I hit
exactly that two days ago - a scripted edit flattened
`spec/project-types.json` and CI caught it, not my local run, which is
also rule 3 in action.

## README

`Rules -> Always` gains the two that read as fleet-wide invariants
rather than technique: run the whole lint gate before pushing, and make
gates fail loud. Both are **bullets under an existing heading**, so the
auto-generated Table of Contents needs no change (and is never
hand-edited per its own rule).

## Validation

`spec/validate.py`, markdownlint, CI-scoped cspell,
editorconfig-checker: clean. CRLF preserved on both files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ck (#359)

The template is private, so any reference to it from a carried file 404s
for that repo's readers and exposes template machinery a consumer should
never have to see. Raised from downstream work where the references were
being stripped by hand, repo by repo.

## Scope of the leak

Swept every cataloged repo. **13 of 20 reference the template; 8 are
public:**

| repo | public | files |
|---|---|---|
| Utilities | yes | copilot-instructions, HISTORY |
| LanguageTags | yes | copilot-instructions |
| aiopurpleair | yes | copilot-instructions |
| homeassistant-purpleair | yes | AGENTS, copilot-instructions |
| PlexCleaner | yes | copilot-instructions |
| VSCode-Server-DotNetCore | yes | copilot-instructions |
| NxWitness | yes | AGENTS, copilot-instructions |
| ESPHome-Config | yes | AGENTS |
| Financial-Modeling / HomeAutomation-Config / KiCadLibrary /
HomeAssistant-Config / MediaTools | no | assorted |

The dominant instance is a stale `copilot-instructions.md` paragraph
telling agents to report drift upstream, which **hard-links the private
template twice** - and one link targets an
`AGENTS.md#staying-in-sync-and-reporting-drift-upstream` section **the
hub no longer has**, so it 404s even for someone with access. The hub
itself carries neither the paragraph nor that section: this is stale
carried content that outlived its source (#305 territory, and exactly
the "prose asserting the old behavior" rule from #356).

## The rule

#344 barred *sibling*-repo references but arguably let the template
through as "the subject". `AGENTS.md` now says explicitly:

> **This includes the template repo itself.** A carried file names it in
neither prose nor a link ... Where a carried file needs to express a
template-level behavior - "report a rule discrepancy upstream" - state
the behavior, not the destination; the maintainer supplies the
destination out of band.

That preserves the agent-to-agent reporting flow (which is real and
valuable) while keeping it out of a public file.

## Mechanized, not left to review

This spread silently across 13 repos, so `spec/audit.py` now checks the
two agent-instruction files for the template name and raises a drift
finding.

The template name is derived from **the git remote**, not the checkout
directory name - a clone into a differently-named folder would otherwise
stop detecting with no signal, which is the fail-open case the check
exists to prevent (AGENTS.md "Verification Discipline": gates fail loud,
never narrow quietly).

```text
PlexCleaner     -> DRIFT carried: .github/copilot-instructions.md references the template repo ...
ESPHome-Config  -> DRIFT carried: AGENTS.md references the template repo ...
Vantage-Config  -> clean
```

## Downstream

Not fixed here - the 13 carried copies need the reference removed in
their own repos. Raising the delivery shape (per-repo issues vs. folding
into each repo's existing conformance issue) rather than filing 13
issues unilaterally.

`spec/validate.py`, markdownlint, editorconfig-checker: clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopts **item 2** of #357, in the sections the owner chose. Item 1 is
#360.

## Comments: no header summary blocks

> **No class-, type-, or file-header summary comment blocks.** A type or
file gets a comment only for a specific non-obvious point, kept terse.

Placed in `AGENTS.md` "Comments", which already bans design narrative,
rule citations, and verbosity creep - a header summary is the same
failure at file scope: it restates the declaration below it and goes
stale as the file grows.

**One carve-out added:** a licence or provenance header that a tool or
policy requires is not a summary, so it is unaffected. That was the only
real objection to a blanket ban.

## Character Set: no clause-joining semicolons

> **No semicolon joining two independent clauses in agent-authored
prose** - documentation, comments, commit messages, and PR descriptions.

Placed beside the ASCII/em-dash rules, as the same class of house-style
constraint on agent prose habits.

**Written to the proposal's actual scope.** It says "as sentence
separators", which is narrower than all semicolons, so the rule bans the
clause-joining use and explicitly preserves two legitimate ones: a
semicolon separating items in a list that already contains commas, and a
statement terminator in code.

## On sweeping existing prose

I measured before writing the rule, because a rule the hub immediately
violates is the trap this repo hit with `set -Eeuo` (#338/#350):

```text
AGENTS.md 79   WORKFLOW.md 80   CODESTYLE.md 33   AUDIT.md 22
copilot-instructions 16   README.md 9   STANDUP.md 5   repo-config/README 4
TOTAL 248 prose lines containing a semicolon
```

Not all are clause-joining - many are list separators the rule preserves
- but a full sweep would still be a sprawling rewrite of dense
governance prose with real risk of changing meaning in text that is
load-bearing.

So the rule states that **existing prose is corrected as each file is
next edited, not swept**. That matches how the fleet already handles
propagation debt, and it is honest about the hub's current state rather
than pretending compliance.

Flagging plainly: this changes my own output style going forward, and
the 248 lines are a known, deliberate residue rather than an oversight.

`spec/validate.py`, markdownlint, editorconfig-checker: clean. CRLF
preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses **item 1** of #357. Item 2 (the two prose/comment style
proposals) is deliberately not in this PR - it is a house-style decision
I am putting to the owner separately.

## The gap this closes

#359 fixed the **hard** leak: a carried file naming or linking the
private template, which 404s for that repo's readers. It also mechanized
a check for it.

A downstream agent caught the **soft** half, which that check cannot see
because it greps for the repo name: carried prose that attributes a rule
to *"the template"*.

> "A repo owner may widen their own CI file list, but **the template
ships** README + HISTORY as the default"
> "Guarantees say 'default branch' portably; **the template implements
it** as the literal `main`"

No repo is named, so nothing 404s - but it still exposes a coordinator
the reader cannot reach, and it frames the repo's own rule as someone
else's. Genuinely good catch; the durable fix belongs here, as the filer
said, or it returns on every reconvergence.

## What changed

Eleven attributions across `AGENTS.md`, `CODESTYLE.md`, and
`WORKFLOW.md` now state the rule directly:

| before | after |
|---|---|
| "the template ships README + HISTORY as the default" | "README +
HISTORY are the default" |
| "the template's default Python profile" | "the default Python profile"
|
| "the template uses `client-id: ...`" | "use `client-id: ...`" |
| "the template implements it as the literal `main`" | "it is
implemented as the literal `main`" |
| "the template constrains only that the output be input-deterministic"
| "the constraint is only that the output be input-deterministic" |
| "not when it matches the template byte for byte" | "not when it
matches a reference implementation byte for byte" |
| "the template ships the tracker but not this consumer wiring" | "the
tracker ships without this consumer wiring" |
| "by exporting the template's two rulesets" | "by importing the
committed `repo-config/*.json` ruleset payloads" |
| "Because the template has no such leaf" | "Because no such leaf ships"
|

Behavior is unchanged throughout; only the framing moves from *reported*
to *owned*.

**One occurrence remains by necessity:** the rule that bans the practice
(`AGENTS.md` "This includes the template repo itself"). It cannot be
stated without referring to the concept, and it names no repo and gives
no location.

Two later passes caught what the first regex missed - "this template"
(AGENTS.md, CODESTYLE.md) and "the `release` template" (WORKFLOW.md).
Remaining matches for the word are different meanings, not attributions:
Serilog structured message templates, and the `reports/_template.md`
filename.

## Note on the mechanical check

`spec/audit.py` greps for the repo **name**, so it catches the hard leak
but not this class - phrasing is a judgment call and a keyword sweep for
"the template" would false-positive on legitimate uses (this PR's own
rule statement, for one). Flagging that limitation rather than
pretending the check covers it.

`spec/validate.py`, markdownlint, editorconfig-checker: clean. CRLF
preserved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reported from a downstream session: Copilot insisted on removing a
`README.md` link pointing to a related device-builder repo, citing the
no-cross-repo-link rule. That link was legitimate content. The rule I
wrote in #344/#359 made that misreading reasonable, so this fixes the
rule rather than the reviewer.

## Two defects in the rule as written

**1. It led with the mechanism and buried the carve-out.** The sentence
opened with "adds no link (inline URL or reference definition) to
another fleet repo" and only qualified it fifteen lines later with
"unless that repo is the subject". Read top-down, the ban looks
absolute.

**2. It claimed `README.md` is carried verbatim. It is not.**
`spec/files.json` gives `README.md` neither `whole: true` nor
`sections`, so it is a per-repo file governed only by
`readme-structure.md`. A repo's own README is its own content. The rule
never should have reached it, which is exactly where the
over-application landed.

## Restated by intent

Two things banned in carried files:

- **Any reference to the template repo**, prose or link. Private, so it
404s, and the coordination flow is machinery a consumer should not see.
- **A sibling fleet repo named as an illustrative example** ("repo X
does it this way", "see repo Y's adoption"). That couples repos and rots
as they diverge.

And explicitly permitted:

> **A contextually relevant link to a related project is not a
coordination reference, and is expected.** Where another repo is part of
this repo's subject matter - the image that consumes this config, the
builder that generates this hardware, a library this depends on - link
it normally. The test is whether the link serves a reader of *this*
repo's content, not whether the target happens to be in the fleet.

Plus the scope correction: the rule governs carried template content,
and a repo's own `README.md` and topical docs are outside it.

`spec/project-types.json`'s `recurring.norepoxref` is reworded to match.

## The mechanical check needed no change

`spec/audit.py` greps only for the **template name** in the two
agent-instruction files. It never looked for sibling links, so it was
not the source of the over-application and its behavior is unchanged -
re-verified against PlexCleaner (flags) and Vantage-Config (clean).

`spec/validate.py`, markdownlint, editorconfig-checker: clean.

*Downstream note: ESPHome-NonRoot is amending its local AGENTS.md for
the same reason. Once this lands, its next carry converges on this
wording.*

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…write rules, runbook fix, and a portable enforcement kit (#364/#365/#366) (#367)

Addresses the cross-repo comment incident (#364, #365, #366): an
autonomous PR-review loop hand-fabricated a GraphQL thread node id and
fired a reply mutation with output suppressed (`>/dev/null 2>&1 ||
true`). Because node ids resolve globally, the guessed id landed on a
real thread in a **stranger's repo** (`mankatcheung/job-finder` #15) and
the write **succeeded under the maintainer's identity**, while the
suppressed output read as a harmless failure.

Three root causes -> three rules, applied in three guard layers.

## 1. Carried template rule (travels to the fleet)

New `AGENTS.md` section **`## Repository Boundaries and Write Safety`**
(after Foundational Principles, beside the existing never-force-push
safety rules), added to the carried-sections allowlist in
`spec/files.json`:

- **Write only to the current project's own repository** - a broad
identity is capability, not permission.
- **Never fabricate, guess, or reuse an identifier passed to a write** -
capture every id from a live query in the same session.
- **A write is never a probe, and its output is never suppressed** - a
write that appears to fail is verified, not assumed harmless.

## 2. Runbook fix (`.github/copilot-instructions.md`, carried whole)

The reply/resolve example **modeled the footgun**: a disembodied `-F
threadId="PRRT_..."` literal disconnected from the
`reviewThreads(first:100)` query above it. Rewritten to capture `$TID`
from that live query with an empty-result guard, use `-F
threadId="$TID"`, show the mutation output, and confirm `isResolved`
before closing. Added an intra-doc cross-reference to the new section.
(The `requestReviews` example already captured its ids from live
queries.)

## 3. Portable per-machine safety kit (`host-setup/agent-safety/`, not
carried)

The prose layers are necessary but not sufficient - the incident
happened *under* a "Verification Discipline" section. This adds the
mechanical backstop and makes it deployable everywhere as the first
thing on a new system:

- **`gh-write-guard.py`** - a PreToolUse hook that DENIES the three
write footguns (suppressed-output mutation, literal-node-id mutation,
cross-origin explicit target). Reads pass through. It fires even in
autonomous / bypass-permissions sessions - the exact condition of the
incident. `--selftest` runs an 11-case decision matrix.
- **`claude-md-safety.md`** - the same rules as host-scope guidance,
appended into `~/.claude/CLAUDE.md` so every session on a machine
inherits them, including ad-hoc work outside any project.
- **`install.py`** + thin `install.sh` / `install.ps1` wrappers - one
tested cross-platform installer: self-tests the hook before registering
it, merges the settings.json entry without clobbering other keys,
updates the CLAUDE.md block in place. Fully idempotent.

This is the rollout #365 tracks; its per-machine checklist becomes "run
`install.sh` / `install.ps1`".

## Deliberately out of scope
- **Credentials** stay out-of-band (per owner decision): a fine-grained
PAT limited to owned repos is stronger structural defense but is
per-machine credential setup, not a carried rule.
- **No `spec/audit.py` change**: section carriage is intent-checked, not
mechanized; a literal-id grep on a whole-carried runbook is fragile.
Raised separately: "Verification Discipline" is also absent from the
carried-sections allowlist - a follow-up, not bundled here.

## Verification (driven, not assumed)
- Hook `--selftest`: 11/11 incl. the exact incident (deny), the empty-id
guard idiom (allow), reads (allow).
- Real stdin path emits `hookSpecificOutput`/`permissionDecision:
"deny"` for the incident command; reads and non-Bash tools pass through;
malformed input never interferes.
- Installer: fresh install, same-version re-run (idempotent), and
version-upgrade replace-in-place all verified against throwaway homes;
`settings.json` merge preserves other keys.
- `spec/validate.py`, markdownlint (whole-repo `**/*.md`),
editorconfig-checker: clean.

**Note on the section-name/anchor lockstep:** the copilot-instructions
cross-reference `#repository-boundaries-and-write-safety` and the
`spec/files.json` `sections` entry both depend on the section title;
rename them together if ever changed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1) (#369)

## What and why

First of four staged PRs for #368: give every governance rule a single
explicit **scope**, so carried docs become granular single-scope pieces
composed per repo rather than large pieces with internal carve-outs a
reader must piece out. This PR is the **inert foundation** — it adds the
mechanism and documents the model, with **zero fleet-finding delta**.
Policy changes (turning on section-presence checking, the doc surgery,
carrying Verification Discipline) follow in later PRs, each held before
`main`.

## Changes

- **`spec/scope-model.md`** (new, hub-only, not in the carried
baseline): the two axes — **home** (host vs repo) and **reach**
(hub-only / all-downstream / type-specific) — and the `appliesTo`
**selector vocabulary**: four disjoint namespaces (project types,
`workflowModel`, `releaseTrigger`, `consumerModel`). States the
load-bearing semantics: `appliesTo` is **any-of** (cross-axis AND is
deliberately not expressible — that's the signal to split a piece), and
entry-level AND section-level `appliesTo` compose. Records
`CODESTYLE.md`'s section→scope mapping (whole-carried, so documented not
mechanized).
- **`spec/files.schema.json`**: a `sections` element may be a bare
string **or** a `{name, appliesTo}` object (`oneOf`).
Backward-compatible — every existing entry stays valid.
- **`spec/audit.py`**: match `appliesTo` against a repo's full selector
set (`repo_selectors`), and **replace the hardcoded operational
`develop.json` path-swap** with two data entries in `files.json`
(`appliesTo: ["release"]` vs `["operational"]`). This is the proof that
the selector model subsumes an existing ad-hoc carve-out.
- **`spec/validate.py`**: first pass over `files.json` — every
`appliesTo` token must resolve to a known selector, no project type may
collide with a reserved token (keeps the namespaces disjoint), and every
cataloged repo must declare `consumerModel` so a `push`/`pull`-scoped
section can't fail open.
- **`AUDIT.md` §3 + `files.json` note**: one-line pointers to the scope
model.

## Inertness — verified

The four selector namespaces are disjoint and no current `appliesTo`
token is non-`*`, so broadening the matcher changes no match. Proven by
computing the required-file set for **all 21 cataloged repos** under
old-code+old-data vs new-code+new-data: **0 differences**.
`spec/validate.py` stays green (and now also validates `files.json`).
`markdownlint`, `editorconfig-checker` clean.

Held on `develop` — no `develop → main` promotion — per the standing
instruction to observe what returns to develop first.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## What and why

Second of the staged PRs for #368. PR-1 gave sections a scope; this
turns on the **section-presence check** in the audit. Previously
`spec/audit.py` verified only file-**path** presence — `sections` were
never read, so a repo carrying an `AGENTS.md` missing half its required
sections passed clean.

## Change

- `required_sections(item, sel)` — the sections a repo must carry from a
baseline entry, filtered by each section's own `appliesTo` (a
bare-string section is `*`; an object section carries its selector).
- `heading_texts(md)` — case-insensitive heading extraction.
- The file-presence loop unions required sections per path, and for a
**markdown** file that exists, reports a **DRIFT** for any required
section heading that is missing.

## Why DRIFT, not LETTER

A heading rename reads as "missing," and equivalence is judged by hand
(AUDIT.md §7). DRIFT surfaces in every report and blocks a "clean"
verdict but does **not** flip the exit code (only DEFECT/LETTER/ERROR
do), so this is safe to turn on fleet-wide. Non-markdown "sections"
(e.g. the tasks.json task group) are intent markers, not headings, so
the heading check is gated to `.md` files.

## Verified

- **File-presence behavior unchanged:** the checked-path list is
identical for all 21 cataloged repos, so this only *adds* section DRIFT
findings — it never changes a LETTER finding or the exit code.
- Hub's own `AGENTS.md` passes clean (all 8 required sections present);
a doc missing a heading is flagged; section-level `appliesTo` filtering
resolves correctly (ready for PR-3's scoped sections).

Held on `develop` — no `develop → main` promotion. The fleet triage
(running the live audit to see which repos' `AGENTS.md` drift) happens
after merge.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…371)

## What and why

Third of the staged PRs for #368 — the first doc surgery. The
`operational`-vs-`release` carve-out was scattered inline across
**Foundational Principles**, **Branching Model**, and **Release Model**,
so those all-downstream sections mixed two scopes a reader (and the
audit) had to piece apart. This makes the operational rules one granular
single-scope piece.

## Change

- **New `## Operational Repositories` section** consolidates the
operational workflow rules — direct-to-`develop`, advisory CI, the
`develop`/`main` ruleset differences, dispatch-only release,
dependabot/merge-bot on every tier. It is tagged **`appliesTo:
["operational"]`** in `spec/files.json`, so PR-2's section-presence
check requires it only on the 4 operational repos.
- The three inline carve-outs slim to one-line pointers, leaving
Foundational, Branching, and Release single-scope (the `release` model).
**No operational detail is lost** — it all moves into the new section.
- The operational **line-ending** rule stays in `## Line Endings`
(findable where EOL rules live) with a cross-pointer, rather than moving
into the new section.

## Scope

Deliberately just the operational extraction. The other two contaminants
from the plan — hub-only pointers inside carried sections, and the
csharp/python/docker examples baked into Release Model / Workflow YAML —
are separate follow-up PRs, to keep each a small composable piece.

## Verified

- Operational repos require the new section; release repos do not.
- The hub's own `AGENTS.md` is complete (no missing sections in either
view).
- `spec/validate.py`, `markdownlint`, `editorconfig-checker` clean.

Once merged, the 4 operational repos will DRIFT on this section until
they adopt it — the intended convergence signal, not a regression. Held
on `develop` — no `main` promotion.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to PR-3 (#368). The carried **Branching Model** and
**Documentation Style Conventions** sections pointed at hub-only docs a
downstream reader cannot follow.

- **Branching Model:** drop the two references to the hub-only
`docs/repo-config-carry.md` (the export/import procedure, and the
brownfield migration procedure). The behavioral content stays — import
the committed `repo-config/*.json` payloads, `configure.sh` selects the
payload, brownfield repos temporarily disable the ruleset to re-sign.
The full setup procedure already lives in the hub-only `STANDUP.md`.
- **Documentation Style:** drop "or the hub-only
`reports/conformance-matrix.md`" from the no-coordination-references
rule; the onboarding or conformance issue remains the destination.

The one `reports/conformance-matrix.md` reference that stays is inside
the **hub-only** Repository Onboarding section, so it is correctly not a
carried contaminant.

Deliberately minimal — `configure.sh` (also hub-only) and the
onboarding-flavored setup mechanics in that Branching bullet are left
for a possible follow-up. Held on `develop`.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…373)

Resolves the original #368 question. **Verification Discipline** is
clean all-downstream guidance (test-asserts-mechanism, gates-fail-loud,
run-all-linters, green-is-not-proof, fix-the-class), so it joins the
carried `AGENTS.md` sections allowlist in `spec/files.json`.

Its two cross-references (WORKFLOW.md D8.4, the `changes`-job under
Branching Model) point at other **carried** content, so no genericizing
is needed.

The section-presence check (PR-2) now requires it fleet-wide — a repo
whose `AGENTS.md` predates it shows a **DRIFT** until it adopts the
section, the intended convergence signal.

Verified: required for both release and operational selectors, the hub's
own `AGENTS.md` already has it, `validate.py` clean. One-line change.
Held on `develop`.

Closes the scope-model doc-surgery series (PR-1 mechanism, PR-2
section-presence, PR-3 operational extraction, PR-3b hub-pointer
removal, PR-3c assessed and skipped, PR-4 this). The
fixed-vs-customizable / per-unit-fidelity verification is the next
design.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
First of the fidelity series (#374). Adds a second per-unit axis to the
carry model: **fidelity** — how faithfully carried content must survive
— orthogonal to the `appliesTo` **scope** (which repos get it). This PR
is the **inert foundation**; the engines that consume it follow.

## Changes

- **`spec/files.schema.json`** — the `fidelity` enum (`presence | intent
| verbatim | interface`) + the optional `contract` object (the interface
engine's spec, used in PR-B).
- **`spec/files.json`** — note cross-links the fidelity model; each
entry gets an explicit `fidelity`: `verbatim` on
`.markdownlint-cli2.jsonc`, `intent` on the whole-carried governed
configs/docs, `presence` (default) on the rest.
- **`spec/validate.py`** — enforces the vocabulary: valid enum,
`contract` only on (and required for) an `interface` unit, and a
`verbatim` unit's canonical source must resolve.
- **`spec/fidelity-model.md`** (new, hub-only, sibling to
`scope-model.md`) — the fixed-vs-overridable contract, the four levels,
why each unit sits where, the workflow override-seam contract,
placeholder normalization, and the **hash-not-version**
stale-vs-violated semantics.

## Inert

`audit.py` is untouched and no baseline path is added, so the
required-file set and every finding are identical — this PR only adds
capability and documents the model. `validate.py` stays green and now
also enforces fidelity; markdownlint/editorconfig-checker clean. Held on
`develop`.

Fidelity levels: **presence** (exists), **intent** (carried faithfully,
judged by meaning), **verbatim** (byte-identical to canonical after
placeholder normalization → content-hash), **interface** (honors a named
contract by name/wiring, body owned). PR-B adds the interface engine +
workflow entries; PR-C adds the verbatim engine.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… PR-B) (#375)

Second of the fidelity series (#374). Consumes `fidelity: interface`:
the audit now verifies a carried workflow honors its **fixed contract by
name and wiring**, never the owned body — the virtual-function signature
check.

## Change

- **`spec/audit.py`**
- `split_jobs(text)` — slices a workflow into `{job_key: block}` by the
jobs-level indent, no YAML parser (stdlib), mirroring `heading_texts`.
- `check_interface(path, contract, text)` — required job keys, the
ruleset-bound check `name:`, the `release-asset-<branch>-<target>`
handoff, and per-job required (`pattern:`/`merge-multiple:`) and
forbidden (`artifact-ids:`) tokens. **All findings DRIFT.**
- An `interface` unit's **absence** is DRIFT too (not a hard LETTER) — a
workflow's naming varies more than a carried config, so everything here
is advisory.
  - `--selftest` (no network) — 7 fixture cases.
- **`spec/files.json`** — two workflow `interface` entries:
`test-pull-request.yml` (`appliesTo: "*"`, the universal
`check-workflow-status` job + its ruleset-bound name) and
`build-release-task.yml` (build types, the
`get-version`/`github-release` jobs + artifact handoff +
no-`artifact-ids:` rule).
- **`AUDIT.md`** — notes the new check, and fixes pre-existing
clause-joining semicolons in this file's comments/docstrings so they
don't fester.

## Verified

- `python3 spec/audit.py --selftest` → **7/7** (conformant, missing
job+name, renamed check, artifact-ids fork, missing merge-multiple,
owned extra-leaf-job no-finding, split_jobs).
- **Positive control:** the hub's own canonical `test-pull-request.yml`
and `build-release-task.yml` are **conformant** (0 findings).
- `validate.py` green (contracts pass PR-A's value-type guards), lints
clean.

Scoping of `build-release-task.yml` is best-effort (excludes
source-only/docs to avoid false "absent" DRIFTs); any mis-scope only
yields advisory DRIFTs to triage. It refines as downstreams are audited.
Held on `develop`. PR-C adds the verbatim engine.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Markdown-doc hygiene on two carried docs.

- **AGENTS.md** "Documentation Style Conventions" — the reference-link
rule required definitions at the bottom, grouped by type, and
alphabetized, but never required the reference **names** to be
contextual, so an agent could satisfy the letter with numeric/opaque
names (which is why it's been inconsistently upheld). State the
convention README.md already uses: a name encodes the target and its
group — `foo-shield` for a shield, `foo-link` for an external URL, a
bare `foo` for a local path/anchor — never numeric (`[1]`) or opaque.
- **AUDIT.md** — fix clause-joining semicolons in prose (house rule bans
them), leaving the legitimate list-separators (a series whose items
contain commas), the code-fence example, and the quoted phrase
untouched.

Verified: markdownlint + editorconfig-checker clean, `validate.py` green
(AGENTS.md sections intact). Held on `develop`.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…374, PR-C) (#377)

Adds the verbatim fidelity engine: content-hash a carried unit (whole file, or a workflow job region) against the hub's canonical after EOL normalization, classifying a mismatch as stale (matches a past hub revision) or modified (matches none) via the canonical's git history. Integrity by hash, never a version stamp. Runs at DRIFT. Enabled on .markdownlint-cli2.jsonc and the github-release job region.
…lements) (#378)

Remove the dead MD033:false (HTML comments pass MD033 regardless; disabling it only permitted unwanted inline HTML elements, of which the hub uses none). Backtick the placeholder tokens in reports/_template.md so MD033 does not misread them as elements. 0 markdownlint errors fleet-wide with MD033 enforcing. Surfaced during the Financial-Modeling fidelity adoption.
ptr727 added 3 commits July 21, 2026 16:14
…379 items 2, 3, 6) (#380)

catalog workflows README file list corrected to the actual .github/workflows/ tree; copilot-instructions CODESTYLE reference generalized from (.NET, Python); repo-config/README.md residual coordination reference dropped and the file added to the spec/files.json manifest as an intent unit (it was carried but unlisted, so downstream copies drifted unnoticed). Surfaced by #379.
…tems 4, 5) (#381)

Source-only repos publish via the standalone publish-release.yml (inlined nbgv + action-gh-release, gated by validate-task), not the multi-target build-release-task.yml edited down. Reconciled all occurrences: AGENTS.md Release Model, WORKFLOW.md section 6 + the no-file-target/D4.3/operational siblings, and the spec/project-types.json sourceonly assertions. No build-release-task.yml, no expect_release_assets for source-only.
…follow-up) (#382)

configure.sh (verbatim) and settings.json (intent) are carried by every repo but were unlisted, so they drifted unaudited - re-vendoring repo-config/README.md alone shipped a doc describing a stale script. The audit now catches a stale configure.sh directly. Surfaced from Financial-Modeling#28.
Copilot AI review requested due to automatic review settings July 22, 2026 02:14

Copilot AI 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 develop to main by bringing in the new fidelity + scope verification system for carried units, hardening agent GitHub write-safety (prose + enforceable host kit), and reconciling workflow/docs/spec guidance surfaced during early adoptee onboarding.

Changes:

  • Add scoped (appliesTo) and fidelity-graded (presence/intent/verbatim/interface) verification across the spec and audit tooling.
  • Introduce a host-level agent write-safety kit (PreToolUse hook + installer) and align governance docs/runbooks with the write-safety model.
  • Reconcile workflow and onboarding documentation (including source-only/operational guidance) and update snippets/configs to match the strengthened conventions.

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
WORKFLOW.md Updates workflow contract text to align with interface/verbatim fidelity and stronger shell/gating guidance.
STANDUP.md Points standup flow at the hub-only repo-config carry/apply procedure doc.
spec/validate.py Adds selector + fidelity validation and shape-checking for spec/files.json (CI-side enforcement).
spec/secrets.json Clarifies Codecov note wording to be repo-agnostic.
spec/scope-model.md Documents the selector model and appliesTo semantics (hub-only).
spec/project-types.json Adds Python profile detection + mixed-language (C# + Python scripts subtree) expectations and related checks.
spec/files.schema.json Extends schema for scoped sections and fidelity/contract fields.
spec/files.json Adds fidelity and scoped sections; introduces interface/verbatim-tracked units and repo-config/self-audit baselines.
spec/fidelity-model.md Defines fidelity levels and the interface/verbatim enforcement rationale (hub-only).
reports/conformance-matrix.md Records the mixed C# + Python shape and how checks apply.
reports/_template.md Tweaks template to avoid MD033 false positives by formatting placeholders as code.
repo-config/README.md Refactors content to be repo-local facts; removes hub-only/carry-process details now housed elsewhere.
repo-config/configure.sh Adds model inference for downstream carries and strengthens safety/usage semantics.
registry/repos.json Reconciles types/ground-truth/driftNotes based on recent operational rollouts and mixed-language classification.
README.md Updates quick rules index for verification discipline and mixed-language (C# + Python) guidance.
host-setup/agent-safety/README.md Documents the per-machine write-safety kit installation and verification steps.
host-setup/agent-safety/install.sh POSIX wrapper to run the cross-platform installer via Python 3.
host-setup/agent-safety/install.py Cross-platform installer that deploys + registers the hook and updates CLAUDE.md.
host-setup/agent-safety/install.ps1 Windows wrapper to run the installer via an unambiguous Python 3 launcher.
host-setup/agent-safety/gh-write-guard.py Implements the PreToolUse guard to deny specific high-risk GitHub-write command shapes.
host-setup/agent-safety/claude-md-safety.md Marker-delimited CLAUDE.md snippet containing the write-safety rules.
host-setup/agent-safety/.markdownlint-cli2.jsonc Local markdownlint override to allow H2-first snippet semantics (MD041 disabled).
docs/repo-config-carry.md New hub-only procedure doc for carrying/applying/regenerating repo-config payloads.
cspell.json Adds new domain terms introduced by the fidelity/scope/write-safety work.
CODESTYLE.md Reconciles wording to be non-template-specific and documents Python “two profiles” convention.
catalog/snippets/workflows/run-codegen-pull-request-task.yml Updates bash strict mode to set -Eeuo pipefail.
catalog/snippets/workflows/README.md Corrects orchestrator set for this repo’s .github/workflows/ reality.
catalog/snippets/workflows/publish-plan-task.yml Adds “fail loud” warning for unrecognized actors pushing to main; updates bash strict mode.
catalog/snippets/workflows/publish-docker-readme-task.yml Updates bash strict mode to set -Eeuo pipefail.
catalog/snippets/workflows/check-upstream-version-task.yml Updates bash strict mode to set -Eeuo pipefail.
catalog/snippets/workflows/build-release-task.yml Updates bash strict mode to set -Eeuo pipefail in key jobs.
catalog/snippets/workflows/build-pypilibrary-task.yml Updates bash strict mode to set -Eeuo pipefail.
catalog/snippets/workflows/build-nugetlibrary-task.yml Updates bash strict mode to set -Eeuo pipefail.
catalog/snippets/workflows/build-executable-task.yml Adds missing set -Eeuo pipefail to the publish step.
catalog/snippets/devcontainer/python/post-create.sh Updates bash strict mode to set -Eeuo pipefail.
catalog/snippets/devcontainer/dotnet/post-create.sh Updates bash strict mode to set -Eeuo pipefail.
AUDIT.md Expands deterministic audit coverage narrative (scope selectors, interface/verbatim checks, run stamping).
.markdownlint-cli2.jsonc Enables MD033 and documents why (HTML comments are allowed; elements are flagged).
.github/workflows/validate-task.yml Updates bash strict mode to set -Eeuo pipefail.
.github/workflows/test-pull-request.yml Updates bash strict mode to set -Eeuo pipefail.
.github/workflows/publish-release.yml Updates bash strict mode to set -Eeuo pipefail.
.github/workflows/merge-bot-pull-request.yml Updates bash strict mode to set -Eeuo pipefail.
.github/copilot-instructions.md Hardens the runbook examples to prevent ID fabrication and suppressed-output writes.
.gitattributes Pins LF for additional shebang-executed Python tooling in agent-safety kit.
.editorconfig Pins LF for agent-safety Python tooling to keep shebang execution reliable.

Comment thread host-setup/agent-safety/install.py Outdated
Comment thread docs/repo-config-carry.md Outdated
Comment thread docs/repo-config-carry.md Outdated
Comment thread docs/repo-config-carry.md Outdated
Comment thread docs/repo-config-carry.md Outdated
…384)

install.py now preserves CLAUDE.md's line endings on append/update (verified CRLF-safe); recast seven prose semicolons in docs/repo-config-carry.md. Pre-existing hygiene surfaced by the develop->main promotion review (#383).
Copilot AI review requested due to automatic review settings July 22, 2026 02:29

Copilot AI 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 47 out of 47 changed files in this pull request and generated 4 comments.

Comment thread docs/repo-config-carry.md Outdated
Comment thread docs/repo-config-carry.md Outdated
Comment thread STANDUP.md Outdated
Comment thread AUDIT.md
…385)

Removed the stale groundTruthBranch: develop override from KiCadLibrary/EspDinIoT/PhotoCleaner/HolidayLights (all have main; AUDIT.md rule stands) and refreshed their driftNotes; aligned repo-config-carry.md's cross-repo-link rule with AGENTS.md; noted configure.sh's payload-inference fallback in repo-config-carry.md + STANDUP.md. Pre-existing drift from the #383 promotion review.
Copilot AI review requested due to automatic review settings July 22, 2026 02:48

Copilot AI 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 47 out of 47 changed files in this pull request and generated 2 comments.

Comment thread WORKFLOW.md
Comment thread WORKFLOW.md
@ptr727
ptr727 merged commit e9f7c96 into main Jul 22, 2026
7 checks passed
ptr727 added a commit that referenced this pull request Jul 22, 2026
…386)

Recast 83 clause-joining semicolons to periods, comma+conjunction, or ASCII dashes, per the AGENTS.md prose rule; kept list-separators-with-commas and code. Meaning preserved throughout. Surfaced by the #383 promotion review.
Sign up for free to 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