Skip to content

test(secrets): failing test for generateDOSpacesKey created_at (PR4a Task 7)#582

Merged
intel352 merged 7 commits into
mainfrom
feat/spaces-key-storage-filter
May 9, 2026
Merged

test(secrets): failing test for generateDOSpacesKey created_at (PR4a Task 7)#582
intel352 merged 7 commits into
mainfrom
feat/spaces-key-storage-filter

Conversation

@intel352

@intel352 intel352 commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a failing test for generateDOSpacesKey that asserts the function surfaces the DO API's created_at timestamp alongside access_key+secret_key in its JSON output.

This is Task 7 of PR4a in the spaces-key-iac-resource plan. Per ADR 0020 same-commit constraint, PR4a Tasks 7 (failing test) → 8 (impl) → 9 (storage-filter test) → 10 (storage-filter impl) all land on this branch and squash-merge atomically.

Why created_at?

The upcoming SpacesKeyDriver IaC resource (PR4b) keys observed-key adoption on creation timestamps — without created_at flowing through generateDOSpacesKey, the driver cannot match an in-state key to its remote counterpart.

Test failure mode

Running go test ./secrets -run TestGenerateDOSpacesKey_IncludesCreatedAt -v against this commit fails — the current implementation only extracts access_key+secret_key from the response. (Failure mode in CI without the env-var hook resolves to a DIGITALOCEAN_API_URL test stub that Task 8 will honor; on a network-attached runner it fails with HTTP 401 from real DO since DIGITALOCEAN_TOKEN=stub.)

Test plan

  • Task 7 (this commit): go test ./secrets -run TestGenerateDOSpacesKey_IncludesCreatedAt -v FAILS
  • Task 8: extends response struct + adds DIGITALOCEAN_API_URL test hook → test PASSES
  • Task 9: adds failing test for bootstrapSecrets storage-filter
  • Task 10: implements storage-filter + tags workflow release

Plan reference

docs/plans/2026-05-08-spaces-key-iac-resource.md (commit 316655f7), ### Task 7: section.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 9, 2026 05:36

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

Adds a new (currently failing) regression test in secrets to require generateDOSpacesKey to surface DigitalOcean’s created_at timestamp in its JSON output, which is needed for the upcoming SpacesKeyDriver IaC resource to match/adopt keys by creation time.

Changes:

  • Adds TestGenerateDOSpacesKey_IncludesCreatedAt asserting access_key, secret_key, and created_at are present in the generator output.
  • Introduces an httptest stub response that includes a created_at field.

Comment on lines +282 to +286
t.Setenv("DIGITALOCEAN_TOKEN", "stub")
t.Setenv("DIGITALOCEAN_API_URL", srv.URL) // hook used by generateDOSpacesKey for tests

