Skip to content

feat(dyndns): IP-detect + DNS-update daemon (provider-agnostic)#736

Closed
intel352 wants to merge 1 commit into
mainfrom
feat/dyndns-1779276297
Closed

feat(dyndns): IP-detect + DNS-update daemon (provider-agnostic)#736
intel352 wants to merge 1 commit into
mainfrom
feat/dyndns-1779276297

Conversation

@intel352
Copy link
Copy Markdown
Contributor

Per workflow#735 SPEC T14..T16. Pure-Go IP-quorum + diff + Update callback. 12 tests; ready to wire to any DNS driver.

Per docs/plans/2026-05-20-dns-providers.md T14..T16.

dyndns/dyndns.go (260 lines):
- IPDetector interface: Detect + Name.
- HTTPDetector default impl; DefaultDetectors() returns the three
  canonical sources (icanhazip / ifconfig.me / ipify.org).
- Updater callback — provider-agnostic; caller wires DO/Namecheap/
  Hover via the IaC ResourceDriver UpdateRecord RPC.
- Config: PollInterval (≥30s), QuorumSize (default ceil(N/2)),
  MaxBackoff (default 1h), Now + Sleep injectable for tests.
- Daemon.Tick(): detect-quorum → diff vs Current → Update on
  change. Concurrent-safe via mu.
- Daemon.Run(): blocking loop until ctx cancel, with exponential
  backoff + ±10% jitter on consecutive failures.
- detectQuorum: fan-out goroutines, tolerate per-detector errors,
  return first IP that QuorumSize detectors agree on.

12 unit tests:
- Update fires on IP change, noop on unchanged.
- Quorum: majority requirement, 2-of-3 success, single-detector-err
  tolerated.
- Failure backoff escalates between consecutive ticks.
- New() rejects nil Updater + <30s PollInterval.
- Updater errors surface + leave Current() unset.
- Run() exits on context cancel.
- Concurrent Tick() calls are safe (10 goroutines).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 20, 2026 11:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new dyndns package that implements a provider-agnostic dynamic DNS daemon: it periodically detects the host’s public IP via a detector quorum, diffs against the last confirmed value, and calls a supplied update callback only when the IP changes.

Changes:

  • Added dyndns.Daemon with New, Run, and Tick, including quorum-based IP detection and exponential backoff with jitter.
  • Added HTTPDetector and DefaultDetectors() for public IP discovery via simple HTTP endpoints.
  • Added unit tests covering core daemon behaviors (update-on-change, quorum, backoff, cancellation, and basic concurrency safety).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
dyndns/dyndns.go New dyndns daemon implementation (quorum detect → diff → update) plus HTTP-based detectors and defaults.
dyndns/dyndns_test.go Unit tests for daemon tick/run behaviors, quorum logic, and backoff/cancellation.

Comment thread dyndns/dyndns.go
Comment on lines +64 to +66

