fix(ci): scope the requirement matcher to the diff and to non-doc files

`ci/touched-requirements.sh` documents its detection as "R-* tokens literally
present in the diff of changed files". It did not do that — it grepped the ENTIRE
CONTENT of every changed file. So a change inherited every requirement those files
had ever mentioned in prose, and `--require-all` then blocked the merge on
requirements the author never went near.

On this branch that meant editing the decision log and the tofu docs dragged in
five unrelated requirements, all on untouched lines, all legitimately uncovered:

  [coverage] REQUIRE: FAILED — required requirement(s) uncovered: [5 IDs]

The practical effect was that the PM could not append to the decision log without
failing CI — which is a fair part of why the log went stale in the first place.
This is the defect already logged as "FOLLOW-UP (WS-TEST): fix the matcher so real
requirements need not be exempted".

Two scoping rules, both learned from the gate blocking merges it should not have:

  1. Scan the ADDED/REMOVED lines of the diff, as the header always claimed.
     Removed lines count too — deleting a test's requirement tag, or a policy's
     self-declared ID, touches that requirement just as much as adding one.

  2. Skip `*.md`. An ID in prose is a REFERENCE, not a declaration: explaining a
     requirement is not changing behaviour against it. Rule 1 alone is not enough —
     a commit that documents a mis-attribution names the IDs involved and thereby
     re-triggers the very mis-attribution it fixes, and the decision log can never
     record which requirements a change concerned.

This TIGHTENS attribution rather than weakening the gate:
  - no requirement is exempted, and DOCUMENTED_GAPS is untouched;
  - authoritative declarations (`conformance.home.k3s/R-*` tags, Kyverno policies,
    ServiceDefinitions) all live in YAML, on lines a real change must touch;
  - the coarse path->requirement fallback is unchanged and file-type agnostic, so
    the doc subtrees that matter (conformance/, schemas/, catalog/, clusters/, ci/)
    stay covered either way.

Alternatives rejected: adding the five IDs to DOCUMENTED_GAPS genuinely weakens the
gate; scrubbing requirement IDs out of prose (precedent f9e9640) would mean editing
the decision log's own history to appease a matcher.

Gate semantics change — flagged in DECISIONS-LOG.md for review.

Also drops three R-TEST-06 mentions this branch had added to prose and refreshes
the now-stale "gate is RED" notes.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5 committed 2026-08-27 00:06:28 -05:00
1 parent 12c40d8c9c
commit f7d3884ad2
5 files changed
+95 -32

No files matched your search

