Skip to content

feat(iac): pass persisted state on update - #1021

Merged
intel352 merged 1 commit into
mainfrom
fix/prior-state-update-20261003
Oct 3, 2026
Merged

intel352 merged 1 commit into
mainfrom
fix/prior-state-update-20261003

Conversation

@intel352

@intel352 intel352 commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Add an optional persisted-state update contract without changing ResourceDriver.Update or the Update RPC name. The CLI rereads its durable backend at dispatch, including saved-plan execution. Both public adapters transmit additive prior_state; the SDK dispatches the native optional interface and leaves other RPCs unchanged.

Existing public clients without the new lookup hook retain legacy Update. State-dependent providers must reject nil state before mutation. Supplied missing, ambiguous, mismatched or malformed state fails closed. No DigitalOcean heuristics, private epoch cache, credential persistence, dependency changes or cloud operations.

Scope Manifest

Plan: workspace docs/plans/2026-10-02-workflow-compute-recovery-0a.md, approved ADR 0152.
PR Count: 14; Tasks: 25; Status: approved locked recovery plan, other phases remain open.
PR row 11: Task 22, fix/prior-state-update-20261003. This PR implements that row only.
Doc-reconciliation: clean.

Verification

  • TDD and revert-RED/GREEN for fresh persisted-state dispatch, saved plan/restart, SDK crossing and legacy compatibility.
  • Fresh lead full serial race: 151 packages, 12,667 passing test events, zero failures, 49 existing skips. Build, vet and full exact-CI golangci-lint v2.12.0 pass with zero lint issues.
  • Buf regeneration idempotent; lint and breaking check against 1017dda pass. Generated protobuf changes are mechanical for field 4.
  • Initial independent adversarial review found one reproduced Important: old public callers without LookupCurrentState were blocked. Added the exact real-SDK regression and minimal legacy fallback. Removing that correction fails both legacy cases; restoring it passes. Targeted serious-delta recheck SHIP-IT [], closed. Other cleared review checks were not repeated.

Runtime Launch Transcript

Actual SDK plugin processes and the public providerclient/shared apply APIs:

go test ./plugin/external/sdk -run UpdateWithState -race -count=1 -v
SDK child pid=99729 received generation=7 native_updates=1 legacy_updates=0; terminated=true
SDK child pid=99840 received generation=9 native_updates=1 legacy_updates=0; terminated=true
legacy and mixed ordinary updates: one legacy RPC, no action errors
state-dependent nil prior: denied, zero mutations

Reopened filesystem state and the actual CLI dispatch see generation 9 rather than captured 7 or saved-plan 1. No prior Diff is used. Invalid identity/config is rejected before mutation.

Failure-signature scrape: no unexpected panic, startup, permission, version or schema failures. Intentional denial cases above return errors. Boundary proof PASS; no live provider/cloud mutation claimed.

Remaining Gates

Publish and verify an immutable Workflow successor before DigitalOcean or private-app adoption. Original installed-agent, A/B, privileged scrub, STG and dogfood acceptance remain open.

Existing full-scan OSV baseline on preceding main reported 173 findings; this PR changes no dependency files and does not disable scanning or claim that baseline fixed.

Reread durable state at dispatch, including saved plans. Preserve legacy clients while state-dependent providers fail closed without prior state.
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

⏱ Benchmark Results

✅ No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:310: parsing iteration count: invalid syntax
baseline-bench.txt:303080: parsing iteration count: invalid syntax
baseline-bench.txt:648189: parsing iteration count: invalid syntax
baseline-bench.txt:970459: parsing iteration count: invalid syntax
baseline-bench.txt:1260257: parsing iteration count: invalid syntax
baseline-bench.txt:1571553: parsing iteration count: invalid syntax
benchmark-results.txt:310: parsing iteration count: invalid syntax
benchmark-results.txt:293924: parsing iteration count: invalid syntax
benchmark-results.txt:611310: parsing iteration count: invalid syntax
benchmark-results.txt:906028: parsing iteration count: invalid syntax
benchmark-results.txt:1220501: parsing iteration count: invalid syntax
benchmark-results.txt:1532730: 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               7.637m ± 59%   7.217m ± 56%       ~ (p=0.937 n=6)
ComponentLoad-4                     3.684m ± 10%   3.798m ±  4%       ~ (p=0.485 n=6)
ComponentExecute-4                  1.983µ ±  2%   1.993µ ±  1%       ~ (p=0.368 n=6)
PoolContention/workers-1-4          1.095µ ±  2%   1.105µ ±  3%       ~ (p=0.331 n=6)
PoolContention/workers-2-4          1.116µ ±  4%   1.120µ ±  2%       ~ (p=0.699 n=6)
PoolContention/workers-4-4          1.105µ ±  1%   1.117µ ±  4%       ~ (p=0.286 n=6)
PoolContention/workers-8-4          1.105µ ±  3%   1.107µ ±  1%       ~ (p=1.000 n=6)
PoolContention/workers-16-4         1.115µ ±  3%   1.099µ ±  2%       ~ (p=0.061 n=6)
ComponentLifecycle-4                3.731m ±  4%   3.655m ±  2%       ~ (p=0.065 n=6)
SourceValidation-4                  2.325µ ±  1%   2.313µ ±  1%       ~ (p=0.087 n=6)
RegistryConcurrent-4                821.6n ±  4%   798.5n ±  5%       ~ (p=0.310 n=6)
LoaderLoadFromString-4              3.671m ±  2%   3.727m ±  3%       ~ (p=0.180 n=6)
geomean                             19.15µ         19.07µ        -0.41%

                            │ 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.909 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.729 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.619 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                  292.8n ± 3%   296.9n ± 7%       ~ (p=0.589 n=6)