// Sleep is injectable for tests. Defaults to time.Sleep.
Sleep func(time.Duration)
Comment thread dyndns/dyndns.go
Comment on lines +134 to +154
// Tick executes one detect/diff/update cycle. Tests call this
// directly to bypass the timer; Run() invokes it in a loop.
func (d *Daemon) Tick(ctx context.Context) error {
ip, err := d.detectQuorum(ctx)
if err != nil {
d.recordFailure()
return err
}

d.mu.Lock()
currentSame := d.current != nil && d.current.Equal(ip)
d.mu.Unlock()
if currentSame {
d.recordSuccess()
return nil
}

if err := d.cfg.Update(ctx, ip); err != nil {
d.recordFailure()
return fmt.Errorf("dyndns: update IP %s: %w", ip, err)
}
Comment thread dyndns/dyndns.go
Comment on lines +192 to +193
ip, err := det.Detect(ctx)
results <- result{ip: ip, name: det.Name(), err: err}
Comment thread dyndns/dyndns.go
Comment on lines +215 to +218
if winner == "" {
return nil, fmt.Errorf("dyndns: no IP reached quorum (%d/%d); errors: %s", d.cfg.QuorumSize, len(d.cfg.Detectors), strings.Join(errs, "; "))
}
return net.ParseIP(winner), nil
Comment thread dyndns/dyndns.go
Comment on lines +287 to +293
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
body, _ := io.ReadAll(io.LimitReader(resp.Body, 256))
s := strings.TrimSpace(string(body))
ip := net.ParseIP(s)
if ip == nil {
return nil, fmt.Errorf("not an IP: %q", s)
Comment thread dyndns/dyndns.go
Comment on lines +143 to +157
d.mu.Lock()
currentSame := d.current != nil && d.current.Equal(ip)
d.mu.Unlock()
if currentSame {
d.recordSuccess()
return nil
}

if err := d.cfg.Update(ctx, ip); err != nil {
d.recordFailure()
return fmt.Errorf("dyndns: update IP %s: %w", ip, err)
}

d.mu.Lock()
d.current = ip
Comment thread dyndns/dyndns.go
Comment on lines +264 to +296
// HTTPDetector queries a simple "what's my IP" HTTP endpoint.
type HTTPDetector struct {
URL string
Label string
HTTP *http.Client
}

// Detect implements IPDetector.
func (h HTTPDetector) Detect(ctx context.Context) (net.IP, error) {
client := h.HTTP
if client == nil {
client = &http.Client{Timeout: 10 * time.Second}
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, h.URL, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
body, _ := io.ReadAll(io.LimitReader(resp.Body, 256))
s := strings.TrimSpace(string(body))
ip := net.ParseIP(s)
if ip == nil {
return nil, fmt.Errorf("not an IP: %q", s)
}
return ip, nil
}
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 71.23288% with 42 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
dyndns/dyndns.go 71.23% 35 Missing and 7 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:274: parsing iteration count: invalid syntax
baseline-bench.txt:307961: parsing iteration count: invalid syntax
baseline-bench.txt:630288: parsing iteration count: invalid syntax
baseline-bench.txt:906328: parsing iteration count: invalid syntax
baseline-bench.txt:1177574: parsing iteration count: invalid syntax
baseline-bench.txt:1472757: parsing iteration count: invalid syntax
benchmark-results.txt:276: parsing iteration count: invalid syntax
benchmark-results.txt:324943: parsing iteration count: invalid syntax
benchmark-results.txt:624908: parsing iteration count: invalid syntax
benchmark-results.txt:960495: parsing iteration count: invalid syntax
benchmark-results.txt:1296511: parsing iteration count: invalid syntax
benchmark-results.txt:1639946: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 9V74 80-Core Processor                
                            │ baseline-bench.txt │       benchmark-results.txt        │
                            │       sec/op       │    sec/op     vs base              │
InterpreterCreation-4               6.522m ± 57%   7.578m ± 60%       ~ (p=0.699 n=6)
ComponentLoad-4                     3.532m ±  3%   3.576m ±  8%       ~ (p=0.180 n=6)
ComponentExecute-4                  1.836µ ±  2%   1.856µ ±  3%       ~ (p=0.058 n=6)
PoolContention/workers-1-4          1.037µ ±  4%   1.030µ ±  4%       ~ (p=0.333 n=6)
PoolContention/workers-2-4          1.044µ ±  3%   1.026µ ±  4%       ~ (p=0.457 n=6)
PoolContention/workers-4-4          1.024µ ±  1%   1.040µ ±  2%       ~ (p=0.165 n=6)
PoolContention/workers-8-4          1.024µ ±  2%   1.042µ ±  1%  +1.76% (p=0.045 n=6)
PoolContention/workers-16-4         1.019µ ±  1%   1.044µ ±  7%       ~ (p=0.065 n=6)
ComponentLifecycle-4                3.617m ±  2%   3.717m ±  3%  +2.76% (p=0.015 n=6)
SourceValidation-4                  2.106µ ±  1%   2.201µ ±  1%  +4.51% (p=0.002 n=6)
RegistryConcurrent-4                767.7n ±  6%   815.9n ±  5%  +6.29% (p=0.002 n=6)
LoaderLoadFromString-4              3.633m ±  2%   3.688m ±  3%       ~ (p=0.180 n=6)
geomean                             17.84µ         18.37µ        +3.00%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=1.000 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.418 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=1.000 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.937 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                  300.4n ± 5%   298.4n ± 4%       ~ (p=0.937 n=6)
CircuitBreakerExecution_Success-4          22.67n ± 0%   22.67n ± 1%       ~ (p=0.669 n=6)
CircuitBreakerExecution_Failure-4          70.95n ± 0%   70.95n ± 0%       ~ (p=0.998 n=6)
geomean                                    78.46n        78.30n       -0.22%

                                  │ 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              295.6n ± 24%   300.0n ± 30%        ~ (p=0.240 n=6)
IaCStateBackend_GRPC-4                   10.25m ±  1%   10.39m ± 11%   +1.37% (p=0.002 n=6)
JQTransform_Simple-4                     651.9n ± 31%   665.2n ± 44%        ~ (p=0.485 n=6)
JQTransform_ObjectConstruction-4         1.427µ ±  1%   1.571µ ±  2%  +10.09% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.505µ ±  1%   3.743µ ±  6%   +6.81% (p=0.002 n=6)
JQTransform_Complex-4                    42.40µ ±  1%   45.16µ ±  4%   +6.51% (p=0.002 n=6)
JQTransform_Throughput-4                 1.784µ ±  2%   1.944µ ±  4%   +8.97% (p=0.002 n=6)
SSEPublishDelivery-4                     64.28n ±  1%   64.11n ±  2%        ~ (p=0.937 n=6)
geomean                                  3.847µ         4.023µ         +4.56%

                                 │ 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.750Mi ± 10%     5.788Mi ± 11%       ~ (p=0.589 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.22Ki ±  0%     16.22Ki ±  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.08%               ²
¹ 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.858k ± 0%     6.865k ± 0%       ~ (p=0.264 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                    324.0 ± 0%      324.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.077µ ± 14%   1.082µ ± 2%       ~ (p=0.675 n=6)
SchemaValidation_AllFields-4                1.641µ ±  4%   1.661µ ± 5%       ~ (p=0.240 n=6)
SchemaValidation_FormatValidation-4         1.575µ ±  1%   1.573µ ± 1%       ~ (p=0.732 n=6)
SchemaValidation_ManySchemas-4              1.601µ ±  2%   1.613µ ± 3%       ~ (p=0.974 n=6)
geomean                                     1.453µ         1.461µ       +0.57%

                                    │ 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                993.5n ±  8%   1075.0n ± 23%        ~ (p=0.065 n=6)
EventStoreAppend_SQLite-4                  1.057m ±  5%    1.052m ±  2%        ~ (p=0.699 n=6)
GetTimeline_InMemory/events-10-4           12.88µ ±  3%    12.62µ ±  3%        ~ (p=0.132 n=6)
GetTimeline_InMemory/events-50-4           58.86µ ± 23%    71.18µ ±  3%        ~ (p=0.093 n=6)
GetTimeline_InMemory/events-100-4          118.7µ ±  1%    113.9µ ±  2%   -4.03% (p=0.002 n=6)
GetTimeline_InMemory/events-500-4          609.8µ ±  2%    577.1µ ±  1%   -5.36% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.232m ±  3%    1.180m ±  2%   -4.24% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             97.45µ ±  1%    86.72µ ±  1%  -11.01% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             244.5µ ±  1%    226.7µ ±  1%   -7.28% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            408.4µ ±  1%    393.8µ ±  2%   -3.58% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.751m ±  1%    1.710m ±  1%   -2.36% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.455m ±  1%    3.347m ±  2%   -3.14% (p=0.002 n=6)
geomean                                    200.3µ          197.4µ         -1.49%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  771.5 ± 9%     746.5 ± 7%       ~ (p=0.485 n=6)
EventStoreAppend_SQLite-4                  1.982Ki ± 2%   1.984Ki ± 2%       ~ (p=0.249 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=1.000 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%       ~ (p=0.636 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%  -0.00% (p=0.022 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%  +0.00% (p=0.013 n=6)
geomean                                    67.21Ki        67.03Ki       -0.27%
¹ 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
Copy link
Copy Markdown
Contributor Author

Superseded by the consolidated PR with lint fixes.

@intel352 intel352 closed this May 20, 2026
intel352 added a commit that referenced this pull request May 20, 2026
Supersedes #736 + #737 with lint fixes applied:

dyndns/ (T14..T16):
- IP-detect multi-source quorum + diff + Update callback +
  exp-backoff with jitter. 12 tests.

cmd/wfctl/secrets_setup_plugin.go (T3+T4):
- secrets setup --plugin reads plugin.json required_secrets[],
  prompts each (masked iff sensitive), writes to chosen GH scope
  (repo|env|org). 7 tests.

Lint fixes vs the original PRs:
- dyndns/dyndns.go Run() — replaced empty-branch (SA9003) with
  explicit `_ = d.Tick(...)` ignoring the error.
- dyndns/dyndns.go jitter — math/rand/v2 G404 false-positive
  silenced with nolint:gosec annotation (decorrelation, not
  crypto).
- dyndns/dyndns.go timeAfter — replaced unlambda wrapper with
  direct `var timeAfter = time.After`.
- cmd/wfctl/secrets.go — hoisted `args[1:]` into a local var
  to silence gosec G602 (already bounded by outer switch).

All tests pass; golangci-lint clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants