Skip to content

fix: bind DNS provider state to RecordSet incarnations - #8

Merged
reoring merged 1 commit into
mainfrom
work/20260909/dns-provider-state-continuity-20260909
Sep 10, 2026
Merged

reoring merged 1 commit into
mainfrom
work/20260909/dns-provider-state-continuity-20260909

Conversation

@reoring

@reoring reoring commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Scope

Source-only correction and review handoff. No merge, release, publication, live adoption, DNS write, ownership/status/finalizer repair, or WLC intervention is requested or performed by this work. The previously authorized dns-api 0.2.7 → 0.2.9 Helm upgrade completed separately.

Problem

Deterministic controller reproductions expose stale whole-array provider-state loss, false deletion completion borrowed from a prior Programmed=True, and same-name RecordSet recreation consuming predecessor completion/provider ownership. These are source-level defects, not proof of the exact historical mutation that caused the live DNS incident.

Changes

  • Carry actual RecordSet UID through ZoneUnit spec, provider receipts, Route53 pending affected records, core projection/finalization, and UI ownership interpretation. Keep fields optional for legacy readability, never as an authorization fallback.
  • Rebase current-UID status deltas without replaying stale sibling/provider state; use optimistic resource-version preconditions.
  • Persist deletion completion only on explicit provider-confirmed cleanup. Block foreign/unbound ownership; do not automatically adopt matching Cloudflare IDs.
  • Retain same-UID ownership across ordinary generation updates while requiring current row and individual-condition generations for readiness. Reobserve Route53 after INSYNC before attesting a newer generation.
  • Fence actual provider mutation dispatch, including DELETE and Route53 fallback batches, against current UID/spec/policy plus an API-server resourceVersion CAS. Ordinary no-op observations do not issue mutation-fence patches.
  • Regenerate/sync ZoneUnit CRDs, document the cutover, and split oversized controller/test/UI files by responsibility.

Verification

  • Full focused Core, Route53, and Cloudflare ZoneUnit controller suites passed; affected DNS/Route53 API tests and focused go vet passed.
  • New behavioral regressions failed against immutable original production cb8b8ddc2e93938b92734d1306eaa2673c8738d5 and passed on the candidate. The original-source harness retained current API/test fixtures; Cloudflare required only a test decoder adapter for its old private helper signature and omission of the new test-context field.
  • Real task-owned Kubernetes API server 1.36.0: current UID and legacy omitted UID admitted; explicit empty spec/status UID rejected at the UID field; current no-op CAS succeeded without changing resourceVersion; stale CAS returned Conflict. Server stopped normally. No external DNS API was used.
  • Pinned controller-gen v0.17.2 deepcopy/CRD/RBAC generation, chart sync/lint/render, UI typecheck, and actual local-browser current/predecessor/missing-UID display checks passed.
  • Two independent read-only source reviews passed after their authority/concurrency findings were corrected.
  • git diff --check passed.
  • Scoped golangci-lint remains nonzero: 17 existing findings versus 18 on the exact original source, with zero added diagnostic instances. No suppressions or blanket lint-pass claim.
  • No repo-wide/Kest/cloud-provider live certification or dual-architecture release build is claimed. PR CI is separate.

Migration / operational boundary

This is not a transparent in-place adoption of legacy UID-less receipts. Existing external records without current UID-bound authority remain conflicts. Updated CRDs must precede the corresponding controllers; Helm does not upgrade existing CRDs from a chart's crds/ directory. Qualify that coordinated rollout and any explicit per-claim adoption separately. Do not manufacture authority by patching UID/status/finalizers or enabling zone-wide adoption as a fallback. This source change does not itself restore already-lost live provider ownership or complete the separately owned WLC/HA acceptance.

@reoring
reoring merged commit 6ec8d75 into main Sep 10, 2026
2 checks passed
reoring added a commit that referenced this pull request Sep 12, 2026
)

After #8 keyed the ZoneUnit ledger by recordSetUID, UID-less entries written
before the upgrade were ignored entirely, so every record the provider had
created for a still-existing claim became Programmed=False:ProviderConflict
after the controller roll (observed on pmc-next 2026-09-12: 30/34 RecordSets).
Deleting such a claim was stuck for the same reason, and the ledger never
dropped entries whose spec item was gone.

Route 53 and Cloudflare now treat a UID-less entry as a pre-upgrade receipt
and bind it to the current claim incarnation in place when the receipt names
exactly the desired record identity and the live record already equals the
desired state; a differing live record stays a conflict. The same evidence
authorizes deletion for a deleting claim. Mismatched non-empty UIDs still
never transfer.

Providers also prune orphaned ledger entries (no spec item) once they are
spent: deletion completed, the named record is absent (Route 53), or no IDs
are recorded (Cloudflare). Orphans that still name a live record are kept as
the only evidence of a leaked record and because core can drop a spec item
transiently.
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