Skip to content

feat(iac): DriftConfigDetector optional interface + applied-config-source sentinel + caller wiring#569

Merged
intel352 merged 7 commits into
mainfrom
feat/iac-drift-config-detector
May 6, 2026
Merged

feat(iac): DriftConfigDetector optional interface + applied-config-source sentinel + caller wiring#569
intel352 merged 7 commits into
mainfrom
feat/iac-drift-config-detector

Conversation

@intel352

@intel352 intel352 commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds OPTIONAL interfaces.DriftConfigDetector interface for config-drift classification (parallel to ComputePlanVersionDeclarer pattern). IaCProvider.DetectDrift is unchanged; sibling plugins (aws/gcp/azure) require zero code changes.
  • Adds AppliedConfigSource field on ResourceState to discriminate "apply" (user-supplied config) vs "adoption" (Outputs reflow) provenance. Empty string = legacy state, conservative default to adoption treatment.
  • Adds buildAppliedSpecMap helper that filters states to "apply"-provenance entries only, preventing false-positive config-drift on adopted resources.
  • Wires gRPC dispatch (remoteIaCProvider.DetectDriftWithApplied) with method-not-found fallback to legacy DetectDrift.
  • Wires caller-side type-assertion in runInfraApplyRefreshPhase and driftInfraModules.
  • Sets AppliedConfigSource at all three write sites: applyWithProviderAndStore, applyPrecomputedPlanWithStore (both "apply"), and resourceStateFromLiveOutput ("adoption").

Scope

PR 1 of 5 in the IaC state-truth + TC2 closeout plan series:

  • Plan: workflow-plugin-digitalocean (branch worktree-agent-ad141ad23872c3337) — docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout.md
  • Tasks: 1, 2, 3, 4, 5, 6
  • Sibling plugins (aws/gcp/azure) require zero changes (optional-declarer pattern).

ADRs

  • decisions/0007-iac-driftconfigdetector-optional-interface.md — records optional-declarer pattern choice over required-signature change
  • decisions/0010-applied-config-source-on-resourcestate.md — records field-on-ResourceState choice over magic-key in AppliedConfig

Pre-flight checks

Out-of-tree plugin survey: gh search code "iac.provider" --owner GoCodeAlone --extension json --limit 50 returned only GoCodeAlone-controlled plugins: workflow-plugin-digitalocean and workflow-plugin-aws. No third-party iac.provider plugins found. Survey confirms the optional-declarer pattern affects only known plugins under our control.

Manifest-field tolerance smoke: Tested wfctl's iacPluginManifest struct (uses json.Unmarshal with no schema validation per code comment at line 110). Go's encoding/json silently ignores unknown fields by design. A manifest with "iacProvider": {"driftConfigSupported": true} parses cleanly in any wfctl version. Minimum wfctl version: none (manifest field is future-only signal for DO plugin PR 2; current wfctl ignores it gracefully).

Test plan

  • go test ./... PASS in worktree
  • go test -race ./... PASS in worktree (interfaces + cmd/wfctl)
  • T1 TDD gate (TestRemoteIaC_OptionalDriftConfigDetector_FallsBackOnLegacyPlugin) — red after T1 commit, green after T6
  • T2: TestDriftConfigDetector_OptionalInterface PASS
  • T3: TestResourceState_AppliedConfigSource* + compat tests PASS
  • T4: TestApply_StateRecordsAppliedConfigSourceApply + TestAdoption_StateRecordsAppliedConfigSourceAdoption PASS
  • T5: TestBuildAppliedSpecMap_* PASS
  • T6: TestRemoteIaC_DetectDriftWithApplied_HappyPath PASS
  • Full go test ./... — zero failures across all packages
  • CI green
  • Copilot reviewed at HEAD

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

intel352 and others added 6 commits May 6, 2026 19:03
…(TDD-gates T1.5)

Adds a compat smoke test that verifies the wire-format fallback story:
when a remote plugin returns "method not found" for DetectDriftWithApplied,
the wfctl caller must fall through to legacy IaCProvider.DetectDrift rather
than propagating the error.

Red phase — test fails until T6 adds DetectDriftWithApplied to remoteIaCProvider.

Plan: workflow-plugin-digitalocean#worktree-agent-ad141ad23872c3337 docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout.md Task 1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ugin-digitalocean#47)

Adds interfaces.DriftConfigDetector as an OPTIONAL plugin-side interface
following the established repo pattern (ComputePlanVersionDeclarer,
ProviderValidator, Enumerator, UpsertSupporter). IaCProvider.DetectDrift
is unchanged; sibling plugins (aws/gcp/azure) require zero code changes.

Callers type-assert and fall back to existence-only DetectDrift on the
negative case. Providers that implement DriftConfigDetector can return
DriftClassConfig with field-level detail when applied config is available.