CircuitBreakerExecution_Success-4          22.51n ± 1%   22.52n ± 0%       ~ (p=0.576 n=6)
CircuitBreakerExecution_Failure-4          66.71n ± 0%   66.59n ± 0%  -0.18% (p=0.022 n=6)
geomean                                    76.04n        76.36n       +0.41%

                                  │ 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              331.9n ± 34%   319.9n ± 37%  -3.62% (p=0.041 n=6)
IaCStateBackend_GRPC-4                   9.461m ± 11%   9.142m ±  4%       ~ (p=0.065 n=6)
JQTransform_Simple-4                     680.6n ± 40%   673.8n ± 38%       ~ (p=0.394 n=6)
JQTransform_ObjectConstruction-4         1.574µ ±  6%   1.524µ ±  1%  -3.11% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.585µ ±  4%   3.507µ ±  2%  -2.16% (p=0.013 n=6)
JQTransform_Complex-4                    41.09µ ±  3%   39.72µ ±  0%  -3.33% (p=0.011 n=6)
JQTransform_Throughput-4                 1.913µ ±  4%   1.862µ ±  0%  -2.72% (p=0.002 n=6)
SSEPublishDelivery-4                     65.19n ±  1%   64.25n ±  1%  -1.43% (p=0.004 n=6)
geomean                                  3.971µ         3.867µ        -2.60%

                                 │ 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.882Mi ± 12%     5.867Mi ± 10%       ~ (p=0.937 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.03%               ²
¹ 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.841k ± 0%     6.836k ± 0%       ~ (p=0.513 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.111µ ± 5%   1.089µ ± 23%       ~ (p=0.310 n=6)
SchemaValidation_AllFields-4                 1.684µ ± 8%   1.639µ ±  2%  -2.67% (p=0.002 n=6)
SchemaValidation_FormatValidation-4          1.602µ ± 3%   1.567µ ±  2%  -2.19% (p=0.015 n=6)
SchemaValidation_ManySchemas-4               1.798µ ± 2%   1.813µ ±  2%       ~ (p=0.145 n=6)
geomean                                      1.523µ        1.500µ        -1.50%

                                    │ 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.249µ ± 14%   1.221µ ± 12%       ~ (p=0.589 n=6)
EventStoreAppend_SQLite-4                  1.187m ±  8%   1.246m ±  4%       ~ (p=0.180 n=6)
GetTimeline_InMemory/events-10-4           14.57µ ±  4%   14.74µ ±  6%       ~ (p=0.937 n=6)
GetTimeline_InMemory/events-50-4           81.29µ ±  4%   79.96µ ±  6%       ~ (p=0.132 n=6)
GetTimeline_InMemory/events-100-4          162.8µ ± 23%   163.7µ ± 23%       ~ (p=0.699 n=6)
GetTimeline_InMemory/events-500-4          648.2µ ±  3%   644.5µ ±  0%  -0.58% (p=0.041 n=6)
GetTimeline_InMemory/events-1000-4         1.334m ±  2%   1.310m ±  1%  -1.78% (p=0.015 n=6)
GetTimeline_SQLite/events-10-4             71.40µ ±  2%   70.44µ ±  1%  -1.34% (p=0.009 n=6)
GetTimeline_SQLite/events-50-4             207.6µ ±  2%   202.6µ ±  1%  -2.39% (p=0.009 n=6)
GetTimeline_SQLite/events-100-4            366.8µ ±  1%   363.9µ ±  1%       ~ (p=0.132 n=6)
GetTimeline_SQLite/events-500-4            1.675m ±  5%   1.636m ±  1%  -2.29% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.285m ±  2%   3.223m ±  0%  -1.87% (p=0.002 n=6)
geomean                                    210.1µ         208.6µ        -0.71%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  824.5 ± 9%     788.5 ± 9%       ~ (p=0.416 n=6)
EventStoreAppend_SQLite-4                  2.056Ki ± 2%   2.050Ki ± 2%       ~ (p=0.225 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.121 n=6)
GetTimeline_SQLite/events-10-4             15.59Ki ± 0%   15.59Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             80.56Ki ± 0%   80.56Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            162.1Ki ± 0%   162.1Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4            777.4Ki ± 0%   777.4Ki ± 0%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-1000-4           1.503Mi ± 0%   1.503Mi ± 0%  +0.00% (p=0.041 n=6)
geomean                                    65.56Ki        65.30Ki       -0.39%
¹ 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                    55.00 ± 0%    55.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               316.0 ± 0%    316.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4              1.486k ± 0%   1.486k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4             2.940k ± 0%   2.940k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4             14.79k ± 0%   14.79k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-1000-4            29.80k ± 0%   29.80k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                     1.065k        1.065k       +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 49e1803 into main Oct 3, 2026
27 checks passed
@intel352
intel352 deleted the fix/prior-state-update-20261003 branch October 3, 2026 17:05
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