Skip to content

feat(dns): canonical record/portfolio schema + import-all --format portfolio#798

Merged
intel352 merged 6 commits into
mainfrom
feat/dns-record-portfolio-2026-05-29T2203
May 30, 2026
Merged

feat(dns): canonical record/portfolio schema + import-all --format portfolio#798
intel352 merged 6 commits into
mainfrom
feat/dns-record-portfolio-2026-05-29T2203

Conversation

@intel352
Copy link
Copy Markdown
Contributor

Summary

Adds a canonical, strict DNS record/portfolio schema (dns/record) and an --format portfolio output mode to wfctl infra import-all, so DNS state can be exported as a provider-neutral workflow.dns-portfolio.export.v1 document. Foundation for the dogfooded DNS catalog (gocodealone-dns) + provider-portability.

Part of the DNS-management reconciliation cascade (design + plan + ADRs 0004/0005/0006 in the GoCodeAlone workspace intent repo, docs/plans/2026-05-29-dns-management-reconciled{,-design}.md). This is PR 1 of 3 (workflow / workflow-scenarios / gocodealone-dns).

Changes

  • dns/record/ (new pkg): flat canonical types Portfolio{Schema,Sanitized,Snapshots[]} / Snapshot{ID,Provider,Domain,Authority,Records[],Extra} / Record{Type,Name,Value,TTL,Priority,Port,Weight,Flags,Tag} matching the scenario-88 dns-portfolio.export.v1 fixture (record value key = value).
    • Validate is structural-only (rejects empty type / negative TTL; preserves unknown record types — snapshots are an open set).
    • FromResourceStates canonicalizes imported IaC state → portfolio, aliasing the per-provider value key (data/content/address/value) so DigitalOcean, Hover (content), and Namecheap (address) all map correctly. Preserves zero-valued priority/port/weight/flags (RFC-7505 null-MX, SRV weight=0).
    • Sanitize (for shareable snapshots): net.ParseIP-based public-IP → RFC-5737/3849 example replacement (private/ULA/CGNAT/loopback preserved), DKIM-key redaction (DMARC policy records preserved), _workflow-dns-policy TXT preserved by record name.
  • wfctl infra import-all: new --format state|portfolio (default state, back-compat) + --sanitize. --format portfolio emits the canonical document; the dump is auxiliary (a dump error warns, never fails a successful import).

Test plan

  • GOWORK=off go test ./dns/record/... ./cmd/wfctl/... — green (dns/record 20 tests).
  • GOWORK=off golangci-lint run ./dns/record/ ./cmd/wfctl/ — 0 issues.
  • Two-stage adversarial review (spec + quality); 2 Critical + 3 Important + minors found and fixed (vacuous sanitize test, DMARC false-redaction, zero-value drop, IPv6 ULA/2001:db over-/under-redaction, missing --sanitize cmd test).

Scope Manifest

PR Count: 3 · Tasks: 11 · Status: Locked 2026-05-30T02:54:30Z
This PR = row 1 (Tasks 1-4).

🤖 Generated with Claude Code

intel352 added 6 commits May 29, 2026 23:00
- C-1: TestSanitizeRedactsPublicIPv4/v6 use genuinely-public IPs (8.8.8.8,
  2606:4700:4700::1111) + assert output differs from input (no longer vacuous)
- C-2: looksLikeSecret no longer false-redacts long DMARC (>80 char p=reject);
  DKIM requires v=DKIM1 + a long base64-ish p= blob (hasLongBase64PField)
- I-1: canonicalize stores priority/port/weight/flags when KEY present even if
  zero (RFC-7505 null-MX priority=0, SRV weight/port=0 no longer dropped)
- I-2: isPublicIPv4/v6 rewritten on net.ParseIP + stdlib predicates; excludes
  RFC-6598 CGNAT (100.64/10), IPv6 ULA (fc00::/7), broadcast; redacts routable
  2001:dbc/2001:db0 (no longer over-broad 2001:db prefix); RFC-5737/3849 spared
