refactor(pgo)!: remove versionPolicy and settle the rest of item 8 - #12
Merged
Conversation
versionPolicy admitted one value, strict, and nothing read it: a Collection resolves one version across the Pods of a round and refuses a round that spans two, whatever the policy holds. It was the only key under pgo.defaults.target, so the whole block goes, refused by name the way the removed pprof lists are, and a file carried forward reads why the key went rather than "unknown key". not_permitted retires with it: target.versionPolicy was its only producer. The removal reaches a client. The field leaves the effective policy GET /pgo publishes and the table profgate pgo policy prints. A write that still carries it is refused as an unknown field, on the policy route and the create route alike, which share one body type. The policy hash an Idempotency-Key stands for is taken over the policy's own encoding, so a key minted before the upgrade replays as 409 idempotency_mismatch for as long as its receipt lives. A fresh key is the answer.
The NATS preflight probes are the only mechanism that verifies write permission, per bucket and per verb, and watch delivery. The bucket-contract check reads stream information and writes nothing, the watches the PGO runtime opens afterwards prove subscribe alone, and the sweeper's artifact listing only warns when it fails. Letting the first real operation fail instead has no bound: every Service starts disabled and the scheduler passes only over Services with a stored override, so an enabled gateway with no override never writes to NATS at all. A denied probe today is a non-zero exit that stalls the rollout with the old Pod still serving. Without it the process would report ready, join the Service, and fail later as a 5xx. The removals list's preamble and its Spec line are corrected in the same pass. Removing versionPolicy did change what a client sees and did revise the spec, so neither line was true any more.
The identifier's packer, the cookie's length prefixes, and the harness revisit all landed in pull request #11, and the item still read as if none of them had. The identifier bullet asked for the grammar kept and the packer deleted. The grammar is unchanged and the five-bit windows packed across bytes are gone: the alphabet is exactly thirty-two characters, so one random byte masked to its low five bits indexes it, and what is left is an index rather than a packer.
`helm template` golden files cannot carry what `deploy/chart_test.go` asserts. 110 of its cases assert that a render fails and read the reason out of stderr, which leaves no stdout for a golden file to record. Another 178 lines assert that the render agrees with the kustomize base, the Prometheus recorder, or the binary's own env struct tags, where a golden records one side and the agreement is the assertion. About 110 more compare two renders to each other. The pod template also carries a sha256 of the rendered ConfigMap, so any golden holding it would redden on every default change with a hex diff. The premise was wrong too: 3,177 test lines against 1,887 of chart is 1.68 times. What replaces the bullet is the one repetition that is real: the type checks in TestChartMountPartsAreValidated.
Twelve rows of TestChartMountPartsAreValidated proved the same helper: profgate.mountPartString refuses a values key that is not a string. Five of them document a trap of their own and stay as they are — a boolean tls.existingSecret renders a secretName no string field decodes, a falsy tls.mountPath would be folded to "" by a `default` ahead of the check, a falsy nats.existingSecret would be reported as empty by the emptiness check, a falsy nats.credsFile selects the no-credentials path, and a numeric tls.certKey pairs with the string-typed key that must still render. The other seven differ only in which key the message names, so they become one table of key, value, and type, after the shape of TestChartBooleanTogglesAreValidated. Deleting the kind check from the helper still reddens all twelve.
Deleting the assertions over the rendered NOTES.txt was considered, on the grounds that two real defects in those notes shipped and neither was caught by them. Both escapes are real and both are the same kind: an instruction that reads correctly and does not work. What the assertions hold is the other kind. The notes are a page of conditionals over four authentication modes, TLS, and PGO, and each mode is held to the instructions it should print and to the ones it must not. A command an operator's configuration does not admit is a defect no render error reports. The record says plainly what is not proven, and names the check that would have caught the first escape without claiming it is written. It sets no revisit trigger: the one in the harness record passed unnoticed until someone went looking.
arloliu
force-pushed
the
refactor/version-policy
branch
from
August 30, 2026 08:49
c4b9ec4 to
2baca95
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roadmap item 8's remaining work. After this, all six bullets have an outcome: three shipped in #11,
one is done here, and two are withdrawn on evidence rather than left open.
pgo.versionPolicyis removed — this is breakingNothing branched on the value.
ResolveVersion(internal/pgo/runtime.go) enforces version pinningunconditionally from
Target.Version, the optional pin, and never readVersionPolicy. So no samplingor merge behavior changes. What goes is a one-valued enum, its validation, and the
not_permittedcodewhose sole producer it was — that code retires from the spec, from
openapi.json, and from thevocabulary the document check reads.
Two things a caller sees, both in the changelog under BREAKING:
decodeBodycallsDisallowUnknownFields, so a client that round-trips the effective policy back asan override now gets
400 invalid_parameterwith anunknown_fielddetail naming/target/versionPolicy, onPUT .../pgoandPOST .../collectionsalike. Observed, not inferred.SnapshotHashcovers the marshalledPolicy, so every hash moved. AnIdempotency-Keyminted beforethe upgrade replays as
409 idempotency_mismatchfor as long as its receipt lives — a week bydefault. This was accepted rather than mitigated: the answer is arguably correct, since the key really
does stand for a differently-represented policy, and a fresh key is the client's move.
An existing configuration file setting the key is refused by name through the node walk
refuseRemovedPprofKeysalready established, rather than failing as a bare unknown key. The wholepgo.defaults.targetmapping goes, sinceversionPolicywas its only field.Item 8 claimed no bullet needed a spec revision and that none changed client-visible behavior. Both were
wrong for this bullet, and the item now says so.
Two bullets withdrawn, with the evidence
The startup probes stay. They are the only thing that verifies NATS write permission, per bucket
and per verb, and watch delivery. The bucket-contract check reads stream information and writes nothing;
the runtime's watches prove subscribe, never publish; the sweeper's artifact listing warns and carries
on. "The first real operation" has no bound either:
DefaultPolicysetsenabledfalse for everyService and the scheduler passes only over Services carrying a stored override, so a gateway with
pgo.enabled: trueand no override anywhere never writes to NATS at all. Today a denied probe is anon-zero exit naming the bucket and the operation, so the new Pod crash-loops and the rollout stalls with
the old Pod still serving; without them the process would connect, pass the contract check, answer
/readyz200, join the Service, and fail later.internal/natskv/andinternal/pgo/sweeper.goareuntouched.
Golden files for the chart are withdrawn. About 110 of the chart tests are refusals — they assert
helm templatefails and grep stderr, so there is no output to record. Another group asserts agreementbetween the render and something else in the repo, where a golden records one side and both sides moving
together would pass. Another asserts a relationship between two renders. And
deployment.yamlputs a sha256 over the whole rendered ConfigMap into the pod template, so any goldenholding it reddens on every
values.yamlchange with a diff that reads as one hex string becominganother. The roadmap's "larger than the chart it tests" is 1.68x, not the multiple it implies, and the
denominator is mostly a validation program with 42
failpaths.It is replaced by narrower work, done here:
TestChartMountPartsAreValidatedproved the same helpertwelve times, once per values key. Five rows document a trap nobody else does and stay; seven are the
same proof with a different key name and are now a table, after the model
TestChartBooleanTogglesAreValidatedalready set.A decision record instead of a deletion
docs/decisions/chart-notes-assertions.mdrecords why the assertions over the renderedNOTES.txtstay.Deleting them was proposed because two real defects in those notes shipped uncaught. Both escapes are the
same kind — an instruction that reads correctly and does not work — and what the assertions hold is the
other kind: each authentication mode printing the instructions belonging to it and none of the others.
The record names what is not proven, and names the unwritten check that would have caught the first
escape, without claiming it is written.
Testing
mise run lint && mise run test && mise run checkclean before every commit.mise run test:e2eon thecurrentlane:ok … 681.226s, all 27 scenarios.internal/pgoandinternal/clientboth changed, and both require it.The collapse was proven not to lose coverage by sabotage: with the
kindIs "string"check deleted fromprofgate.mountPartString, twelve subtests go red before the change and twelve after — no looped rowstayed green. One failed differently and is the strongest evidence:
--set nats.credsFile=falserenderedcleanly, showing the kind check is the only barrier for that key.