Skip to content

No gate reports success over a corpus that is not there - #241

Merged
stxkxs merged 3 commits into
mainfrom
floor-every-corpus
Sep 4, 2026
Merged

No gate reports success over a corpus that is not there#241
stxkxs merged 3 commits into
mainfrom
floor-every-corpus

Conversation

@stxkxs

@stxkxs stxkxs commented Sep 2, 2026

Copy link
Copy Markdown
Member

The vacuity class, enumerated rather than instanced.

Partly covered already — what is and is not new

You asked me to say so if an open branch covers this. #239 covers four gates
(check-hardcoded-org, check-platform-crs, check-policy-admission,
check-image-vulnerabilities) with picked-number floors and a two-sided test.
What it does not do is enumerate — it fixes the three I happened to find.

This branch is the enumeration, and it found two things #239 does not reach.

Overlap to reconcile at merge: check-hardcoded-org.py's MIN_APPSETS block
appears on both branches, written identically so it resolves as one hunk rather
than two designs. check-platform-crs.py and check-policy-admission.py are
touched by both, in different places.

The enumeration

scripts/tests/empty-corpus.py copies the tracked tree, removes every file under
the directories the gates read, and runs each gate. The population is every
executable under scripts/, derived from the tree — a gate added later is
probed without anyone remembering to add it. Corpus emptying is wholesale rather
than per gate, for the same reason: a per-gate map of what each one walks is the
hand-maintained list this replaces, and it would go stale in the direction that
matters.

Of 25 gates, 23 are probed and refuse. Two are exempted and asserted — they
answer about .github/workflows, which this probe does not empty, so running
them here would assert nothing about them.

What it found

One gate exited 0. check-hardcoded-org.py, printing Scanned 0 applied ApplicationSet(s) and its success line under --blocking. Already fixed on #239;
fixed identically here so the harness can pass.

Four gates crashed rather than refusing — and this is the part #239 does not
reach. An unguarded read_text() on an absent manifest raises FileNotFoundError,
which exits 1, the status this repo uses for "the gate rejected the tree". By
status alone a crash and a finding are the same answer, and the traceback names a
pathlib internal rather than the input that is missing:

gatelib.read_yaml is the guarded single-document read they now use, alongside
the read_yaml_all and read_json already there.

Every verdict has a planted violation it names

FAIL  check-vacuous-probe.py exited 0 over an empty corpus — ✓ examined 0
      ApplicationSet(s), nothing wrong

FAIL  check-log-volume-budget.py CRASHED rather than refusing (exit 1) —
      FileNotFoundError: [Errno 2] No such file or directory: …

FAIL  check-gone.py is exempted as answering about nothing rather than the
      catalog, but no such gate exists under scripts/ — the exemption outlived
      its file.

FAIL  only 23 gate(s) were probed, under the floor of 200 — the population this
      harness reads shrank, and a clean run over it says nothing about the rest.

The first plants a gate whose body is the vacuous shape itself; the second
reverts one of the four fixes; the third and fourth attack the harness's own
population and exemption list.

On "derived rather than picked"

I could not make the floors themselves derived, and the reason is worth stating
rather than hiding. A derived floor needs a second enumerator that does not
go empty with the corpus. git ls-files does not qualify — empty the directory
and the index agrees, so an equality between them holds at zero and closes
nothing. git ls-tree HEAD does qualify but is circular in CI, where HEAD is the
working tree.

So the floors stay constants, and what is derived is the enumeration of who needs
one and the proof that each refuses. #239's test_corpus_floors.py holds the
other half against the tree: above zero, and below the real corpus.

Verification

All exit 0 on the branch head:

  • task validate — 25 gates, now including validate:empty-corpus
  • ruff check .; mypy over 34 files
  • ./scripts/tests/empty-corpus.py — 23 probed, all refuse, none by crashing
  • ./scripts/tests/controls.py — 17 controls
  • yamllint; ./scripts/check-workflows.sh; ./scripts/check-named-things.py

Wired into task validate and the gate-tests CI job; exempted in NOT_GATES
as a harness, with the exemption asserted.

A gate reports on the population it read, and no exit code separates "this
catalog holds no violation" from "this run held no catalog". Naming the gates
that confuse the two is a list of the ones somebody found, so this asks all of
them.