- I-3: TestDumpPortfolioToFile_WithSanitize asserts Sanitized==true + example IP
- M-2: isWFInfraPolicyTXT guards on record NAME (_workflow-dns-policy) too
@codecov
Copy link
Copy Markdown

codecov Bot commented May 30, 2026

Codecov Report

❌ Patch coverage is 70.53571% with 66 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
dns/record/sanitize.go 76.14% 15 Missing and 11 partials ⚠️
dns/record/canonicalize.go 70.42% 17 Missing and 4 partials ⚠️
cmd/wfctl/infra_import_all.go 51.72% 10 Missing and 4 partials ⚠️
dns/record/record.go 66.66% 3 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:301: parsing iteration count: invalid syntax
baseline-bench.txt:331430: parsing iteration count: invalid syntax
baseline-bench.txt:683649: parsing iteration count: invalid syntax
baseline-bench.txt:999001: parsing iteration count: invalid syntax
baseline-bench.txt:1291802: parsing iteration count: invalid syntax
baseline-bench.txt:1602729: parsing iteration count: invalid syntax
benchmark-results.txt:303: parsing iteration count: invalid syntax
benchmark-results.txt:308200: parsing iteration count: invalid syntax
benchmark-results.txt:587336: parsing iteration count: invalid syntax
benchmark-results.txt:906720: parsing iteration count: invalid syntax
benchmark-results.txt:1232206: parsing iteration count: invalid syntax
benchmark-results.txt:1523534: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │       benchmark-results.txt        │
                            │       sec/op       │    sec/op     vs base              │
InterpreterCreation-4               9.606m ± 68%   9.528m ± 67%       ~ (p=0.818 n=6)
ComponentLoad-4                     3.557m ±  9%   3.569m ±  7%       ~ (p=0.485 n=6)
ComponentExecute-4                  1.924µ ±  1%   1.944µ ±  1%  +1.01% (p=0.037 n=6)
PoolContention/workers-1-4          1.126µ ±  2%   1.095µ ±  2%  -2.71% (p=0.015 n=6)
PoolContention/workers-2-4          1.100µ ±  3%   1.091µ ±  2%       ~ (p=0.459 n=6)
PoolContention/workers-4-4          1.072µ ±  1%   1.089µ ±  1%  +1.54% (p=0.002 n=6)
PoolContention/workers-8-4          1.074µ ±  1%   1.097µ ±  1%  +2.09% (p=0.002 n=6)
PoolContention/workers-16-4         1.075µ ±  1%   1.101µ ±  4%  +2.47% (p=0.002 n=6)
ComponentLifecycle-4                3.577m ±  1%   3.585m ±  2%       ~ (p=0.240 n=6)
SourceValidation-4                  2.306µ ±  1%   2.329µ ±  0%  +1.00% (p=0.004 n=6)
RegistryConcurrent-4                778.4n ±  7%   823.6n ±  3%  +5.81% (p=0.041 n=6)
LoaderLoadFromString-4              3.603m ±  1%   3.605m ±  0%       ~ (p=0.699 n=6)
geomean                             19.08µ         19.24µ        +0.83%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.937 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.366 n=6)
ComponentExecute-4                  1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4         1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                2.183Mi ± 0%   2.183Mi ± 0%       ~ (p=0.937 n=6)
SourceValidation-4                  1.984Ki ± 0%   1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                1.133Ki ± 0%   1.133Ki ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4              2.182Mi ± 0%   2.182Mi ± 0%       ~ (p=0.974 n=6)
geomean                             15.25Ki        15.25Ki       -0.00%
¹ all samples are equal

                            │ baseline-bench.txt │        benchmark-results.txt        │
                            │     allocs/op      │  allocs/op   vs base                │
