fix(gate): close build.map coverage gap, run release negative control - #196
Conversation
build.map.java covered seven path classes while CLAUDE.md declared twelve, so a change touching only a workflow, Dockerfile, compose file, stylesheet or TypeScript source made build-decision answer not_necessary and shipped without the gate the rule demands. Add the five missing globs (.github/workflows/*, Dockerfile*, docker-compose*.yml, *.css, *.ts) as role=config build_class=verify, verified against build-decision with a probe ladder including a negative control over the probe method itself. Pin the five hand-added entries with BuildGateCoverageContractTest: they are derivable from no extension's classify_globs(), so build-map seed --force and a steward reconcile erase them silently. The guard was mutation-proved rather than trusted green. State the agreement, and its cost, next to the rule in CLAUDE.md.
Record, before the merge that produces it, the negative control over the version-changed guard: the design, an identification rule rather than a run id, the predicted four-row signature, why publish-image SKIPPED alone is insufficient, the two inert rows, the release.yml:87-101 provenance, and the escalation condition. Record what the control does NOT prove: the same signature is what a guard permanently stuck on unchanged would render. Cite the 0.1.1 cut (run 31157727796) and the pinned reusable workflow's checkout depth, and record the fail-safe / fail-dangerous asymmetry with the prohibition on rebalancing by weakening the negative control. Raise github-automation.auto-merge-build-timeout from 300 to 900 — a non-release key, so merging it fires the release workflow and the guard refuses. The release: block is untouched; that is the control. No run id and no placeholder for one is committed.
…ontract test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @cuioss-oliver, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: cuioss/coderabbit/.coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe change expands verification path classification, adds contract coverage for the build map and detector, clarifies pre-commit rules, documents release negative controls, clarifies release workflow output behavior, and increases the auto-merge build timeout. ChangesBuild and release controls
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Reviewer Guide 🔍(Review updated until commit fdf78a5)
|
There was a problem hiding this comment.
Actionable comments posted: 5
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 5db4939e-e9d3-4841-aec7-2e015cf64460
📒 Files selected for processing (5)
.github/project.yml.plan/marshal.jsonCLAUDE.mdapi-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/BuildGateCoverageContractTest.javadoc/development/release-process.adoc
…Exception Both test methods declared `throws Exception` while only ever surfacing `IOException`: javaBuildMap() declares exactly `throws IOException`, and MAPPER.readTree(String) throws JsonProcessingException, a subclass of it. `java.io.IOException` was already imported, so only the two signature lines change. Addresses PR #196 review finding 3f152d (CodeRabbit). Co-Authored-By: Claude <noreply@anthropic.com>
…e paragraph
release-process.adoc stated the two-predicate refusal contract at
[#guard-refusal-predicates], then cited the release.yml comment as "the
mechanism's declared contract" — and that comment made exactly the
one-predicate claim the same document warns against. The provenance paragraph
endorsed as authoritative the very misreading named two sections earlier.
Both halves are corrected in one commit:
- release-process.adoc: the provenance paragraph now states what an empty
released-version actually proves — that no release was produced — and that
emptiness is the union of both refusal predicates, so it cannot
discriminate a version that did not move from a tag that already exists.
- release.yml: the run/skip discriminator comment no longer asserts the
version-unchanged predicate alone. The comment block is held at exactly
the same line span so the doc's `lines 87--101` citation stays accurate.
The two-predicate statement is unchanged; this makes the provenance paragraph
consistent with it, not the reverse. No release.yml logic, expression,
condition, job or step is touched — the workflow edit is comment prose only.
The negative control's preamble, predicted signature and escalation condition
are untouched.
Addresses PR #196 review finding 0c6358 (CodeRabbit).
Co-Authored-By: Claude <noreply@anthropic.com>
…es to IOException" This reverts commit 7e699ec.
|
/review |
The negative control: observedThis PR's The run, resolved by the identification rule the artifact committed ("the Release workflow run Predicted vs observed
Every predicted row matched. The escalation condition did not fire, so the artifact's claim The two inert rows are named deliberately. Both are gated on a non-empty End-state corroboration, labelled as suchNo new tag was produced ( What this does and does not establishIt establishes that the guard refused. It does not establish that the guard can proceed — One scope noteThis PR edited |
Summary
Closes a gate-coverage hole and runs the release guard's negative control, in that order.
CLAUDE.mdenumerates the file classes whose change obliges a full quality gate..plan/marshal.json'sbuild.map.javablock — the file-to-build contractbuild-decisionis the sole authority over — did notcover five of them:
.github/workflows/**,Dockerfile*,docker-compose*.yml,*.css,*.ts. Afootprint made only of those paths answered
not_necessaryand reachedmainungated. The direction ofthe fix is not up for re-litigation: the map was wrong, the declared rule is right —
CLAUDE.mdwasnever weakened to match the map.
The five entries are then pinned by a test, because the eraser is a command, not a reader: they are
hand-added, no extension's
classify_globs()derives them, andbuild-map seed --forceor amarshall-stewardreconcile rewrites the block from the derivation and drops them silently. The contracttest converts that silence into a red build.
Finally, one non-
release:key of.github/project.ymlis changed, which is the merge that exercises therelease guard's refusal path.
Changes
.plan/marshal.json— fivebuild.map.javaentries added, eachrole: config/build_class: verify:.github/workflows/*,Dockerfile*,docker-compose*.yml,*.css,*.ts. The recursive**spellingof the prose declaration is respelled
.github/workflows/*because the route contract admits single-*fnmatch globs and bare basenames but never
**.*.tshas no live file in the tree today and is addedanyway — the map encodes the rule, not the current file census.
api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/BuildGateCoverageContractTest.java— newcontract test pinning those five globs with their
roleandbuild_class. Containment, not equality, soa legitimate future seed adding a Maven route does not fail it; only losing one of the five, or demoting a
required entry's
build_classaway fromverify, may. Carries two vacuity guards (required-setcardinality, parsed-array non-emptiness) and a matched positive/negative control over the extraction
helper, so the primary assertion cannot pass by iterating over nothing or by accepting everything.
CLAUDE.md— one paragraph beside the Pre-Commit Process rule recording that the enumeration and thebuild.mapcontract now cover the same classes, and stating the cost.doc/development/release-process.adoc— two subsections under the existingTrigger rulesspine: thenegative-control design with its prediction, and what that control proves and does not.
.github/project.yml—github-automation.auto-merge-build-timeout300 → 900. A single non-release:key; the
release:block is untouched. This edit is the negative control's trigger.What this costs, stated plainly
Every workflow, Dockerfile, compose, CSS and TypeScript change now pays a full quality gate instead of
passing as documentation-only. That is the intended trade — the price of the rule
CLAUDE.mdalreadydeclared — not a regression to be tuned away later.
Correction: the claimed "free self-test" is WITHDRAWN
The plan's request predicted a positive control obtained for free: sequence the map fix first, and the
.github/project.ymledit becomes the first footprint the fix gates. That claim is false and is withdrawnhere rather than quietly dropped.
.github/project.ymlis under neither.github/workflows/**(the classactually added to the map) nor a bare
.github/**(never proposed as an entry). The fix therefore neverinstalls the class the self-test needed, and this PR's own footprint is gated by its
*.javaand*.adoccontent, not by the new entries. The new globs are exercised by the contract test, not by this diff.
The negative control is falsifiable, and NOT YET OBSERVED
The run this PR predicts does not exist until this PR merges. Everything below was written before the
merge that tests it; that ordering is the whole point, since an artifact written afterwards can only agree
with whatever happened.
Identification rule (no run id is committed, and no placeholder for one): the Release workflow run
attached to this pull request's merge commit.
Predicted signature — a conjunction of four rows:
release / guard— successrelease.outputs.released-version— emptyrelease / release— skippedPublish the tested image to GHCR(publish-image) — skippedRow 4 alone is insufficient: the same rendering occurs when the
releasejob fails. Without row 1 youcannot distinguish refused by decision from died before deciding, and the crashed guard — the more
alarming case — wears exactly the same clothes.
Two further rows are inert in both directions.
wait-for-maven-centralandpropagate-to-consumersareif:-gated on a non-emptyconsumersoutput, and.github/project.ymldeclares noconsumerskey at all.They skip on every run of this workflow and carry no information either way. Left unnamed, a reader counts
three skips and credits two of them.
Escalation condition. A rendering that does not match this prediction is a finding about the guard, not
a run id to file. It goes in-tree in a follow-up PR, and the artifact's claim is withdrawn until it holds.
The observed run id and the rendered conclusions are posted as a comment on this PR after the merge.
The caveat is undiminished. This signature is also what a guard permanently stuck on "unchanged" would
render. The control proves the guard refused; it does not prove the guard can proceed, and it must
never be cited as though it did. The positive direction is cited rather than re-manufactured (the 0.1.1 cut,
run
31157727796) together with the pinned reusable workflow's checkout depth, which is what settles themechanism. The asymmetry is recorded rather than erased: stuck-unchanged fails SAFE, stuck-changed publishes
irrevocably — so the dangerous direction is the one worth an empirical control, and anyone later tempted to
"balance" the two must not do so by weakening this one.
Test Plan
verify -Ppre-commit)verify)BuildGateCoverageContractTestgreen, including its matched positive/negative detector controlThe gate was run explicitly for this footprint, regardless of the
build-decisionverdict. Thedeliverable that installs the gate must not itself ship through the hole it closes — accepting a
not_necessaryverdict here would have been the exact failure this PR exists to remove.Intent
The problem.
CLAUDE.mddeclares which file classes oblige a full quality gate, but.plan/marshal.json'sbuild.map.java— the contractbuild-decisionactually reads — covered five of them with nothing: workflow YAML,Dockerfile*,docker-compose*.yml,*.css,*.ts. A footprint made only of those answerednot_necessaryand reachedmainungated. Separately, this epic had verified the release guard only in the direction where it permits a release; it had never observed it refuse.The chosen approach. Fix the map, not the rule — the declaration is right and was never weakened to match. Then pin the five entries with a JUnit contract test, because they are hand-added, no extension derives them, and
build-map seed --forceor a steward reconcile rewrites the block from the derivation and drops them with no local symptom. A caveat in prose does not survive a command; a red build does. Finally, change one non-release:key of.github/project.yml, which is the merge that exercises the guard's refusal path — and commit the prediction before that merge, so the observation is falsifiable rather than confirmatory.Explicit non-goals. No file under
.github/workflows/is edited, and therelease:block is untouched. No release is cut and no second positive control is manufactured — the 0.1.1 cut is cited instead. No run id and no placeholder for[Intent truncated — 1396 of 1702 characters shown; full outline in the plan workspace]
Generated by plan-finalize skill
Summary by CodeRabbit
Build & Automation
Documentation
Tests