diff --git a/docs/ci-cd.md b/docs/ci-cd.md
index 97ee42b21..5ad5f92c2 100644
--- a/docs/ci-cd.md
+++ b/docs/ci-cd.md
@@ -579,7 +579,14 @@ Enforces decision-record lifecycle invariants (ersatztv#521, supersedes the ersa
append-only mechanic): well-formed 5-field metadata, exactly one `active` record per `key`,
reciprocal `supersedes`/`superseded-by` links, no record vanishing from the active set without an
archive copy, no rationale-prose rewrite without a `Decisions-Edit: yes` trailer on a non-merge commit
-in the range (ersatztv#609),
+in the range (ersatztv#609), a structural per-path check that every `*.md` under
+`docs/decisions/records/**` and `docs/decisions/archive/**` parses to **exactly one keyed
+record** (ersatztv#621 — without it, a file the dependency-free frontmatter reader cannot parse,
+such as one using a YAML block scalar, yields `[]` and vanishes from the corpus with every check
+still reporting green; a file directly in `archive/` is exempt only when it really is a stripped index — one keyless
+record with a known generated heading — never merely by its location; the single further exemption,
+`archive/README.md`, is by exact relative path, never by basename, which would otherwise exempt the
+same filename in the active wing),
and the generated active catalog (`docs/decisions/README.md`) in sync with source. Two steps:
`scripts/decisions_validate.py --base origin/ --head HEAD` (the merge-base diff checks, which
need a base/head range — CI-only) and `scripts/build_decisions_catalog.py --check` (catalog drift).
diff --git a/docs/decisions/README.md b/docs/decisions/README.md
index d5a2834c4..f24dbcbd2 100644
--- a/docs/decisions/README.md
+++ b/docs/decisions/README.md
@@ -66,6 +66,7 @@ the link for rationale. Superseded/retired history lives in `archive/`. Regenera
| `docs.decision-lifecycle` | every decision `##` record (active or archived) carries a 5-field metadata block (`key`, `status`, `since`, `supersedes`, `superseded-by`) checked by `scripts/decisions_validate.py`; a record is never deleted or line-edited to reverse a call — it is moved to `docs/decisions/archive/` with `status: superseded`/`retired` and a reciprocal `superseded-by`/`supersedes` key pair to its replacement. | 2026-07-21 | [link](records/docs/decision-lifecycle.md) |
| `docs.decision-one-file-per-record` | Each decision record is its own file at `docs/decisions/records//.md` (archived ones at `docs/decisions/archive//.md`) with YAML frontmatter; the filename IS the key, so one-active-record-per-key is a filesystem property rather than a validator check, and supersession is a `git mv`. | 2026-07-25 | [link](records/docs/decision-one-file-per-record.md) |
| `docs.decision-optional-provenance` | Decision records gain two OPTIONAL fields — `stale-after: YYYY-MM-DD` on the metadata line and a `**Sources:**` line in the metadata block; the Open Knowledge Format (OKF) itself is NOT adopted as the record format. | 2026-07-25 | [link](records/docs/decision-optional-provenance.md) |
+| `docs.record-wing-parse-guard` | `decisions_validate.py` asserts, per PATH, that every `*.md` under `docs/decisions/records/**` and `docs/decisions/archive/**` parses to exactly one record carrying a `key` — an ERROR, not a warning, since a file in the record wings that is not a record is a mistake by definition. A file sitting DIRECTLY in `archive/` is exempt only when it actually looks like a #610 stripped index — exactly one keyless record with a known generated heading — never merely by living there. The one other exemption, `archive/README.md`, is by exact RELATIVE PATH; nothing is ever exempt by BASENAME, since that would exempt the same filename in the active wing too. `_read_frontmatter` is deliberately NOT extended to accept YAML block scalars: every record value goes on ONE line, and the structural check is what makes that limitation loud instead of silent. | 2026-07-26 | [link](records/docs/record-wing-parse-guard.md) |
| `docs.tracker-comment-retrofit` | When the knowledge exporter flags an over-cap tracker issue and excludes it from ingestion, triage its comments instead of assuming a retrofit is owed — and for each decision-shaped item check the **worked issue first**, because a tracker session comment is by construction a précis of the fuller closing record posted on the issue it narrates. Applied to #237 (111 comments) this yielded **zero** records, so server-management#642's "a fact found only in a #237 comment" retrieval row has no valid subject and its interim target (an already-migrated record) is permanent. | 2026-07-21 | [link](records/docs/tracker-comment-retrofit.md) |
| `ffmpeg.external-logo-graphics-engine` | External-URL channel logos pass through to the graphics engine like any other watermark source; `WatermarkSelector` must never gate them on `File.Exists` (always false for a URL) and never route them through the ffmpeg-native overlay shortcut. | 2026-07-20 | [link](records/ffmpeg/external-logo-graphics-engine.md) |
| `ffmpeg.hls-cold-start-burst` | HLS cold-start latency is fixed with a bounded `-readrate_initial_burst` (gated on FFmpeg ≥6.1 capability detection), not by raising `work_ahead_limit`, which would remove the concurrency guarantee it exists for. | 2026-07-20 | [link](records/ffmpeg/hls-cold-start-burst.md) |
diff --git a/docs/decisions/records/docs/record-wing-parse-guard.md b/docs/decisions/records/docs/record-wing-parse-guard.md
new file mode 100644
index 000000000..288e38c22
--- /dev/null
+++ b/docs/decisions/records/docs/record-wing-parse-guard.md
@@ -0,0 +1,56 @@
+---
+key: docs.record-wing-parse-guard
+title: '2026-07-26 — Every file in the record wings must parse to exactly one keyed record; the frontmatter reader stays flat-scalar-only (#621)'
+status: active
+since: '2026-07-26'
+supersedes: none
+superseded-by: none
+rule: '`decisions_validate.py` asserts, per PATH, that every `*.md` under `docs/decisions/records/**` and `docs/decisions/archive/**` parses to exactly one record carrying a `key` — an ERROR, not a warning, since a file in the record wings that is not a record is a mistake by definition. A file sitting DIRECTLY in `archive/` is exempt only when it actually looks like a #610 stripped index — exactly one keyless record with a known generated heading — never merely by living there. The one other exemption, `archive/README.md`, is by exact RELATIVE PATH; nothing is ever exempt by BASENAME, since that would exempt the same filename in the active wing too. `_read_frontmatter` is deliberately NOT extended to accept YAML block scalars: every record value goes on ONE line, and the structural check is what makes that limitation loud instead of silent.'
+signals: 'record silently invisible, parsed to 0 records, block scalar rule >- , unterminated frontmatter, file vanishes from corpus, validator OK but record absent, catalog up to date but missing, one keyed record per file, stripped legacy archive index exempt · paths: `scripts/decisions_validate.py`, `scripts/decisions_lib.py`, `scripts/tests/test_decisions_validate.py` · issues: #621, #610, #609, #603'
+mechanics: '`scripts/decisions_validate.py` -> `record_wing_files` / `record_wing_faults`; `docs/ci-cd.md` -> "`decisions-guard` job"'
+---
+
+Found in adversarial review of #610 / PR #619. Nothing enforced that a file under the record wings
+is actually a record. A file the dependency-free frontmatter reader cannot parse returns `[]` and
+simply **vanishes** — `decisions_validate.py` prints OK, `build_decisions_catalog.py --check` says
+up to date, the record is absent from the corpus, and there is no error anywhere.
+
+**This is the corpus's own failure mode turned on itself.** The whole point of the decision system is
+retrievability; the one thing worse than a missing record is a missing record that reports success.
+Same family as the #609 marker that printed `OK` while doing nothing and the #603 `stale-after` field
+that silently never fired.
+
+**What was already safe, and what wasn't.** An *existing* record disappearing was always loud — the
+no-vanish diff check catches it. The hole is a **newly added** record, which the diff check
+structurally cannot see because there is no base state to compare against, so the author's own PR
+looks clean. That asymmetry is why the fix had to be path-driven rather than diff-driven.
+
+**Why per-path rather than per-construct.** Enumerating the YAML the reader rejects would have to be
+re-done every time the reader meets something new. Asserting *"this path must yield exactly one keyed
+record"* converts a whole CLASS of reader limitations from silent to loud in one move — block
+scalars, indented structure, unterminated frontmatter, a stray note file, an empty file. It does
+NOT catch parse-to-WRONG (frontmatter yielding one keyed record with corrupted values); a separate
+junk-key check covers the realistic instance of that, so this is a strong guard, not a total one.
+
+**The block-scalar decision: NO.** `rule: >-` is the natural thing to reach for on this corpus's very
+long `rule:` values, and under PyYAML it parsed fine — so #610's dependency-free reader *widened* a
+hole rather than creating it. Teaching `_read_frontmatter` to fold block scalars was still declined:
+
+- Correct folding is a real subset of YAML (`>` vs `|`, strip/clip/keep chomping, indentation-relative
+ continuation). A subtly wrong folder is *worse* than a rejection, because it would silently alter
+ rule text — the exact class of failure this record exists to close — while
+ `test_frontmatter_reader_matches_pyyaml` (which compares against PyYAML on real records) would keep
+ passing on the flat records that make up the entire corpus.
+- The reader must stay dependency-free: it runs in CI's `decisions lifecycle` job, in the Husky
+ pre-commit hook, and on every contributor's machine, none of which install anything.
+- With the structural check in place the cost of not supporting block scalars is a **loud error
+ naming the fix**, and all 168 record files already keep each value on ONE line. (The invariant is
+ single-**line**, not single-**quoted**: 117 `rule:` values are unquoted plain scalars, 51 are
+ quoted.) There is no value a flat scalar cannot hold.
+
+Verified by mutation, not by a green tick: with `record_wing_faults` short-circuited to return `[]`,
+**11** tests go red (10 if only the scan loop is neutered, leaving the empty-wing check live),
+and separately, deleting the single line that WIRES it into `main()` reddens a dedicated test — that
+one line's removal previously left the whole suite green while a real record vanished, which is the
+guard's own failure mode applied to the guard. `test_real_repo_record_wings_are_all_parseable` additionally asserts the
+wings are non-empty (>100 files) so a clean result can never be vacuous.
diff --git a/scripts/decisions_validate.py b/scripts/decisions_validate.py
index 06aa9b484..5151a0928 100644
--- a/scripts/decisions_validate.py
+++ b/scripts/decisions_validate.py
@@ -96,9 +96,15 @@ def validate(
rewritten,
archive_records=None,
demoted=(),
+ wing_faults=(),
) -> list[str]:
errs: list[str] = []
archive_records = archive_records or []
+ # #621: structural "one keyed record per file". ERRORS, not warnings — a file under the record
+ # wings that is not a record is a mistake by definition. Reported first because a file that
+ # failed to parse is absent from `records`, so every downstream check below is silently
+ # evaluating an incomplete corpus.
+ errs += list(wing_faults)
decision_recs = [r for r in records if r.heading not in SKIP_HEADINGS and r.status != "legacy-unmigrated"]
active_by_key: dict[str, int] = {}
@@ -406,6 +412,154 @@ def _diff_findings(base: str, head: str) -> tuple[list[str], list[str], list[str
return sorted(set(removed)), sorted(set(rewritten)), sorted(set(demoted))
+def record_wing_files(records_dir: Path | None = None, archive_dir: Path | None = None) -> list[Path]:
+ """Every file that MUST be exactly one keyed record.
+
+ Scope is EVERY `*.md` under `records/**` and `archive/**` — including the files directly in
+ `archive/`, which are scanned but may qualify for the stripped-index exemption applied in
+ `record_wing_faults` (see there). Nothing is excluded by BASENAME.
+
+ Excluding by BASENAME was a real hole: `_NON_DECISION_FILES` is `{README.md, migration-map.md,
+ retrieval-eval.md}` — three TOPIC-dir names — and applying it to the wings meant a genuine
+ record at `records/docs/retrieval-eval.md` was silently skipped. That path is not hypothetical:
+ the path<->key rule forces key `docs.retrieval-eval` to live at exactly that filename, and
+ `docs/decisions/retrieval-eval.md` is a real unmigrated file, i.e. a plausible migration target.
+ Worse, `dl.active_files()` applies that filter only to the TOPIC_DIR glob, not to
+ `RECORDS_DIR.rglob` — so such a file IS a corpus source while being exempt from the guard.
+
+ So the exemption is by exact RELATIVE PATH, never by basename. The only entry is
+ `archive/README.md`, a hand-written directory README that really does exist — an earlier
+ version of this function excluded any wing-root `README.md` "since no such file exists today",
+ which was simply false and would additionally have exempted a future `records/README.md`, i.e.
+ reintroduced the very hole one directory over.
+ """
+ records_dir = dl.RECORDS_DIR if records_dir is None else records_dir
+ archive_dir = dl.ARCHIVE_DIR if archive_dir is None else archive_dir
+ exempt_paths = {archive_dir / "README.md"}
+ files: list[Path] = []
+ for wing in (records_dir, archive_dir):
+ if wing.exists():
+ files += sorted(p for p in wing.rglob("*.md") if p not in exempt_paths)
+ return files
+
+
+def _is_stripped_index(path: Path, archive_dir: Path, recs: list) -> bool:
+ """True for a #610 stripped legacy topic file sitting DIRECTLY in `archive/`.
+
+ Those five files (`api.md`, `scan.md`, `spa.md`, `startup.md`, `release-ci-governance.md`) are
+ generated "Records formerly in this file" indexes — keyless by construction, and what keeps
+ older date-based pointers resolvable. They are indexes, not records.
+
+ The exemption is by IDENTITY, not by location. Exempting everything directly in `archive/`
+ would leave that one directory unguarded: a new unparseable `archive/foo.md` would vanish
+ silently — the same defect this guard exists to close, in the last place it isn't watched. So a
+ file there is exempt only if it actually LOOKS like a stripped index: exactly one keyless
+ record whose heading is one of the known generated ones (`dl.SKIP_HEADINGS`).
+ """
+ return (
+ path.parent == archive_dir
+ and len(recs) == 1
+ and not recs[0].key
+ and recs[0].heading in dl.SKIP_HEADINGS
+ )
+
+
+def record_wing_faults(records_dir: Path | None = None, archive_dir: Path | None = None) -> list[str]:
+ """Structural check (#621): every file in the record wings parses to exactly ONE keyed record.
+
+ Without this, a file the frontmatter reader cannot parse yields `[]` and simply VANISHES — no
+ error, no warning, validator OK, catalog "up to date", record absent from the corpus. That is
+ the corpus's own failure mode turned on itself: the one thing worse than a missing record is a
+ missing record that reports success (cf. the #609 marker that printed OK while doing nothing and
+ the #603 `stale-after` that silently never fired).
+
+ An EXISTING record disappearing was already loud — the no-vanish diff check catches it. This
+ closes the case the diff check structurally cannot see: a NEWLY ADDED record, where the author's
+ own PR looks clean because there is no prior state to diff against.
+
+ Making it path-driven rather than record-driven is the point: it converts a whole CLASS of
+ reader limitations — parse-to-zero, parse-to-many, keyless — from silent to loud in one move,
+ instead of enumerating the constructs we happen to know about today.
+
+ It does NOT catch parse-to-WRONG: frontmatter that yields one keyed record with corrupted
+ values. `rule: >-` followed by an UNINDENTED continuation containing a colon parses to a valid
+ record whose `rule` is literally `>-`, plus a junk key from the continuation line. The junk-key
+ check below is what catches that case; a mis-parse producing only known fields would still slip
+ through, so this is a strong guard, not a total one.
+ """
+ records_dir = dl.RECORDS_DIR if records_dir is None else records_dir
+ archive_dir = dl.ARCHIVE_DIR if archive_dir is None else archive_dir
+ faults: list[str] = []
+ files = record_wing_files(records_dir, archive_dir)
+
+ # A wing that is absent or empty must be LOUD, not silently clean. Otherwise a partial checkout,
+ # a renamed directory, or a bad monkeypatch turns the whole guard into a no-op that reports
+ # success — which is the defect this function exists to close, applied to itself.
+ # DELIBERATELY asymmetric: only the ACTIVE wing must be non-empty. A corpus with zero active
+ # records is definitionally broken (and means the scan is measuring nothing, so a clean result
+ # would be vacuous). An empty ARCHIVE is a perfectly normal state — it just means nothing has
+ # been superseded or retired yet, which is true of any young repo and of a fresh clone before
+ # the first supersession. Faulting on it would fail a correct corpus. Review asked for symmetry
+ # here; the semantics genuinely differ, so this stays asymmetric with the reason stated.
+ if not records_dir.exists() or not any(records_dir.rglob("*.md")):
+ faults.append(
+ f"{records_dir}: the active record wing is missing or contains no *.md files — refusing "
+ f"to report a clean corpus from an empty scan."
+ )
+
+ for p in files:
+ try:
+ recs = dl.parse_file(p)
+ except Exception as exc: # unreadable/undecodable file is a fault, not a crash
+ faults.append(f"{p}: could not be read as a decision record ({exc})")
+ continue
+ if _is_stripped_index(p, archive_dir, recs):
+ continue
+ if len(recs) != 1:
+ faults.append(
+ f"{p}: parsed to {len(recs)} records, expected exactly 1 — a file under the record "
+ f"wings must be one record. Common cause: YAML the dependency-free frontmatter "
+ f"reader does not accept (a block scalar such as `rule: >-` or `rule: |`, any "
+ f"indented/nested structure, or unterminated `---` frontmatter). Put the value on "
+ f"ONE line."
+ )
+ continue
+ if not recs[0].key:
+ faults.append(f"{p}: parsed to a record with no `key` — every record in the wings must be keyed.")
+ continue
+ # Junk keys: the reader accepted a line it should not have. The realistic source is a block
+ # scalar whose continuation was read as its own `k: v` pair, which silently truncates the
+ # real value (`rule` becomes `>-`). PyYAML REJECTS that input, so the hand reader is more
+ # permissive than the writer and nothing else in the pipeline notices.
+ unknown = _unknown_frontmatter_keys(p)
+ if unknown:
+ faults.append(
+ f"{p}: frontmatter has unrecognized key(s) {sorted(unknown)} — a value was probably "
+ f"split across lines (a block scalar continuation read as its own key), which "
+ f"silently truncates the real value. Put each value on ONE line."
+ )
+ return faults
+
+
+def _unknown_frontmatter_keys(path: Path) -> set[str]:
+ """Frontmatter keys outside the known schema. Empty on any read/parse failure (reported elsewhere)."""
+ try:
+ text = path.read_text(encoding="utf-8")
+ except Exception:
+ return set()
+ if not dl.has_frontmatter(text):
+ return set()
+ lines = text.splitlines()
+ end = next((i for i, ln in enumerate(lines[1:], start=1) if ln.rstrip() == "---"), None)
+ if end is None:
+ return set()
+ meta = dl._read_frontmatter("\n".join(lines[1:end]))
+ if not meta:
+ return set()
+ known = set(dl._FM_TO_FIELD) | {"title"}
+ return {k for k in meta if k not in known}
+
+
def _archive_keys() -> set[str]:
keys: set[str] = set()
if dl.ARCHIVE_DIR.exists():
@@ -491,6 +645,7 @@ def main(argv=None) -> int:
rewritten=rewritten,
archive_records=archive_records,
demoted=demoted,
+ wing_faults=record_wing_faults(),
)
if not budget_ok:
diff --git a/scripts/tests/test_decisions_validate.py b/scripts/tests/test_decisions_validate.py
index cb39c79c2..663a818ce 100644
--- a/scripts/tests/test_decisions_validate.py
+++ b/scripts/tests/test_decisions_validate.py
@@ -700,3 +700,241 @@ def test_retitling_a_record_is_not_reported_as_a_removal(tmp_path):
assert removed == [], f"a retitle was reported as a removal: {removed}"
assert rewritten == [], f"a retitle was reported as a prose rewrite: {rewritten}"
assert demoted == [], f"a retitle was reported as a demotion: {demoted}"
+
+
+# --- #621: structural "one keyed record per file" over the record wings -----------------------
+#
+# The defect these cover: a file the dependency-free frontmatter reader cannot parse yields `[]`
+# and vanishes from the corpus with NO error anywhere — validator OK, catalog "up to date", record
+# absent. Only a NEWLY ADDED record is affected; an existing one disappearing is already caught by
+# the no-vanish diff check, which structurally cannot see a record that never existed in the base.
+
+
+def _wing(tmp_path) -> tuple[Path, Path]:
+ """A tmp record-wing pair: (records_dir, archive_dir)."""
+ records = tmp_path / "docs" / "decisions" / "records"
+ archive = tmp_path / "docs" / "decisions" / "archive"
+ (records / "ci").mkdir(parents=True)
+ (archive / "ci").mkdir(parents=True)
+ return records, archive
+
+
+_GOOD = (
+ "---\n"
+ "key: ci.good\n"
+ "title: '2026-01-01 — Good (#1)'\n"
+ "status: active\n"
+ "since: '2026-01-01'\n"
+ "supersedes: none\n"
+ "superseded-by: none\n"
+ "rule: 'a rule on one quoted line'\n"
+ "signals: 'concept · paths: a/b.py · issues: #1'\n"
+ "mechanics: 'x'\n"
+ "---\n\nRationale prose.\n"
+)
+
+
+def test_wing_faults_clean_corpus_is_silent(tmp_path):
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "good.md").write_text(_GOOD)
+ assert dv.record_wing_faults(records, archive) == []
+
+
+def test_wing_faults_block_scalar_record_fails_loudly(tmp_path):
+ """The exact reproduction from #621: a YAML block scalar — the natural thing to reach for on
+ this corpus's very long `rule:` values — makes the whole record silently invisible."""
+ records, archive = _wing(tmp_path)
+ bad = records / "ci" / "blockscalar.md"
+ bad.write_text(_GOOD.replace("rule: 'a rule on one quoted line'\n", "rule: >-\n a long rule wrapped\n over two lines\n"))
+
+ # Precondition: this really is the silent-vanish case, not some other parse error.
+ assert dl.parse_file(bad) == [], "expected the reader to drop the record entirely"
+
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1, faults
+ assert "blockscalar.md" in faults[0]
+ assert "expected exactly 1" in faults[0]
+
+
+def test_wing_faults_unterminated_frontmatter_fails_loudly(tmp_path):
+ records, archive = _wing(tmp_path)
+ bad = records / "ci" / "unterminated.md"
+ bad.write_text("---\nkey: ci.unterminated\nstatus: active\n\nNo closing delimiter.\n")
+ assert dl.parse_file(bad) == []
+
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1, faults
+ assert "unterminated.md" in faults[0]
+
+
+def test_wing_faults_stray_note_file_fails_loudly(tmp_path):
+ """A file under records/ that is not a record at all — no frontmatter whatsoever."""
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "notes.md").write_text("# Scratch notes\n\nNot a record.\n")
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1, faults
+ assert "notes.md" in faults[0]
+
+
+def test_wing_faults_keyless_record_fails_loudly(tmp_path):
+ """Parses to exactly one record, but carries no `key` — still a fault."""
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "keyless.md").write_text(_GOOD.replace("key: ci.good\n", ""))
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1, faults
+ assert "no `key`" in faults[0]
+
+
+def test_wing_faults_exempts_stripped_legacy_archive_files(tmp_path):
+ """The #610 split left five KEYLESS stripped topic files at the archive TOP level (api.md,
+ scan.md, spa.md, startup.md, release-ci-governance.md) whose only content is a generated
+ "Records formerly in this file" index — that index is what keeps older date-based pointers
+ resolvable. They are indexes, not records, and must stay exempt. Their area-NESTED siblings
+ are real records and are checked."""
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "good.md").write_text(_GOOD)
+ # a stripped legacy file at the archive top level: parses to a single keyless pseudo-record
+ (archive / "api.md").write_text("# api\n\n## Records formerly in this file\n\n- `api.thing`\n")
+ (archive / "README.md").write_text("# archive\n")
+ assert dv.record_wing_faults(records, archive) == []
+
+ # ...but a real archived record nested under an area IS checked
+ (archive / "ci" / "broken.md").write_text("# not a record\n")
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1 and "broken.md" in faults[0], faults
+
+
+def test_validate_surfaces_wing_faults_as_errors(tmp_path):
+ """Faults must arrive as validator ERRORS (exit 1), not warnings."""
+ errs = _v([_rec(key="ci.a", source=Path("docs/decisions/records/ci/a.md"), heading="A")],
+ wing_faults=["docs/decisions/records/ci/x.md: parsed to 0 records, expected exactly 1"])
+ assert any("x.md" in e for e in errs), errs
+
+
+def test_real_repo_record_wings_are_all_parseable():
+ """Positive control against the LIVE corpus: every one of the real record files parses to
+ exactly one keyed record. This is what makes the check's clean result meaningful rather than
+ vacuous — if the wings were empty or unreadable, the checks above would pass trivially."""
+ files = dv.record_wing_files()
+ assert len(files) > 100, f"record wings look empty ({len(files)} files) — check is vacuous"
+ assert dv.record_wing_faults() == []
+
+
+def test_wing_faults_sees_a_DEEPER_nested_archive_record(tmp_path):
+ """A one-level `archive/*/*.md` glob would exempt the top-level stripped files correctly but
+ silently skip anything nested deeper — a path escaping the check, which is the very failure
+ mode this guard exists to close. The exemption must be 'directly in archive/', not 'exactly
+ one level down'."""
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "good.md").write_text(_GOOD)
+ (archive / "api.md").write_text("# api\n\n## Records formerly in this file\n") # still exempt
+ deep = archive / "ci" / "sub"
+ deep.mkdir(parents=True)
+ (deep / "broken.md").write_text("# not a record\n")
+
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1 and "broken.md" in faults[0], faults
+
+
+# --- #621 cold-review findings ------------------------------------------------------------------
+
+
+def test_main_actually_CALLS_the_wing_scan(monkeypatch, capsys):
+ """The guard's only wiring was untested: deleting `wing_faults=record_wing_faults()` from
+ main() left the entire suite green, and a real block-scalar record vanished again with
+ `decisions-validate: OK`. Every other test either calls the collector directly or hands
+ validate() a hand-built list, so nothing pinned that main() invokes it at all — the #609
+ 'marker that printed OK while doing nothing' defect, one level up."""
+ monkeypatch.setattr(dv, "record_wing_faults", lambda *a, **k: ["SENTINEL-WING-FAULT"])
+ rc = dv.main([])
+ assert rc == 1, "a wing fault must fail the validator"
+ assert "SENTINEL-WING-FAULT" in capsys.readouterr().err
+
+
+def test_a_record_is_not_exempted_by_its_BASENAME(tmp_path):
+ """`_NON_DECISION_FILES` is a set of TOPIC-dir names ({README, migration-map, retrieval-eval}).
+ Applying it to the wings meant a genuine record at `records/docs/retrieval-eval.md` was
+ silently skipped — and that path is forced, not hypothetical: the path<->key rule puts key
+ `docs.retrieval-eval` at exactly that filename. Worse, dl.active_files() applies the filter
+ only to the TOPIC_DIR glob, so such a file IS a corpus source while being exempt from the
+ guard."""
+ records, archive = _wing(tmp_path)
+ (records / "docs").mkdir(parents=True, exist_ok=True)
+ (records / "ci" / "good.md").write_text(_GOOD)
+ (records / "docs" / "retrieval-eval.md").write_text("# just a note, not a record\n")
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1 and "retrieval-eval.md" in faults[0], faults
+
+
+def test_archive_toplevel_is_exempt_by_IDENTITY_not_by_location(tmp_path):
+ """Exempting everything directly in `archive/` left that one directory unguarded: a new
+ unparseable `archive/foo.md` would vanish silently. The exemption is for the five #610 stripped
+ INDEX files, so it must test for that shape — one keyless record with a known generated
+ heading — not merely for sitting in that directory."""
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "good.md").write_text(_GOOD)
+ (archive / "api.md").write_text("# api\n\n## Records formerly in this file\n\n- `api.thing`\n")
+ assert dv.record_wing_faults(records, archive) == [], "a real stripped index must stay exempt"
+
+ (archive / "foo.md").write_text("rule: >-\n wrapped\n value\n")
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1 and "foo.md" in faults[0], faults
+
+
+def test_junk_frontmatter_key_from_a_split_value_is_faulted(tmp_path):
+ """parse-to-WRONG, the case the structural check alone cannot see. A block scalar with an
+ UNINDENTED continuation containing a colon parses to ONE valid keyed record whose `rule` is
+ literally `>-`, plus a junk key from the continuation — silently truncating the real value.
+ PyYAML rejects this input, so the hand reader is more permissive than the writer."""
+ records, archive = _wing(tmp_path)
+ bad = records / "ci" / "corrupt.md"
+ bad.write_text(_GOOD.replace("rule: 'a rule on one quoted line'\n",
+ "rule: >-\nthe real rule: with a colon\n"))
+ recs = dl.parse_file(bad)
+ assert len(recs) == 1 and recs[0].key, "precondition: this parses to one KEYED record"
+ assert recs[0].rule == ">-", f"precondition: the real value was truncated, got {recs[0].rule!r}"
+
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1 and "unrecognized key" in faults[0], faults
+
+
+def test_an_empty_or_missing_record_wing_is_LOUD(tmp_path):
+ """A guard that reports a clean corpus from a scan of nothing is the defect it exists to close,
+ applied to itself. Reachable via a partial checkout, a renamed directory, or a bad monkeypatch."""
+ missing = dv.record_wing_faults(tmp_path / "nope" / "records", tmp_path / "nope" / "archive")
+ assert missing and "missing or contains no" in missing[0], missing
+
+ records, archive = _wing(tmp_path) # exists but holds no *.md
+ empty = dv.record_wing_faults(records, archive)
+ assert empty and "missing or contains no" in empty[0], empty
+
+
+def test_a_wing_root_README_is_exempt_by_PATH_not_by_basename(tmp_path):
+ """`docs/decisions/archive/README.md` really exists (a hand-written directory README), so it
+ must be exempt — but by exact relative path, not by basename.
+
+ An earlier version excluded ANY wing-root `README.md` on the stated grounds that no such file
+ existed. That was false, and it would additionally have exempted a future
+ `records/README.md` — reintroducing the basename hole one directory over, in the wing that
+ matters most."""
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "good.md").write_text(_GOOD)
+ (archive / "README.md").write_text("# archive\n\nHand-written directory README.\n")
+ assert dv.record_wing_faults(records, archive) == [], "archive/README.md must stay exempt"
+
+ (records / "README.md").write_text("# not a record\n")
+ faults = dv.record_wing_faults(records, archive)
+ assert len(faults) == 1 and "README.md" in faults[0], (
+ f"a README in the ACTIVE wing must NOT inherit the archive exemption: {faults}"
+ )
+
+
+def test_an_empty_ARCHIVE_wing_is_legitimate(tmp_path):
+ """Deliberate asymmetry with the active-wing check, pinned so nobody 'fixes' it into symmetry.
+
+ Zero active records means the scan measured nothing and any clean result is vacuous. Zero
+ ARCHIVED records just means nothing has been superseded yet — normal for a young repo and for
+ every fresh clone before the first supersession."""
+ records, archive = _wing(tmp_path)
+ (records / "ci" / "good.md").write_text(_GOOD)
+ assert dv.record_wing_faults(records, archive) == [], "an empty archive must not fault"