`scripts/tests/empty-corpus.py` copies the tracked tree, removes every file under
the directories the gates read, and runs each gate. The population is every
executable under `scripts/`, derived from the tree, so a gate added later is
probed without anyone remembering to add it. A gate must exit non-zero, and must
not do it by crashing.

That second requirement is its own defect. An unguarded `read_text()` on an
absent manifest raises FileNotFoundError, which exits 1 — the status this repo
uses for "the gate rejected the tree". By status alone a crash and a finding are
the same answer, and the traceback then names a pathlib internal rather than the
input that is missing.

Four gates read a manifest that way and crashed instead of refusing:
check-falco-rule-floor, check-log-volume-budget, check-platform-crs and
check-policy-admission. `gatelib.read_yaml` is the guarded single-document read
they now use, alongside the `read_yaml_all` and `read_json` already there.
check-hardcoded-org exited 0 outright, printing `Scanned 0 applied
ApplicationSet(s)` and its success line under `--blocking`.

Of the 25 gates, 23 are probed and refuse. Two are exempted and asserted: they
answer about `.github/workflows`, which this probe does not empty, so running
them here would assert nothing about them.

The harness rejects its own failure modes:

    FAIL  check-vacuous-probe.py exited 0 over an empty corpus — ✓ examined 0
          ApplicationSet(s), nothing wrong

    FAIL  check-log-volume-budget.py CRASHED rather than refusing (exit 1) —
          FileNotFoundError: [Errno 2] No such file or directory: …

    FAIL  check-gone.py is exempted as answering about nothing rather than the
          catalog, but no such gate exists under scripts/ — the exemption
          outlived its file.

    FAIL  only 23 gate(s) were probed, under the floor of 200 — the population
          this harness reads shrank, and a clean run over it says nothing about
          the rest.

Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

CI Results

Check Status
Zero-placeholder gate ✅ success
YAML Lint ✅ success
Dashboard gate (live grafana.com ids + AMG-saveable) ✅ success
Kyverno policy tests (+ verify-images contract) ✅ success
Fork-safety gate (no hardcoded org, blocking) ✅ success
Helm-render (every addon × every env) ✅ success
Policy-admission (Enforce-tier Kyverno vs the fleet) ✅ success
ApplicationSet schema + sync waves ✅ success
Appset render (Karpenter subnet selector) ✅ success
Secret scan (gitleaks) ✅ success
Render + assert + schema + misconfig (all environments) ✅ success
Renovate manager coverage ✅ success
Loki volume warns before cutoff ✅ success
Falco loads every rule set it installs ✅ success
Gate scripts lint and typecheck ✅ success
Policy validity (kustomize + kyverno) ✅ success
The gates themselves are tested ✅ success
Pods name ServiceAccounts that exist ✅ success
Catalog CRs admissible against the operator chart ✅ success
Catalog source reads its revision ✅ success

All checks passed.

… masking

Three gates reported success over a corpus that was not there, inside the change
that exists to close that class.

`kubeconform-scan.sh` takes its corpus as an argument. The probe ran every gate
with no argv, so this one read nothing and exited 0 — and rather than being
caught it was exempted, on the claim that it answers about `.github/workflows`.
It does not: its callers pass `applicationsets/` and `rendered`. Both halves of
that exemption's assertion held while the claim was false, which is what an
exemption asserted on the wrong operand looks like. The probe now carries
GATE_ARGS, the same mechanism controls.py already had for the same reason, and
the gate itself floors on what kubeconform reported reading:

    FAIL  kubeconform validated 0 resources under: applicationsets/
          Nothing was schema-checked, which is not the same as everything being
          valid.

`check-platform-crs.py` printed `walked` and gated on nothing;
`check-policy-admission.py` printed its rendered count and compared it only
against a render failure. Both now refuse:

    FAIL  4 custom resource(s) carry an operator API group, below the floor of 5.
    FAIL  7 manifest(s) rendered, below the floor of 40.

`validate-dashboards.py` declared MIN_DASHBOARD_REFS and never compared it; the
enforced floor was one. It is compared now.