+48 -19
View File
@@ -1041,30 +1041,59 @@ apps-vms); **10/10 HelmReleases Ready**; zero failing pods; 3 CNPG Clusters heal
nextcloud, semaphore); UniFi VMI Running with guest agent connected (Ubuntu 24.04.4,
LiveMigratable). Conformance scaffold coverage **36/51 (70.6%)**.
**⛔ OPEN — `main` is RED on the CI lint gate.** `make -C ci lint` fails with **yamllint `commas`
errors in the hand-authored `apps/vms/unifi/` manifests** (`30-virtualmachine.yaml`,
`40-networkpolicy.yaml`, `50-service-l4.yaml` — aligned inline comment tables using multiple spaces
after a comma). Every other static stage is green in the devcontainer: spec-immutability ✅
(spec pinned at `b633b7f`, matching the sibling repo), Kyverno L0 ✅, L1 render 3/3 ✅. This is
cosmetic whitespace, but it **blocks the merge gate** and was introduced with the UniFi commits —
i.e. the VM work went in without the gate passing. Fix is a whitespace pass; flagged, not yet done.
**GATE WAS RED — two independent defects found and FIXED (branch `docs/reconcile-with-reality`).**
`main` had been failing the merge gate since the UniFi commits landed. Two separate causes:
1. **yamllint `commas` errors** in the hand-authored `apps/vms/unifi/` manifests
(`30-virtualmachine.yaml`, `40-networkpolicy.yaml`, `50-service-l4.yaml` — column-aligned inline
flow mappings). Cosmetic, but `lint` blocks the merge, so the VM work went in without a passing
gate. Fixed by normalizing the spacing; `kubectl kustomize apps/vms` renders **byte-identical**
before and after. The lint config was deliberately NOT relaxed — loosening a rule to make failing
files pass is moving the goalposts.
2. **`ci/touched-requirements.sh` mis-attributed requirements** — the real defect, and the one
previously logged as "FOLLOW-UP (WS-TEST): fix the matcher so real requirements need not be
exempted". The script's own header says it scans "R-* tokens literally present in the **diff**",
but it actually grepped **whole file contents**. So any edit to a long-lived file pulled in every
requirement that file had ever mentioned in prose: appending one entry to **this log** dragged in
`R-SEC-01`/`R-SEC-03`/`R-TEST-06`, and touching `infra/tofu/` dragged in `R-ENV-01`/`R-PLAT-01`.
Several are legitimately uncovered, so `--require-all` then **BLOCKED the merge on lines the
author never touched** — i.e. the PM could not append to the decision log without failing CI.
Fixed with two scoping rules: scan the added/removed lines of the diff (as the header always
claimed), and **skip `*.md`** — an ID in prose is a *reference*, not a declaration. Without the
second rule the log cannot record which requirements a change concerned without thereby claiming
to have touched them, and the very commit that explains a mis-attribution re-triggers it.
Removed lines still count (deleting a `conformance.home.k3s/R-*` tag touches that requirement too).
**This is a gate-semantics change — flagged for review.** It is a tightening of attribution, not
a weakening: no requirement is exempted, `DOCUMENTED_GAPS` is untouched, authoritative
declarations (test tags, Kyverno policies, ServiceDefinitions) all live in YAML on lines a real
change necessarily touches, and the doc subtrees that matter (`conformance/`, `schemas/`,
`catalog/`, `clusters/`, `ci/`) stay covered by the path→requirement fallback regardless. The alternative fixes were worse:
adding five more `DOCUMENTED_GAPS` genuinely weakens the gate, and scrubbing requirement IDs out
of prose (precedent `f9e9640`) would mean vandalizing this log's own history to appease a matcher.
Gate after both fixes, run in the devcontainer: spec-immutability ✅ (spec pinned at `b633b7f`,
matching the sibling repo), lint ✅, Kyverno L0 ✅, L1 render 3/3 ✅ — **MERGE GATE: PASS**.
**OPEN / NEXT (carried forward):**
1. Fix the lint RED above.
2. **Install UniFi OS Server in the guest** — the VM runs bare Ubuntu; `:11443` answers nothing
1. **Install UniFi OS Server in the guest** — the VM runs bare Ubuntu; `:11443` answers nothing
(CNP permits `cluster`→`:11443`, so it is genuinely not listening). Install is interactive with
a rotating signed URL — needs an operator decision (hand-install vs. a verified silent path).
3. **`R-STATE-02`** — the last safety-critical requirement with no L3 test (Longhorn node-loss).
2. **`R-STATE-02`** — the last safety-critical requirement with no L3 test (Longhorn node-loss).
Currently exempted via `DOCUMENTED_GAPS`.
4. **oauth2-proxy → post-platform tier** (twice-proven cold-start deadlock).
5. **OQ-BKP** — off-cluster backup target; gates Velero, CNPG DR depth, and `R-STATE-03`.
6. **OQ-2** — the `bulk` StorageClass exists but has `server/share: REPLACE-ME` and provisions
3. **oauth2-proxy → post-platform tier** (twice-proven cold-start deadlock).
4. **OQ-BKP** — off-cluster backup target; gates Velero, CNPG DR depth, and `R-STATE-03`.
5. **OQ-2** — the `bulk` StorageClass exists but has `server/share: REPLACE-ME` and provisions
nothing; plex/arr/qbittorrent ship config-only until it is resolved.
7. **image-automation** still authored-but-disabled (needs a Gitea write-cred).
8. **Coverage matcher** — real requirements (`R-ENV-02`, `R-GITOPS-01`) are exempted as
DOCUMENTED_GAPS only because the path→requirement matcher is coarse. Fix the matcher (WS-TEST).
9. **`dev.sh` KUBECONFIG** defaults to the dead libvirt-era path
6. **image-automation** still authored-but-disabled (needs a Gitea write-cred).
7. **Coverage matcher, remaining half.** The content-scan half is fixed (see above). The coarse
`path_requirements` half still over-matches: any `clusters/*` edit claims `R-ENV-02` +
`R-GITOPS-01`, which is why both remain in `DOCUMENTED_GAPS`. Tighten the path map, or scaffold
those two, and drop the exemptions (WS-TEST).
8. **`dev.sh` KUBECONFIG** defaults to the dead libvirt-era path
`infra/ansible/.cache/dev.kubeconfig` and `~/.kube` is not mounted — in-container kubectl needs
an explicit `DEV_KUBECONFIG` pointing at a break-glass cert kubeconfig (OIDC needs a browser).
10. **tofu state** is absent on a fresh workstation; a `tofu apply` with empty state would try to
recreate the three live nodes. Recover the state file or `tofu import` before planning changes.
9. **tofu state** is absent on a fresh workstation; a `tofu apply` with empty state would try to
recreate the three live nodes. Recover the state file or `tofu import` before planning changes.
10. **Docs hygiene.** This log going two days stale is what made the whole reconciliation necessary.
Append per work item, not per session.