ADR 0007 records the decision and the rejected required-signature alternative.

Plan: workflow-plugin-digitalocean#worktree-agent-ad141ad23872c3337 docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout.md Task 2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…doption provenance)

Adds AppliedConfigSource string (json:"applied_config_source,omitempty") to
ResourceState to discriminate user-supplied config ("apply") from
adoption-shaped Outputs reflow ("adoption"). Empty string = legacy state,
treated as "adoption" (conservative default).

DriftConfigDetector.DetectDriftWithApplied uses this field (via
buildAppliedSpecMap) to refuse false-positive config-drift on adopted
resources. JSON compat is bidirectional: omitempty ensures old state files
decode cleanly; new state files are silently tolerated by old code.

ADR 0010 records the decision and the rejected magic-key-in-AppliedConfig alternative.

Plan: workflow-plugin-digitalocean#worktree-agent-ad141ad23872c3337 docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout.md Task 3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n sites

Sets AppliedConfigSource="apply" in applyWithProviderAndStore and
applyPrecomputedPlanWithStore (user-supplied config from ResourceSpec.Config).
Sets AppliedConfigSource="adoption" in resourceStateFromLiveOutput (Outputs
reflow via liveConfigFromOutputs). DriftConfigDetector consumers use this
field (via buildAppliedSpecMap) to refuse false-positive config-drift on
adoption-shaped entries.

Plan: workflow-plugin-digitalocean#worktree-agent-ad141ad23872c3337 docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout.md Task 4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds buildAppliedSpecMap(states, refs) that walks ResourceState entries and
emits only "apply"-provenance entries as the applied map for
DetectDriftWithApplied. Adoption and legacy (empty AppliedConfigSource)
entries are omitted to prevent false-positive config-drift on adoption-shaped
Outputs. Returns nil when no safe entries exist so callers can short-circuit.

Plan: workflow-plugin-digitalocean#worktree-agent-ad141ad23872c3337 docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout.md Task 5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ertion with legacy fallback

Adds DetectDriftWithApplied to remoteIaCProvider: dispatches to the v2 RPC
when the remote plugin supports it; falls back to legacy DetectDrift when the
remote returns "method not found" (isMethodNotFound sentinel). This is the
wire-format counterpart of the caller-side type-assertion.

Wires caller-side type-assertion in:
- runInfraApplyRefreshPhase (infra_apply_refresh.go)
- driftGroup closure in driftInfraModules (infra_status_drift.go)

Both sites use buildAppliedSpecMap to filter states for safe "apply"-
provenance entries only before calling DetectDriftWithApplied.

T1 TDD gate now passes (TestRemoteIaC_OptionalDriftConfigDetector_FallsBackOnLegacyPlugin).

Plan: workflow-plugin-digitalocean#worktree-agent-ad141ad23872c3337 docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout.md Task 6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 6, 2026 23:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 extends the IaC drift detection pipeline to optionally support config-drift classification by (a) adding an opt-in interfaces.DriftConfigDetector capability and (b) introducing an AppliedConfigSource provenance field on persisted ResourceState so drift detection can safely ignore adoption-shaped applied configs. It also wires the new capability through wfctl’s drift callers and the remote (plugin) dispatch layer with backward-compatible fallbacks.

Changes:

  • Add optional interfaces.DriftConfigDetector interface and wire wfctl callers + remote provider dispatch with legacy fallback behavior.
  • Add ResourceState.AppliedConfigSource sentinel to distinguish “apply” vs “adoption” provenance and prevent false-positive config drift.
  • Add buildAppliedSpecMap helper plus tests, and set AppliedConfigSource at all relevant state write sites.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
interfaces/iac_state.go Adds AppliedConfigSource field to ResourceState with documented semantics.
interfaces/iac_state_test.go Adds JSON compatibility/round-trip tests for AppliedConfigSource.
interfaces/iac_provider.go Introduces optional DriftConfigDetector interface for drift-with-applied detection.
interfaces/iac_provider_test.go Adds test pinning the optional-interface type-assertion behavior.
cmd/wfctl/infra_status_drift.go Updates drift status path to use optional drift-with-applied when supported.
cmd/wfctl/infra_drift_applied.go Adds helper to construct per-resource applied-config map filtered by provenance.
cmd/wfctl/infra_drift_applied_test.go Adds unit tests for applied-config map construction and copy semantics.
cmd/wfctl/infra_apply.go Writes AppliedConfigSource at apply/adoption state persistence sites.
cmd/wfctl/infra_apply_test.go Adds tests asserting state persistence records correct provenance.
cmd/wfctl/infra_apply_refresh.go Updates refresh-phase drift check to use optional drift-with-applied path.
cmd/wfctl/deploy_providers.go Adds remote dispatch for DetectDriftWithApplied with method-not-found fallback.
cmd/wfctl/deploy_providers_remote_iac_test.go Adds happy-path test for remote DetectDriftWithApplied.
cmd/wfctl/deploy_providers_remote_iac_compat_test.go Adds compat test ensuring fallback to legacy DetectDrift when RPC is missing.
decisions/0007-iac-driftconfigdetector-optional-interface.md Adds ADR documenting the optional-interface design decision.
decisions/0010-applied-config-source-on-resourcestate.md Adds ADR documenting the AppliedConfigSource sentinel decision.