Both floors are constants, and the note at each says why rather than dressing one
up. A derivation was written for both and both were circular: check-platform-crs
counting candidates by API group against candidates by schema kind reads the same
walk on both sides, and check-policy-admission bounding the render by
`discover()` reads the same ApplicationSets the render does — so a corpus that
shrinks shrinks the bound with it and the comparison holds. Nothing else in this
tree enumerates either population. `test_corpus_floors.py` holds both bounds
against the tree instead, above zero and below the real corpus.

The probe records what it cannot separate. Several gates derive their coordinates
from an ApplicationSet, so emptying every corpus takes that input away too and
they exit 2 naming the appset rather than saying anything about the corpus they
check. Those runs are marked. A two-pass probe keeping `applicationsets/` was
written and does not separate the cases — for a gate whose corpus IS
applicationsets, the same file is both — and what would separate them is the
per-gate map this harness exists to avoid. The limit is in the header.

24 gates probed, all refusing, none by crashing.

Co-authored-by: stxkxs <275011021+stxkxsbot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

CI Results

Check Status
Zero-placeholder gate ✅ success
YAML Lint ✅ success
Dashboard gate (live grafana.com ids + AMG-saveable) ✅ success
Kyverno policy tests (+ verify-images contract) ✅ success
Fork-safety gate (no hardcoded org, blocking) ✅ success
Helm-render (every addon × every env) ✅ success
Policy-admission (Enforce-tier Kyverno vs the fleet) ✅ success
ApplicationSet schema + sync waves ✅ success
Appset render (Karpenter subnet selector) ✅ success
Secret scan (gitleaks) ✅ success
Render + assert + schema + misconfig (all environments) ✅ success
Renovate manager coverage ✅ success
Loki volume warns before cutoff ✅ success
Falco loads every rule set it installs ✅ success
Gate scripts lint and typecheck ✅ success
Policy validity (kustomize + kyverno) ✅ success
The gates themselves are tested ❌ failure
Pods name ServiceAccounts that exist ✅ success
Catalog CRs admissible against the operator chart ❌ failure
Catalog source reads its revision ✅ success

One or more checks failed — see the job logs.

The probe's population was the executables under `scripts/`. This repository runs
other things as gates in the same workflow, blocking the same merges, and a
harness named for flooring every corpus whose population quietly excludes them
has the defect it exists to find.

Three are now probed as their callers invoke them: `kyverno test`, `trivy config`
over the rendered tree, and the kustomize build loop. One passed over nothing:

    FAIL  kyverno test: exited 0 with policies/ emptied — No test yamls available

`scripts/kyverno-test.sh` floors it on tests executed. The CLI exits 0 over a
directory holding no tests, which is the status a passing suite gets, and this is
the job standing behind every Kyverno policy in the catalog — a renamed directory
or a narrowed path reported success.

The probe's own floor was unfloored. `MIN_PROBED` decides whether a run proved
anything and nothing read it, so setting it to zero left the tree green over a
probe that examined nothing — the harness's shape one level up. It is now bounded
both ways, and the upper bound is the gate population itself rather than a second
constant, so it cannot be raised past what any tree can satisfy.

MIN_CRS drops from 5 to 2. `candidates` counts documents whose API group the
operator owns, but the walk keeps only kinds the pinned chart ships a schema for,
so a healthy run's number depends on what that chart resolves to where the gate
runs — a reviewer measured four where this tree walks eight, and the job was red
on the tip as pushed. A floor above the smallest resolution is red somewhere it
should be green.

28 gates probed, all refusing, none by crashing.

Co-authored-by: stxkxs <275011021+stxkxsbot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

CI Results

Check Status
Zero-placeholder gate ✅ success
YAML Lint ✅ success
Dashboard gate (live grafana.com ids + AMG-saveable) ✅ success
Kyverno policy tests (+ verify-images contract) ✅ success
Fork-safety gate (no hardcoded org, blocking) ✅ success
Helm-render (every addon × every env) ✅ success
Policy-admission (Enforce-tier Kyverno vs the fleet) ✅ success
ApplicationSet schema + sync waves ✅ success
Appset render (Karpenter subnet selector) ✅ success
Secret scan (gitleaks) ✅ success
Render + assert + schema + misconfig (all environments) ✅ success
Renovate manager coverage ✅ success
Loki volume warns before cutoff ✅ success
Falco loads every rule set it installs ✅ success
Gate scripts lint and typecheck ✅ success
Policy validity (kustomize + kyverno) ✅ success
The gates themselves are tested ✅ success
Pods name ServiceAccounts that exist ✅ success
Catalog CRs admissible against the operator chart ✅ success
Catalog source reads its revision ✅ success

