Skip to content

feat(lint): harden R-A9 from warning to error (PR1: Tasks 3+4)#583

Merged
intel352 merged 5 commits into
mainfrom
feat/r-a9-error
May 9, 2026
Merged

feat(lint): harden R-A9 from warning to error (PR1: Tasks 3+4)#583
intel352 merged 5 commits into
mainfrom
feat/r-a9-error

Conversation

@intel352

@intel352 intel352 commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

PR1 of the spaces-key-iac-resource plan
(docs/plans/2026-05-08-spaces-key-iac-resource.md, commit 316559f7) —
two tasks land here as a TDD pair:

  • Task 3 (commit 2f5bfd7): failing test — flips
    TestCheckRA9_SuspiciousProviderCredentialKey from WARN to ERROR,
    adds new positive fixture TestRA9_CanonicalSingleEntry_Passes.
  • Task 4 (commit 288f68d): implementation — flips R-A9 severity from
    WARN to ERROR in cmd/wfctl/infra_align_rules.go and rewrites the
    diagnostic with a fix-suggestion. Also updates
    TestInfraAlign_RA9_SuspiciousKey_Fires (line 976) which still asserted
    WARN at the integration level, plus the R-A9 row in docs/WFCTL.md.

After this lands, wfctl infra align --strict exits non-zero whenever a
secrets.generate entry has a provider_credential key ending in a known
sub-key suffix (_access_key, _secret_key, ...) for a source registered
in providerCredentialSubKeys — blocking the doubled-create anti-pattern
at lint time, before plan/apply touches the cloud.

Test plan

  • GOWORK=off go test ./cmd/wfctl -run "TestRA9|TestCheckRA9|TestInfraAlign_RA9" -v → all 4 PASS
  • GOWORK=off go test ./cmd/wfctl -count=1 → entire wfctl suite PASS
  • GOWORK=off go build ./... → clean
  • After merge: PR2 (Tasks 5+6) migrates core-dump/infra.yaml to canonical single-entry shape so it passes --strict under the new error severity

🤖 Generated with Claude Code

Flips the table-driven assertion in TestCheckRA9_SuspiciousProviderCredentialKey
from `Severity=WARN` to `Severity=ERROR` (rev3 of the spaces-key plan), and
adds TestRA9_CanonicalSingleEntry_Passes as the positive happy-path fixture
verifying that a canonical single-entry SPACES key passes `--strict`.

This is a deliberately failing test for Task 3 of the spaces-key-iac-resource
plan; Task 4 will flip the rule severity in infra_align_rules.go to make it
pass. Task 4 will also need to update TestInfraAlign_RA9_SuspiciousKey_Fires
(line 976) which still asserts WARN at the integration level.

Plan: docs/plans/2026-05-08-spaces-key-iac-resource.md (commit 316559f7),
Task 3 of PR1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 9, 2026 05:38

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

Updates wfctl infra align test coverage around rule R-A9 (suspicious provider_credential secret key shape) as part of the spaces-key-iac-resource plan, by asserting the planned severity change and adding a strict-mode “canonical key” fixture.

Changes:

  • Change the unit test TestCheckRA9_SuspiciousProviderCredentialKey to expect Severity=ERROR (currently fails until the rule implementation is updated).
  • Add a new end-to-end strict-mode test ensuring the canonical single-entry SPACES key produces no R-A9 findings and exits 0.