InterpreterCreation-4                15.68k ± 0%   15.68k ± 0%       ~ (p=1.000 n=6)
ComponentLoad-4                      18.02k ± 0%   18.02k ± 0%       ~ (p=1.000 n=6)
ComponentExecute-4                    25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4           25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                 18.07k ± 0%   18.07k ± 0%       ~ (p=1.000 n=6) ¹
SourceValidation-4                    32.00 ± 0%    32.00 ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                  2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4               18.06k ± 0%   18.06k ± 0%       ~ (p=1.000 n=6) ¹
geomean                               183.3         183.3       +0.00%
¹ all samples are equal

pkg: github.com/GoCodeAlone/workflow/middleware
                                  │ baseline-bench.txt │       benchmark-results.txt       │
                                  │       sec/op       │   sec/op     vs base              │
CircuitBreakerDetection-4                 287.1n ± 11%   285.5n ± 5%       ~ (p=0.197 n=6)
CircuitBreakerExecution_Success-4         21.54n ±  1%   21.54n ± 0%       ~ (p=0.732 n=6)
CircuitBreakerExecution_Failure-4         66.28n ±  0%   66.23n ± 1%       ~ (p=0.420 n=6)
geomean                                   74.29n         74.13n       -0.21%

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │        B/op        │    B/op     vs base                │
CircuitBreakerDetection-4                 144.0 ± 0%     144.0 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │     allocs/op      │ allocs/op   vs base                │
CircuitBreakerDetection-4                 1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │       sec/op       │    sec/op     vs base              │
IaCStateBackend_InProcess-4              312.6n ± 28%   315.0n ±  6%       ~ (p=0.485 n=6)
IaCStateBackend_GRPC-4                   9.496m ± 13%   9.804m ± 18%       ~ (p=0.589 n=6)
JQTransform_Simple-4                     670.5n ± 29%   690.3n ± 30%       ~ (p=0.485 n=6)
JQTransform_ObjectConstruction-4         1.412µ ±  0%   1.463µ ±  1%  +3.58% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.240µ ±  0%   3.357µ ±  1%  +3.63% (p=0.002 n=6)
JQTransform_Complex-4                    37.81µ ±  1%   38.56µ ±  1%  +1.99% (p=0.002 n=6)
JQTransform_Throughput-4                 1.722µ ±  1%   1.786µ ±  0%  +3.72% (p=0.002 n=6)
SSEPublishDelivery-4                     64.75n ±  3%   64.72n ±  0%       ~ (p=0.784 n=6)
geomean                                  3.741µ         3.834µ        +2.47%

                                 │ baseline-bench.txt │        benchmark-results.txt         │
                                 │        B/op        │     B/op      vs base                │
IaCStateBackend_InProcess-4              416.0 ± 0%       416.0 ± 0%       ~ (p=1.000 n=6) ¹
IaCStateBackend_GRPC-4                 5.865Mi ± 6%     5.929Mi ± 6%       ~ (p=1.000 n=6)
JQTransform_Simple-4                   1.273Ki ± 0%     1.273Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4       1.773Ki ± 0%     1.773Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4              2.625Ki ± 0%     2.625Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                  16.31Ki ± 0%     16.31Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4               1.984Ki ± 0%     1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                 +0.14%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │        benchmark-results.txt        │
                                 │     allocs/op      │  allocs/op   vs base                │
IaCStateBackend_InProcess-4              2.000 ± 0%      2.000 ± 0%       ~ (p=1.000 n=6) ¹
IaCStateBackend_GRPC-4                  6.830k ± 0%     6.837k ± 1%       ~ (p=0.058 n=6)
JQTransform_Simple-4                     10.00 ± 0%      10.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4         15.00 ± 0%      15.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4                30.00 ± 0%      30.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                    328.0 ± 0%      328.0 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4                 17.00 ± 0%      17.00 ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%      0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                +0.01%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
                                    │ baseline-bench.txt │       benchmark-results.txt       │
                                    │       sec/op       │   sec/op     vs base              │