All checks passed.

@stxkxs
stxkxs marked this pull request as ready for review September 4, 2026 22:43
@stxkxs
stxkxs merged commit ce16611 into main Sep 4, 2026
29 checks passed
@stxkxs
stxkxs deleted the floor-every-corpus branch September 4, 2026 22:43
stxkxs added a commit that referenced this pull request Sep 4, 2026
#241 landed the vacuity harness, GATE_ARGS, gatelib.read_yaml across four
gates, and corpus floors on check-platform-crs, check-policy-admission,
validate-dashboards and kyverno-test.sh. This branch plants violations
against those same offline verdicts and asserts the control-exempt gates,
so the two work the same ground from opposite directions.

Four textual conflicts, all of them both-sides-added rather than
either-side-changed. Each is resolved by keeping both, in one order that
the Taskfile and the workflow now share:

  .github/workflows/ci.yml — both branches inserted a step after the
  positive controls. `empty-corpus.py` asserts a GATE rejects an absent
  corpus and `reverify-tests.sh` asserts the TESTS reject a reverted
  behaviour, so the gate-level probe runs beside the other gate-level ones
  and the suite-level one after them.

  Taskfile.yaml, twice — the target definitions and the `validate` list,
  ordered to match the workflow so reading either gives the same sequence.

  scripts/tests/controls.py — both added a NOT_GATES entry. Both harnesses
  are exempt for the same reason and both keep their recorded reason.

  scripts/tests/run.py — both added to EXPECTED. `test_corpus_floors` joins
  the harness layer with a line saying what it holds. The comment above
  `test_controls` counted the exempt harnesses; it now states the rule
  instead, because the count was about to be wrong and would have stayed
  readable while being false.

──────────────── What the merge changed beyond the conflicts ────────────────

`empty-corpus.py` is a harness registered in NOT_GATES, so it enters the
population of this branch's newest rule: a harness exempted for
self-assertion must be invoked. It is — by `validate:empty-corpus` and by
its own CI step — and the rule reaches it. Removing both commands while
leaving the task named after it makes `invoked_anywhere` answer False and
`check_vacuity` name the file with its recorded reason, so the coverage is
real rather than incidental.

`check-platform-crs.py` gained a `Cannot run` refusal when the operator
ApplicationSet is absent, and its per-gate coverage floor read 49% against a
floor of 50 — a floor doing exactly what a ratchet is for. Resolved by
covering the verdict rather than lowering the number:
`TheVersionThePinResolves` holds the four things `pinned_chart_version`
decides, since the version it reads is upstream of every schema assertion in
that module. Four probes in `reverify-tests.sh`, floor 27 → 31, each killed
and named. Coverage 52%, floors raised to 51 and 31.

`reverify-tests.sh` re-runs `test_corpus_floors` too. The floors #241 added
sit in three gates this harness already mutates against, so a mutant
reverting the floor half is held by that module and by nothing else in the
list — without it, a caught mutant read as a miss. Its "all N modules green"
line now says these are the modules the harness mutates against, which is
what N was always counting.

──────────────── Verification on the merged tree ────────────────

ruff clean; mypy clean over 42 files; 297 tests across 15 modules; coverage
ratchet 31.5% combined (floor 30 → 31), 12 per-gate floors held, 10 of 23
gates with no unit coverage against a ceiling of 11; controls.py 17 controls
each rejecting at exit 1 naming the mutated file, so no planted violation
trips a new floor first; empty-corpus.py 28 probed gates; reverify-tests.sh
31/31; task validate exit 0; check-named-things.py 187 references resolve;
check-workflows.sh clean at MEDIUM and above; renovate-config-validator
accepts the config.

Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
stxkxs added a commit that referenced this pull request Sep 5, 2026
#241 landed the vacuity harness, GATE_ARGS, gatelib.read_yaml, and corpus
floors on check-platform-crs, check-policy-admission, validate-dashboards
and kyverno-test.sh. This branch had independently added floors to two of
those same gates and a module by the same name, so most of the conflict is
two answers to one question rather than two changes to one file.