Comment on lines +942 to +943
if tc.wantFinding && findings[0].Severity != "ERROR" {
t.Errorf("expected Severity=ERROR, got %q", findings[0].Severity)
Comment thread cmd/wfctl/infra_align_test.go Outdated
Comment on lines +1007 to +1014
// TestRA9_CanonicalSingleEntry_Passes is the positive happy-path fixture for
// the R-A9 severity flip (rev3): the canonical single-entry SPACES key with
// no doubled-create anti-pattern must pass `wfctl infra align --strict` with
// exit code 0 and produce zero R-A9 findings.
//
// This is the inverse of TestInfraAlign_RA9_SuspiciousKey_Fires: it locks in
// that the rule does not regress into false positives once it fires as ERROR.
func TestRA9_CanonicalSingleEntry_Passes(t *testing.T) {
intel352 and others added 2 commits May 9, 2026 01:44
Implements Task 4 of the spaces-key-iac-resource plan
(docs/plans/2026-05-08-spaces-key-iac-resource.md, commit 316559f7).

Changes:
- cmd/wfctl/infra_align_rules.go: flip checkRA9 severity from "WARN" to
  "ERROR" and rewrite the diagnostic message with a fix-suggestion
  ("...use canonical %q (auto-derives sub-keys via
  providerCredentialSubKeys[%q])"). With this change `wfctl infra align
  --strict` exits non-zero whenever a secrets.generate entry has a
  `provider_credential` key ending in a known sub-key suffix
  (`_access_key`, `_secret_key`, ...). This blocks the doubled-create
  anti-pattern at lint time, before plan/apply touches the cloud.
- cmd/wfctl/infra_align_test.go:976: update
  TestInfraAlign_RA9_SuspiciousKey_Fires to assert "ERROR" instead of
  "WARN" (the unit-level table test was flipped to ERROR by Task 3;
  the integration-level helper still asserted WARN, which would have
  regressed under the new rule severity).
- docs/WFCTL.md R-A9 rule table row: severity column WARN → ERROR with a
  clarifying parenthetical ("doubled-create anti-pattern"). The plan
  referenced docs/dsl-reference-embedded.md but that file does not exist
  in the repo; the rule table that documents R-A9 lives in WFCTL.md.

Verification:
- GOWORK=off go test ./cmd/wfctl -run "TestRA9|TestCheckRA9|TestInfraAlign_RA9" -v
  → all 4 tests PASS
- GOWORK=off go test ./cmd/wfctl -count=1 → entire wfctl suite PASS
- GOWORK=off go build ./... → clean

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

Renames TestRA9_CanonicalSingleEntry_Passes to
TestInfraAlign_RA9_CanonicalSingleEntry_Passes per Copilot review (PR #583
inline comment, line 1014). Aligns with the existing TestInfraAlign_RA9_*
naming convention used by sibling end-to-end tests in the same file
(TestInfraAlign_RA9_SuspiciousKey_Fires, TestInfraAlign_RA9_CleanKey_DoesNotFire).

No behavior change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@intel352 intel352 changed the title test(lint): failing test for R-A9 error severity feat(lint): harden R-A9 from warning to error (PR1: Tasks 3+4) May 9, 2026
Copilot AI review requested due to automatic review settings May 9, 2026 05:45
@intel352

intel352 commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Copilot inline review (commit 22cc1a7):

Line 1014 (test naming convention) — fixed. Renamed TestRA9_CanonicalSingleEntry_PassesTestInfraAlign_RA9_CanonicalSingleEntry_Passes to match the existing TestInfraAlign_RA9_* prefix used by sibling end-to-end tests in the same file.

Line 943 (CI red until rule changes) — by design, will not change. This is the failing-test commit in a TDD pair (Task 3 of PR1). Task 4's implementation commit (288f68d) now stacks on top of Task 3's commit on this same branch, so the PR as a whole goes green when both commits are in place. Verified locally:

$ GOWORK=off go test ./cmd/wfctl -run "TestInfraAlign_RA9|TestCheckRA9" -v
--- PASS: TestCheckRA9_SuspiciousProviderCredentialKey
--- PASS: TestInfraAlign_RA9_SuspiciousKey_Fires
--- PASS: TestInfraAlign_RA9_CleanKey_DoesNotFire
--- PASS: TestInfraAlign_RA9_CanonicalSingleEntry_Passes
PASS

Per the spaces-key-iac-resource plan (PR1 = Task 3 failing test + Task 4 implementation, same branch, single PR), CI gates only on the merged-PR state, not on each individual commit.

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

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

Comment on lines 717 to 721
findings = append(findings, AlignFinding{
Rule: "R-A9",
Severity: "WARN",
Severity: "ERROR",
Resource: gen.Key,
Message: fmt.Sprintf(
Comment thread cmd/wfctl/infra_align_rules.go Outdated
Message: fmt.Sprintf(
"provider_credential key %q ends with auto-generated suffix %q — use root key (e.g. %q) and let bootstrapSecrets derive sub-keys",
gen.Key, suffix, strings.TrimSuffix(gen.Key, suffix),
"provider_credential key %q ends in %q; use canonical %q (auto-derives sub-keys via providerCredentialSubKeys[%q])",
Comment thread docs/WFCTL.md
| R-A7 | Plan-output sanity (requires `--plan`) | FAIL or WARN |
| R-A8 | WebAuthn RP_ID alignment | FAIL |
| R-A9 | Suspicious `provider_credential` key suffix | WARN |
| R-A9 | Suspicious `provider_credential` key suffix (doubled-create anti-pattern) | ERROR |
Comment on lines 972 to +976
findings, err := runInfraAlignChecks(opts)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !findingsHaveRuleAndSeverity(findings, "R-A9", "WARN") {
t.Errorf("expected R-A9 WARN, got: %v", findings)
if !findingsHaveRuleAndSeverity(findings, "R-A9", "ERROR") {
@github-actions

github-actions Bot commented May 9, 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:342321: parsing iteration count: invalid syntax
baseline-bench.txt:641251: parsing iteration count: invalid syntax
baseline-bench.txt:890447: parsing iteration count: invalid syntax
baseline-bench.txt:1185120: parsing iteration count: invalid syntax
baseline-bench.txt:1470232: parsing iteration count: invalid syntax
benchmark-results.txt:260: parsing iteration count: invalid syntax
benchmark-results.txt:404475: parsing iteration count: invalid syntax
benchmark-results.txt:715999: parsing iteration count: invalid syntax
benchmark-results.txt:1310852: parsing iteration count: invalid syntax
benchmark-results.txt:1653720: parsing iteration count: invalid syntax
benchmark-results.txt:2040247: 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 │
                            │       sec/op       │
InterpreterCreation-4              4.682m ± 128%
ComponentLoad-4                    3.600m ±  11%
ComponentExecute-4                 1.934µ ±   2%
PoolContention/workers-1-4         1.083µ ±   2%
PoolContention/workers-2-4         1.091µ ±   2%
PoolContention/workers-4-4         1.086µ ±   1%
PoolContention/workers-8-4         1.082µ ±   0%
PoolContention/workers-16-4        1.088µ ±   1%
ComponentLifecycle-4               3.599m ±   1%
SourceValidation-4                 2.319µ ±   1%
RegistryConcurrent-4               795.2n ±   3%
LoaderLoadFromString-4             3.615m ±   1%
geomean                            18.03µ

                            │ baseline-bench.txt │
                            │        B/op        │
InterpreterCreation-4               2.027Mi ± 0%
ComponentLoad-4                     2.180Mi ± 0%
ComponentExecute-4                  1.203Ki ± 0%
PoolContention/workers-1-4          1.203Ki ± 0%
PoolContention/workers-2-4          1.203Ki ± 0%
PoolContention/workers-4-4          1.203Ki ± 0%
PoolContention/workers-8-4          1.203Ki ± 0%
PoolContention/workers-16-4         1.203Ki ± 0%
ComponentLifecycle-4                2.183Mi ± 0%
SourceValidation-4                  1.984Ki ± 0%
RegistryConcurrent-4                1.133Ki ± 0%
LoaderLoadFromString-4              2.182Mi ± 0%
geomean                             15.25Ki

                            │ baseline-bench.txt │
                            │     allocs/op      │
InterpreterCreation-4                15.68k ± 0%
ComponentLoad-4                      18.02k ± 0%
ComponentExecute-4                    25.00 ± 0%
PoolContention/workers-1-4            25.00 ± 0%
PoolContention/workers-2-4            25.00 ± 0%
PoolContention/workers-4-4            25.00 ± 0%
PoolContention/workers-8-4            25.00 ± 0%
PoolContention/workers-16-4           25.00 ± 0%
ComponentLifecycle-4                 18.07k ± 0%
SourceValidation-4                    32.00 ± 0%
RegistryConcurrent-4                  2.000 ± 0%
LoaderLoadFromString-4               18.06k ± 0%
geomean                               183.3

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                            │ benchmark-results.txt │
                            │        sec/op         │
InterpreterCreation-4                 3.029m ± 232%
ComponentLoad-4                       3.451m ±  11%
ComponentExecute-4                    1.866µ ±   1%
PoolContention/workers-1-4            1.191µ ±   2%
PoolContention/workers-2-4            1.194µ ±   1%
PoolContention/workers-4-4            1.201µ ±   1%
PoolContention/workers-8-4            1.200µ ±   1%
PoolContention/workers-16-4           1.203µ ±   2%
ComponentLifecycle-4                  3.533m ±   0%
SourceValidation-4                    2.270µ ±   1%
RegistryConcurrent-4                  922.5n ±   7%
LoaderLoadFromString-4                3.565m ±   7%
geomean                               18.14µ

                            │ benchmark-results.txt │
                            │         B/op          │
InterpreterCreation-4                  2.027Mi ± 0%
ComponentLoad-4                        2.180Mi ± 0%
ComponentExecute-4                     1.203Ki ± 0%
PoolContention/workers-1-4             1.203Ki ± 0%
PoolContention/workers-2-4             1.203Ki ± 0%
PoolContention/workers-4-4             1.203Ki ± 0%
PoolContention/workers-8-4             1.203Ki ± 0%
PoolContention/workers-16-4            1.203Ki ± 0%
ComponentLifecycle-4                   2.183Mi ± 0%
SourceValidation-4                     1.984Ki ± 0%
RegistryConcurrent-4                   1.133Ki ± 0%
LoaderLoadFromString-4                 2.182Mi ± 0%
geomean                                15.25Ki

                            │ benchmark-results.txt │
                            │       allocs/op       │
InterpreterCreation-4                   15.68k ± 0%
ComponentLoad-4                         18.02k ± 0%
ComponentExecute-4                       25.00 ± 0%
PoolContention/workers-1-4               25.00 ± 0%
PoolContention/workers-2-4               25.00 ± 0%
PoolContention/workers-4-4               25.00 ± 0%
PoolContention/workers-8-4               25.00 ± 0%
PoolContention/workers-16-4              25.00 ± 0%
ComponentLifecycle-4                    18.07k ± 0%
SourceValidation-4                       32.00 ± 0%
RegistryConcurrent-4                     2.000 ± 0%
LoaderLoadFromString-4                  18.06k ± 0%
geomean                                  183.3

pkg: github.com/GoCodeAlone/workflow/middleware
cpu: AMD EPYC 7763 64-Core Processor                
                                  │ baseline-bench.txt │
                                  │       sec/op       │
CircuitBreakerDetection-4                  288.5n ± 4%
CircuitBreakerExecution_Success-4          21.56n ± 0%
CircuitBreakerExecution_Failure-4          66.17n ± 0%
geomean                                    74.39n

                                  │ baseline-bench.txt │
                                  │        B/op        │
CircuitBreakerDetection-4                 144.0 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │
                                  │     allocs/op      │
CircuitBreakerDetection-4                 1.000 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                  │ benchmark-results.txt │
                                  │        sec/op         │
CircuitBreakerDetection-4                     457.3n ± 5%
CircuitBreakerExecution_Success-4             59.73n ± 0%
CircuitBreakerExecution_Failure-4             65.36n ± 0%
geomean                                       121.3n

                                  │ benchmark-results.txt │
                                  │         B/op          │
CircuitBreakerDetection-4                    144.0 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

                                  │ benchmark-results.txt │
                                  │       allocs/op       │
CircuitBreakerDetection-4                    1.000 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
cpu: AMD EPYC 7763 64-Core Processor                
                                 │ baseline-bench.txt │
                                 │       sec/op       │
JQTransform_Simple-4                     937.4n ± 18%
JQTransform_ObjectConstruction-4         1.417µ ±  1%
JQTransform_ArraySelect-4                3.250µ ±  2%
JQTransform_Complex-4                    38.30µ ±  2%
JQTransform_Throughput-4                 1.726µ ±  0%
SSEPublishDelivery-4                     63.14n ±  1%
geomean                                  1.619µ

                                 │ baseline-bench.txt │
                                 │        B/op        │
JQTransform_Simple-4                   1.273Ki ± 0%
JQTransform_ObjectConstruction-4       1.773Ki ± 0%
JQTransform_ArraySelect-4              2.625Ki ± 0%
JQTransform_Complex-4                  16.22Ki ± 0%
JQTransform_Throughput-4               1.984Ki ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │
                                 │     allocs/op      │
JQTransform_Simple-4                     10.00 ± 0%
JQTransform_ObjectConstruction-4         15.00 ± 0%
JQTransform_ArraySelect-4                30.00 ± 0%
JQTransform_Complex-4                    324.0 ± 0%
JQTransform_Throughput-4                 17.00 ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                 │ benchmark-results.txt │
                                 │        sec/op         │
JQTransform_Simple-4                        982.3n ± 19%
JQTransform_ObjectConstruction-4            1.467µ ±  1%
JQTransform_ArraySelect-4                   3.140µ ±  1%
JQTransform_Complex-4                       34.80µ ±  1%
JQTransform_Throughput-4                    1.771µ ±  8%
SSEPublishDelivery-4                        76.61n ±  2%
geomean                                     1.666µ

                                 │ benchmark-results.txt │
                                 │         B/op          │
JQTransform_Simple-4                      1.273Ki ± 0%
JQTransform_ObjectConstruction-4          1.773Ki ± 0%
JQTransform_ArraySelect-4                 2.625Ki ± 0%
JQTransform_Complex-4                     16.22Ki ± 0%
JQTransform_Throughput-4                  1.984Ki ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                 │ benchmark-results.txt │
                                 │       allocs/op       │
JQTransform_Simple-4                        10.00 ± 0%
JQTransform_ObjectConstruction-4            15.00 ± 0%
JQTransform_ArraySelect-4                   30.00 ± 0%
JQTransform_Complex-4                       324.0 ± 0%
JQTransform_Throughput-4                    17.00 ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
cpu: AMD EPYC 7763 64-Core Processor                
                                    │ baseline-bench.txt │
                                    │       sec/op       │
SchemaValidation_Simple-4                   1.106µ ± 19%
SchemaValidation_AllFields-4                1.663µ ±  4%
SchemaValidation_FormatValidation-4         1.593µ ±  7%
SchemaValidation_ManySchemas-4              1.799µ ±  3%
geomean                                     1.515µ

                                    │ baseline-bench.txt │
                                    │        B/op        │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │
                                    │     allocs/op      │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                    │ benchmark-results.txt │
                                    │        sec/op         │
SchemaValidation_Simple-4                      1.043µ ±  6%
SchemaValidation_AllFields-4                   1.569µ ± 14%
SchemaValidation_FormatValidation-4            1.523µ ±  5%
SchemaValidation_ManySchemas-4                 1.513µ ±  4%
geomean                                        1.393µ

                                    │ benchmark-results.txt │
                                    │         B/op          │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

                                    │ benchmark-results.txt │
                                    │       allocs/op       │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
cpu: AMD EPYC 7763 64-Core Processor                
                                   │ baseline-bench.txt │
                                   │       sec/op       │
EventStoreAppend_InMemory-4                1.139µ ± 21%
EventStoreAppend_SQLite-4                  1.350m ±  5%
GetTimeline_InMemory/events-10-4           14.60µ ±  3%
GetTimeline_InMemory/events-50-4           81.33µ ± 19%
GetTimeline_InMemory/events-100-4          131.8µ ±  1%
GetTimeline_InMemory/events-500-4          672.3µ ±  2%
GetTimeline_InMemory/events-1000-4         1.376m ±  0%
GetTimeline_SQLite/events-10-4             111.6µ ±  1%
GetTimeline_SQLite/events-50-4             262.5µ ±  0%
GetTimeline_SQLite/events-100-4            442.3µ ±  1%
GetTimeline_SQLite/events-500-4            1.877m ±  0%
GetTimeline_SQLite/events-1000-4           3.648m ±  2%
geomean                                    228.0µ

                                   │ baseline-bench.txt │
                                   │        B/op        │
EventStoreAppend_InMemory-4                  812.5 ± 8%
EventStoreAppend_SQLite-4                  1.985Ki ± 1%
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%
geomean                                    67.50Ki

                                   │ baseline-bench.txt │
                                   │     allocs/op      │
EventStoreAppend_InMemory-4                  7.000 ± 0%
EventStoreAppend_SQLite-4                    53.00 ± 0%
GetTimeline_InMemory/events-10-4             125.0 ± 0%
GetTimeline_InMemory/events-50-4             653.0 ± 0%
GetTimeline_InMemory/events-100-4           1.306k ± 0%
GetTimeline_InMemory/events-500-4           6.514k ± 0%
GetTimeline_InMemory/events-1000-4          13.02k ± 0%
GetTimeline_SQLite/events-10-4               382.0 ± 0%
GetTimeline_SQLite/events-50-4              1.852k ± 0%
GetTimeline_SQLite/events-100-4             3.681k ± 0%
GetTimeline_SQLite/events-500-4             18.54k ± 0%
GetTimeline_SQLite/events-1000-4            37.29k ± 0%
geomean                                     1.162k

cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                   │ benchmark-results.txt │
                                   │        sec/op         │
EventStoreAppend_InMemory-4                   1.144µ ±  3%
EventStoreAppend_SQLite-4                     1.041m ± 10%
GetTimeline_InMemory/events-10-4              13.88µ ±  1%
GetTimeline_InMemory/events-50-4              77.46µ ±  3%
GetTimeline_InMemory/events-100-4             156.1µ ±  2%
GetTimeline_InMemory/events-500-4             792.9µ ±  2%
GetTimeline_InMemory/events-1000-4            1.286m ± 28%
GetTimeline_SQLite/events-10-4                86.84µ ± 11%
GetTimeline_SQLite/events-50-4                239.9µ ±  5%
GetTimeline_SQLite/events-100-4               459.5µ ±  6%
GetTimeline_SQLite/events-500-4               1.957m ±  0%
GetTimeline_SQLite/events-1000-4              3.833m ±  2%
geomean                                       222.4µ

                                   │ benchmark-results.txt │
                                   │         B/op          │
EventStoreAppend_InMemory-4                     788.5 ± 6%
EventStoreAppend_SQLite-4                     1.985Ki ± 2%
GetTimeline_InMemory/events-10-4              7.953Ki ± 0%
GetTimeline_InMemory/events-50-4              46.62Ki ± 0%
GetTimeline_InMemory/events-100-4             94.48Ki ± 0%
GetTimeline_InMemory/events-500-4             472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4            944.3Ki ± 0%
GetTimeline_SQLite/events-10-4                16.74Ki ± 0%
GetTimeline_SQLite/events-50-4                87.14Ki ± 0%
GetTimeline_SQLite/events-100-4               175.4Ki ± 0%
GetTimeline_SQLite/events-500-4               846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4              1.639Mi ± 0%
geomean                                       67.34Ki

                                   │ benchmark-results.txt │
                                   │       allocs/op       │
EventStoreAppend_InMemory-4                     7.000 ± 0%
EventStoreAppend_SQLite-4                       53.00 ± 0%
GetTimeline_InMemory/events-10-4                125.0 ± 0%
GetTimeline_InMemory/events-50-4                653.0 ± 0%
GetTimeline_InMemory/events-100-4              1.306k ± 0%
GetTimeline_InMemory/events-500-4              6.514k ± 0%
GetTimeline_InMemory/events-1000-4             13.02k ± 0%
GetTimeline_SQLite/events-10-4                  382.0 ± 0%
GetTimeline_SQLite/events-50-4                 1.852k ± 0%
GetTimeline_SQLite/events-100-4                3.681k ± 0%
GetTimeline_SQLite/events-500-4                18.54k ± 0%
GetTimeline_SQLite/events-1000-4               37.29k ± 0%
geomean                                        1.162k

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

Closes the scope gap flagged by code-reviewer + Copilot on Task 4
(implementer-1's commit 288f68d) of the spaces-key-iac-resource plan: the
R-A9 rule now emits Severity="ERROR", but alignExitCode, runInfraAlign's
exit-code error formatting, and renderAlignMarkdown all only recognized
"FAIL" and "WARN". Result: R-A9's flip from WARN → ERROR silently
downgraded to non-blocking — a hidden regression on the rev3 requirement
that the doubled-create anti-pattern fail CI without --strict.

Changes:
- alignExitCode: treat ERROR identically to FAIL (always blocks; --strict
  irrelevant). Documented severity contract in the function comment.
- runInfraAlign: include ERROR in the failCount tallied for the error
  message after a non-zero exit.
- renderAlignMarkdown: count ERROR alongside FAIL/WARN; surface "N ERROR"
  in the summary line so CI consumers see the deploy-blocking signal.
- AlignFinding doc comment: enumerate the three severities + their
  blocking semantics so future rule authors don't repeat this mistake.

New tests:
- TestAlignExitCode_ErrorSeverity_Returns1: ERROR returns exit 1 with
  strict=false AND strict=true (regression sentinel).
- TestAlignRender_ErrorSeverity_CountedInSummary: summary line includes
  "1 ERROR" alongside "1 WARN".

Smoke-tested locally:
  $ /tmp/wfctl infra align -c /tmp/test-ra9.yaml
  …
  | R-A9 | ERROR | SPACES_access_key | … |
  1 ERROR
  error: align: 1 finding(s) require attention
  exit=1

Plan: docs/plans/2026-05-08-spaces-key-iac-resource.md (commit 316559f7),
extending Task 4 of PR1 per code-reviewer scope finding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@intel352

intel352 commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Scope extension — closes the gap flagged by code-reviewer (and Copilot independently) on Task 4's implementation: alignExitCode, runInfraAlign exit-code formatting, and renderAlignMarkdown only recognized FAIL/WARN, so R-A9's WARN→ERROR flip silently downgraded to non-blocking.

Commit 33df18c:

  • alignExitCode: treats ERROR identically to FAIL (always blocks; --strict irrelevant). Severity contract documented.
  • runInfraAlign: counts ERROR in failCount.
  • renderAlignMarkdown: counts ERROR alongside FAIL/WARN; "N ERROR" appears in summary line.
  • AlignFinding doc comment now enumerates all three severities + blocking semantics.
  • New tests: TestAlignExitCode_ErrorSeverity_Returns1 (regression sentinel for both strict=true and strict=false) + TestAlignRender_ErrorSeverity_CountedInSummary.

Smoke-tested:

$ /tmp/wfctl infra align -c /tmp/test-ra9.yaml
…
| R-A9 | ERROR | SPACES_access_key | … |
1 ERROR
error: align: 1 finding(s) require attention
exit=1

Full alignment suite stays green: go test ./cmd/wfctl -run "TestInfraAlign|TestCheckRA|TestAlign" → ok.

Addresses three Copilot inline review comments on PR #583:

1. cmd/wfctl/infra_align_rules.go:727 — R-A9 diagnostic message no longer
   leaks the internal `providerCredentialSubKeys[%q]` Go symbol. Reworded
   to user-facing language: "use canonical key %q — bootstrap auto-derives
   the sub-keys for source %q".

2. docs/WFCTL.md (`infra align` section) — replaces the FAIL-or-WARN-only
   prose with an explicit three-tier severity table that documents ERROR's
   semantics (always non-zero exit, equivalent to FAIL for the gate, used
   for fix-suggestion-bearing rules like R-A9). Updates the --strict flag
   row to clarify that FAIL/ERROR always block regardless of --strict.

3. cmd/wfctl/infra_align_test.go:1014 — extends
   TestInfraAlign_RA9_SuspiciousKey_Fires to assert the user-visible CI
   gate behavior (alignExitCode returns 1 for both strict=false and
   strict=true), not just the severity label. Pairs the rule severity
   assertion with the exit-code assertion at the same boundary.

All RA9/RA10 + alignment fixture suites stay green:
  go test ./cmd/wfctl -run "TestInfraAlign|TestCheckRA|TestAlign" → ok

Smoke-tested via built wfctl: message + exit code both render correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 9, 2026 05:55
@intel352

intel352 commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Copilot round 2 addressed (commit 3ee2594):

  1. R-A9 message wording (cmd/wfctl/infra_align_rules.go:727) — reworded to drop the internal providerCredentialSubKeys[%q] symbol. Now reads: "use canonical key %q — bootstrap auto-derives the sub-keys for source %q". User-facing, no implementation details.

  2. infra align severity docs (docs/WFCTL.md) — replaced the FAIL-or-WARN-only prose with an explicit three-tier severity bullet list explaining ERROR's semantics (always non-zero exit, equivalent to FAIL for the gate, paired with fix-suggestions). Also updated the --strict flag row to clarify that FAIL/ERROR always block regardless of the flag.

  3. End-to-end exit-code assertion (cmd/wfctl/infra_align_test.go:1014) — extended TestInfraAlign_RA9_SuspiciousKey_Fires to assert the user-visible CI gate behavior: alignExitCode returns 1 for both strict=false and strict=true, not just the severity label.

Smoke-tested:

| R-A9 | ERROR | SPACES_access_key | provider_credential key "SPACES_access_key" ends in "_access_key"; use canonical key "SPACES" — bootstrap auto-derives the sub-keys for source "digitalocean.spaces" |
1 ERROR
error: align: 1 finding(s) require attention
exit=1

Full alignment suite green: go test ./cmd/wfctl -run "TestInfraAlign|TestCheckRA|TestAlign" → ok.

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

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

Comment on lines +707 to +708
// ERROR in rev3 so `wfctl infra align --strict` blocks deploy when the
// anti-pattern is present).
Comment on lines 726 to 729
Message: fmt.Sprintf(
"provider_credential key %q ends with auto-generated suffix %q — use root key (e.g. %q) and let bootstrapSecrets derive sub-keys",
gen.Key, suffix, strings.TrimSuffix(gen.Key, suffix),
"provider_credential key %q ends in %q; use canonical key %q — bootstrap auto-derives the sub-keys for source %q",
gen.Key, suffix, strings.TrimSuffix(gen.Key, suffix), gen.Source,
),
Comment on lines +886 to +891
// TestAlignExitCode_ErrorSeverity_Returns1 verifies that ERROR severity
// (introduced in rev3 of the spaces-key plan for R-A9) blocks deploy with
// exit 1 even without --strict. Without this, an ERROR finding silently
// downgrades to non-blocking — defeating the rev3 requirement that the
// doubled-create anti-pattern fail CI.
func TestAlignExitCode_ErrorSeverity_Returns1(t *testing.T) {
Comment on lines +903 to +909
// TestAlignRender_ErrorSeverity_CountedInSummary verifies that the markdown
// summary includes ERROR alongside FAIL/WARN counts. Without this, ERROR
// findings would render in the table but be invisible in the summary line,
// hiding the deploy-blocking signal from CI consumers.
func TestAlignRender_ErrorSeverity_CountedInSummary(t *testing.T) {
findings := []AlignFinding{
{Rule: "R-A9", Severity: "ERROR", Resource: "SPACES_access_key", Message: "doubled-create"},
@intel352 intel352 merged commit 8de95b4 into main May 9, 2026
24 checks passed
@intel352 intel352 deleted the feat/r-a9-error branch May 9, 2026 06:10
intel352 added a commit that referenced this pull request May 9, 2026
#586)

Records the closeout for Tasks 5+6 of the spaces-key-iac-resource plan
(docs/plans/2026-05-08-spaces-key-iac-resource.md, commit 316559f7).

The plan's PR2 was specified as a migration of core-dump/infra.yaml from
a two-entry SPACES_access_key/SPACES_secret_key provider_credential
schema to canonical single-entry. At impl time, verification against
origin/main HEAD 3bb46833 showed the file already had the canonical
shape — the migration had landed in PR #190 (TC1 cutover) or PR #194
(TC2 cutover) before the plan was authored.

Smoke-confirmed: `wfctl infra align --strict -c infra.yaml --env staging`
returns exit 0 with "No alignment issues found." — no R-A9 firing
because there's nothing to fire on.

Tasks 5+6 are marked completed as a no-op confirmation. PR1's R-A9
severity flip (workflow #583, merged) provides the ongoing regression
protection: any future reintroduction of the two-entry shape will hit
ERROR R-A9 at align-strict time, exit 1.

ADR also captures the planner-blindspot lesson (operator memory about
file shapes is unreliable; re-fetch origin/main before locking a plan
that mutates external repo files) for the post-merge retro.

Per team-lead's user-direction routing of the (a/b/c) options I had
surfaced.

Co-authored-by: Claude Opus 4.7 <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