Skip to content

feat(dnsprovider): DNS provider v2 — registry-map + Route53 adapter#15

Merged
intel352 merged 12 commits into
masterfrom
feat/dns-provider-v2
May 26, 2026
Merged

feat(dnsprovider): DNS provider v2 — registry-map + Route53 adapter#15
intel352 merged 12 commits into
masterfrom
feat/dns-provider-v2

Conversation

@intel352
Copy link
Copy Markdown
Contributor

Summary

DNS provider v2 — PR 1 of 6. Refactors internal/dnsprovider/NewAdapter switch to an init()-based registry map and adds the Route53 (libdns/route53 v1.6.2) adapter. Subsequent PRs (#2-6) each self-register one provider (GCP/Azure/Namecheap/GoDaddy/Hover) with zero conflicts on adapter.go.

Scope

  • Registry refactor: Register(key, factory) + sync.RWMutex + sorted supportedList() for deterministic error messages.
  • v1 self-registration: digitalocean.go + cloudflare.go get init() { Register(...) } + compile-time var _ dnspolicy.Adapter assertions. No other v1 changes.
  • Route53 adapter: internal/dnsprovider/route53.go + route53_test.go. Iface-typed .provider field for stub injection (cycle-3 architectural fix). Cred keys verified against upstream JSON tags 2026-05-26: region, access_key_id, secret_access_key, session_token, profile. Ambient/IAM-role fallback supported.
  • Docs: docs/providers/README.md skeleton + docs/providers/route53.md.
  • Design + plan + scope-lock: docs/plans/2026-05-26-dns-provider-v2{-design,}.md + .scope-lock (4 adversarial review cycles).

Out of scope

  • AWS assume_role_arn (v3)
  • Provider aliases aws/gcp/azure (v3)
  • GCP inline JSON cred form (v3)
  • Live cloud integration tests (deferred)
  • Other 5 v2 providers (PRs 2-6)

Test plan

  • GOWORK=off go test ./internal/dnsprovider/... — 15/15 PASS
  • GOWORK=off go vet ./... — clean
  • GOWORK=off go build -o /tmp/wfinfra ./cmd/workflow-plugin-infra — built (76.7MB)
  • Plan-scope-check verified locked manifest (sha256 b835b5f69f2c)
  • 4 adversarial cycles (design) + 4 adversarial cycles (plan) PASS

🤖 Generated with Claude Code

intel352 and others added 12 commits May 26, 2026 01:44
…addy, Hover)

Extends internal/dnsprovider/NewAdapter switch from 2 → 8 providers.
v1 contract NewAdapter(provider string, creds map[string]string)
already supports multi-cred; v2 ships per-provider adapters + cred-key
docs. 6 PRs (one per provider). PR 6 (Hover) blocked on
workflow-plugin-hover#25 pkg/hoverclient extraction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cycle 1 had 4 Critical cred-key mismatches against upstream libdns
struct JSON tags (verified via go doc 2026-05-26):

- Route53: access_key/secret_key → access_key_id/secret_access_key.
  AWS assume_role_arn deferred to v3 (requires aws-sdk-go-v2 STS chain).
- GCP: ServiceAccountJSON is a FILE PATH not inline JSON. Project tag is
  gcp_project not project_id. Inline-JSON form deferred to v3.
- Namecheap: api_user → user. sandbox=true → api_endpoint. client_ip
  made strictly required (no discovery fallback on private CI subnets).
  Whole-zone-replace pre-merge verification commit gating for PR 4.
- GoDaddy: two-key api_key+api_secret → single api_token (sso-key
  format). Added 50-domain API-restriction warning.
- Azure: managed-identity path surfaced (all-3-empty triggers MI).

Additional fixes:
- Drop dual-aliasing YAGNI (single canonical libdns-aligned key).
- Per-provider docs files (no merge contention across PRs 1-5).
- Per-provider RRset-semantics table added (Namecheap gating).
- Rollback window assumption stated.
- Engine log scrubbing flagged as open question (A8).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Critical:
- Adapter pseudo-code now shows actual signatures from
  internal/dnspolicy/{reader,writer}.go (UpsertRecord returns
  recordID + err, not just err).
- Namecheap whole-zone risk RESOLVED via upstream-source spike —
  libdns/namecheap.SetRecords already does Get-merge-Set per
  (name,type) internally. No adapter logic needed.

Important:
- Engine log scrubbing VERIFIED (workflow/engine.go:826/:848 use
  module.RedactStepOutput; test coverage at engine_test.go:229/:271).
- ExpandCredsMap semantics CONFIRMED via source (os.ExpandEnv
  returns empty for unset env — ambient fallback paths work).
- A4 weakened to honest framing: GoDaddy/Hover/Namecheap lack per-
  record scoping; gate is only barrier for those.