SchemaValidation_Simple-4                   1.095µ ± 21%   1.113µ ± 3%       ~ (p=0.420 n=6)
SchemaValidation_AllFields-4                1.650µ ±  3%   1.675µ ± 3%       ~ (p=0.513 n=6)
SchemaValidation_FormatValidation-4         1.588µ ±  1%   1.582µ ± 1%       ~ (p=0.784 n=6)
SchemaValidation_ManySchemas-4              1.795µ ±  3%   1.815µ ± 3%       ~ (p=0.394 n=6)
geomean                                     1.506µ         1.521µ       +0.97%

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │        B/op        │    B/op     vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │     allocs/op      │ allocs/op   vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │       sec/op       │    sec/op     vs base               │
EventStoreAppend_InMemory-4                1.179µ ± 12%   1.139µ ±  9%        ~ (p=0.288 n=6)
EventStoreAppend_SQLite-4                  1.340m ±  8%   1.387m ±  4%        ~ (p=0.180 n=6)
GetTimeline_InMemory/events-10-4           13.84µ ±  5%   14.36µ ±  3%        ~ (p=0.290 n=6)
GetTimeline_InMemory/events-50-4           79.24µ ±  2%   80.82µ ± 10%        ~ (p=0.102 n=6)
GetTimeline_InMemory/events-100-4          157.1µ ± 20%   124.2µ ± 29%  -20.95% (p=0.026 n=6)
GetTimeline_InMemory/events-500-4          629.8µ ±  4%   635.2µ ±  1%        ~ (p=0.394 n=6)
GetTimeline_InMemory/events-1000-4         1.286m ±  2%   1.306m ±  2%        ~ (p=0.093 n=6)
GetTimeline_SQLite/events-10-4             104.3µ ±  0%   107.0µ ±  1%   +2.57% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             244.6µ ±  1%   249.7µ ±  0%   +2.07% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            415.1µ ±  1%   424.9µ ±  2%   +2.35% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.782m ±  1%   1.804m ±  1%   +1.20% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.468m ±  1%   3.509m ±  2%   +1.16% (p=0.002 n=6)
geomean                                    222.1µ         221.0µ         -0.52%

                                   │ baseline-bench.txt │         benchmark-results.txt         │
                                   │        B/op        │     B/op       vs base                │
EventStoreAppend_InMemory-4                  797.5 ± 7%     782.0 ± 11%       ~ (p=0.937 n=6)
EventStoreAppend_SQLite-4                  1.985Ki ± 3%   1.985Ki ±  1%       ~ (p=0.714 n=6)
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%   7.953Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%   46.62Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%   94.48Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%   472.8Ki ±  0%       ~ (p=0.545 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ±  0%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%   16.74Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%   87.14Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%   175.4Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%   846.1Ki ±  0%       ~ (p=0.636 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ±  0%       ~ (p=1.000 n=6)
geomean                                    67.40Ki        67.29Ki        -0.16%
¹ all samples are equal

                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │     allocs/op      │  allocs/op   vs base                │
EventStoreAppend_InMemory-4                  7.000 ± 0%    7.000 ± 0%       ~ (p=1.000 n=6) ¹
EventStoreAppend_SQLite-4                    53.00 ± 0%    53.00 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-10-4             125.0 ± 0%    125.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4             653.0 ± 0%    653.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4           1.306k ± 0%   1.306k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4           6.514k ± 0%   6.514k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-1000-4          13.02k ± 0%   13.02k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-10-4               382.0 ± 0%    382.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4              1.852k ± 0%   1.852k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4             3.681k ± 0%   3.681k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4             18.54k ± 0%   18.54k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-1000-4            37.29k ± 0%   37.29k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                     1.162k        1.162k       +0.00%
¹ all samples are equal

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

@intel352 intel352 merged commit 4c4e04a into main May 30, 2026
24 checks passed
@intel352 intel352 deleted the feat/dns-record-portfolio-2026-05-29T2203 branch May 30, 2026 03:49
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