feat(retention): load the history gate — Postgres projection + backfill #1996
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/postgres-history-projection"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Loads the retention gate: the sweeper now writes every domain CR to
pipeline_historyin the shared CNPG before it deletes it, and a failed write blocks the delete.Revised after three adversarial reviews returned FIX_REQUIRED with five HIGH defects. Four of the five lived in the backfill CronJob, which is removed from this branch and deferred; the fifth and the upsert key are fixed here.
The key is
(kind, namespace, uid), not(..., name)Both objections to the original upsert were correct, which means its premise was wrong. It keyed on the name because "the name is the LOGICAL identity". But Build and Change names are deterministic per (project, commit) and per (project, PR), so a swept name is recreated by a genuinely DIFFERENT object — and one row for two objects forced two mutually exclusive requirements onto one predicate:
or excluded.activity_at > ...The uid is the identity; the name is a label that repeats. Keyed on the uid, two distinct objects are two rows, neither failure can occur, and
activity_atreverts to an idempotency guard for a genuine re-write of the same object.uidwas already a column. The duplicate-pane worry that argued for the name is a READ-PATH problem and the schema comment now says so.v1is rewritten in place rather than appended to, under a stated exemption: no released image carries--history-database-url, so no process can have created the table anywhere.A test changed on purpose.
postgres_test.gopinned theactivity_atclause with the rationale "this clause keeps the newest record for a NAME". That is the wrong invariant — it is the sentence that made the data loss mandatory, and it also banned the natural fix. The clause survives; its stated job does not.history.Newis no longer fatal at bootpgconnreadssslrootcertEAGERLY atParseConfig, andcmd/main.goturns aNewerror intoos.Exit(1)— so a pod scheduled before trust-manager writes ConfigMapca-bundleCrashLooped the whole CI control plane on every fresh cluster and everymake rebuild.ParseConfigmoves intosession.connect();Newstill validates the URL's text, which needs no file. An unreadable CA now fails the DIAL, so the sweep records nothing and deletes nothing.One DSN, three consumers
No Go test constructed the DSN that ships.
history.DefaultDatabaseURLis now the flag default, the value the wiring guard pins the manifest to by exact equality, and the valueTestShippedDSNIsAcceptedparses — asserting verify-full on what pgx built, not on a substring.Guards anchored, not substring-matched
tests/history-store-contract.shdecided "a real exporter is wired" with a whole-file substring test that a combined mutation walked through. It now starts at the WorkflowProjector'sHistoryfield and follows the identifier to a terminalhistory.Newthat must namehistory.SourceSweep; an unresolvable chain FAILS.runBackfill'sSourceBackfillis asserted positively — flipping it was a live defect nothing caught.DEFERRED: the periodic backfill
operator/config/seanfarm/history-backfill.yamland its kustomization entry are removed. The binary keeps--mode=backfillandinternal/backfill; nothing schedules it, andtests/history-postgres-wiring.shFAILS if any rendered workload passes--mode=backfill, and fails if it scanned no pod templates at all.The follow-up starts from these four, all found by review:
pipelines-operator:v0.1.149— a bare tag on an abandoned release line — so it could never start;pipelines-operator-querythat the dashboard and pipectl depend on;Consequence while deferred: the archive covers what the SWEEPER removes and nothing else. The ~4,800 objects alive today are outside it, and a CR that
make rebuildor an ArgoCD prune deletes leaves no record.docs/DOMAIN-MODEL.mdandDURABLE-STATE-AND-PLUGGABLE-EXECUTION.mdsay so rather than claiming the hourly pass.Also deferred (low severity, review-verified as correct in the live code, missing only a spec):
sweepRetentionswallowing aBeginerror, andstoreHistory's|| sess == nilhalf. Both mutations stay green today.Verification
go build ./... && go vet ./... && gofmt -l .cleango test -count=1 ./...— 17 packages ok, envtest included (internal/controller 37.936s)PIPELINE_GUARDS_STRICT=1 tests/check.sh→guards: 119 invoked — 119 passed, 0 skipped, 0 failed (strict=1)go mod tidy— movespgxinto the direct require block. It also movesprometheus/client_golang, whose mis-marking predates this branch; tidy is deterministic and cannot produce one without the other.Mutation rehearsal: 10/10 caught, each one asserted to have actually applied (
git diff --statafter the edit) before its result was trusted, and the harness itself sanity-checked green on an unmutated tree. The first harness run reported a false GREEN because a pipe intotailswallowed the exit code;pipefailfixed it.TestTheKeyIsTheUIDAndNotTheNameTestConflictTargetMatchesThePrimaryKeyParseConfigmoved back intoNewTestNewToleratesAnUnreadableCABundle+ the fail-closed pairDefaultDatabaseURLweakened tosslmode=requireTestShippedDSNIsAcceptedAND the wiring guardmanager.yamlDSN drifts (connect_timeout10→30)runBackfillwritesSourceSweep(previously caught by NOTHING)mainno-op writer +SourceSweepmoved to the backfill--mode=backfillscan--history-database-urlat allNo retention cap moved. That is P4-d.
2fd299a0be9c551288af9c551288af80fd143b2d80fd143b2d0d72e0acca0d72e0acca1572b83787