From 67800ab7dec67b70b22e58378955831bbbd05ddb Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 22 Jul 2026 18:03:55 -0700 Subject: [PATCH 1/5] Track verbatim sections in the fleet burn-down report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the fidelity-honesty tool to the per-section verbatim units #397 added, so the fleet-wide propagation state is one regenerable artifact - the #305 answer. - fidelity_honesty.py: check_units enumerates whole intent/verbatim files plus each verbatim section; fidelity_pass computes each unit's fleet spread through the same region extractor and git-history stale check the audit uses (canonical_text folded in). A verbatim section is labelled "path § section". - validate.py: a divergence-ledger disposition may target a section unit ("path § section"), so a section-scoped divergence can carry its own disposition. - divergences.json: note updated for section-scoped dispositions. - reports/divergences.md: regenerated. It now surfaces the three universal AGENTS.md rule sections as diverging in all 19 cataloged repos - every repo carries an older paraphrase. That is the #305 divergence made visible and tracked. Per the idempotent-artifact model this is current state, not a gate; the sections read as untriaged until a convergence disposition is recorded, which is appetite-driven. Co-Authored-By: Claude Opus 4.8 --- reports/divergences.md | 6 ++-- spec/divergences.json | 2 +- spec/fidelity_honesty.py | 64 +++++++++++++++++++++++++++------------- spec/validate.py | 8 +++++ 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/reports/divergences.md b/reports/divergences.md index 6cc13700..bc0154db 100644 --- a/reports/divergences.md +++ b/reports/divergences.md @@ -22,14 +22,16 @@ Generated by `python3 spec/fidelity_honesty.py --report` - do not hand-edit. Cur ## Untriaged - add a disposition to `spec/divergences.json` -_None - every live divergence has a recorded disposition._ +- **AGENTS.md § Repository Boundaries and Write Safety** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) +- **AGENTS.md § Git and Commit Rules** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) +- **AGENTS.md § Verification Discipline** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) ## Mechanical re-vendor (verbatim stale copies) A past hub revision, not the current canonical - the audit already flags these as DRIFT. Copy the current file down. No judgment needed. - **.markdownlint-cli2.jsonc** (15): DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, homeassistant-purpleair -- **repo-config/configure.sh** (8): ESPHome-Config, Financial-Modeling, HomeAssistant-Config, HomeAutomation-Config, PlexCleaner, ProjectTemplate, Utilities, Vantage-Config +- **repo-config/configure.sh** (7): ESPHome-Config, Financial-Modeling, HomeAssistant-Config, HomeAutomation-Config, PlexCleaner, Utilities, Vantage-Config ## Promote candidates (intent uniform -> verbatim) diff --git a/spec/divergences.json b/spec/divergences.json index 2c95d3c1..e53987c5 100644 --- a/spec/divergences.json +++ b/spec/divergences.json @@ -1,6 +1,6 @@ { "$schema": "./divergences.schema.json", - "note": "Curated dispositions for known fleet divergences from the manifest canonicals - the burn-down ledger. spec/fidelity_honesty.py --report joins this against live fleet reality to write reports/divergences.md. A recorded divergence still present renders as a burn-down task with its disposition. A live divergence absent here renders as UNTRIAGED. A recorded divergence no longer live renders as resolved. Edit this file (not the generated report) and regenerate. dispositions cover per-repo file divergences from a verbatim or intent canonical. gaps cover files carried by the fleet but absent from spec/files.json. disposition vocabulary: re-vendor (drift-to-fix, copy the current canonical down), track (a gap to add to the manifest), accepted (a legitimate permanent divergence, no action), upstream-candidate (the downstream carries an improvement the hub should adopt, then re-vendor), investigate (recorded, decision pending).", + "note": "Curated dispositions for known fleet divergences from the manifest canonicals - the burn-down ledger. spec/fidelity_honesty.py --report joins this against live fleet reality to write reports/divergences.md. A recorded divergence still present renders as a burn-down task with its disposition. A live divergence absent here renders as UNTRIAGED. A recorded divergence no longer live renders as resolved. Edit this file (not the generated report) and regenerate. dispositions cover per-repo divergences from a verbatim or intent canonical - a whole file, or a verbatim section labelled path then section name. gaps cover files carried by the fleet but absent from spec/files.json. disposition vocabulary: re-vendor (drift-to-fix, copy the current canonical down), track (a gap to add to the manifest), accepted (a legitimate permanent divergence, no action), upstream-candidate (the downstream carries an improvement the hub should adopt, then re-vendor), investigate (recorded, decision pending).", "dispositions": [ { "path": ".editorconfig-checker.json", "repos": ["HomeAutomation-Config", "HolidayLights"], "disposition": "accepted", "reason": "Both carry a legitimate repo-specific Exclude list (HomeAutomation-Config excludes a Vantage/ subtree, HolidayLights excludes .fseq sequence files). The uniform Disable block is carried intent-equivalent. Exclude is inherently repo-local, which is why the unit is intent, not verbatim.", "tracking": null }, { "path": ".markdownlint-cli2.jsonc", "repos": ["aiopurpleair", "PhotoCleaner", "AudioCleaner"], "disposition": "re-vendor", "reason": "Verbatim config held as a hand-modified copy in these three (not a past hub revision). Restore the current canonical.", "tracking": null }, diff --git a/spec/fidelity_honesty.py b/spec/fidelity_honesty.py index e2ccc9bb..d40e3f74 100644 --- a/spec/fidelity_honesty.py +++ b/spec/fidelity_honesty.py @@ -29,13 +29,7 @@ REPORT_PATH = "reports/divergences.md" # the generated, checked-in burn-down report (--report) -def canonical_text(entry): - """The hub's canonical for a unit: its reference snippet, else its own root copy.""" - ref = entry.get("reference") or entry["path"] - try: - return (audit.ROOT / ref).read_text(encoding="utf-8", errors="replace") - except OSError: - return None +SECTION_SEP = " § " # joins a file path and a verbatim section name into one unit label (path § section) def fetch(slug, path, ref): @@ -46,46 +40,76 @@ def fetch(slug, path, ref): return base64.b64decode(content["content"]).decode("utf-8", "replace") +def check_units(spec): + """Yield the fleet's check units - whole intent/verbatim files, plus each verbatim section of any file. + + Each is (label, fidelity, entry_appliesTo, section_appliesTo, canonical_path, extract). extract is None for + a whole file, or a section extractor for a verbatim section (the region is compared, not the whole file). + """ + for e in spec["files"]["baseline"]: + fid = e.get("fidelity") + if fid in ("intent", "verbatim"): + yield (e["path"], fid, e.get("appliesTo", "*"), "*", e.get("reference") or e["path"], None) + for elt in e.get("sections", []): + if isinstance(elt, dict) and elt.get("fidelity") == "verbatim": + name = elt["name"] + yield (f"{e['path']}{SECTION_SEP}{name}", "verbatim", e.get("appliesTo", "*"), + elt.get("appliesTo", "*"), e["path"], (lambda t, n=name: audit.extract_section(t, n))) + + def fidelity_pass(spec): defaults = spec["registry"].get("defaults", {}) repos = [r for r in spec["registry"]["repos"] if r.get("status") == "cataloged"] - units = [e for e in spec["files"]["baseline"] if e.get("fidelity") in ("intent", "verbatim")] - spreads = [] # (entry, spread dict) for the full table + spreads = [] # (unit dict, spread dict) for the full table promote = [] # intent units that are uniform fleet-wide mislabel = [] # verbatim units that diverge non-stale - for e in units: - path, fid = e["path"], e["fidelity"] - canon = canonical_text(e) + for label, fid, ent_ap, sec_ap, canon_path, extract in check_units(spec): + unit = {"path": label, "fidelity": fid} + try: + canon_raw = (audit.ROOT / canon_path).read_text(encoding="utf-8", errors="replace") + except OSError: + canon_raw = None + canon = None if canon_raw is None else (canon_raw if extract is None else extract(canon_raw)) if canon is None: - spreads.append((e, None)) + spreads.append((unit, None)) continue canon_hash = audit.content_hash(canon) - history = {audit.content_hash(t) for t in audit.git_file_history(e.get("reference") or path)} + # Hash each past revision's same region (a section may be absent in an old revision -> skip it). + history = set() + for t in audit.git_file_history(canon_path): + region = t if extract is None else extract(t) + if region is not None: + history.add(audit.content_hash(region)) spread = {"match": [], "stale": [], "differs": [], "unavailable": []} for r in repos: - if not audit.applies(e.get("appliesTo", "*"), audit.repo_selectors(r, defaults)): + sel = audit.repo_selectors(r, defaults) + if not (audit.applies(ent_ap, sel) and audit.applies(sec_ap, sel)): continue - text = fetch(audit.repo_slug(r), path, r.get("groundTruthBranch", "main")) + text = fetch(audit.repo_slug(r), canon_path, r.get("groundTruthBranch", "main")) if text is None: # missing (404) or present-but-non-inline (too large / encoding "none") spread["unavailable"].append(r["name"]) continue - dh = audit.content_hash(text) + region = text if extract is None else extract(text) + if region is None: # a verbatim section whose heading is absent downstream + spread["differs"].append(r["name"]) + continue + dh = audit.content_hash(region) if dh == canon_hash: spread["match"].append(r["name"]) elif dh in history: spread["stale"].append(r["name"]) else: spread["differs"].append(r["name"]) - spreads.append((e, spread)) + spreads.append((unit, spread)) # A verbatim candidate has NO hand-modified copy ("differs") and at least one confirmed match with # the current canonical. Stale copies do not disqualify it - verbatim would flag them "stale -> # re-vendor", which is the point. A unit that is entirely stale/unavailable is not confirmed uniform. if fid == "intent" and spread["match"] and not spread["differs"]: - promote.append((e, spread)) + promote.append((unit, spread)) if fid == "verbatim" and spread["differs"]: - mislabel.append((e, spread)) + mislabel.append((unit, spread)) return spreads, promote, mislabel diff --git a/spec/validate.py b/spec/validate.py index 06c60227..f63001f5 100644 --- a/spec/validate.py +++ b/spec/validate.py @@ -304,6 +304,14 @@ def check_selector(where, applies_to): div = load("spec/divergences.json") repo_names = {r.get("name") for r in repos["repos"] if isinstance(r, dict)} manifest_paths = {i.get("path") for i in baseline if isinstance(i, dict)} + # A verbatim section is an addressable unit too, labelled "path § section" (matches fidelity_honesty), + # so a section-scoped divergence can carry its own disposition. + for i in baseline: + if not isinstance(i, dict): + continue + for elt in i.get("sections", []): + if isinstance(elt, dict) and elt.get("fidelity") == "verbatim": + manifest_paths.add(f"{i.get('path')} § {elt.get('name')}") # Guard the root type: a non-object root (a list from a bad edit) would crash the .get() calls below. if not isinstance(div, dict): errors.append("divergences.json: root must be an object") From 0e0712aefab3a379385989881a7908847663ce3f Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 22 Jul 2026 18:12:44 -0700 Subject: [PATCH 2/5] Address Copilot round 1 on the fleet section burn-down - fidelity_honesty.py: separate the downstream path from the canonical path. A reference-backed intent unit (e.g. codecov.yml) has its downstream copy at the entry's `path` but its canonical at the `reference` snippet; the refactor had fetched downstream content at the snippet path, making such a unit read as unavailable fleet-wide. Fetch at the downstream path, compare against the canonical path. - fidelity_honesty.py / validate.py: SECTION_SEP is ASCII " > " (path > section), not the non-ASCII section sign, per the ASCII-only prose rule. - validate.py: guard the section-label interpolation on string path/name so a malformed section entry does not synthesize a bogus "None > None" unit. - reports/divergences.md: regenerated (ASCII labels, codecov.yml no longer false-unavailable). Co-Authored-By: Claude Opus 4.8 --- reports/divergences.md | 6 +++--- spec/fidelity_honesty.py | 16 +++++++++------- spec/validate.py | 11 ++++++----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/reports/divergences.md b/reports/divergences.md index bc0154db..f17f039a 100644 --- a/reports/divergences.md +++ b/reports/divergences.md @@ -22,9 +22,9 @@ Generated by `python3 spec/fidelity_honesty.py --report` - do not hand-edit. Cur ## Untriaged - add a disposition to `spec/divergences.json` -- **AGENTS.md § Repository Boundaries and Write Safety** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) -- **AGENTS.md § Git and Commit Rules** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) -- **AGENTS.md § Verification Discipline** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) +- **AGENTS.md > Repository Boundaries and Write Safety** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) +- **AGENTS.md > Git and Commit Rules** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) +- **AGENTS.md > Verification Discipline** - hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair (verbatim canonical) ## Mechanical re-vendor (verbatim stale copies) diff --git a/spec/fidelity_honesty.py b/spec/fidelity_honesty.py index d40e3f74..876c130b 100644 --- a/spec/fidelity_honesty.py +++ b/spec/fidelity_honesty.py @@ -29,7 +29,7 @@ REPORT_PATH = "reports/divergences.md" # the generated, checked-in burn-down report (--report) -SECTION_SEP = " § " # joins a file path and a verbatim section name into one unit label (path § section) +SECTION_SEP = " > " # joins a file path and a verbatim section name into one unit label (path > section), ASCII def fetch(slug, path, ref): @@ -43,18 +43,20 @@ def fetch(slug, path, ref): def check_units(spec): """Yield the fleet's check units - whole intent/verbatim files, plus each verbatim section of any file. - Each is (label, fidelity, entry_appliesTo, section_appliesTo, canonical_path, extract). extract is None for - a whole file, or a section extractor for a verbatim section (the region is compared, not the whole file). + Each is (label, fidelity, entry_appliesTo, section_appliesTo, downstream_path, canonical_path, extract). + downstream_path is where the repo's copy lives (always the entry's `path`); canonical_path is the hub's + canonical (the `reference` snippet for a reference-backed unit, else the same path). extract is None for a + whole file, or a section extractor for a verbatim section (the region is compared, not the whole file). """ for e in spec["files"]["baseline"]: fid = e.get("fidelity") if fid in ("intent", "verbatim"): - yield (e["path"], fid, e.get("appliesTo", "*"), "*", e.get("reference") or e["path"], None) + yield (e["path"], fid, e.get("appliesTo", "*"), "*", e["path"], e.get("reference") or e["path"], None) for elt in e.get("sections", []): if isinstance(elt, dict) and elt.get("fidelity") == "verbatim": name = elt["name"] yield (f"{e['path']}{SECTION_SEP}{name}", "verbatim", e.get("appliesTo", "*"), - elt.get("appliesTo", "*"), e["path"], (lambda t, n=name: audit.extract_section(t, n))) + elt.get("appliesTo", "*"), e["path"], e["path"], (lambda t, n=name: audit.extract_section(t, n))) def fidelity_pass(spec): @@ -65,7 +67,7 @@ def fidelity_pass(spec): promote = [] # intent units that are uniform fleet-wide mislabel = [] # verbatim units that diverge non-stale - for label, fid, ent_ap, sec_ap, canon_path, extract in check_units(spec): + for label, fid, ent_ap, sec_ap, down_path, canon_path, extract in check_units(spec): unit = {"path": label, "fidelity": fid} try: canon_raw = (audit.ROOT / canon_path).read_text(encoding="utf-8", errors="replace") @@ -87,7 +89,7 @@ def fidelity_pass(spec): sel = audit.repo_selectors(r, defaults) if not (audit.applies(ent_ap, sel) and audit.applies(sec_ap, sel)): continue - text = fetch(audit.repo_slug(r), canon_path, r.get("groundTruthBranch", "main")) + text = fetch(audit.repo_slug(r), down_path, r.get("groundTruthBranch", "main")) if text is None: # missing (404) or present-but-non-inline (too large / encoding "none") spread["unavailable"].append(r["name"]) continue diff --git a/spec/validate.py b/spec/validate.py index f63001f5..021595a1 100644 --- a/spec/validate.py +++ b/spec/validate.py @@ -304,14 +304,15 @@ def check_selector(where, applies_to): div = load("spec/divergences.json") repo_names = {r.get("name") for r in repos["repos"] if isinstance(r, dict)} manifest_paths = {i.get("path") for i in baseline if isinstance(i, dict)} - # A verbatim section is an addressable unit too, labelled "path § section" (matches fidelity_honesty), - # so a section-scoped divergence can carry its own disposition. + # A verbatim section is an addressable unit too, labelled "path > section" (matches fidelity_honesty's + # SECTION_SEP), so a section-scoped divergence can carry its own disposition. Only well-formed section + # entries produce a label - a malformed one is already reported by the files.json checks above. for i in baseline: - if not isinstance(i, dict): + if not isinstance(i, dict) or not isinstance(i.get("path"), str): continue for elt in i.get("sections", []): - if isinstance(elt, dict) and elt.get("fidelity") == "verbatim": - manifest_paths.add(f"{i.get('path')} § {elt.get('name')}") + if isinstance(elt, dict) and elt.get("fidelity") == "verbatim" and isinstance(elt.get("name"), str): + manifest_paths.add(f"{i['path']} > {elt['name']}") # Guard the root type: a non-object root (a list from a bad edit) would crash the .get() calls below. if not isinstance(div, dict): errors.append("divergences.json: root must be an object") From eafe69c1405be666cc661c8c9fc6b51723b44ca1 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 22 Jul 2026 18:17:17 -0700 Subject: [PATCH 3/5] Address Copilot round 2 on the fleet section burn-down - fidelity_honesty.py: a verbatim section's canonical_path now honors the entry's reference snippet when present (reference or path), matching whole-file units and the docstring, instead of always the entry path. Inert for AGENTS.md (no reference) but correct for a future reference-backed section-bearing entry. - validate.py: guard that sections is a list before iterating it for section-label paths, so an already-invalid sections type does not iterate a non-list. - divergences.json: the note now describes the actual section unit-key format (file path, a space-greater-space delimiter, then the section name) with an example, instead of the vague "path then section name". Co-Authored-By: Claude Opus 4.8 --- spec/divergences.json | 2 +- spec/fidelity_honesty.py | 3 ++- spec/validate.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/divergences.json b/spec/divergences.json index e53987c5..76217ab8 100644 --- a/spec/divergences.json +++ b/spec/divergences.json @@ -1,6 +1,6 @@ { "$schema": "./divergences.schema.json", - "note": "Curated dispositions for known fleet divergences from the manifest canonicals - the burn-down ledger. spec/fidelity_honesty.py --report joins this against live fleet reality to write reports/divergences.md. A recorded divergence still present renders as a burn-down task with its disposition. A live divergence absent here renders as UNTRIAGED. A recorded divergence no longer live renders as resolved. Edit this file (not the generated report) and regenerate. dispositions cover per-repo divergences from a verbatim or intent canonical - a whole file, or a verbatim section labelled path then section name. gaps cover files carried by the fleet but absent from spec/files.json. disposition vocabulary: re-vendor (drift-to-fix, copy the current canonical down), track (a gap to add to the manifest), accepted (a legitimate permanent divergence, no action), upstream-candidate (the downstream carries an improvement the hub should adopt, then re-vendor), investigate (recorded, decision pending).", + "note": "Curated dispositions for known fleet divergences from the manifest canonicals - the burn-down ledger. spec/fidelity_honesty.py --report joins this against live fleet reality to write reports/divergences.md. A recorded divergence still present renders as a burn-down task with its disposition. A live divergence absent here renders as UNTRIAGED. A recorded divergence no longer live renders as resolved. Edit this file (not the generated report) and regenerate. dispositions cover per-repo divergences from a verbatim or intent canonical - a whole file, or a verbatim section whose unit key is the file path, then a space-greater-space delimiter, then the section name (for example AGENTS.md > Git and Commit Rules). gaps cover files carried by the fleet but absent from spec/files.json. disposition vocabulary: re-vendor (drift-to-fix, copy the current canonical down), track (a gap to add to the manifest), accepted (a legitimate permanent divergence, no action), upstream-candidate (the downstream carries an improvement the hub should adopt, then re-vendor), investigate (recorded, decision pending).", "dispositions": [ { "path": ".editorconfig-checker.json", "repos": ["HomeAutomation-Config", "HolidayLights"], "disposition": "accepted", "reason": "Both carry a legitimate repo-specific Exclude list (HomeAutomation-Config excludes a Vantage/ subtree, HolidayLights excludes .fseq sequence files). The uniform Disable block is carried intent-equivalent. Exclude is inherently repo-local, which is why the unit is intent, not verbatim.", "tracking": null }, { "path": ".markdownlint-cli2.jsonc", "repos": ["aiopurpleair", "PhotoCleaner", "AudioCleaner"], "disposition": "re-vendor", "reason": "Verbatim config held as a hand-modified copy in these three (not a past hub revision). Restore the current canonical.", "tracking": null }, diff --git a/spec/fidelity_honesty.py b/spec/fidelity_honesty.py index 876c130b..ee3a1be2 100644 --- a/spec/fidelity_honesty.py +++ b/spec/fidelity_honesty.py @@ -56,7 +56,8 @@ def check_units(spec): if isinstance(elt, dict) and elt.get("fidelity") == "verbatim": name = elt["name"] yield (f"{e['path']}{SECTION_SEP}{name}", "verbatim", e.get("appliesTo", "*"), - elt.get("appliesTo", "*"), e["path"], e["path"], (lambda t, n=name: audit.extract_section(t, n))) + elt.get("appliesTo", "*"), e["path"], e.get("reference") or e["path"], + (lambda t, n=name: audit.extract_section(t, n))) def fidelity_pass(spec): diff --git a/spec/validate.py b/spec/validate.py index 021595a1..a7e09f17 100644 --- a/spec/validate.py +++ b/spec/validate.py @@ -308,9 +308,9 @@ def check_selector(where, applies_to): # SECTION_SEP), so a section-scoped divergence can carry its own disposition. Only well-formed section # entries produce a label - a malformed one is already reported by the files.json checks above. for i in baseline: - if not isinstance(i, dict) or not isinstance(i.get("path"), str): + if not isinstance(i, dict) or not isinstance(i.get("path"), str) or not isinstance(i.get("sections"), list): continue - for elt in i.get("sections", []): + for elt in i["sections"]: if isinstance(elt, dict) and elt.get("fidelity") == "verbatim" and isinstance(elt.get("name"), str): manifest_paths.add(f"{i['path']} > {elt['name']}") # Guard the root type: a non-object root (a list from a bad edit) would crash the .get() calls below. From 7e7a8f4358451ce3ab7ac880da4b04c7cc6dbf33 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 22 Jul 2026 18:22:07 -0700 Subject: [PATCH 4/5] Address Copilot round 3: harden malformed sections in both tools - fidelity_honesty.py: check_units skips a non-list sections and requires a string name on a verbatim section, so a malformed manifest degrades to no section unit rather than crashing. - validate.py: actually error on a section object without a non-empty string name, so the ledger block's claim that malformed sections are reported by the files.json checks above now holds. Verified with a negative test (non-list sections, a nameless verbatim section, and a bare string section all skipped, the well-formed section still yielded). Co-Authored-By: Claude Opus 4.8 --- spec/fidelity_honesty.py | 7 +++++-- spec/validate.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/fidelity_honesty.py b/spec/fidelity_honesty.py index ee3a1be2..57573e83 100644 --- a/spec/fidelity_honesty.py +++ b/spec/fidelity_honesty.py @@ -52,8 +52,11 @@ def check_units(spec): fid = e.get("fidelity") if fid in ("intent", "verbatim"): yield (e["path"], fid, e.get("appliesTo", "*"), "*", e["path"], e.get("reference") or e["path"], None) - for elt in e.get("sections", []): - if isinstance(elt, dict) and elt.get("fidelity") == "verbatim": + sections = e.get("sections") + if not isinstance(sections, list): + continue + for elt in sections: + if isinstance(elt, dict) and elt.get("fidelity") == "verbatim" and isinstance(elt.get("name"), str): name = elt["name"] yield (f"{e['path']}{SECTION_SEP}{name}", "verbatim", e.get("appliesTo", "*"), elt.get("appliesTo", "*"), e["path"], e.get("reference") or e["path"], diff --git a/spec/validate.py b/spec/validate.py index a7e09f17..64b5ee09 100644 --- a/spec/validate.py +++ b/spec/validate.py @@ -285,6 +285,8 @@ def check_selector(where, applies_to): continue for elt in sections: if isinstance(elt, dict): + if not isinstance(elt.get("name"), str) or not elt.get("name"): + errors.append(f"files.json: {path} section object missing a non-empty string 'name': {elt!r}") check_selector(f"{path} section '{elt.get('name', '?')}'", elt.get("appliesTo", "*")) # A section may carry its own fidelity (intent default, or verbatim for a universal rule block # checked byte-for-byte). verbatim is meaningful only on a markdown file, where the heading From a7a384207e65fb291c01eed8f8bd8cd60885ccb7 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 22 Jul 2026 18:25:30 -0700 Subject: [PATCH 5/5] Address Copilot round 4: reject an empty-string section name An empty name passes the isinstance str check but is still invalid. check_units and the validate.py ledger-label loop now also require a non-empty name, so an empty name never produces a bogus "path > " unit label. Co-Authored-By: Claude Opus 4.8 --- spec/fidelity_honesty.py | 2 +- spec/validate.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/fidelity_honesty.py b/spec/fidelity_honesty.py index 57573e83..2ab36f90 100644 --- a/spec/fidelity_honesty.py +++ b/spec/fidelity_honesty.py @@ -56,7 +56,7 @@ def check_units(spec): if not isinstance(sections, list): continue for elt in sections: - if isinstance(elt, dict) and elt.get("fidelity") == "verbatim" and isinstance(elt.get("name"), str): + if isinstance(elt, dict) and elt.get("fidelity") == "verbatim" and isinstance(elt.get("name"), str) and elt["name"]: name = elt["name"] yield (f"{e['path']}{SECTION_SEP}{name}", "verbatim", e.get("appliesTo", "*"), elt.get("appliesTo", "*"), e["path"], e.get("reference") or e["path"], diff --git a/spec/validate.py b/spec/validate.py index 64b5ee09..75523c2e 100644 --- a/spec/validate.py +++ b/spec/validate.py @@ -313,7 +313,7 @@ def check_selector(where, applies_to): if not isinstance(i, dict) or not isinstance(i.get("path"), str) or not isinstance(i.get("sections"), list): continue for elt in i["sections"]: - if isinstance(elt, dict) and elt.get("fidelity") == "verbatim" and isinstance(elt.get("name"), str): + if isinstance(elt, dict) and elt.get("fidelity") == "verbatim" and isinstance(elt.get("name"), str) and elt["name"]: manifest_paths.add(f"{i['path']} > {elt['name']}") # Guard the root type: a non-object root (a list from a bad edit) would crash the .get() calls below. if not isinstance(div, dict):