Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
205 commits
Select commit Hold shift + click to select a range
deac816
chore(ui-deps): upgrade frontend dependencies, gated green
Jul 5, 2026
f959827
chore(deps): full go get -u ./... upgrade, gated green
Jul 5, 2026
6528c97
chore: sync beads tracker, drop obsolete spa/.keep placeholder
Jul 5, 2026
a41e91c
chore: untrack .claude/settings.local.json, add to gitignore
Jul 5, 2026
8aad822
chore: gitignore transient checkpoint.md
Jul 5, 2026
ae10a86
chore(ci): add go fix modernize gate, apply remaining modernization
Jul 5, 2026
708d196
fix(s3): AWS-parity fixes — SSE persistence, ACL/replication/presign …
Jul 5, 2026
c18fa9b
fix(ec2): tag every taggable resource type; real instance-attribute/l…
Jul 5, 2026
f459c9f
fix(dynamodb): close Select/COUNT, batch-write duplicate-key, and tra…
Jul 5, 2026
5e8140e
chore: restore dashboard/static/spa/.keep placeholder
Jul 5, 2026
8baa462
fix(sns): wire SNS->Lambda and SNS->Firehose delivery in composition …
Jul 5, 2026
c3b5d46
fix(lambda): RemovePermission wire-shape, ESM qualifier ARN parsing, …
Jul 5, 2026
71cd544
fix(iam): disguised stubs, HTTP status codes, policy-doc encoding, pe…
Jul 5, 2026
58e50f3
docs(parity): add per-service PARITY.md manifests + schema template
Jul 5, 2026
647a8ce
fix(sns): PublishBatch attribute wire-shape, signed-envelope, replay …
Jul 5, 2026
17a215e
fix(sqs): duplicate XML decl, FIFO requeue ordering, RedriveAllowPoli…
Jul 5, 2026
58eec06
fix(cloudwatchlogs): drop deprecated seq-token validation, metric-fil…
Jul 5, 2026
ede7169
fix(cloudwatch): PutMetricData all-or-nothing, Values/Counts arrays, …
Jul 5, 2026
6548cf8
fix(kms): GrantTokens on 8 grant-valid ops, error class, grant-index …
Jul 5, 2026
86c2f9a
fix(cloudformation): idempotent DeleteStack, export-in-use enforcemen…
Jul 5, 2026
f2750d4
fix(ecs): rebuild serviceIndex on restore, persist resourceTags, real…
Jul 5, 2026
fba3c78
fix(cli): match cwBk.PutMetricData new single-return signature
Jul 5, 2026
23de3aa
fix(ecr): layer-upload FK/sequencing validation, PutImage digest veri…
Jul 5, 2026
f093a92
fix(rds): delete final-snapshot contract, DescribeDBInstances filters…
Jul 5, 2026
647d201
fix(secretsmanager): data race on lazy store, wire-field names, versi…
Jul 5, 2026
0b1194b
fix(ssm): parameter tier/version/hierarchy rules, document wire-shape…
Jul 5, 2026
e069888
fix(apigatewayv2): per-protocol integration timeout, stage-tag 404, m…
Jul 5, 2026
f222f37
fix(apigateway): rewrite PATCH semantics — multi-segment paths, value…
Jul 5, 2026
e5a9ac6
fix(kinesis): tag persistence data-loss, PutRecords not-found, ON_DEM…
Jul 5, 2026
9f33680
fix(stepfunctions): Map failure swallowing, empty history events, EXP…
Jul 5, 2026
d0ebe97
fix(eventbridge): target-type param structs, cron day-of-week, PutEve…
Jul 5, 2026
d118e0d
fix(autoscaling): revive dead lifecycle-hook timers, LaunchInstances …
Jul 5, 2026
017fc20
fix(elbv2): trust-store revocation wire shape, 400 error status, ALPN…
Jul 5, 2026
e5205db
fix(route53): tag routing/persistence, CallerReference idempotency, e…
Jul 5, 2026
e783037
fix(cloudfront): InconsistentQuantities validation, per-resource Alre…
Jul 5, 2026
7d0baf7
fix(elasticache): systematic error code/status, unreached sentinel, S…
Jul 5, 2026
a40e7cc
fix(cognitoidp): real TOTP MFA (was accept-any-code stub), user-exist…
Jul 5, 2026
0407b38
fix(ses): void-result XML wire shape, sending-paused/quota enforcemen…
Jul 5, 2026
704d7cd
fix(sts): TradeInToken expiry validation, signing algorithms, GetWebI…
Jul 5, 2026
7d16709
fix(glue): BatchGetPartition stub, error codes, orphan partitions, dr…
Jul 5, 2026
fb339b3
feat(store): generic typed datalayer store (Phase 3.1)
Jul 5, 2026
0f09d77
refactor(sqs): convert backend maps to pkgs/store (Phase 3.2 pilot)
Jul 5, 2026
12e611a
refactor(ec2): convert 147 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
f2e87b4
refactor(sagemaker): convert 70 region-nested backend maps to pkgs/st…
Jul 6, 2026
1ffc93d
refactor(cloudformation): convert backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
2472bb7
refactor(dynamodb): convert 7 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
496c52c
refactor(s3): convert backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
031a438
refactor(lambda): convert 9 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
91c888d
refactor(iam): convert 14 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
f5b6ec3
refactor(glue): convert 37 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
6db6e2a
refactor(ecs): convert 15 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
4179a2f
refactor(rds): convert 26 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
4c68677
refactor(ssm): convert 18 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
58b7152
refactor(iot): convert 35 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
e7dbfa8
refactor(quicksight): convert 27 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
6da0334
refactor(apigateway): convert 18 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
be6963d
refactor(cognitoidp): convert 12 backend maps to pkgs/store + elimina…
Jul 6, 2026
490d797
refactor(cloudfront): convert 23 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
82f8d5c
refactor(redshift): convert 29 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
e12611d
refactor(cloudwatch): convert 8 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
3506632
refactor(kms): convert 4 backend maps to pkgs/store + collapse grant …
Jul 6, 2026
3297f38
refactor(cloudwatchlogs): convert 26 backend maps to pkgs/store (Phas…
Jul 6, 2026
9e5d75b
refactor(bedrock): convert 34 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
7a7bc74
refactor(pinpoint): convert 15 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
b1a4815
refactor(eventbridge): convert 13 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
0680631
refactor(elasticache): convert 12 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
58a799d
refactor(sns): convert 5 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
9c8d209
refactor(elbv2): convert 5 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
870feb9
refactor(appsync): convert 12 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
3a8aa26
refactor(apigatewayv2): convert 17 backend maps to pkgs/store (Phase …
Jul 6, 2026
6b78072
refactor(opensearch): convert 17 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
848e055
refactor(memorydb): convert 10 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
7cc61d2
refactor(ecr): convert 10 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
df4bc6c
refactor(route53): convert 8 backend maps to pkgs/store (Phase 3.3)
Jul 6, 2026
328c34f
refactor(kinesis): convert streams map to pkgs/store (Phase 3.3)
Jul 6, 2026
de91ba9
refactor(secretsmanager): convert secrets map to pkgs/store (Phase 3.3)
Jul 6, 2026
76c4915
refactor(autoscaling): convert 7 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
e9af4cc
refactor(ses): convert 9 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
9b93862
refactor(organizations): convert 7 backend maps to pkgs/store (Phase …
Jul 9, 2026
6424eb0
refactor(stepfunctions): convert 6 backend maps to pkgs/store (Phase …
Jul 9, 2026
34e114e
refactor(neptune): convert 9 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
bf42810
refactor(dms): convert 16 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
96d883b
refactor(backup): convert 20 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
9cc7220
refactor(athena): convert 12 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
417908d
refactor(eks): convert 10 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
c98e953
refactor(transfer): convert 15 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
a8dd759
refactor(sts): convert sessions map to pkgs/store (Phase 3.3)
Jul 9, 2026
41c81ab
refactor(kafka): convert 6 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
b9236bd
refactor(s3control): convert 12 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
5897b82
refactor(wafv2): convert 6 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
6488386
refactor(securityhub): convert 16 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
a734f65
refactor(xray): convert 11 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
6a90d7a
refactor(pipes): convert 3 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
f5f0b01
refactor(elb): convert 2 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
fa4b730
refactor(docdb): convert 8 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
ba6258a
refactor(iotwireless): convert 14 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
35cb872
refactor(glacier): convert 4 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
7996d85
fix(docdb): complete the pkgs/store conversion (backend/persistence w…
Jul 9, 2026
0e196c5
refactor(fis): convert 3 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
ab1be83
refactor(route53resolver): convert 13 backend maps to pkgs/store (Pha…
Jul 9, 2026
b3b1da6
refactor(ssoadmin): convert 9 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
621b95c
refactor(sesv2): convert 14 backend maps to pkgs/store (Phase 3.3)
Jul 9, 2026
e45329a
refactor(ram): convert 3 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
32ca766
refactor(medialive): convert 15 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
3611783
refactor(directoryservice): convert 16 region-nested maps to pkgs/sto…
Jul 10, 2026
9505aa3
refactor(lakeformation): convert 7 backend maps to pkgs/store (Phase …
Jul 10, 2026
c2f0664
refactor(codecommit): convert 8 maps to pkgs/store + add persistence …
Jul 10, 2026
6f3979f
refactor(timestreamwrite): convert 3 backend maps to pkgs/store (Phas…
Jul 10, 2026
e15fbc9
refactor(firehose): convert region-nested streams map to pkgs/store (…
Jul 10, 2026
1e9d187
refactor(mwaa): convert region-nested environments map to pkgs/store …
Jul 10, 2026
1ecba47
refactor(awsconfig): convert 14 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
3d0a615
refactor(codepipeline): convert 5 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
bc6bbb7
refactor(scheduler): convert region-nested maps to pkgs/store (Phase …
Jul 10, 2026
43fce84
refactor(emr): convert 8 region-nested maps to pkgs/store (Phase 3.3)
Jul 10, 2026
9b20684
refactor(codebuild): convert 21 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
f79892e
refactor(efs): convert 4 region-nested maps to pkgs/store (Phase 3.3)
Jul 10, 2026
4dcf99f
refactor(workspaces): convert 12 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
798a164
refactor(batch): convert 8 region-nested maps to pkgs/store (Phase 3.3)
Jul 10, 2026
8b971ef
refactor(mq): convert 2 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
0379814
refactor(inspector2): convert 16 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
08a3b21
refactor(workmail): convert 18 org-nested maps to pkgs/store + add pe…
Jul 10, 2026
3202904
refactor(macie2): convert 13 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
2d0a562
refactor(ce): convert 6 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
a325594
refactor(managedblockchain): convert 6 backend maps to pkgs/store (Ph…
Jul 10, 2026
7d8e8a8
refactor(resourcegroups): convert backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
aab2aa5
refactor(textract): convert 5 region-nested maps to pkgs/store (Phase…
Jul 10, 2026
eb853a7
refactor(appstream): convert 14 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
4308c48
refactor(codeartifact): convert 7 region-nested maps to pkgs/store (P…
Jul 10, 2026
0dd6d24
refactor(verifiedpermissions): convert 5 backend maps to pkgs/store (…
Jul 10, 2026
e124fd4
refactor(cognitoidentity): convert 4 region-nested maps to pkgs/store…
Jul 10, 2026
c0737c7
refactor(elasticsearch): convert 6 region-nested maps to pkgs/store (…
Jul 10, 2026
fac6c04
refactor(rekognition): convert 10 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
39101ca
refactor(cloudtrail): convert 7 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
b5ad7af
refactor(elasticbeanstalk): convert 5 region-nested maps to pkgs/stor…
Jul 10, 2026
d6dd82e
refactor(shield): convert 4 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
f1e86e7
refactor(codestarconnections): convert 10 maps to pkgs/store (Phase 3.3)
Jul 10, 2026
934f382
refactor(guardduty): convert 16 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
ca4a898
refactor(dax): convert 3 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
47e8c98
refactor(mediaconvert): convert 7 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
af4009b
refactor(iotanalytics): convert 4 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
7c4803e
refactor(transcribe): convert 9 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
95d6ea7
refactor(codeconnections): convert 6 region-nested maps to pkgs/store…
Jul 10, 2026
49a7328
refactor(omics): convert 24 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
8279c71
refactor(identitystore): convert 3 region-nested maps to pkgs/store (…
Jul 10, 2026
a10491b
refactor(codedeploy): convert 5 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
8963c56
refactor(swf): convert 6 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
ad23bce
refactor(iotdataplane): convert 3 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
363cf2f
refactor(acm): convert certs + handler tags to pkgs/store (Phase 3.3)
Jul 10, 2026
a55b927
refactor(opsworks): convert 15 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
bc66be5
refactor(servicediscovery): convert 9 maps to pkgs/store (Phase 3.3)
Jul 10, 2026
50b67a5
refactor(cleanrooms): convert 16 maps to pkgs/store + build persisten…
Jul 10, 2026
fa6b4b5
refactor(datasync): convert 4 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
25f22ae
refactor(mediatailor): convert 8 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
c8385f4
refactor(appconfig): convert 8 maps to pkgs/store + build persistence…
Jul 10, 2026
8bebbda
refactor(kinesisanalyticsv2): convert 2 backend maps to pkgs/store (P…
Jul 10, 2026
6983961
refactor(kinesisanalytics): convert region-nested maps to pkgs/store …
Jul 10, 2026
e6a24c2
refactor(waf): convert 13 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
09b1cbb
refactor(serverlessrepo): convert 4 backend maps to pkgs/store (Phase…
Jul 10, 2026
0a2d02b
refactor(s3tables): convert 6 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
731150e
refactor(bedrockagent): convert 15 maps to pkgs/store + build persist…
Jul 10, 2026
b85f394
refactor(vpclattice): convert 11 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
fb76ecd
refactor(fsx): convert 9 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
e3ffade
refactor(amplify): convert 8 maps to pkgs/store + build persistence (…
Jul 10, 2026
221bc56
refactor(emrserverless): convert 3 backend maps to pkgs/store (Phase …
Jul 10, 2026
616ec71
refactor(databrew): convert 6 region-nested maps to pkgs/store + buil…
Jul 10, 2026
3d90002
refactor(resourcegroupstaggingapi): convert reportStates to pkgs/stor…
Jul 10, 2026
47d2073
refactor(acmpca): convert 4 region-nested maps to pkgs/store (Phase 3.3)
Jul 10, 2026
0044b23
refactor(appmesh): convert 7 maps to pkgs/store + build persistence (…
Jul 10, 2026
1db87b7
refactor(rolesanywhere): convert 4 region-nested maps to pkgs/store (…
Jul 10, 2026
51dec5d
refactor(accessanalyzer): convert 7 backend maps to pkgs/store (Phase…
Jul 10, 2026
6d8d051
refactor(applicationautoscaling): convert 3 backend maps to pkgs/stor…
Jul 10, 2026
cc167a4
refactor(timestreamquery): convert 2 backend maps to pkgs/store (Phas…
Jul 10, 2026
d787b88
refactor(apprunner): convert 6 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
05be505
refactor(redshiftdata): snapshot version guard + persistence dead-wir…
Jul 10, 2026
70cf61b
refactor(bedrockruntime): convert asyncInvokes to pkgs/store + build …
Jul 10, 2026
93fbe22
refactor(detective): convert 3 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
369bef4
refactor(personalize): convert 16 maps to pkgs/store + build persiste…
Jul 10, 2026
ad08c83
refactor(support): convert 5 backend maps to pkgs/store (Phase 3.3)
Jul 10, 2026
4014e38
refactor(appconfigdata): convert 3 maps to pkgs/store + build persist…
Jul 10, 2026
d3e6c73
refactor(mediastore): convert container map to pkgs/store + build per…
Jul 10, 2026
ee58b42
refactor(comprehend): convert 3 maps to pkgs/store + build persistenc…
Jul 10, 2026
9f22398
refactor(polly): convert 2 maps to pkgs/store + build persistence (Ph…
Jul 10, 2026
a93b558
refactor(mediapackage): convert 4 maps to pkgs/store (Phase 3.3)
Jul 10, 2026
f9669fe
refactor(translate): convert 3 maps to pkgs/store + build persistence…
Jul 10, 2026
0da7acd
refactor(rdsdata): convert transactions map to pkgs/store (Phase 3.3)
Jul 10, 2026
1a22b55
refactor(cloudcontrol): convert 2 maps to pkgs/store + build persiste…
Jul 10, 2026
e7015e3
refactor(networkmonitor): convert monitors map to pkgs/store (Phase 3.3)
Jul 10, 2026
904422b
refactor(apigatewaymanagementapi): add snapshot version guard (Phase …
Jul 10, 2026
57b51ef
refactor(forecast): convert resources to pkgs/store + build persisten…
Jul 10, 2026
0af1453
refactor(mediastoredata): convert objects map to pkgs/store + build p…
Jul 10, 2026
216a67f
refactor(dlm): convert policies map to pkgs/store (Phase 3.3)
Jul 10, 2026
9e3a8eb
test(dynamodbstreams): assert Handler owns no persistable state (Phas…
Jul 10, 2026
a12572c
refactor(account): convert alternateContacts map to pkgs/store + buil…
Jul 10, 2026
7535f42
refactor(sagemakerruntime): convert 2 maps to pkgs/store + version gu…
Jul 10, 2026
710b0fa
chore(beads): file Phase 3.3 follow-up issues (account cli wiring, lo…
Jul 10, 2026
ceb0e22
chore(beads): file test/terraform serial-timeout observation (non-reg…
Jul 10, 2026
a002d51
fix(cli): handle PutEvents error at eventbridge adapters (Phase 3.3 f…
Jul 11, 2026
cdae9a6
test(eks): acknowledge CancelUpdate in notImplemented (closes gophers…
Jul 11, 2026
c691d3f
chore(beads): file int-3/e2e verification follow-up (post-3.3)
Jul 11, 2026
303b66d
test(integration): fix stale apigatewayv2 HTTP timeout assertion (290…
Jul 11, 2026
b7af136
chore(beads): file e2e Svelte-5 + CodeQL-config issues; note int-3 re…
Jul 11, 2026
e240d57
fix(logger): redact sensitive request headers in API console capture
Jul 11, 2026
c8cf506
fix(ui): break Vite 8 Rolldown circular import blanking all dashboard…
Jul 11, 2026
a707793
fix(chaos): sanitize extracted AWS headers to clear CodeQL taint
agbishop Jul 11, 2026
7778af0
fix(httputils): sanitize request region and account to fix CodeQL cle…
agbishop Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
168 changes: 167 additions & 1 deletion .beads/issues.jsonl

Large diffs are not rendered by default.

54 changes: 0 additions & 54 deletions .claude/settings.local.json

This file was deleted.

18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v9

modernize:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
token: ${{ env.GH_CI_TOKEN }}
ref: ${{ github.event.workflow_run.head_sha || github.sha }}

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
cache: true

- name: go fix (modernize)
run: go fix -diff ./...

govulncheck:
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ lambda.zip
.terraform.lock.hcl
.terraform.lock.hcl
/releaser

# personal local Claude Code settings (never sync bypass perms to origin)
.claude/settings.local.json

# session resume checkpoint (transient)
checkpoint.md
61 changes: 58 additions & 3 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2584,6 +2584,10 @@ func initializeServices(appCtx *service.AppContext) ([]service.Registerable, err
// Wire SNS→SQS delivery: when SNS publishes a message, deliver it to SQS queues.
wireSNSToSQS(byName["SNS"], byName["SQS"])

// Wire SNS→Lambda and SNS→Firehose subscription delivery, plus the SQS sender
// used for Lambda/Firehose subscription DLQ redelivery.
wireSNSToLambdaFirehose(byName["SNS"], byName["Lambda"], byName["Firehose"], byName["SQS"])

// Wire SQS → CloudWatch metric emission for NumberOfMessagesSent/Received/Deleted.
wireSQSMetrics(byName["SQS"], byName["CloudWatch"])

Expand Down Expand Up @@ -3145,6 +3149,54 @@ func wireSNSToSQS(snsReg, sqsReg service.Registerable) {
sqsBk.SubscribeToSNS(emitter)
}

// wireSNSToLambdaFirehose connects the SNS backend to Lambda and Firehose so that
// Lambda- and Firehose-protocol subscriptions actually receive published messages,
// and wires an SQS sender so failed Lambda/Firehose deliveries with a RedrivePolicy
// land in the subscription's dead-letter queue. This is independent of wireSNSToSQS:
// that function wires the SNS→SQS *subscription* delivery path via a publish emitter,
// while the SQS sender wired here only serves DLQ redelivery on failed Lambda/Firehose
// invocations (SNS backend's sqsSender field), so there is no overlap or double-wiring.
func wireSNSToLambdaFirehose(snsReg, lambdaReg, firehoseReg, sqsReg service.Registerable) {
snsH, ok := snsReg.(*snsbackend.Handler)
if !ok {
return
}

snsBk, ok := snsH.Backend.(*snsbackend.InMemoryBackend)
if !ok {
return
}

if lambdaH, lambdaOk := lambdaReg.(*lambdabackend.Handler); lambdaOk {
if lambdaBk, bkOk := lambdaH.Backend.(*lambdabackend.InMemoryBackend); bkOk {
snsBk.SetLambdaBackend(lambdaBk)
}
}

if firehoseH, firehoseOk := firehoseReg.(*firehosebackend.Handler); firehoseOk {
if firehoseBk, bkOk := firehoseH.Backend.(*firehosebackend.InMemoryBackend); bkOk {
snsBk.SetFirehoseBackend(&snsFirehosePutterAdapter{backend: firehoseBk})
}
}

if sqsH, sqsOk := sqsReg.(*sqsbackend.Handler); sqsOk {
if sqsBk, bkOk := sqsH.Backend.(*sqsbackend.InMemoryBackend); bkOk {
snsBk.SetSQSSender(&sqsSenderAdapter{backend: sqsBk})
}
}
}

// snsFirehosePutterAdapter adapts the Firehose backend to the sns.FirehosePutter
// interface, which omits the context parameter that the Firehose backend's
// PutRecordBatch requires.
type snsFirehosePutterAdapter struct {
backend *firehosebackend.InMemoryBackend
}

func (a *snsFirehosePutterAdapter) PutRecordBatch(streamName string, records [][]byte) (int, error) {
return a.backend.PutRecordBatch(context.Background(), streamName, records)
}

// wireSQSMetrics wires the CloudWatch metric emitter into the SQS backend so that
// SendMessage, ReceiveMessage, and DeleteMessage operations emit CloudWatch metrics.
func wireSQSMetrics(sqsReg, cwReg service.Registerable) {
Expand All @@ -3165,7 +3217,7 @@ func wireSQSMetrics(sqsReg, cwReg service.Registerable) {
sqsBk.SetMetricEmitter(
sqsbackend.MetricEmitterFunc(
func(namespace, name string, value float64, unit string) error {
_, err := cwBk.PutMetricData(namespace, []cwbackend.MetricDatum{
err := cwBk.PutMetricData(namespace, []cwbackend.MetricDatum{
{
MetricName: name,
Value: value,
Expand Down Expand Up @@ -3309,7 +3361,10 @@ func (a *s3EventBridgeAdapter) PublishS3Event(
ctx context.Context,
source, detailType, detail string,
) {
a.backend.PutEvents(ctx, []ebbackend.EventEntry{
// Best-effort S3 -> EventBridge forwarding: PublishS3Event has no error
// channel (s3.EventBridgePublisher is fire-and-forget), so the PutEvents
// error (added by the Phase 3.3 store conversion) is explicitly discarded.
_, _ = a.backend.PutEvents(ctx, []ebbackend.EventEntry{
{Source: source, DetailType: detailType, Detail: detail},
})
}
Expand Down Expand Up @@ -4204,7 +4259,7 @@ func wireCWLogsMetricEmitter(cwlogsReg, cwReg service.Registerable) {
cwlogsBk.SetMetricEmitter(
cwlogsbackend.MetricEmitterFunc(
func(namespace, name string, value float64, unit string) error {
_, err := cwBk.PutMetricData(namespace, []cwbackend.MetricDatum{
err := cwBk.PutMetricData(namespace, []cwbackend.MetricDatum{
{
MetricName: name,
Namespace: namespace,
Expand Down
4 changes: 2 additions & 2 deletions cli_adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ func (a *schedEventBusAdapter) PutSchedulerEvent(
Time: &now,
},
}
a.backend.PutEvents(ctx, entries)
_, err := a.backend.PutEvents(ctx, entries)

return nil
return err
}

type schedKinesisAdapter struct {
Expand Down
143 changes: 143 additions & 0 deletions cli_sns_wiring_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package main

import (
"context"
"encoding/json"
"io"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
s3sdk "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/blackbirdworks/gopherstack/pkgs/arn"
"github.com/blackbirdworks/gopherstack/pkgs/config"
firehosebackend "github.com/blackbirdworks/gopherstack/services/firehose"
lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda"
snsbackend "github.com/blackbirdworks/gopherstack/services/sns"
sqsbackend "github.com/blackbirdworks/gopherstack/services/sqs"
)

// wiringMockS3Storer implements firehose.S3Storer so the test can observe that a
// record delivered through the SNS→Firehose path actually reached S3.
type wiringMockS3Storer struct {
calls []wiringS3PutCall
}

type wiringS3PutCall struct {
bucket string
key string
body []byte
}

func (m *wiringMockS3Storer) PutObject(
_ context.Context,
input *s3sdk.PutObjectInput,
) (*s3sdk.PutObjectOutput, error) {
body, _ := io.ReadAll(input.Body)
m.calls = append(m.calls, wiringS3PutCall{
bucket: aws.ToString(input.Bucket),
key: aws.ToString(input.Key),
body: body,
})

return &s3sdk.PutObjectOutput{}, nil
}

// TestWireSNSToLambdaFirehose_EndToEndDelivery exercises the exact composition-root
// wiring used by cli.go's setup (wireSNSToLambdaFirehose) against REAL Lambda, Firehose,
// and SQS backends — not test doubles — to prove that subscribing a Lambda function or
// Firehose delivery stream to an SNS topic and publishing actually delivers, instead of
// silently no-opping as it did before SetLambdaBackend/SetFirehoseBackend/SetSQSSender
// were wired in the running binary.
func TestWireSNSToLambdaFirehose_EndToEndDelivery(t *testing.T) {
t.Parallel()

snsBk := snsbackend.NewInMemoryBackend()
snsH := snsbackend.NewHandler(snsBk)

lambdaBk := lambdabackend.NewInMemoryBackend(
nil, nil, lambdabackend.DefaultSettings(), config.DefaultAccountID, config.DefaultRegion,
)
lambdaH := lambdabackend.NewHandler(lambdaBk)

firehoseBk := firehosebackend.NewInMemoryBackend(config.DefaultAccountID, config.DefaultRegion)
firehoseH := firehosebackend.NewHandler(firehoseBk)

sqsBk := sqsbackend.NewInMemoryBackend()
sqsH := sqsbackend.NewHandler(sqsBk)

// This is the exact call cli.go's setupServices makes.
wireSNSToLambdaFirehose(snsH, lambdaH, firehoseH, sqsH)

ctx := context.Background()

// --- SNS -> Firehose: publish must reach the real Firehose backend and flush to S3. ---
s3mock := &wiringMockS3Storer{}
firehoseBk.SetS3Backend(s3mock)

_, err := firehoseBk.CreateDeliveryStream(ctx, firehosebackend.CreateDeliveryStreamInput{
Name: "wiring-test-stream",
S3Destination: &firehosebackend.S3DestinationDescription{
BucketARN: "arn:aws:s3:::wiring-test-bucket",
BufferingHints: &firehosebackend.BufferingHints{
SizeInMBs: 128,
IntervalInSeconds: 900,
},
},
})
require.NoError(t, err)

firehoseTopic, err := snsBk.CreateTopic("firehose-topic", nil)
require.NoError(t, err)

firehoseStreamARN := arn.Build(
"firehose", config.DefaultRegion, config.DefaultAccountID, "deliverystream/wiring-test-stream",
)
_, err = snsBk.Subscribe(firehoseTopic.TopicArn, "firehose", firehoseStreamARN, "")
require.NoError(t, err)

_, err = snsBk.Publish(firehoseTopic.TopicArn, "hello-firehose", "", "", nil)
require.NoError(t, err)

firehoseBk.FlushAll(ctx)

require.Len(t, s3mock.calls, 1,
"SNS publish must have been delivered to the real Firehose backend via cli.go's wireSNSToLambdaFirehose")
assert.Contains(t, string(s3mock.calls[0].body), "hello-firehose")

// --- SNS -> Lambda with DLQ: publish must reach the real Lambda backend. There is
// no Docker runtime available in this test, so the invocation fails deterministically
// (a real failure mode, not a stub) — proving delivery requires the failed message to
// be redirected to the real SQS DLQ via the wired SQS sender. ---
dlqOut, err := sqsBk.CreateQueue(&sqsbackend.CreateQueueInput{QueueName: "lambda-dlq"})
require.NoError(t, err)

lambdaTopic, err := snsBk.CreateTopic("lambda-topic", nil)
require.NoError(t, err)

functionARN := arn.Build(
"lambda", config.DefaultRegion, config.DefaultAccountID, "function:wiring-test-fn",
)
sub, err := snsBk.Subscribe(lambdaTopic.TopicArn, "lambda", functionARN, "")
require.NoError(t, err)

dlqARN := arn.Build("sqs", config.DefaultRegion, config.DefaultAccountID, "lambda-dlq")
redrivePolicy, err := json.Marshal(map[string]string{"deadLetterTargetArn": dlqARN})
require.NoError(t, err)
require.NoError(t, snsBk.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)))

_, err = snsBk.Publish(lambdaTopic.TopicArn, "hello-lambda", "", "", nil)
require.NoError(t, err)

recvOut, err := sqsBk.ReceiveMessage(&sqsbackend.ReceiveMessageInput{
QueueURL: dlqOut.QueueURL,
MaxNumberOfMessages: 1,
})
require.NoError(t, err)
require.Len(t, recvOut.Messages, 1,
"failed Lambda delivery must have been redirected to the real SQS DLQ via cli.go's wireSNSToLambdaFirehose "+
"(proves both SetLambdaBackend and SetSQSSender were wired)")
assert.Equal(t, "hello-lambda", recvOut.Messages[0].Body)
}
Loading
Loading