Skip to content

feat(iac): wfctl infra refresh-outputs + cheap apply-time refresh (W-2 of 12)#524

Closed
intel352 wants to merge 6 commits into
feat/iac-plan-schema-diagnosticfrom
feat/iac-refresh-outputs
Closed

feat(iac): wfctl infra refresh-outputs + cheap apply-time refresh (W-2 of 12)#524
intel352 wants to merge 6 commits into
feat/iac-plan-schema-diagnosticfrom
feat/iac-refresh-outputs

Conversation

@intel352
Copy link
Copy Markdown
Contributor

@intel352 intel352 commented May 4, 2026

Summary

W-2 of the 12-PR IaC plan series. Adds iac/refreshoutputs/ package + wfctl infra refresh-outputs subcommand + opt-in apply-time refresh pre-step. Read-only refresh of cloud-resource Outputs without invoking Update/Replace; bounded concurrency (default 8); operator-controlled via WFCTL_REFRESH_OUTPUTS env var (strconv.ParseBool semantics) + --skip-refresh flag.

Plan reference: docs/plans/2026-05-03-iac-conformance-and-replace.md rev10 (commit on design/iac-conformance-and-replace branch).

Base: feat/iac-plan-schema-diagnostic (W-1, PR #523). When W-1 merges to main, this PR will retarget main automatically.

What ships

6 commits in dependency order:

  • 09bfe8e — refreshoutputs.Refresh (T2.1) — bounded-concurrency Read-per-resource helper
  • 181e579 — wfctl infra refresh-outputs subcommand (T2.2)
  • bfd1bbe — apply-time refresh as opt-in pre-step (T2.3)
  • 2d77af9 — concurrency stress test (T2.5; 100 fake resources, peak inflight ≤ N, race-detector clean)
  • a892c1d — docs/WFCTL.md infra refresh-outputs section + T2.7 transcript (T2.6 + T2.7)
  • 8dafaa5 — ADR 006: WFCTL_REFRESH_OUTPUTS ParseBool semantics deviation from plan §T2.3

ADR 006 — non-trivial decision

Plan §T2.3 line 1061 specified os.Getenv("WFCTL_REFRESH_OUTPUTS") != "" as the gate. T2.3 quality-review caught a foot-gun: an operator setting WFCTL_REFRESH_OUTPUTS=0 to opt out would have been mis-enabled (any non-empty value qualifies). After spec/code-review consensus, implementation uses strconv.ParseBool: truthy values enable; explicit falsy disable; empty/unset/unrecognized disable. Documented in docs/adr/006-wfctl-refresh-outputs-env-var-parsebool.md.

T2.7 runtime-launch validation transcript

Folded into the T2.6 commit body. Verified independently by code-reviewer:

  • wfctl infra refresh-outputs --help → exit 0, prints Usage of infra refresh-outputs: banner
  • wfctl infra refresh-outputs -c <fake.yaml> --env staging (no provider configured) → exit 1, stderr-only literal error: refresh-outputs: provider not configured for env "staging"
  • No panic; no stack trace

Test plan

  • GOWORK=off go test -race -count=1 ./iac/refreshoutputs/... ./cmd/wfctl/... PASS
  • mdformat --check docs/WFCTL.md — pre-existing failure (not introduced by W-2; same on baseline + this PR)
  • markdown-link-check docs/WFCTL.md clean
  • CI: standard test + lint pipeline green
  • Manual: smoke against staging-PG (deferred per plan §T2.7 line 1102; out-of-scope for in-worktree validation)

Out of scope (deferred to later PRs)

  • Replace action emission (W-3a/W-3b)
  • ApplyResult fields for drift report (W-3a/T3.0.4)
  • In-process apply path drift postcondition (W-3a/T3.1.5)
  • conformance scenarios (W-7)

🤖 Generated with Claude Code

intel352 and others added 6 commits May 3, 2026 19:37
T2.1 — bounded-concurrency Refresh(ctx, provider, states, opts) that calls
ResourceDriver.Read per resource and returns a copy of the state slice with
Outputs reconciled to the live values. Default concurrency 8 when
Options.Concurrency < 1; otherwise honor the caller's value. On any Read or
driver-resolution failure, returns (nil, err) so callers don't half-persist
a refresh. Foundation for wfctl infra refresh-outputs (T2.2) and the opt-in
apply pre-step (T2.3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
T2.2 — `wfctl infra refresh-outputs [-c CONFIG] [--env ENV] [--concurrency N]`
reads live Outputs for each resource already in state and persists any
field-level changes back to the state backend. Read-only at the cloud
level — never invokes Update or Replace.

Discovers iac.provider modules in the config (with per-env resolution),
groups state entries by their owning iac.provider module (ProviderRef-first,
falling back to provider type when exactly one module of that type exists),
loads each provider once, calls iac/refreshoutputs.Refresh per group, and
SaveResource()s any state whose Outputs map changed.

When the resolved config has no usable iac.provider module for the
requested env, emits the literal error
  refresh-outputs: provider not configured for env "<env>"
verbatim per `fmt.Errorf("refresh-outputs: provider not configured for
env %q", env)`. T2.7's runtime-launch-validation asserts against this
exact line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ESH_OUTPUTS)

T2.3 — wires iac/refreshoutputs.Refresh into runInfraApply as a pre-plan
read-only state reconciliation. Default OFF: operators get pre-W-2
behavior unless they explicitly opt in.

Activation rules:
- WFCTL_REFRESH_OUTPUTS unset, empty, or unrecognised → no-op (default).
- WFCTL_REFRESH_OUTPUTS="1"/"true"/"t" (strconv.ParseBool truthy) →
  run pre-step.
- WFCTL_REFRESH_OUTPUTS="0"/"false"/"f" (strconv.ParseBool falsey) →
  no-op. Operators who use the "0"/"false" convention to disable a
  feature get the expected behaviour rather than a presence-only
  foot-gun.
- --skip-refresh → suppress pre-step regardless of env var (for CI
  environments that force the env var on globally).

Behavior: after the existing --refresh drift/prune phase and before the
plan/apply dispatch, discovers iac.provider modules with per-env
resolution, loads current state, and calls
refreshOutputsAcrossProviders to read live Outputs and persist any
field-level changes. On any Read or driver-resolution failure, apply
aborts with the wrapped error from T2.1's helper (no half-persisted
refresh, no plan computed against stale state). Only fires for
infra.* configs (legacy platform.* path is silently skipped).