- GoDaddy pre-merge live test dropped (contradicted user "unit tests
  only" + workspace "Cost discipline" guidance). Operator-side smoke
  optional, not gating.
- Hover sequencing explicitly accepted as deferral; user-intent
  reconciliation note retained.
- Rollback deprecation cycle defined (1 minor warning + CHANGELOG).

Minor:
- Hover cred table notes "custom client; no upstream JSON tag".
- GoDaddy 50-domain deprecation followup added.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adversarial cycle 3 PASS verdict. Author applied 2 follow-up fixes
from cycle-3 reviewer suggestions:

- I-c3-1 (Azure MI source citation): added godoc citation from
  libdns/azure v0.5.0 confirming "Do not set any value to authenticate
  using a managed identity" for all 3 optional fields. Verified via
  go doc 2026-05-26.

- I-c3-2 (DO precedent rationale): added "why SetRecords works"
  column to RRset semantics table. Explains asymmetry: v1 DO chose
  DELETE+APPEND because libdns/digitalocean.SetRecords requires
  record IDs; v2 providers' libdns wrappers are ID-free. Key
  asymmetry callout added below table.

Design now ready for writing-plans handoff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
One task per provider; one PR per provider. Each task: TDD test
writing → libdns dep add → adapter impl → switch wire → docs file →
verify build/test/vet → commit. All adapters mirror v1
digitalocean/cloudflare shape with provider-specific cred-key
mappings verified against upstream JSON tags 2026-05-26.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plan-cycle-1 FAIL findings addressed:

Critical:
- C-1: worktree path corrected (single shared worktree, branch-switch
  between PRs; per-PR sub-worktree removed).
- C-2: adapter.go switch refactored to init()-based registry-map.
  Each provider file self-registers via Register("<key>", factory).
  supportedList() computes from sorted keys. PRs 2-6 now genuinely
  parallel — zero adapter.go conflicts.
- C-3: var _ dnspolicy.Adapter = (*<prov>Adapter)(nil) compile-check
  added to every adapter file (incl. v1 DO + CF as part of PR 1).

Important:
- I-1: real stub-injection round-trip tests added per provider
  (TestX_StubRoundTrip_UpsertTXT) using upsertTXTViaX helper
  mirroring adapter shape.
- I-2: plugin-load deviation made explicit in Out-of-scope +
  Multi-Component Validation rows of guidance fit table.
- I-3: rollback notes expanded with go.mod ordering caveat +
  PR 1 dependency (registry refactor non-revertable in isolation).
- I-5: Task 6 gets Step 0 "go doc hoverclient" gating verification
  before any code.
- I-6: priority drop documented in docs/providers/README.md.

Design backport: registry-map dispatch documented at top of design
doc. Scope Manifest unchanged (6 PRs, 6 tasks) — no scope-lock
amendment needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plan-cycle-2 FAIL findings addressed:

Critical:
- C-1: upsertTXTViaX helpers moved from production source into _test.go
  files (all 5: route53/gcp/azure/namecheap/godaddy). Production
  UpsertTXT now calls a.provider.SetRecords directly. Boundary lock
  fully provided by `var _ <prov>ProviderIface = (*libdns Provider)(nil)`
  compile assertion. Test helper mirrors production algorithm shape
  (renamed TestXUpsertTXTAlgorithmShape) — explicitly documents this.
- C-3: sequencing reconciled. "Parallelism note" → "Sequencing"
  block: PR 1 blocking; PRs 2-6 parallelizable AMONG THEMSELVES
  only post-PR-1-merge; branch-stacking documented as alternative.

Important:
- I-2: misleading test names renamed:
  AmbientCredsOK → OnlyRegionRequiredAtConstruction (Route53)
  ADCMode → OnlyProjectRequiredAtConstruction (GCP)
  ManagedIdentityMode → OnlySubAndRGRequiredAtConstruction (Azure)
  Test bodies now clearly state "libdns lazy-resolves at first API call".
- I-11: priority drop reframed as explicit wontfix (v1 precedent
  match; v3 typed-record dispatch followup). README updated.

No scope manifest change (still 6 PRs / 6 tasks).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plan-cycle-3 FAIL findings addressed:

Critical:
- C3-1: time import already present in plan's test file imports
  (route53_test.go listed "time" — reviewer mis-flagged, fix
  carried forward to other adapter test files which DO need it).
  Note: all 5 v2 adapter test files now have "time" import in their
  block headers per plan.
- C3-2: adapter struct field changed from *libdns<x>.Provider to
  <prov>ProviderIface (5 adapters). Iface def moved from test to
  production file. var _ assertion in prod. Tests construct
  &<prov>Adapter{provider: stub} and call a.UpsertTXT — real
  adapter method runs against stub. Algorithm-shape helpers
  removed entirely. Test renamed TestXAdapter_UpsertTXT_ExercisesAdapter.
  MapsFieldsExact tests updated with type-assert pattern.
- C3-3: Hover Step 0 gated on tag existence via git ls-remote.
  Skeleton explicitly marked TBD-pending-godoc with ⚠ warning.
  NewClient I/O check added.

Important:
- I3-1: PR 1 commit message body expanded (mentions DO+CF
  re-registration, registry refactor).
- I3-2: TestNewAdapter_RegistryIsSorted guards len(parts) < 2.
- I3-3: per-task git checkout uses BASE=$(git rev-parse ...
  feat/dns-provider-v2-route53 || echo master) for branch-stacking
  vs master switch.
- I3-4: Hover Step 0 explicitly checks NewClient is no-I/O.
- I3-5: GoDaddy validation strengthened — SplitN+both-non-empty
  rejects ":", ":foo", "foo:".
- I3-6: TestXAdapter_UpsertRecord_PriorityDroppedForA test per
  adapter (Route53/GCP/Azure/Namecheap/GoDaddy) verifies non-zero
  priority on A record doesn't error + payload type=A preserved.

Scope manifest unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plan cycle 4 adversarial review: PASS (4 Minors, no Crit/Imp,
converged per skill rule).

M4-1 trivial fix applied: route53_test.go imports libdnsr53 for
type-assert in TestNewRoute53Adapter_MapsFieldsExact.

M4-2 (Hover iface deferred until godoc), M4-3 (priority comment
spread), M4-4 (tag regex narrow) — cosmetic, accepted.

Ready for alignment-check + scope-lock + execute.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Refactor adapter.go switch to init()-based registry-map.
- Re-register v1 digitalocean+cloudflare via init() Register calls.
- Add Route53 adapter (libdns/route53 v1.6.2) with iface-typed
  .provider field for test injection.
- Add docs/providers/{README.md,route53.md} skeleton.
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