──────────────────────────── Conflicts ────────────────────────────

scripts/check-platform-crs.py — both sides added a floor at the same point.
This branch floored `walked`; main asserts completeness instead: candidates
by API GROUP against reached by SCHEMA KIND, with the number floor on
candidates alone. main's is strictly stronger and subsumes the other —
`walked` is incremented exactly where `reached` gains an entry, so
candidates >= MIN_CRS with candidates - reached empty forces walked >=
MIN_CRS. Keeping both would leave a rule whose failure is unreachable, so
this branch's floor and its duplicate MIN_CRS go.

scripts/check-policy-admission.py, twice — the same floor at the same value,
arrived at independently. main's returns the named gatelib.CANNOT_RUN rather
than a literal 2 and records why the bound cannot be derived and why the
canary makes the floor necessary. Taken whole.

scripts/tests/test_corpus_floors.py (add/add) — two modules of the same name
written against overlapping gates. Resolved as their union, not as a choice:
main's structure and its record of the two circular derivations that were
rejected, plus this branch's degenerate-render bound, its IMAGELESS_CHARTS
rot check, and its per-chart image-floor assertion. The
`TheFloorsGuardTheRightQuantity` table is main's, because it names the
expressions the merged gates actually contain.

scripts/tests/run.py — both sides list test_corpus_floors; kept once.
CLAUDE.md — two different task targets added to one list; both exist.

──────────────── A digest with no repository is not a reference ────────────────

The image-vulnerability gate refuses on any reference the classifier cannot
place, and five of the ten it named in CI are bare `sha256:<hex>` values
with no repository before them. Those name no image: there is no registry
and no repository in one, only the value.

Excluded by SHAPE rather than by a declaration, because the two rules this
repository already has cannot both hold over it. A NOT_A_CONTAINER entry
excuses whatever carries its bare name, so an entry for `sha256` would
excuse every reference whose only matched token is a digest — and
declaration_rot deletes an entry the render does not support, which is every
tree that happens not to carry a bare digest. One rule demands the entry and
the other removes it; a shape that can never be a reference is not an
exemption to keep re-reading.

Nothing is lost. A digest belonging to a repository is matched with that
repository, whole, by the alternative above it, and stays classified as a
digest.

Two mutants, both killed and named: the exclusion removed, and the exclusion
written unanchored so it eats repository digests too.

──────────────── Verification on the merged tree ────────────────

ruff clean; mypy clean over 38 files; 171 tests across 10 modules; coverage
ratchet 23.1% combined (floor 22 → 23); controls.py 17 controls each
rejecting at exit 1 naming the mutated file; empty-corpus.py 29 probed
gates, up from 28 because check-image-vulnerabilities.py joins the sweep;
reverify-gates.sh 30/30; task validate exit 0; check-image-pins.py 80 images
across 28 charts; check-named-things.py 191 references resolve;
check-workflows.sh clean at MEDIUM and above.

Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
stxkxs added a commit that referenced this pull request Sep 5, 2026
No conflicts. #241's changes and this branch's touch the same four files —
.github/workflows/ci.yml, CLAUDE.md, Taskfile.yaml, scripts/tests/run.py —
in disjoint regions: #241 registers the vacuity harness, this branch
registers the Node pin and the Renovate manager-default record.

Nothing else changed. The Renovate work is unaltered by the merge.

Verification on the merged tree: ruff clean; mypy clean over 36 files; 190
tests across 9 modules; coverage ratchet 24.9% combined; controls.py 17
controls all proving; empty-corpus.py 28 probed gates; reverify-gates.sh
35/35; task validate exit 0; check-named-things.py 191 references resolve;
check-workflows.sh clean at MEDIUM and above; renovate-config-validator
accepts the config; check-renovate-defaults.mjs exit 0 with the record and
the node pin both OK against renovate 44.65.0; check-renovate-coverage.py
exit 0 at 32 chart pins, 18 other-family pins and 27 toolchain pins.

Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
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.

1 participant