Rollback: unset WFCTL_REFRESH_OUTPUTS, pass --skip-refresh, or revert
this commit. Reverting removes the pre-step entirely (helper file plus
the gated block in infra.go).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
T2.5 — pure-package stress test in iac/refreshoutputs/. Drives Refresh
with 100 fake resources at Concurrency=8 and asserts:

  1. No deadlock (10s watchdog around the call).
  2. Read called exactly once per ProviderID (atomic per-ID counter).
  3. Every refreshed state carries the live Outputs map — no
     write-into-wrong-slot bug under concurrency.
  4. Concurrent in-flight peak between 2 and the requested cap, proving
     both that parallelism happened AND that the semaphore enforced
     its limit.

The countingDriver introduces a 5ms sleep per Read so the bounded pool
actually queues at the cap (5ms × 100 / 8 ≈ 63ms total at peak; well
under the 10s watchdog). Test runs ~1.5s wall.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
T2.6 — adds the infra refresh-outputs section to docs/WFCTL.md:

- New row in the Command Tree mermaid graph.
- New row in the infra Action table.
- Dedicated #### subsection with usage, flag table, behavior summary,
  literal-error contract (load-bearing per T2.7), apply-time pre-step
  semantics (WFCTL_REFRESH_OUTPUTS, --skip-refresh), and three
  representative examples.

See also: docs/adr/006-wfctl-refresh-outputs-env-var-parsebool.md
records the T2.3 plan-deviation (ParseBool vs plan-literal presence
check) that the docs in this commit accurately reflect.