Comment thread cmd/wfctl/infra_apply_refresh.go Outdated
Comment on lines +33 to +38
// configDetector is a local interface alias for DriftConfigDetector to avoid
// importing the full interfaces package name in the type-assertion switch.
// It matches interfaces.DriftConfigDetector exactly.
type configDetector interface {
DetectDriftWithApplied(ctx context.Context, refs []interfaces.ResourceRef, applied map[string]map[string]any) ([]interfaces.DriftResult, error)
}
Comment on lines +56 to +65
// Use DriftConfigDetector when the provider supports it (optional interface).
// Falls back to existence-only DetectDrift on the negative type-assertion.
var results []interfaces.DriftResult
var err error
if d, ok := provider.(configDetector); ok {
appliedMap := buildAppliedSpecMap(states, refs)
results, err = d.DetectDriftWithApplied(ctx, refs, appliedMap)
} else {
results, err = provider.DetectDrift(ctx, refs)
}
Comment on lines +103 to +110
// Use DriftConfigDetector when the provider supports it (optional interface).
// Falls back to existence-only DetectDrift on the negative type-assertion.
type configDetectorLocal interface {
DetectDriftWithApplied(ctx context.Context, refs []interfaces.ResourceRef, applied map[string]map[string]any) ([]interfaces.DriftResult, error)
}
var results []interfaces.DriftResult
if d, ok := provider.(configDetectorLocal); ok {
appliedMap := buildAppliedSpecMap(states, g.refs)
- **Status:** Accepted
- **Date:** 2026-05-06
- **Deciders:** Claude (autonomous design pipeline), Jon Langevin (mandate)
- **Refs:** workflow-plugin-digitalocean#47, docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout-design.md
- **Status:** Accepted
- **Date:** 2026-05-06
- **Deciders:** Claude (autonomous design pipeline), Jon Langevin (mandate)
- **Refs:** ADR 0007, docs/plans/2026-05-06-iac-state-truth-and-tc2-closeout-design.md
@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:260: parsing iteration count: invalid syntax
baseline-bench.txt:294459: parsing iteration count: invalid syntax
baseline-bench.txt:645739: parsing iteration count: invalid syntax
baseline-bench.txt:959570: parsing iteration count: invalid syntax
baseline-bench.txt:1279101: parsing iteration count: invalid syntax
baseline-bench.txt:1624033: parsing iteration count: invalid syntax
benchmark-results.txt:260: parsing iteration count: invalid syntax
benchmark-results.txt:339228: parsing iteration count: invalid syntax
benchmark-results.txt:647068: parsing iteration count: invalid syntax
benchmark-results.txt:926612: parsing iteration count: invalid syntax
benchmark-results.txt:1268157: parsing iteration count: invalid syntax
benchmark-results.txt:1832914: 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              3.148m ± 192%   3.050m ± 202%       ~ (p=0.240 n=6)
ComponentLoad-4                    3.671m ±   7%   3.456m ±   9%  -5.85% (p=0.026 n=6)
ComponentExecute-4                 1.921µ ±   3%   1.870µ ±   1%  -2.68% (p=0.002 n=6)
PoolContention/workers-1-4         1.089µ ±   3%   1.040µ ±   6%  -4.41% (p=0.041 n=6)
PoolContention/workers-2-4         1.088µ ±   1%   1.066µ ±   2%  -2.02% (p=0.002 n=6)
PoolContention/workers-4-4         1.090µ ±   0%   1.067µ ±   2%  -2.02% (p=0.002 n=6)
PoolContention/workers-8-4         1.101µ ±   4%   1.058µ ±   2%  -3.91% (p=0.002 n=6)
PoolContention/workers-16-4        1.100µ ±   2%   1.058µ ±   7%       ~ (p=0.065 n=6)
ComponentLifecycle-4               3.670m ±   1%   3.515m ±   1%  -4.23% (p=0.002 n=6)
SourceValidation-4                 2.276µ ±   1%   2.233µ ±   2%  -1.87% (p=0.004 n=6)
RegistryConcurrent-4               810.3n ±   4%   826.2n ±   9%       ~ (p=0.818 n=6)
LoaderLoadFromString-4             3.696m ±   1%   3.539m ±   1%  -4.25% (p=0.002 n=6)
geomean                            17.57µ          17.04µ         -3.03%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.920 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.485 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.623 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.087 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                  286.4n ± 2%   283.4n ± 5%       ~ (p=0.065 n=6)
CircuitBreakerExecution_Success-4          21.59n ± 1%   21.53n ± 0%       ~ (p=0.113 n=6)
CircuitBreakerExecution_Failure-4          65.91n ± 0%   65.90n ± 1%       ~ (p=0.970 n=6)
geomean                                    74.13n        73.81n       -0.43%

                                  │ 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              │
JQTransform_Simple-4                     943.0n ± 22%   942.9n ± 20%       ~ (p=0.699 n=6)
JQTransform_ObjectConstruction-4         1.478µ ± 10%   1.466µ ±  1%       ~ (p=0.095 n=6)
JQTransform_ArraySelect-4                3.392µ ±  2%   3.297µ ±  1%  -2.79% (p=0.002 n=6)
JQTransform_Complex-4                    39.22µ ±  0%   38.06µ ±  1%  -2.97% (p=0.002 n=6)
JQTransform_Throughput-4                 1.806µ ±  2%   1.757µ ±  1%  -2.74% (p=0.002 n=6)
SSEPublishDelivery-4                     73.70n ±  0%   68.60n ±  3%  -6.93% (p=0.002 n=6)
geomean                                  1.706µ         1.660µ        -2.74%

                                 │ baseline-bench.txt │        benchmark-results.txt         │
                                 │        B/op        │     B/op      vs base                │
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.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │     allocs/op      │ allocs/op   vs base                │
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.00%               ²
¹ 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.117µ ± 21%   1.095µ ± 19%       ~ (p=0.310 n=6)
SchemaValidation_AllFields-4                1.678µ ±  2%   1.674µ ±  1%       ~ (p=0.574 n=6)
SchemaValidation_FormatValidation-4         1.589µ ±  2%   1.568µ ±  1%  -1.35% (p=0.009 n=6)
SchemaValidation_ManySchemas-4              1.845µ ±  6%   1.811µ ±  2%       ~ (p=0.093 n=6)
geomean                                     1.531µ         1.510µ        -1.35%

                                    │ 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.165µ ±  8%   1.139µ ± 12%        ~ (p=0.732 n=6)
EventStoreAppend_SQLite-4                  1.313m ±  6%   1.285m ±  3%        ~ (p=0.065 n=6)
GetTimeline_InMemory/events-10-4           13.72µ ±  4%   13.55µ ±  3%        ~ (p=0.240 n=6)
GetTimeline_InMemory/events-50-4           76.36µ ±  2%   74.32µ ±  2%   -2.66% (p=0.009 n=6)
GetTimeline_InMemory/events-100-4          120.6µ ± 25%   150.0µ ±  4%  +24.40% (p=0.041 n=6)
GetTimeline_InMemory/events-500-4          621.9µ ±  1%   770.1µ ± 22%        ~ (p=0.065 n=6)
GetTimeline_InMemory/events-1000-4         1.267m ±  3%   1.243m ±  0%   -1.86% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             109.4µ ±  1%   103.8µ ±  3%   -5.13% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             248.6µ ±  1%   239.9µ ±  3%   -3.47% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            417.3µ ±  1%   408.4µ ±  1%   -2.12% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.766m ±  1%   1.733m ±  2%   -1.86% (p=0.009 n=6)
GetTimeline_SQLite/events-1000-4           3.446m ±  1%   3.377m ±  1%   -1.99% (p=0.002 n=6)
geomean                                    216.4µ         219.6µ         +1.52%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  817.0 ± 6%     802.0 ± 6%       ~ (p=0.372 n=6)
EventStoreAppend_SQLite-4                  1.986Ki ± 2%   1.983Ki ± 1%       ~ (p=1.000 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.121 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%       ~ (p=0.494 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.212 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%       ~ (p=1.000 n=6)
geomean                                    67.54Ki        67.43Ki       -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.

…Map short-circuit

Addresses Copilot review:
- Remove redundant configDetector alias in infra_apply_refresh.go; use
  interfaces.DriftConfigDetector directly (no type alias drift risk).
- Add nil-appliedMap short-circuit in both call sites (infra_apply_refresh.go
  and infra_status_drift.go): when buildAppliedSpecMap returns nil (no
  "apply"-provenance entries), fall through to legacy DetectDrift to avoid
  unnecessary RPC round-trips and remote-plugin method-not-found fallbacks.
- Use interfaces.DriftConfigDetector in infra_status_drift.go instead of
  duplicated function-local interface definition.
- Update ADR cross-references to use GitHub URLs for the plan doc in the DO
  plugin repo (the doc doesn't exist in this repo).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@intel352 intel352 merged commit cf7a21a into main May 6, 2026
23 checks passed
@intel352 intel352 deleted the feat/iac-drift-config-detector branch May 6, 2026 23:52
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