raw, err := generateDOSpacesKey(context.Background(), map[string]any{"name": "test-key"})
if err != nil {
Comment on lines +267 to +269
if r.URL.Path != "/v2/spaces/keys" || r.Method != http.MethodPost {
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
}
…crets

Adds TestBootstrapSecrets_StorageFilter_OnlyPersistsSubKeys: with the
post-Task-8 generateDOSpacesKey shape (access_key + secret_key + created_at),
bootstrapSecrets must store only the canonical sub-keys defined in
providerCredentialSubKeys. The created_at sidecar field must NOT leak into
GH Secrets, where it would surface as a phantom SPACES_created_at entry and
break the audit-keys/prune contract.

This is the failing test for Task 9 of the spaces-key-iac-resource plan
(PR4a). Task 10 will implement the sub-key allow-list filter in
bootstrapSecrets to make it pass.

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

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

intel352 commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Task 9 stackedtest(wfctl): bootstrapSecrets must filter sidecar metadata from GH Secrets (commit 23d17b4)

Adds TestBootstrapSecrets_StorageFilter_OnlyPersistsSubKeys: verifies that with the post-Task-8 generateDOSpacesKey shape (access_key + secret_key + created_at), bootstrapSecrets must filter to the canonical sub-keys before persisting to the GH Secrets store. Currently fails with:

SPACES_created_at MUST NOT be stored as a GH Secret (storage-filter regression);
stored=map[SPACES_access_key:AK SPACES_created_at:2026-05-08T10:00:00Z SPACES_secret_key:SK]

Task 10 (implementer-3) will add the sub-key allow-list filter in bootstrapSecrets to make this pass. Per ADR 0020 same-commit constraint, Tasks 7+8+9+10 land together in this PR's squash-merge.

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 thread secrets/generators.go Outdated
Comment on lines +168 to +175
// DIGITALOCEAN_API_URL is a test-only hook so unit tests can redirect
// generateDOSpacesKey at a httptest.Server. In production the env var is
// unset and we fall through to the canonical DO endpoint.
apiURL := "https://api.digitalocean.com"
if v := os.Getenv("DIGITALOCEAN_API_URL"); v != "" {
apiURL = v
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL+"/v2/spaces/keys", bytes.NewReader(body))
Comment thread secrets/generators.go Outdated
Comment on lines +171 to +175
apiURL := "https://api.digitalocean.com"
if v := os.Getenv("DIGITALOCEAN_API_URL"); v != "" {
apiURL = v
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL+"/v2/spaces/keys", bytes.NewReader(body))
Comment thread secrets/generators.go
Comment on lines 193 to 208
var result struct {
Key struct {
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
CreatedAt string `json:"created_at"`
} `json:"key"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("secrets: DO spaces key parse: %w", err)
}

out, err := json.Marshal(map[string]string{
"access_key": result.Key.AccessKey,
"secret_key": result.Key.SecretKey,
"created_at": result.Key.CreatedAt,
})
Comment on lines +188 to +191
// This is the failing test for Task 9 of the spaces-key-iac-resource plan.
// Until Task 10 implements the sub-key allow-list filter in bootstrapSecrets,
// this test fails at the SPACES_created_at assertion.
func TestBootstrapSecrets_StorageFilter_OnlyPersistsSubKeys(t *testing.T) {
intel352 and others added 2 commits May 9, 2026 01:46
…sub-keys (ADR 0020)

bootstrapSecrets now persists only the canonical provider_credential sub-keys
listed in providerCredentialSubKeys[gen.Source]. Sidecar metadata (e.g.
created_at returned by generateDOSpacesKey since Task 8) is captured into a
new RotationResult value AND emitted to stderr as `WFCTL_NEW_KEY_<UPPER>=<v>`
lines for operator observability + audit logs, but is no longer stored as a
GH Secret. This closes the contract gap where every bootstrap of a
digitalocean.spaces credential would produce a phantom SPACES_created_at
secret that audit-keys/prune cannot reconcile against the upstream provider.

Behavior changes:
- bootstrapSecrets is now declared as `var bootstrapSecrets = func(...)` so
  Task 20 (rotate-and-prune) can override it without going through the real
  provider/network path. Same pattern as the existing `var generateSecret`
  test hook.
- New return shape: (map[string]string, []RotationResult, error). The
  rotations slice is non-empty only when --force-rotate triggered new
  credential creation; nil for idempotent skip-if-exists runs. Eliminates
  the subprocess + stderr-parse pattern PR-side that Task 21 would have
  needed.
- New exported type RotationResult{Key, Source, AccessKey, CreatedAt}.
- 18 test callsites + 1 production caller updated for the new signature
  (rotations slice discarded as `_` everywhere except future Task 21).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@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:316842: parsing iteration count: invalid syntax
benchmark-results.txt:605110: parsing iteration count: invalid syntax
benchmark-results.txt:950063: parsing iteration count: invalid syntax
benchmark-results.txt:1278051: parsing iteration count: invalid syntax
benchmark-results.txt:1605508: 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              4.682m ± 128%   4.627m ± 108%       ~ (p=0.699 n=6)
ComponentLoad-4                    3.600m ±  11%   3.552m ±   1%  -1.32% (p=0.002 n=6)
ComponentExecute-4                 1.934µ ±   2%   1.963µ ±   2%  +1.50% (p=0.019 n=6)
PoolContention/workers-1-4         1.083µ ±   2%   1.082µ ±   3%       ~ (p=0.981 n=6)
PoolContention/workers-2-4         1.091µ ±   2%   1.085µ ±   3%       ~ (p=0.394 n=6)
PoolContention/workers-4-4         1.086µ ±   1%   1.093µ ±   1%       ~ (p=0.437 n=6)
PoolContention/workers-8-4         1.082µ ±   0%   1.101µ ±   2%  +1.76% (p=0.006 n=6)
PoolContention/workers-16-4        1.088µ ±   1%   1.096µ ±   1%       ~ (p=0.258 n=6)
ComponentLifecycle-4               3.599m ±   1%   3.619m ±   1%       ~ (p=0.180 n=6)
SourceValidation-4                 2.319µ ±   1%   2.337µ ±   1%  +0.75% (p=0.017 n=6)
RegistryConcurrent-4               795.2n ±   3%   842.5n ±   4%  +5.94% (p=0.004 n=6)
LoaderLoadFromString-4             3.615m ±   1%   3.623m ±   2%       ~ (p=0.240 n=6)
geomean                            18.03µ          18.16µ         +0.73%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.909 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=1.000 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.818 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.543 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                  288.5n ± 4%   286.9n ± 14%       ~ (p=1.000 n=6)
CircuitBreakerExecution_Success-4          21.56n ± 0%   21.57n ±  2%       ~ (p=0.481 n=6)
CircuitBreakerExecution_Failure-4          66.17n ± 0%   66.33n ±  0%  +0.24% (p=0.026 n=6)
geomean                                    74.39n        74.31n        -0.10%

                                  │ 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                     937.4n ± 18%   857.7n ± 30%       ~ (p=0.937 n=6)
JQTransform_ObjectConstruction-4         1.417µ ±  1%   1.420µ ±  1%       ~ (p=0.619 n=6)
JQTransform_ArraySelect-4                3.250µ ±  2%   3.276µ ±  0%       ~ (p=0.288 n=6)
JQTransform_Complex-4                    38.30µ ±  2%   37.72µ ±  1%  -1.52% (p=0.002 n=6)
JQTransform_Throughput-4                 1.726µ ±  0%   1.727µ ±  0%       ~ (p=0.738 n=6)
SSEPublishDelivery-4                     63.14n ±  1%   63.22n ±  1%       ~ (p=0.734 n=6)
geomean                                  1.619µ         1.594µ        -1.54%

                                 │ 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.106µ ± 19%   1.111µ ±  3%       ~ (p=0.818 n=6)
SchemaValidation_AllFields-4                1.663µ ±  4%   1.701µ ± 17%       ~ (p=0.368 n=6)
SchemaValidation_FormatValidation-4         1.593µ ±  7%   1.600µ ±  1%       ~ (p=0.608 n=6)
SchemaValidation_ManySchemas-4              1.799µ ±  3%   1.816µ ±  2%       ~ (p=0.329 n=6)
geomean                                     1.515µ         1.531µ        +1.05%

                                    │ 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.139µ ± 21%   1.207µ ±  9%       ~ (p=0.699 n=6)
EventStoreAppend_SQLite-4                  1.350m ±  5%   1.360m ±  3%       ~ (p=0.485 n=6)
GetTimeline_InMemory/events-10-4           14.60µ ±  3%   13.51µ ±  3%  -7.49% (p=0.002 n=6)
GetTimeline_InMemory/events-50-4           81.33µ ± 19%   76.87µ ±  3%       ~ (p=0.394 n=6)
GetTimeline_InMemory/events-100-4          131.8µ ±  1%   153.1µ ± 22%       ~ (p=0.394 n=6)
GetTimeline_InMemory/events-500-4          672.3µ ±  2%   624.5µ ±  0%  -7.11% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.376m ±  0%   1.277m ±  1%  -7.19% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             111.6µ ±  1%   106.0µ ±  0%  -5.01% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             262.5µ ±  0%   246.6µ ±  0%  -6.06% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            442.3µ ±  1%   415.3µ ±  0%  -6.10% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.877m ±  0%   1.773m ±  1%  -5.51% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.648m ±  2%   3.452m ±  0%  -5.38% (p=0.002 n=6)
geomean                                    228.0µ         221.4µ        -2.92%

                                   │ baseline-bench.txt │         benchmark-results.txt         │
                                   │        B/op        │     B/op       vs base                │
EventStoreAppend_InMemory-4                  812.5 ± 8%     777.5 ± 10%       ~ (p=0.699 n=6)
EventStoreAppend_SQLite-4                  1.985Ki ± 1%   1.984Ki ±  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.242 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ±  0%  +0.00% (p=0.041 n=6)
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%   16.74Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%   87.14Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%   175.4Ki ±  0%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%   846.1Ki ±  0%  -0.00% (p=0.006 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ±  0%  +0.00% (p=0.006 n=6)
geomean                                    67.50Ki        67.25Ki        -0.37%
¹ 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.

Copilot AI review requested due to automatic review settings May 9, 2026 05:49

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 10 out of 10 changed files in this pull request and generated 4 comments.

Comment on lines 657 to +660
if gen.Type == "provider_credential" {
var subKeyMap map[string]string
if jsonErr := json.Unmarshal([]byte(value), &subKeyMap); jsonErr == nil {
allowedSubKeys, ok := providerCredentialSubKeys[gen.Source]
Comment thread secrets/generators.go Outdated
Comment on lines +171 to +175
apiURL := "https://api.digitalocean.com"
if v := os.Getenv("DIGITALOCEAN_API_URL"); v != "" {
apiURL = v
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL+"/v2/spaces/keys", bytes.NewReader(body))
Comment on lines +260 to +264
// TestGenerateDOSpacesKey_IncludesCreatedAt asserts that generateDOSpacesKey
// surfaces the DO API's `created_at` timestamp alongside access_key+secret_key
// in its JSON output. This is required by the upcoming SpacesKeyDriver IaC
// resource (PR4b), which keys observed-key adoption on creation timestamps.
func TestGenerateDOSpacesKey_IncludesCreatedAt(t *testing.T) {
Comment on lines +188 to +190
// This is the failing test for Task 9 of the spaces-key-iac-resource plan.
// Until Task 10 implements the sub-key allow-list filter in bootstrapSecrets,
// this test fails at the SPACES_created_at assertion.
…verride security risk

Removes the DIGITALOCEAN_API_URL env-var override from generateDOSpacesKey
that was added in Task 8 (commit 1f03a7d). The override was honored
unconditionally in production: an attacker who could set the env var
(malicious .env, hostile CI step, multi-tenant runner, compromised
dependency) would silently redirect the
`Authorization: Bearer <DIGITALOCEAN_TOKEN>` POST to their own server,
exfiltrating the production credential with no detection signal.

Switches TestGenerateDOSpacesKey_IncludesCreatedAt (Task 7's failing test,
commit 03a9ad7) to the package's existing rewriteTransport helper —
defined in secrets/github_provider_test.go and already used by the three
sister DOSpaces tests at generators_test.go lines 107-109, 156-158,
211-213. Hermetic in-test mechanism, zero production attack surface.

Plan deviation recorded as ADR 0021. The original plan
(docs/plans/2026-05-08-spaces-key-iac-resource.md, Tasks 7-8) literally
prescribed the env-var hook; security review caught it. Per
feedback_proper_fixes_over_workarounds we fix the mechanism, not patch
around it.

Verification:
- `GOWORK=off go test ./secrets -run TestGenerateDOSpacesKey_IncludesCreatedAt -v` → PASS
- `GOWORK=off go test ./secrets` → all PASS
- `GOWORK=off go build ./...` → clean

Thanks to code-reviewer (and Copilot at PR #582 comment 3212559090) for
catching this — exactly the class of issue the 2-stage review exists for.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@intel352 intel352 merged commit 6c9ef6b into main May 9, 2026
23 checks passed
@intel352 intel352 deleted the feat/spaces-key-storage-filter branch May 9, 2026 06:09
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