Skip to content

Commit 6b688c1

Browse files
committed
docs(plan): record the semver guard-chain round
Two coupled findings. The operand decode is the one the ledger asked for; the carrier union is the pre-existing defect that decode uncovered when its first measurement loosened 408 probes instead of tightening two. Records that the carrier repair's 2,575 tightenings are declared-shape policy restorations rather than helm-abort-justified rejections, with the ci-values sweep as the evidence they are safe, and notes that the repair changes the arithmetic of the reverted traefik `not`-decode lane: part of what its 111 airflow flips measured was the union dropping sibling typings, not base classification.
1 parent f242398 commit 6b688c1

2 files changed

Lines changed: 132 additions & 13 deletions

File tree

plan/chart-corpus-expansion.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10960,3 +10960,84 @@ integration tests (`task test:integration`), `task lint` and `cargo fmt
1096010960
the pre-round fixtures. `task -t …/luup2/deployment/charts/taskfile.yaml
1096110961
check:local` exits 0. Eleven corpus fixtures and three gen fixtures
1096210962
regenerated.
10963+
10964+
## Semver guard chain and member carriers (2026-07-28, forty-seventh round)
10965+
10966+
kube-prometheus-stack gates all 41 rule files on
10967+
`and (semverCompare ">=1.14.0-0" $kubeTargetVersion)
10968+
(semverCompare "<9.9.9-9" $kubeTargetVersion) .Values.defaultRules.create
10969+
… .Values.defaultRules.rules.X`, where `$kubeTargetVersion :=
10970+
default .Capabilities.KubeVersion.GitVersion
10971+
.Values.kubeTargetVersionOverride`. Go's `and` short-circuits, so deleting
10972+
`defaultRules` or `windowsMonitoring` aborts on the dereference that
10973+
follows — unless an override too old for the constraint stops the chain
10974+
first, which is a real, renderable state.
10975+
10976+
The condition decoder has answered `semverCompare` exactly since the
10977+
capabilities lanes landed. What it could not do was answer for an OPERAND
10978+
of a short-circuit chain: `eval_short_circuit_args` derives each operand's
10979+
execution guard from `direct_raw_identity_path`, so anything that is not a
10980+
raw values path contributes `Approximate { "and operand truthiness" }` —
10981+
and one approximate conjunct silences every capture the LATER operands
10982+
make. Both the policy version and the locals bound to a
10983+
Capabilities-defaulted version now reach the evaluator through `EvalEnv`
10984+
(the same channel `local_truthy_reductions` already uses for a reduced
10985+
boolean flag), and `semver_constraint_predicate` is the one model both the
10986+
decoder and the evaluator call. The predicate stays exact: with the
10987+
override falsy it is the policy version's own verdict, with the override
10988+
truthy it is that string's own match against the constraint pattern.
10989+
10990+
### The carrier split this exposed
10991+
10992+
The first measurement closed the two deletions and LOOSENED 408 probes:
10993+
every one of `defaultRules.rules`' 38 declared boolean leaves stopped being
10994+
typed. The host's base had become
10995+
`anyOf[{properties: {alertmanager: …}}, {type: object, properties: {…37}}]`,
10996+
so a document with a junk `alertmanager` simply satisfied the second arm
10997+
and a junk `configReloaders` satisfied the first.
10998+
10999+
That union is a pre-existing defect the new guard merely reached. A host
11000+
whose object-ness is claimed only under a guard leaves an UNTYPED member
11001+
carrier (`{properties: …}` with no `type`), and `try_merge_compatible`
11002+
opens with `schema_type(a)?` — an untyped carrier fails that test, so two
11003+
carriers for the SAME path fell through to the union fallback. Both
11004+
describe one value and their members conjoin;
11005+
`merge_untyped_member_carrier` merges them member-wise and keeps the weaker
11006+
domain by staying untyped, because re-stamping `type: object` would
11007+
reinstate exactly the unconditional host claim the untyped side dropped.
11008+
The rule is narrow — only fragments whose keys are member keywords
11009+
(`properties`/`additionalProperties`/`patternProperties`/`required`) and
11010+
that actually carry members — so a scalar alternative beside an object arm
11011+
still unions.
11012+
11013+
### Result
11014+
11015+
kube-prometheus-stack `defaultRules` and `windowsMonitoring` reject, both
11016+
helm-confirmed ("nil pointer evaluating interface {}.create" /
11017+
".enabled" at `windows.pod.rules.yaml:7`). The 1,848-probe root sweep moves
11018+
those two cells and nothing else, with and without the carrier fix.
11019+
11020+
The carrier fix is the larger change: 45 of 55 corpus schemas move and the
11021+
full battery reports 2,575 flips over 214 distinct paths, **every one a
11022+
TIGHTEN, zero loosenings anywhere**. They are one class — a declared
11023+
mapping's leaves (`*.enabled`, `*.create`, `service.type`, `pathType`)
11024+
regaining the type their values.yaml default declares, which the union had
11025+
been dropping wherever the host had a single guarded member. They are NOT
11026+
helm-abort-justified: `argo-cd controller.startupProbe.enabled: "audit"`
11027+
renders, because a truthy string works as a gate. They are the
11028+
declared-shape typing this ledger already keeps as policy (see the F80
11029+
kyverno scalar-shadow entry), restored to paths that had silently lost it.
11030+
The strongest evidence that the restoration is safe is the ci-values sweep:
11031+
116 real values files, the same 4 rejected as before.
11032+
11033+
18 of the audit's 70 root deletions remain.
11034+
11035+
### Validation
11036+
11037+
947 unit tests (two new focused reproducers, each verified failing pre-fix:
11038+
the semver-gated host claim and the untyped-carrier merge), 497 integration
11039+
tests (`task test:integration`), `task lint` and `cargo fmt --all --check`
11040+
clean. The ci-values sweep rejects the same 4 of 116 files as the pre-round
11041+
fixtures. `task -t …/luup2/deployment/charts/taskfile.yaml check:local`
11042+
exits 0 over 234 resources. 45 corpus fixtures and 2 gen fixtures
11043+
regenerated.

plan/chart-corpus-status.md

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Chart-corpus findings: status ledger
22

3-
Last reconciled 2026-07-28 after the call-dict navigation round
4-
(forty-sixth), which extended the navigation base one level over: a helper's
3+
Last reconciled 2026-07-28 after the semver guard-chain round
4+
(forty-seventh), which taught a short-circuit chain's operands to carry
5+
their own decoded condition instead of an approximate marker, and repaired
6+
the member-carrier union that decode exposed. The call-dict navigation
7+
round (forty-sixth) extended the navigation base one level over: a helper's
58
call dict binds `.config` to `.Values.pdb`, so the body's `.config.enabled`
69
navigates that path. The root-scoped `with` round (forty-fifth)
710
closed the 5 nats deletions the preceding round could reach but not
@@ -2074,15 +2077,49 @@ strict Kubernetes schema while their valid controls passed.
20742077
battery finds exactly one acceptance flip: the other ten already claimed
20752078
those hosts through their own direct reads.
20762079

2077-
The 20 that remain: three are the TEST-TEMPLATE roots (grafana's and
2080+
**Semver guard chain CLOSED (forty-seventh round).**
2081+
kube-prometheus-stack's `defaultRules` and `windowsMonitoring` reject.
2082+
Every rule file gates on `and (semverCompare ">=1.14.0-0" $v)
2083+
(semverCompare "<9.9.9-9" $v) .Values.defaultRules.create …`, and the
2084+
condition decoder has answered `semverCompare` exactly for a long time —
2085+
but an OPERAND of a short-circuit chain took its execution guard from
2086+
`direct_raw_identity_path` alone, so anything that is not a raw values
2087+
path stood in as `Approximate`, and one approximate conjunct silences
2088+
every capture the later operands make. The policy version and the locals
2089+
bound to a Capabilities-defaulted version now reach the evaluator through
2090+
`EvalEnv`, and `semver_constraint_predicate` is the single model both the
2091+
decoder and the evaluator call, so the claim keeps the override's own
2092+
semantics: a too-old `kubeTargetVersionOverride` short-circuits the chain
2093+
and the deletion stays accepted. This is the general repair the datadog
2094+
`fips` lane needs too — its blocking conjuncts are an `include` equality
2095+
and a `not (or …)`, not a semver comparison.
2096+
2097+
The first measurement LOOSENED 408 probes, which uncovered a
2098+
pre-existing defect the new guard merely reached: a host whose
2099+
object-ness is claimed only under a guard leaves an UNTYPED member
2100+
carrier, `try_merge_compatible` opens with `schema_type(a)?`, and two
2101+
carriers for the SAME path therefore fell through to the union fallback —
2102+
`anyOf[{properties: {alertmanager}}, {type: object, properties: {…37}}]`,
2103+
where a junk value in either half satisfies the other arm.
2104+
`merge_untyped_member_carrier` conjoins them member-wise and keeps the
2105+
weaker (untyped) domain. That repair moves 45 of 55 corpus schemas and
2106+
2,575 battery probes, EVERY one a tightening: declared mapping leaves
2107+
(`*.enabled`, `*.create`, `service.type`) regaining the type their
2108+
values.yaml default declares. They are not helm-abort-justified — a
2109+
truthy string works as a gate — they are the declared-shape typing this
2110+
ledger keeps as policy (see the F80 kyverno scalar-shadow entry),
2111+
restored where the union had silently dropped it. The ci-values sweep
2112+
still rejects the same 4 of 116 real values files.
2113+
2114+
The 18 that remain: three are the TEST-TEMPLATE roots (grafana's and
20782115
fluent-bit's `testFramework`, kyverno's `test`) — helm RENDERS
20792116
`templates/tests/*` and only filters the output afterwards, so switching
20802117
the corpus harness to the shipped `include_tests` default closes them, but
20812118
kyverno's `test.nodeSelector`/`tolerations` then accept 21 values
20822119
`kubeconform -strict` rejects. Those values RENDER — the claim is about the
20832120
rendered sink, not about the guards a test template adds — so they belong
20842121
to the provider-required-leaf entry (F56/F62/F8) below, not here; the
2085-
forty-third round corrected that attribution. The other 17 are NOT this mechanism:
2122+
forty-third round corrected that attribution. The other 15 are NOT this mechanism:
20862123
2 string-parameter calls behind the reverted `upper` catalog entry (harbor
20872124
`logLevel`, oauth2-proxy `httpScheme`), 1 typed call through a `tpl`
20882125
program (airflow `multiNamespaceMode`), 5 YAML-sink text failures (the
@@ -2092,23 +2129,24 @@ strict Kubernetes schema while their valid controls passed.
20922129
reduction from the four `include` results to `$message`'s truthiness is
20932130
what is missing, not the absence case alone), 1 dotted root key (grafana's
20942131
literal `grafana.ini`,
2095-
"can't evaluate field paths in type string"), and 6 nil-pointer sites the
2132+
"can't evaluate field paths in type string"), and 4 nil-pointer sites the
20962133
navigated-host lane still cannot reach: istiod's root rewrite and signoz'
20972134
`global` have their own entries, grafana `route` needs `tpl`-program
2098-
evaluation, datadog `fips` reads `.Values.fips.enabled` directly as the
2099-
third conjunct of an `and` whose earlier operands do not decode — the
2100-
capture exists and the guard chain is what blocks it,
2135+
evaluation, and datadog `fips` reads `.Values.fips.enabled` directly as
2136+
the third conjunct of an `and` whose earlier operands do not decode — the
2137+
capture exists and the guard chain is what blocks it, so it wants the
2138+
same operand-predicate treatment the forty-seventh round gave
2139+
`semverCompare`, extended to an `include` equality and a `not (or …)`.
21012140
traefik `log` needs an `and` operand's `not` decoded
21022141
exactly (attempted and REVERTED in the forty-second round — the exact
21032142
decode turned previously-approximate captures into guarded member-host
21042143
arms, which emptied declared-mapping bases across airflow, argo-cd, cilium
21052144
and datadog; the forty-third round confirmed that emptying is CORRECT for a
21062145
genuinely guarded-only host, so this needs its own adjudication of the
2107-
111 airflow flips rather than a fix to base classification), and
2108-
kube-prometheus-stack's `defaultRules`/`windowsMonitoring` sit behind
2109-
`semverCompare` against `.Capabilities.KubeVersion` — the accepted
2110-
approximate-guard limitation, though a structural evaluation of
2111-
`semverCompare` at the configured target version would close them.
2146+
111 airflow flips rather than a fix to base classification) — and the
2147+
forty-seventh round's carrier repair changes that lane's arithmetic,
2148+
since part of what those flips measured was the union fallback dropping
2149+
sibling typings rather than the base classification itself.
21122150

21132151
- **F56/F62/F8/F80/F98 — provider contracts still lose required leaves
21142152
and nested/helper payloads.** The remaining losses are bounded and have

0 commit comments

Comments
 (0)