Verification — plan §T2.6 line 1090 invocation `mdformat --check
docs/WFCTL.md && find docs -name "*.md" -exec markdown-link-check {} +`
ran with locally-installed mdformat 1.0.0 (pip) and markdown-link-check
3.14.2 (npm):

  $ mdformat --check docs/WFCTL.md
  Error: File "docs/WFCTL.md" is not formatted.
  exit=1

  This failure is PRE-EXISTING. Verified by checking out the file at
  the W-2 T2.2 tip (181e579) before any T2.6 edits and rerunning
  mdformat against it: identical error. docs/WFCTL.md has never been
  mdformat-formatted in this repo. Reformatting the entire file is
  out of scope for T2.6 (would introduce a multi-thousand-line
  unrelated diff). T2.6's own additions follow the existing in-file
  conventions exactly.

  $ markdown-link-check docs/WFCTL.md
  FILE: docs/WFCTL.md
    [✓] https://github.com/GoCodeAlone/workflow
    [✓] #build-ui
    [✓] mcp.md
    3 links checked.
  exit=0

  docs/WFCTL.md has zero broken links — including the new
  refresh-outputs section. The directory-wide scan reports 7 broken
  links in unrelated files (self-improvement-tutorial.md,
  getting-started.md, etc.); all are pre-existing and out of scope.

T2.7 runtime-launch-validation transcript (folded into this commit
body per the "Files: none new" plan note for T2.7):

  $ GOWORK=off go build -o /tmp/wfctl ./cmd/wfctl
  exit=0

  $ /tmp/wfctl infra refresh-outputs --help
  Usage of infra refresh-outputs:
    -c string
      	Config file (short for --config)
    -concurrency int
      	Maximum concurrent Read calls (default 8)
    -config string
      	Config file
    -e string
      	Environment name (short for --env)
    -env string
      	Environment name (resolves per-module overrides)
  exit=0

  $ cat /tmp/t27-fake.yaml
  modules:
    - name: state-store
      type: iac.state
      config:
        backend: filesystem
        directory: /tmp/t27-fake-state

  $ /tmp/wfctl infra refresh-outputs -c /tmp/t27-fake.yaml --env staging
  error: refresh-outputs: provider not configured for env "staging"
  exit=1

  No panic, no stack trace. Stderr line is the verbatim literal pinned
  by T2.7 (plan line 1098), produced by T2.2's
  fmt.Errorf("refresh-outputs: provider not configured for env %q",
  env) at cmd/wfctl/infra_refresh_outputs.go:49.

  PR W-2 mandate (plan line 1101):
  $ GOWORK=off go test ./iac/refreshoutputs/... ./cmd/wfctl/... -count=1 -race
  ok  	github.com/GoCodeAlone/workflow/iac/refreshoutputs	1.405s
  ok  	github.com/GoCodeAlone/workflow/cmd/wfctl	10.485s

  Manual smoke against staging-PG: not run — no staging-PG available
  in this worktree environment. Plan line 1102 marks this "if
  available", so deferring to the operator landing the PR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… from plan §T2.3

ADR 006 — formalises the spec-vs-quality-review trade-off recorded
during W-2 T2.3 review:

- Plan §T2.3 line 1061 specified `os.Getenv("WFCTL_REFRESH_OUTPUTS") != ""`.
- Code-reviewer flagged this as a foot-gun (=0 mis-enables).
- Implementation at cmd/wfctl/infra_apply_refresh_pre.go (bfd1bbe) uses
  strconv.ParseBool so falsey values explicitly disable.
- Spec-reviewer accepted post-hoc and requested this ADR per
  superpowers:recording-decisions.
- Team-lead approved option-1 (approve-as-is + follow-up ADR) over a
  plan revert; provenance recorded in the ADR itself.

Captures the rejected alternative, the rationale, references back to
the plan spec, the implementation site, the pinning test, and the
operator-facing docs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@intel352 intel352 deleted the branch feat/iac-plan-schema-diagnostic May 4, 2026 01:20
@intel352 intel352 closed this May 4, 2026
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