| service | stepfunctions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| sdk_module | aws-sdk-go-v2/service/sfn@v1.51.4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_audit_commit | 88924fa3b | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_audit_date | 2026-10-09 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| overall | A | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ops |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| families |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filter_semantics |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gaps | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| structural_gaps |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| items_still_open |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deferred | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| leaks |
|
JSONata Map ToleratedFailureCount/ToleratedFailurePercentage accept {% %} expressions. TestState honours Mock (Task), Context and StateConfiguration.RetrierRetryCount, and reports RETRIABLE/CAUGHT_ERROR (with NextState) instead of waiting out Retry or following Catch.Next into a missing state. Proof: asl/jsonata_tolerated_failure_test.go, test_state_mock_status_test.go.
Every non-mocked Task attempt records TaskStarted; .waitForTaskToken, ECS runTask.sync and Glue startJobRun.sync record TaskSubmitted (output = submit response) before waiting, and other .sync tasks record it on completion. Proof: execution_history_task_lifecycle_test.go.
With --enforce-iam, direct Lambda ARN resources, arn:aws:states:::lambda:invoke, ecs:runTask and
glue:startJobRun now require the state machine role to allow lambda:InvokeFunction / ecs:RunTask (on the task
definition ARN) / glue:StartJobRun (on the job ARN). Trust is checked against states.amazonaws.com; an unassumable
role fails with States.Permissions (States.Permissions: "insufficient privileges", Step Functions error-handling docs,
concepts-error-handling), a policy denial with <Service>.AccessDeniedException (Lambda., Ecs., Glue.), the same
naming the aws-sdk path uses. The <Service>.AccessDeniedException name for the optimized lambda:invoke and direct Lambda
paths is not citable from the docs and is recorded as emulator behavior. Enforcement off is unchanged. Sub-executors
(Parallel/Map branches) now inherit the SDK integration and the authorizer (previously dropped).
Mechanism: pkgs/roleauth.Authorizer, implemented by services/iam.RoleAuthorizer (same evaluator as the enforcement
middleware, plus permissions boundary and resource policies), wired in internal/app/role_authorizer.go.
- Limits (variables guide, "Limits"):
setVars(asl/variables.go) fails the state withStates.DataLimitExceededwhen one Assign field's variables total over 256 KiB (JSON-serialized) or the stored variables of an execution (outer plus inner scopes) pass 10 MiB. Re-assigning a name replaces its size. The guide does not name the error;States.DataLimitExceededis the code the service uses for payload quota breaches. Parallel branches/Map iterations count the outer total at spawn time only, not sibling branches. - JSONPath
$namereferences now also resolve in InputPath, OutputPath, ChoiceVariableand the*Pathcomparison fields, ItemsPath, SecondsPath/TimestampPath and the Task/Map*Pathfields (Executor.pathArg).ResultPathrejects them, as documented. - Distributed Map: a child workflow reading an outer-scope variable is rejected at CreateStateMachine/ValidateStateMachineDefinition (
checkDistributedScope, heuristic textual scan for$nameof an outer-assigned variable; the guide documents the restriction but no error text) and the in-process fallback hides outer variables. ItemSelector/ItemsPath are the Map state's own fields and may read them. - Proof:
variables_limits_sdk_test.go(SDK-driven, red before the change).
Map Runs now round-trip through Snapshot/Restore (a RUNNING run is saved as FAILED, matching the TIMED_OUT promotion of its execution); proven by TestMapRun_SurvivesSnapshotRestore. Stale entries already fixed at HEAD were removed (ClientRequestToken, history event fields, RedriveStatusReason/InputDetails, ListExecutions itemCount/mapRunArn, Map ProcessorConfig.Mode).
Follow-up to the ItemReader Resource sweep below, which flagged CSVDelimiter and ItemsPointer as newly-discovered, still-unimplemented ReaderConfig fields. Re-read input-output-itemreader.html for both fields' exact semantics and implemented:
ReaderConfig.CSVDelimiter(new field):COMMA(default),PIPE,SEMICOLON,SPACE,TAB, case-insensitive; an unrecognized value is aStates.ItemReaderFailederror rather than silently falling back to comma. Wired intodecodeCSVItemsviacsv.Reader.Comma, and threaded through toS3_INVENTORYmanifest data files too (the docs: "You can specify this field when InputType is CSV or MANIFEST") -- previouslyresolveS3InventoryManifestbuilt its ownfileCfgwith no way to carry the outerReaderConfig's delimiter through, so it's now passed the fullcfgand copiesCSVDelimiterontofileCfg.ReaderConfig.ItemsPointer(new field): an RFC 6901 JSON Pointer (/data/items, forward-slash-separated, numeric array indices,~1/~0escapes) selecting a nested array within a JSONInputTypefile, per the docs' example ({"data": {"items": [...]}}->"/data/items"). Resolving to anything other than a JSON array (an object, scalar, or a path that doesn't exist) is aStates.ItemReaderFailederror. Only applies toInputType: JSON(or omitted, its default);JSONL/CSVare unaffected and still auto-detect as before whenItemsPointeris unset.
Verified CSVHeaderLocation (FIRST_ROW/GIVEN+CSVHeaders) and
MaxItems/MaxItemsPath were already correctly implemented and tested
(TestDecodeReaderItems's csv_first_row_header/csv_given_headers/
csv_max_items_truncates cases, TestExecutor_ItemReaderMaxItemsPath) --
no changes needed there.
InputType=PARQUET and ManifestType=ATHENA_DATA remain unimplemented,
unchanged from the prior sweep: no pure-Go Parquet reader dependency exists
in go.mod and adding one was out of scope (explicitly disallowed for this
pass), and ATHENA_DATA's manifest format isn't documented precisely enough
to implement against confidently. Both still fail with their existing
dedicated sentinel error (ErrParquetUnsupported), not silently (ATHENA_DATA is now implemented: TestItemReader_S3Manifest).
New table-driven cases in TestDecodeReaderItems
(asl/intrinsics_extras_test.go): CSV with PIPE/lowercase semicolon/
TAB/SPACE delimiters, an unsupported delimiter error, ItemsPointer
selecting a nested array (including a path segment that indexes into an
array), and error cases (points at a non-array, path doesn't exist, path
doesn't start with /). New SDK-roundtrip tests: a CSVDelimiter: PIPE
case added to TestItemReader_S3Manifest (delimiter carried through to the
manifest's data file), and a new TestItemReader_ItemsPointer
(item_reader_s3_resource_test.go, nested-array selection and the
not-an-array failure, both driven through the real
aws-sdk-go-v2/service/sfn client with the in-process S3 backend).
Gates green: gofmt, go build ./..., go vet ./services/stepfunctions/...,
go test -race -count=1 (this package), golangci-lint run (0 findings),
go test ./pkgs/persistence/, go run ./cmd/parityfmtcheck -dir services.
No go.mod/go.sum changes.
Closed most of the items_still_open gap the WriterConfig sweep below found
in the same session: ItemReader ignored ItemReader.Resource entirely and
only ever did a single s3:getObject decoded as JSON/JSON Lines/CSV. Read
input-output-itemreader.html end to end for the exact item shapes and error
behavior. Implemented:
- Resource
arn:aws:states:::s3:listObjectsV2: a newasl.S3ListReaderinterface (ListObjectsV2Items), implemented by the existings3Adapteragainstservices/s3.StorageBackend.ListObjectsV2, paginating viaContinuationTokenuntil exhausted. Default mode returns one item per object:{"Etag","Key","LastModified","Size","StorageClass"}, matching the docs' example exactly (LastModifiedas epoch seconds viapkgs/awstime.Epoch). ReaderConfig.Transformation: LOAD_AND_FLATTEN(new field): reads and decodes each listed object's content perInputType(JSON/JSONL/CSV) and flattens every object's records into one item array, per the docs' "Processing nested data sets" section. Zero-byte keys ending in/(S3 console folder placeholders) are skipped, since they have no content.ReaderConfig.ManifestType: S3_INVENTORY(new field), and the legacy bareInputType: MANIFESTthe docs' own worked example uses withoutManifestTypeset (treated identically): reads amanifest.json(fileSchema,files[].key), fetches each listed CSV data file (gzip-decompressed when the key ends.gz), and decodes it usingfileSchema's comma-separated column names as CSV headers.- Error behavior: every ItemReader failure (missing bucket/key,
unsupported
Resource, unsupportedManifestType/InputType) is now wrapped as aStates.ItemReaderFailedFailError-- AWS's own documented predefined error name for this failure class -- instead of leaking the raw Go error string as the Catch-match code. A Map state'sCatchcan now matchStates.ItemReaderFailedspecifically, not only viaStates.ALL.
Not implemented, each behind a dedicated sentinel error rather than a silent
stub (see the narrowed items_still_open entry and the asl_map family
note): ManifestType: ATHENA_DATA (the docs describe its manifest only as
"a structured CSV list of the data files", which isn't precise enough to
implement against confidently -- Athena's own UNLOAD manifest format is
documented elsewhere as JSON, not CSV -- and $states.context.Map.Item.Source
is unmodeled too); InputType: PARQUET (no pure-Go Parquet reader dependency
exists in go.mod, and this pass does not add one, per instructions).
CSVDelimiter and ItemsPointer remain unparsed (ReaderConfig has no
fields for either) -- discovered while reading the docs for this pass but
out of the four originally-recorded gaps, so left as-is and disclosed above
rather than silently addressed. STALE, corrected same-day by the 2026-09-26
ItemReader gap-closure sweep above: both are now implemented.
New table-driven tests, driven through a real aws-sdk-go-v2/service/sfn
client over httptest with objects seeded in the in-process S3 backend
(item_reader_s3_resource_test.go): TestItemReader_S3ListObjectsV2
(metadata mode, LOAD_AND_FLATTEN JSON, LOAD_AND_FLATTEN CSV, missing-bucket
error), TestItemReader_S3Manifest (S3_INVENTORY with a gzip data file, the
legacy InputType: MANIFEST alias, and the ATHENA_DATA gap), and
TestItemReader_S3GetObject_Errors (missing key, the PARQUET gap).
Gates green: gofmt, go build ./..., go vet ./services/stepfunctions/...,
go test -race -count=1 (this package), golangci-lint run (0 findings),
go run ./cmd/parityfmtcheck -dir services. No go.mod/go.sum changes.
go test ./pkgs/persistence/ fails on this branch, but only on a
pre-existing services/sqs snapshot-version-guard finding from a different,
concurrently-in-progress change to that package -- unrelated to this sweep
and services/sqs was not touched here.
Implemented ResultWriter.WriterConfig (Transformation: NONE/COMPACT/FLATTEN,
OutputType: JSON/JSONL), the item this file's own items_still_open named
as the open gap, per input-output-resultwriter.html. Also threaded real
DISTRIBUTED Map child-execution identity (ExecutionArn/Name/StartDate/
StopDate) into NONE-transformation records via a new
asl.DistributedMapItemResult return type on DistributedMapRunner. RunDistributedMapItem (previously a bare any) -- the other half of the
same gap, closing gopherstack-8j8's remaining scope. See the asl_map
family note for the full before/after and the new
TestDistributedMapResultWriter_* tests (table-driven over all 6
Transformation x OutputType combinations, plus FAILED-item and DISTRIBUTED-
identity cases, all driven through the real aws-sdk-go-v2 sfn client with
the in-process S3 backend wired).
Also read input-output-itemreader.html and input-output-itembatcher.html
end to end per this sweep's brief. ItemBatcher and ToleratedFailureCount/
ToleratedFailurePercentage (and their *Path siblings) were already
correctly implemented -- no changes needed there. Found, but did not fix,
that ItemReader has never supported Resource: arn:aws:states::: s3:listObjectsV2, InputType: MANIFEST, or InputType: PARQUET -- only
s3:getObject with JSON/JSON Lines/CSV. This was not previously documented
anywhere in this file; recorded in items_still_open and the asl_map
family note rather than attempted, since ListObjectsV2 needs a wire-shape
citation this pass didn't chase down and MANIFEST/PARQUET are meaningfully
larger builds (a manifest-driven GetObject fan-out; a binary columnar
decoder) than fit this pass's scope.
Gates green: gofmt, go build ./..., go vet, go test -race (this
package), golangci-lint run (0 findings), go test ./pkgs/persistence/,
cmd/parityfmtcheck. No go.mod/go.sum changes.
ListExecutions/ListExecutionsByMapRun value-copied+sorted every matching
execution on every call; now sort []*Execution and copy only the
returned page. BenchmarkListExecutions_5000: 2379754ns/1629201B ->
400580ns/164389B (3-run medians); golden test pins order unchanged.
Re-checked all 23 ops the census flags with >=1 required output member (54
fields), on top of the prior batch-10 nested-member pass
(wire_output_required_r80d_test.go). Found and fixed one real bug this
service's own prior "not reachable, not a bug" note for StopExecutionOutput. StopDate had missed: Snapshot's RUNNING->TIMED_OUT promotion (for
surviving a persistence restart) never set StopDate, so StopExecution
called on such an execution post-restore returned a nil required field.
Fixed in persistence.go; proven via a real aws-sdk-go-v2/service/sfn client
round trip that fails pre-fix (see ops.StopExecution). Also normalized
MapRun.MaxConcurrency/ToleratedFailureCount/ToleratedFailurePercentage
off omitempty (required scalars; harmless in practice since the SDK's own
fields are non-pointer, but corrected for wire-shape hygiene). Everything
else in the flat 23-op/54-field surface was already always-populated.
2026-08-15 (gopherstack-3gbe): investigated whether Step Functions
shares Omics' (gopherstack-keee) client-side host-prefix-rewrite
reachability gap. It does: 2 ops, one literal prefix, sync- (TestState
api_op_TestState.go:265, StartSyncExecution
api_op_StartSyncExecution.go:232), confirmed against the pinned
sfn@v1.45.4 module, exactly matching gopherstack-3gbe's filing.
No routing/auth code needed changing. Handler.RouteMatcher
(handler.go:160) matches on the X-Amz-Target header prefix
("AmazonStates." or "AWSStepFunctions."), never Host or Path, so
header-based dispatch is structurally immune to the path-collision class
this bug family could otherwise cause. The reachability gap is a pure
client-side DNS/dial failure, same as Omics.
stepfunctions already had a real-SDK-client round trip
(wire_updatedate_test.go), but it never exercised TestState or
StartSyncExecution, so this family's real-client reachability had never
been proven either way. Added host_prefix_reachability_test.go following
services/omics/host_prefix_reachability_test.go's before/after pattern: a
before-fix test proving the unmodified client can't dial either op, and an
after-fix test that drives TestState directly and StartSyncExecution (via
CreateStateMachine of an EXPRESS state machine, since StartSyncExecution is
EXPRESS-only) through a redial-to-the-real-listener transport, leaving the
SDK's real, un-disabled sync- rewrite intact on the wire, and asserts both
succeed with correctly decoded values. Gates green: build, vet, race,
go fix -diff (no diff), golangci-lint (0 findings).
This pass's brief was to deep-audit the 3 previously "spot-checked only" deferred families by actually field-diffing them against aws-sdk-go-v2/service/sfn v1.40.8 types, not just re-asserting "appeared correct". All 3 had real, previously-undiscovered gaps:
1. State machine CRUD -- qualified-ARN resolution was entirely missing.
AWS's StartExecutionInput.StateMachineArn doc explicitly describes three
valid input shapes: unqualified, version-qualified (stateMachineArn:N),
and alias-qualified (stateMachineArn:name, with weighted routing across
1-2 versions). This emulator's CreateStateMachineAlias/
PublishStateMachineVersion/routing-weight-validation (the resource CRUD
side) were previously verified ok and are indeed correct -- but
StartExecution/StartSyncExecution/DescribeStateMachine never consulted
any of that data; they did a direct, exact-ARN b.stateMachines.Get()
lookup that always failed for a qualified ARN. This is the same shape of
bug as the Map/asl_map disguised-stub found in a prior pass: two halves of
a feature (resource management + resource consumption) were each
individually correct and individually tested, but never connected, so a
green test suite for either half never caught it. Fixed via
resolveExecutionTarget() in the new qualified_arn.go. Trap for the
next auditor: when a resource type has both a "create/configure" API
surface and a "consume/reference" API surface (aliases+StartExecution,
Map's ItemReader+the S3 integration below), audit them together -- a
family status of "ok" on the config side proves nothing about whether the
consuming side actually resolves what was configured.
2. DescribeStateMachineVersion is a FABRICATED, non-AWS operation --
deleted. Verified against the full aws-sdk-go-v2/service/sfn@v1.40.8
api_op_*.go file listing (37 files, 37 real operations): there is no
DescribeStateMachineVersion. AWS's real mechanism for fetching version
details is calling DescribeStateMachine with a version-qualified ARN (the
SDK's own DescribeStateMachineOutput.CreationDate doc literally says "For
a state machine version, creationDate is the date the version was created"
and DescribeStateMachineInput.StateMachineArn's doc says "If you specify a
state machine version ARN, this API returns details about that version").
This emulator had invented a whole separate wire op (route in
handler_state_machine_versions.go, entry in GetSupportedOperations(),
StorageBackend interface method) for this instead of implementing the
real mechanism. Fix: removed the op from GetSupportedOperations() and
the handler's dispatch table, removed it from the StorageBackend
interface (so Handler can no longer route it), and instead extended
DescribeStateMachine itself to resolve a version-qualified ARN (echoing
the version ARN back as StateMachineArn, per AWS's documented behavior --
notably different from execution-start's base-ARN-always semantics). The
backend method InMemoryBackend.DescribeStateMachineVersion was left in
place as a plain internal helper (existing tests call it directly on the
concrete type) since it's harmless non-wire-surface Go code, not a
fabricated AWS operation -- only the wire-level op was deleted. This is the
"gopherstack-invented op, not in the real SDK, DELETE it" bug class from the
polly tagging-surface precedent.
3. Distributed Map ItemReader S3 decoding: the SEVERE internal/app/cli.go-style wiring
gap recurred, one level down. A prior pass fixed internal/app/cli.go never wiring
SetECSIntegration/SetGlueIntegration/SetEventBridgeIntegration despite
asl.Executor fully implementing and unit-testing those integrations. This
pass found the exact same bug class for S3: asl.Executor.SetS3Reader /
the S3Reader interface / resolveItemsFromReader's CSV/JSON/JSONL
decoding were all correctly implemented and mock-tested
(asl/intrinsics_extras_test.go), but InMemoryBackend.SetS3Reader didn't
even exist, and nothing called it. Any real Distributed Map with an
ItemReader hard-failed with ErrS3ReaderNotConfigured in every actual
running gopherstack process. Trap for the next auditor, restated from the
prior ECS/Glue/EventBridge finding because it just recurred: an
asl_*-family or executor-level "ok"/mock-tested verdict proves the
executor dispatches correctly -- it says nothing about whether
services/stepfunctions/'s InMemoryBackend (let alone internal/app/cli.go) actually
wires the concrete integration through. Every asl.Executor.SetXIntegration
call needs a matching audit trail: backend field -> SetXIntegration
method -> threaded through startExecutionLocked/runParsedExecution/
StartSyncExecution/RedriveExecution -> adapter in integrations.go ->
internal/app/cli.go wiring call. Missing any link in that chain reproduces this bug
silently.
4. Activities: encryptionConfiguration and tags were real, entirely
unparsed CreateActivityInput fields. Field-diffed
api_op_CreateActivity.go/api_op_DescribeActivity.go against
activities.go/handler_activities.go and found both fields simply
absent -- not stubbed, just never referenced anywhere. Added
Activity.EncryptionConfiguration + SetActivityEncryptionConfiguration
(mirrors SetStateMachineConfigurations's established optional-post-create-
config pattern) and inline-tags handling (mirrors CreateStateMachine's).
Also found and fixed a real leak while in this code: DeleteActivity's
handler never cleaned up h.tags, unlike DeleteStateMachine's.
RevisionId / StateMachineVersionArn on Create/UpdateStateMachine. Field-
diffing CreateStateMachineOutput/UpdateStateMachineOutput found both
response types missing fields the emulator's own backend logic already had
the data for: PublishStateMachineVersion's result was already being
computed when publish=true but thrown away (_, _ = h.Backend.PublishStateMachineVersion(...)) instead of echoed back, and
RevisionId didn't exist as a concept anywhere in the backend. Added
StateMachine.RevisionID (opaque token, regenerated every
UpdateStateMachine call, absent/empty until the first update -- matching
AWS's documented "compare between versions ... without performing a diff"
semantics) and wired it + StateMachineVersionArn into both response
types, DescribeStateMachine, and added the versionDescription-requires-
publish=true ValidationException AWS documents for both ops.
Retry.JitterStrategy validation (bd: gopherstack-xtl, closed this pass):
added a recursive walk (asl/parser.go's validateJitterStrategies, covers
nested Iterator/ItemProcessor/Branches) rejecting anything other than
"FULL"/"NONE"/omitted at Parse time, which is called from
CreateStateMachine/UpdateStateMachine/ValidateStateMachineDefinition/
StartExecution/TestState uniformly.
Confirmed already-fixed (not by this pass): the previously-documented
SEVERE internal/app/cli.go ECS/Glue/EventBridge wiring gap. git log showed two
commits (efc42cbc "Parity 4", 9d7e36e0 "Go refactoring 2") landed on
services/stepfunctions/ since the 43aa6d65 baseline despite the prior
pass's "zero drift" framing (that framing compared against a different,
even-older baseline, ce30166a) -- internal/app/cli.go now calls
sfnBk.SetECSIntegration/SetGlueIntegration/SetEventBridgeIntegration
(verified at internal/app/cli.go's wireStepFunctionsServiceIntegrations, ~L3855-3867).
Removed the stale gap entry.
Protocol: json-1.0, unchanged this pass.
See git history for this file's content as of commit 43aa6d65 for the
full prior-pass notes on: the Map runMapItem disguised-stub fix, Map/
Parallel Retry+Catch addition, GetExecutionHistory detail-object
population fix, the Catch error-output two-key-shape fix, and the
StartExecution-on-EXPRESS / StartSyncExecution-on-STANDARD error-code fixes.
Those families:/ops: verdicts are carried forward unchanged in the
front-matter above (marked ok / not re-noted) since this pass found no new
drift in them.
cmd/requiredoutputfields puts stepfunctions at 54 required output fields
across 23 ops-with-required (37 ops total) -- the largest remaining
candidate not off-limits (sagemaker, 459 fields, is excluded: a concurrent
agent was mid-conversion of its inline request structs for most of this
session, confirmed via git status before and during this pass).
Wire shape: stepfunctions is NOT the "one wrapper key around a nested
domain object" shape pinpoint/bedrockagent/cleanrooms have -- most ops have
several top-level required scalar members directly (CreateActivity:
activityArn+creationDate; DescribeMapRun: nine required members
directly on the op's own output struct), and every response is built via
tagged Go structs (not map[string]any literals like s3tables/codecommit).
So neither of the two documented shapes from prior batches applies at the
op level -- but the flat 54-field/23-op count still badly undercounts the
real required surface, for a THIRD reason not yet named in this campaign:
stepfunctions' List ops return arrays of dedicated *ListItem structs
(ActivityListItem, ExecutionListItem, MapRunListItem,
StateMachineAliasListItem, StateMachineListItem,
StateMachineVersionListItem), and GetExecutionHistory returns an array
of polymorphic HistoryEvent structs whose *EventDetails sub-objects
(TaskScheduledEventDetails, TaskSucceededEventDetails,
TaskFailedEventDetails, plus nine more event-kind detail types this
emulator never emits) each carry their own required members --
cmd/requiredoutputfields's flat per-op scan only sees each op's own
top-level <Op>Output struct and has no visibility into a list element
type's own requiredness, or a HistoryEvent's own nested detail structs. Read
every nested/list-item/detail type in types.go (extracted via a one-off
AST-style walk over every type X struct { ... } block, not a grep window,
per this campaign's own standing method warning) against models.go and
every handler that constructs one, not just the 23 ops the tool names.
Trusted vs re-derived: this file already had several prior passes
(2026-07-11 zero-drift baseline, 2026-07-23 re-audit, plus dv4s's List-item
over-wide-leak fixes for every List* op named above) that verified
ListActivities/ListExecutions/ListMapRuns/ListStateMachines/
ListStateMachineAliases/ListStateMachineVersions each already emit a
correctly-narrow *ListItem struct with all of ITS required members
present and non-omitempty -- re-spot-checked one (ActivityListItem) against
types.go rather than re-deriving all six from scratch; all six confirmed
still correct, 0 bugs there. Did NOT trust the existing DescribeMapRun
and GetExecutionHistory "wire: ok" verdicts for this specific bug class --
both were re-derived field-by-field and both were wrong (see below).
TaskScheduledEventDetails.Region/.Parameters(types.go:1311-1339, both required).RecordTaskScheduled(execution_history.go) only ever setResource/ResourceType;Region/Parameterswere never assigned at all, decoding as empty strings on every real client'sGetExecutionHistorycall for any Task-state execution -- the common case, not an edge case. Fixed by threading the resolved (post-Parameters- template) task input through asParameters, and derivingRegionvia the sameregionFromARN(resource, backend.region)helper already used for activity ARNs (activities.go:138), applied here to the Task state's resource ARN instead.TaskSucceededEventDetails.Resource/.ResourceType(types.go:1431-1450, both required).RecordTaskSucceedednever set either field. Fixed by threadingstate.Resourcethrough fromexecuteTask's already-in-scopestate(asl/executor.go).TaskFailedEventDetails.Resource/.ResourceType(types.go:1289-1307, both required).RecordTaskFailednever set either field on either of its two call sites (the direct-failure path inexecuteTaskand the Catcher path incheckCatchers), both of which already havestatein scope. Fixed the same way.asl.HistoryRecorder'sRecordTaskSucceeded/RecordTaskFailed(an exported interface) both gained aresource stringparameter to carry this through; the one other implementation (mockHistoryRecorder, asl/executor_test.go) was updated to match.go build ./...,go vet -tags e2e ./...,go vet -tags integration ./...all re-run repo-wide and clean.DescribeMapRun'sExecutionCounts(api_op_DescribeMapRun.go:57, required;types.MapRunExecutionCounts, types.go:841-906) had no backing field onMapRunat all -- see the REVERSED note onDescribeMapRunabove for why the prior pass's "correctly so" verdict was wrong, and why the fix is a genuinely-zero (not fabricated)MapRunExecutionCounts, always marshaled. Verified via the AWS API reference page forDescribeMapRun(fetched to check whether it documents ExecutionCounts mirroring ItemCounts for non-distributed Map Runs before considering that derivation -- it does not make that claim anywhere, so this pass did not fabricate that mapping and used genuine zeros instead).
ValidateStateMachineDefinitionDiagnostic.Severitycounted above as fixed (added to the map), but two adjacent fields on the same struct were checked and found fine:Code/Messageare both already populated ("SCHEMA_VALIDATION_FAILED"/err.Error()) --Codeis a free-form*stringon the real type (types.go:1560-1563), not an enum, so an invented-looking constant string is not a fabrication concern the way an enum value would be.EncryptionConfiguration.Type(types.go, required within the struct) is only ever populated from a caller-suppliedEncryptionConfigurationonCreateStateMachine/UpdateStateMachine/CreateActivity-- reviewed and left as-is: a realaws-sdk-go-v2client round trip constructing this input always setsType(it is the one field the SDK's own type gives no zero-value-safe default for), so the omitempty-tagged zero-value case needs a deliberately malformed request to reach, not a normal round trip. Same "reviewed, not a bug" class the cleanrooms batch-8 note names (required on input too, never actually reachable-empty from a real client).stateMachineAliasEntry.UpdateDate(wire keyupdateDate, required onUpdateStateMachineAlias) is taggedomitemptybutUpdateStateMachineAlias(aliases.go) unconditionally setsalias.UpdatedDate = time.Now().Unix()before returning -- the zero-value/omitted case is not reachable from any code path, so left as-is rather than "fixed but not proven."StopExecutionOutput.StopDate(*float64, noomitempty) -- STALE, CORRECTED 2026-09-19 (gopherstack-r80d): this note previously claimed every placeexec.Statustransitions offRUNNING(finalizeExecutionRecordLocked/StopExecutionitself) setsStopDatein the same statement, so the no-op branch could never observe nil. That trace missed a third transition:Snapshot(persistence.go) promotes any execution stillRUNNINGat snapshot time toTIMED_OUTwithout settingStopDate.StopExecutioncalled on such an execution after a restore hit the no-op branch with a genuinely nilStopDate. Fixed -- seeops.StopExecutionabove.RoutingConfigurationListItem(StateMachineVersionArn/Weight, both required) matchesAliasRoutingConfigexactly, both fields already non-omitempty. Clean.ActivityListItem/ExecutionListItem/MapRunListItem/StateMachineAliasListItem/StateMachineListItem/StateMachineVersionListItem-- all six re-verified againsttypes.gofield-by-field (see "trusted vs re-derived" above); all required members present, non-omitempty. Clean, matches this file's existingdv4s-pass notes.
Nine *EventDetails types this pass read against types.go have required
members (ActivityScheduledEventDetails.Resource,
LambdaFunctionScheduledEventDetails.Resource,
EvaluationFailedEventDetails.State, TaskStartedEventDetails.Resource/
.ResourceType, TaskSubmittedEventDetails.Resource/.ResourceType,
TaskStartFailedEventDetails.Resource/.ResourceType,
TaskSubmitFailedEventDetails.Resource/.ResourceType,
TaskTimedOutEventDetails.Resource/.ResourceType) but this backend never
emits the corresponding HistoryEventType values at all -- execution_history.go's
historyRecorder only ever produces
TaskScheduled/TaskSucceeded/TaskFailed (generic, resource-agnostic
event kinds) regardless of resource type or .sync/.waitForTaskToken
integration pattern, never the Lambda-specific
(LambdaFunctionScheduled/...), Activity-specific
(ActivityScheduled/...), or submitted/started/timed-out variants real AWS
emits for those patterns. Since no HistoryEvent in this emulator ever
claims to be one of these kinds, there is no reachable case where an
incomplete instance of one of these structs is returned -- this is the
GetExecutionHistory scope of bd gopherstack-996 that remains open (a
missing-feature gap, not a dropped-required-field bug), not something this
pass fixed or fabricated a partial implementation of. Also confirmed via
asl.HistoryRecorder's interface: it has no method for any of these event
kinds, and adding real Lambda/Activity-specific event-type differentiation
(the arn:aws:lambda:... vs arn:aws:states:::lambda:invoke resource-form
distinction real AWS uses) is out of this cut's scope -- a wire-shape
feature gap, not a required-output-field bug, tracked at gopherstack-996.
Total for this pass: 54 required output fields plus every nested
list-item/history-event-detail struct read end to end across 23 ops with
required output fields, 4 real bugs found and fixed, all proven via real
aws-sdk-go-v2/service/sfn client round-trip tests
(wire_output_required_r80d_test.go), each hand-reverted (all 5 touched
files reverted to HEAD together, confirmed all 4 tests fail against the
pre-fix code), confirmed failing, restored, md5sum-verified byte-identical.
2026-08-23 pass (manifest harvest): stale gap notes corrected, RedriveStatusReason fixed, Fail-state-with-no-error-code bug found and fixed
Read this file's gaps: block against the current code instead of trusting
its prose. Two of the seven entries were stale:
DescribeExecutionOutput missing RedriveStatus/RedriveStatusReason/ MapRunArn/TraceHeader/InputDetails/OutputDetails(bd gopherstack-f5dc): four of the six named fields (RedriveStatus,TraceHeader,InputDetails,OutputDetails) were already declared onExecutionand already assigned real values at every relevant transition (initializeExecutionRecord/finalizeExecutionRecordLocked/StopExecution/resetExecutionForRedrive,executions.go) -- the note was simply wrong.RedriveStatusReasonwas the one real gap: declared (models.go) but never assigned anywhere, so a real client always decoded an empty string regardless ofredriveStatus. Fixed by populating it with AWS's exact documented reason strings (api_op_DescribeExecution.go: "Execution is RUNNING and cannot be redriven." / "Execution is SUCCEEDED and cannot be redriven.") at everyNOT_REDRIVABLEtransition, and clearing it at everyREDRIVABLEtransition.MapRunArnremains genuinely unset -- confirmed via grep that no code path ever assignsExecution.MapRunArn-- but that is the same structural gap already tracked under bd gopherstack-8j8 (Map iterations aren't backed by real child executions), not a distinct bug; folded into that entry rather than kept as a separate false gap.TaskScheduledEventDetails/TaskSucceededEventDetails still omit resourceType/region/parameters/...(bd gopherstack-996, written 2026-07-11):resourceType/region/parameterswere fixed by the 2026-08-21 batch-10 pass above, which never updated this older note.TimeoutInSeconds/HeartbeatInSecondsand the missing TaskSubmitted/TaskStarted events remain genuinely open.
Real bug found via the round-trip test written to prove the
RedriveStatusReason fix (wire_redrivestatusreason_test.go, table
covering a Pass-only state machine and a bare {"Type":"Fail"} state
machine): the bare-Fail case decoded as Status: SUCCEEDED, not
FAILED. Root cause: asl.Executor.Execute turns a FailError into
&ExecutionResult{Error: failErr.ErrCode, Cause: failErr.Cause}, and every
consumer (executions.go's async and sync finalizers, handler_util.go's
TestState, and asl/executor.go's own Parallel-branch and Map-iteration
result handling) treated result.Error != "" as the failure signal. Fail
states' Error/Cause are both optional per the ASL spec (real AWS: a
bare {"Type":"Fail"} still ends the execution as FAILED, just with
error/cause absent from the output), so an empty ErrCode was
indistinguishable from success -- a genuine wrong-answer bug reachable by
any minimal Fail state, not an edge case. Fixed by adding
ExecutionResult.Failed bool, set true on the FailError path, and
switching every one of the five Error != "" call sites listed above to
check Failed instead (Error/Cause are still copied through
unchanged, so already-passing tests that assert specific error text were
unaffected).
Proof: wire_redrivestatusreason_test.go's two subtests fail against the
pre-fix code (succeeded: RedriveStatusReason expected non-empty, got
""; failed: Status expected FAILED, got SUCCEEDED, and
RedriveStatus expected REDRIVABLE, got NOT_REDRIVABLE) and pass after
it. Hand-reverted all four touched files
(store.go/executions.go/asl/executor.go/handler_util.go) to HEAD
via cp, confirmed both subtests fail with the errors quoted above,
restored the fix via cp, md5sum-verified byte-identical to the
pre-revert state. go build ./..., go test ./services/stepfunctions/...,
golangci-lint run ./services/stepfunctions/... (0 issues) all clean.
No persisted struct changed (ExecutionResult is an in-process handoff
type, never persisted; Execution.RedriveStatusReason was already a
persisted field, just newly populated).
2026-09-07 pass (bd gopherstack-2hdk): errtargetaudit's 4 class-A findings triaged, 3 false positives, 1 real gap left as a landmine
Audit tool (cmd/errtargetaudit) flagged 4 class-A findings for stepfunctions
(coverage warning: only 37/205 ops resolved to a handler, so treat as
unverified leads, not a clean bill). All 4 verified against
aws-sdk-go-v2/service/sfn@v1.45.4 deserializers.go
(awk "/deserializeOpError<Op>\(/,/^}/" deserializers.go | grep -oE '"[A-Za-z0-9]+"'), not trusted as-reported.
Protocol: application/x-amz-json-1.0 (handler.go's handleError
sets this Content-Type; models.go confirms AWS JSON 1.0). Errors are
shaped as service.JSONErrorResponse{Type, Message} via a single
service-wide classifyError table (handler.go) mapping each backend
sentinel error to one (__type string, HTTP status) pair -- there is no
per-operation filtering at this layer, so any backend call site can leak
any sentinel through any operation if the call site itself is wrong. No
override-helper pattern exists in this service (grepped for
override/reclassify/remap/translate in handler_*.go -- none); class-7
(handler overrides code at call site) does not apply here.
- CreateActivity / ActivityDoesNotExist (
activities.go:119,handler_activities.go:113) -- false positive, class 4 (guard cannot fire).handleCreateActivitycallsSetActivityEncryptionConfiguration(a.ActivityArn, ...)with the ARN just returned byCreateActivityin the same request; the guard it trips (ErrActivityDoesNotExist) requires the activity to be absent, which is impossible moments afterb.activities.Put(a). Ground truth:CreateActivitydeclaresActivityAlreadyExists, ActivityLimitExceeded, InvalidEncryptionConfiguration, InvalidName, KmsAccessDeniedException, KmsThrottlingException, TooManyTags, UnknownError-- noActivityDoesNotExist; correctly declared instead byDescribeActivity/GetActivityTask. Regression coverage already exists and asserts 200:TestCreateActivity_EncryptionConfiguration(handler_activities_test.go). - CreateStateMachine / StateMachineDoesNotExist
(
handler_state_machines.go:117,state_machines.go:51,state_machine_versions.go:18) -- false positive, class 4, same shape:createStateMachineActioncallsSetStateMachineConfigurations(sm.StateMachineArn, ...)and (whenpublish=true)PublishStateMachineVersion(sm.StateMachineArn, ...)with the ARN just created in the same request; both guards tripErrStateMachineDoesNotExist, which cannot fire on an ARN that wasPutmoments earlier under the same lock region. Ground truth:CreateStateMachinedeclaresConflictException, InvalidArn, InvalidDefinition, InvalidEncryptionConfiguration, InvalidLoggingConfiguration, InvalidName, InvalidTracingConfiguration, KmsAccessDeniedException, KmsThrottlingException, StateMachineAlreadyExists, StateMachineDeleting, StateMachineLimitExceeded, StateMachineTypeNotSupported, TooManyTags, UnknownError, ValidationException-- noStateMachineDoesNotExist; correctly declared instead byDescribeStateMachine/ListExecutions/ListStateMachineAliases/PublishStateMachineVersion/StartExecution. Regression coverage already exists and asserts 200:TestHandler_CreateStateMachine_PropagatesTracingAndLogging(handler_tracing_logging_test.go) and thepublish_true_creates_versionsubtest ofTestHandler_CreateStateMachine_Publish(handler_state_machines_test.go). - DescribeStateMachineForExecution / StateMachineDoesNotExist
(
executions.go:776, now:790) -- real bug, left unfixed. Ground truth: this op declares onlyExecutionDoesNotExist, InvalidArn, KmsAccessDeniedException, KmsInvalidStateException, KmsThrottlingException, UnknownError-- noStateMachineDoesNotExist, and no code in that set semantically fits "the execution exists but its state machine's record is gone" (ExecutionDoesNotExistwould be false: the execution does exist). Reachable, not theoretical: the function's!hasSnapshotfallback fires for any execution started before the last persistence restore, becauseexecutionDefinitionsis deliberately excluded frombackendSnapshot(persistence.go's documented Phase-3.3 boundary) -- so a restart followed by deleting that execution's state machine hits this branch. The sibling branch three lines down (hasSnapshot == true, SM also gone) answers the identical real-world condition with a synthetic 200 (&StateMachine{StateMachineArn: ..., Definition: definition}), which is the strongest same-function precedent for what AWS actually does here, but silently turning this branch's error into success needs its own evidence per the audit brief's evidence-trap rule, and expanding persistence to close the root cause (persistexecutionDefinitions, bumpsfnSnapshotVersion) is out of scope for this pass. Left a landmine comment at the call site naming both candidate fixes and this issue (gopherstack-2hdk). No test asserts this path either way today. - StartSyncExecution / InvalidDefinition (
executions.go:118) -- false positive, a variant of class 4 (defensive re-validation of already-validated data, not "just created" but "already validated at an earlier lifecycle step").StartSyncExecutionre-parsessm.Definitionwithasl.Parseafter pulling it from the store; butCreateStateMachine(state_machines.go:86-88) andUpdateStateMachine(state_machines.go:259-262) both already reject any definition that failsasl.Parsebefore it is ever stored, and every path that hands a definition toStartSyncExecution-- unqualified ARN, version ARN (stateMachineForVersionLockedcopies aPublishStateMachineVersionsnapshot, itself copied from an already-validatedsm.Definition), or alias ARN routed to a version -- ultimately traces back to one of those two validated writes. So the second parse cannot fail via any documented API sequence. Ground truth:StartSyncExecutiondeclaresInvalidArn, InvalidExecutionInput, InvalidName, KmsAccessDeniedException, KmsInvalidStateException, KmsThrottlingException, StateMachineDeleting, StateMachineDoesNotExist, StateMachineTypeNotSupported, UnknownError-- noInvalidDefinition; correctly declared instead byCreateStateMachine/TestState/UpdateStateMachine. The identicalasl.Parse(definition)->ErrInvalidDefinitionpattern also appears, unflagged by the tool (coverage gap, not a clean bill), instartExecutionLocked(executions.go:305, backsStartExecution) andredriveExecutionLocked(executions.go:688, backsRedriveExecution) -- same reasoning applies to both; neither op's declared set includesInvalidDefinitioneither. Regression coverage already exists and asserts success:TestStartSyncExecution_Express_Succeeds/TestStartSyncExecution_Express_InputPayload(executions_asl_test.go) plus the broaderStartExecution/RedriveExecutionhappy-path suites inexecutions_test.go.
The audit brief specifically asked whether a deleting state machine is
modeled as distinct from a missing one. It is not: there is no
ErrStateMachineDeleting sentinel anywhere in this package, and no
classifyError mapping for AWS's StateMachineDeleting code (which
CreateStateMachine/StartExecution/StartSyncExecution all declare).
DeleteStateMachine (state_machines.go:134-150) sets
sm.Status = statusDeleting and then, in the same locked critical
section, immediately calls b.stateMachines.Delete(arn) -- deletion is
synchronous, so no other request can ever observe a state machine with
Status == DELETING; CreateStateMachine's sm.Status != statusDeleting
duplicate-name guard (state_machines.go:99) is consequently dead code.
This is a real, documented AWS distinction this backend cannot produce
(async delete simply isn't modeled) -- not one of the 4 flagged findings,
too large a change to fold into this pass, worth its own tracked issue.
GOTOOLCHAIN=go1.26.6 go test -race -count=1 ./services/stepfunctions/...: pass.GOTOOLCHAIN=go1.26.6 golangci-lint run services/stepfunctions/...:0 issues.- No pre-existing test was found asserting a wrong error code for any of
the 4 call sites (searched all
*_test.goforActivityDoesNotExist/StateMachineDoesNotExist/InvalidDefinitionnearCreateActivity/CreateStateMachine/StartSyncExecution/DescribeStateMachineForExecution); 0 corrected. - Only change: a landmine comment on finding 3
(
executions.go,DescribeStateMachineForExecution) -- no functional line touched. Verified withgit stashon just that file: builds andgo test -run TestDescribeStateMachineForExecutionpass identically with and without it, confirming it's documentation-only. - No persisted struct was touched;
pkgs/persistenceguard not re-run (nothing to check -- no field added).
2026-09-08 pass (bd gopherstack-kx95): DELETING made observable, StateMachineDeleting wired to all 8 declaring ops
Title claimed two linked things: (1) DELETING is never observable, and (2)
therefore StateMachineDeleting can never be returned. Both were true of
the code as of the 2026-09-07 pass's finding above, and both are now fixed
-- this was a real, fixable defect, not a modelling gap, contra some of
this campaign's other enum-reachability closes (amplify, networkmonitor,
support, detective): those were async/externally-driven states a
synchronous emulator has nothing to produce, but AWS's own docs say
DeleteStateMachine is asynchronous with a real, bounded, backend-driven
window -- not fabricated timing.
Ground truth (two independent oracles agreed):
aws-sdk-go-v2/service/sfn@v1.49.0types/errors.go:683-684:// The specified state machine is being deleted./type StateMachineDeleting struct { Message *string; ... }.types/enums.go:362-375:StateMachineStatushas exactly two values,ACTIVEandDELETING.deserializers.go-- found via each op'sawsAwsjson10_deserializeOpError<Op>function boundaries (notgrep -n 'case "', which returns nothing for this shape):StateMachineDeletingis declared byCreateStateMachine(:287),CreateStateMachineAlias(:428),ListStateMachineAliases(:2381),PublishStateMachineVersion(:2858),StartExecution(:3509),StartSyncExecution(:3647),UpdateStateMachine(:4406),UpdateStateMachineAlias(:4535). Not declared byDeleteStateMachineitself,DescribeStateMachine,ListStateMachines, eitherDelete*op,ListStateMachineVersions, or the tagging ops.- botocore
stepfunctions/2016-11-23/service-2.json.gz(gzip+json): identical 8-operationerrorslist forStateMachineDeleting, plus the decisive fact the Go SDK doesn't carry --DeleteStateMachine's own documentation: "This is an asynchronous operation. It sets the state machine's status to DELETING and begins the deletion process. A state machine is deleted only when all its executions are completed. On the next state transition, the state machine's executions are terminated."StateMachineStatusenum:["ACTIVE", "DELETING"], nohttpStatusCodeoverride on any of the three exception shapes checked.
Backend write-site enumeration (boundary-matched \.Status\s*= across
non-test *.go): persistence.go:122-123 (execution timeout, unrelated),
executions.go x6 (execution status, unrelated), map_runs.go:120 (map
run, unrelated), and the one that matters: state_machines.go:144
(pre-fix) sm.Status = statusDeleting immediately followed by
b.stateMachines.Delete(arn) in the same locked critical section --
confirming the prior pass's finding verbatim: deletion was fully
synchronous, so no request could ever observe Status == DELETING, and
CreateStateMachine's sm.Status != statusDeleting guard
(state_machines.go:99, pre-fix) was dead code.
The fix: DeleteStateMachine now only completes the physical delete
immediately when the state machine has zero currently-RUNNING executions
(len(b.smExecsByStatus[arn][statusRunning]) > 0 gates it) -- this is the
common case and keeps every pre-existing delete test's synchronous
assertions valid unchanged. When executions are still running, the record
is left in place with Status = statusDeleting (and its name-index entry,
so the revived CreateStateMachine guard can see it); a new janitor tick
(sweepDeletingStateMachines, alongside the existing ExecutionPruner,
same 1m default interval) calls the new SweepDeletingStateMachines,
which physically completes deletion once the running-execution count drops
to zero -- no artificial sleep, the window is bounded by real in-flight
work plus the janitor's existing cadence. CreateStateMachine's
duplicate-name guard now returns StateMachineDeleting instead of
silently falling through to create a second record at the same
(name-derived, non-unique) ARN -- AWS ARNs have no uniquifying suffix, so
that fallthrough was a latent same-ARN collision bug waiting on this same
fix. StateMachineDeleting was wired into all 8 declaring ops
(CreateStateMachine, CreateStateMachineAlias, ListStateMachineAliases,
PublishStateMachineVersion, StartExecution, StartSyncExecution,
UpdateStateMachine, UpdateStateMachineAlias -- the last required a new
aliasParentStateMachineLocked reverse lookup since aliases don't store
their parent SM ARN). Mapped in classifyError as (StateMachineDeleting, 409 Conflict), matching this service's existing convention for the
AlreadyExists/ConflictException family (AWS's own JSON-1.0 wire status
isn't per-exception-customized for this shape, so the repo's own
established classifyError convention -- not literal AWS parity -- governs
the HTTP status choice here, same as the pre-existing DoesNotExist →
404 / AlreadyExists → 409 mappings).
Regression tests (state_machines_test.go,
handler_state_machines_test.go), all written and confirmed failing
against unmodified code before the fix:
TestDeleteStateMachine_DeletingObservableWhileExecutionRunning: starts an execution on aWait(3600s)state machine, deletes it while running, assertsDescribeStateMachinestill succeeds withStatus == "DELETING"(pre-fix:require.NoErroronErrStateMachineDoesNotExistfailed immediately), assertsCreateStateMachineon the same name returnsErrStateMachineDeleting, then stops the execution and assertsSweepDeletingStateMachinescompletes the deletion and frees the name.TestStateMachineDeleting_BlocksClientCallableOps: table of all 8 declaring ops against one shared DELETING state machine; pre-fix all 8 failed (err == nil, or the wrong sentinel --StateMachineDoesNotExist/StateMachineAliasDoesNotExist-- since the unmodified code fully deleted the record before any of them could run); post-fix all 8 asserterrors.Is(gotErr, ErrStateMachineDeleting).TestHandler_StartExecution_StateMachineDeleting: wire-level check through the real HTTP handler -- asserts the actual emittedresp["__type"] == "StateMachineDeleting"andrec.Code == http.StatusConflict(409), not merely that an error occurred.
GOTOOLCHAIN=go1.27.0 go test -race -count=1 ./services/stepfunctions/...: pass (bothstepfunctionsandstepfunctions/aslpackages).GOTOOLCHAIN=go1.27.0 golangci-lint run ./services/stepfunctions/...:0 issues.GOTOOLCHAIN=go1.27.0 go build ./...: clean (full-repo build, confirms no downstream breakage from the new exportedSweepDeletingStateMachines/ErrStateMachineDeleting).- No integration test (
test/integration/stepfunctions*) was added this pass -- the backend-level and handler-level regression tests above already assert the real emitted wire code end-to-end through the HTTP handler; a full SDK-client round trip would needtask build-linux+ Docker for a P3 fix already covered at the wire-format layer.
2026-09-08 pass (bd gopherstack-s9zy): DescribeStateMachineForExecution / StateMachineDoesNotExist re-audited, left unfixed
Second evidence pass on the one real finding carried over from the
2026-09-07 audit above (item 3). Re-extracted independently and confirmed
identical: awsAwsjson10_deserializeOpErrorDescribeStateMachineForExecution
(sfn@v1.49.0 deserializers.go:1588-1648) declares exactly
ExecutionDoesNotExist, InvalidArn, KmsAccessDeniedException, KmsInvalidStateException, KmsThrottlingException -- no
StateMachineDoesNotExist. botocore's stepfunctions/2016-11-23/service-2.json.gz
operations.DescribeStateMachineForExecution.errors lists the identical
five. The live
docs.aws.amazon.com/step-functions/latest/apireference/API_DescribeStateMachineForExecution.html
Errors section (fetched fresh this pass) agrees verbatim and adds no
special-case language for a deleted underlying state machine; the op's own
description only notes it is "eventually consistent" and unsupported for
EXPRESS state machines. Emission site unchanged:
executions.go:794-798, the !hasSnapshot && !smExists branch of
DescribeStateMachineForExecution, reachable via the documented
Phase-3.3 persistence boundary (executions restored after a restart carry
no executionDefinitions snapshot; if the referenced state machine is
later removed, this branch fires for a still-real execution).
New finding this pass, narrowing (not resolving) remedy (2): the
2026-09-07 note called the sibling branch three lines down
(hasSnapshot == true, SM also gone -- executions.go:806-813, returns
&StateMachine{StateMachineArn: ..., Definition: definition}) "the
strongest same-function precedent" for converting this branch to a
synthetic 200 too. That precedent doesn't actually transfer cleanly: the
sibling has a real definition string to populate (from the persisted
execution-time snapshot); the !hasSnapshot branch by construction has
none. A synthetic success here could only populate StateMachineArn,
leaving Definition empty -- but AWS's own wire shape constrains
definition: "Length Constraints: Minimum length of 1" (same API
reference page, Response Elements). A 200 with an empty definition field
would violate the op's own modeled shape, arguably a worse defect (silently
wrong data passing as success) than today's undeclared-but-visible error
code. This is new counter-evidence, not merely "still no evidence for" --
it actively weakens remedy (2) for this specific branch.
Remedy (1) (persist executionDefinitions, closing the root cause) remains
correct in principle but is a persistence-shape change out of scope for a
P3 error-code fix (bumps sfnSnapshotVersion, discards user snapshots on
restore per gopherstack-c8sa).
Also checked per this pass's brief: the 2026-09-08 StateMachineDeleting
work (gopherstack-kx95, section above) does not touch this op --
DescribeStateMachineForExecution is not among the 8 ops that declare
StateMachineDeleting, and kx95's DELETING window only narrows this
branch's reachability (a state machine with running executions now stays
present with Status = statusDeleting instead of vanishing immediately,
so smExists stays true longer) without closing it -- the branch still
fires once the janitor completes the sweep, or after a persistence
restore whose referenced state machine was deleted before the restore.
Left unfixed. No declared code fits "execution exists, state machine
gone" (ExecutionDoesNotExist would be affirmatively false), and the one
candidate remedy with same-function precedent (synthetic success) would
trade a wrong-typed-but-visible error for a schema-violating silent
success. No test was added or changed -- there is no fix to regress-test
against, and no pre-existing test asserts this path either way (confirmed
by re-grepping *_test.go for StateMachineDoesNotExist near
DescribeStateMachineForExecution; none found). Landmine comment at
executions.go:784-793 left as-is, now cross-referenced by this entry.
Added realclient_activities_and_execution_test.go, covering this service's last 7
typed-client-blind ops (DescribeActivity, ListActivities, ListStateMachines,
RedriveExecution, SendTaskFailure, SendTaskHeartbeat, UpdateStateMachine) --
typed coverage 30/37 -> 37/37 (0 uncovered). Zero real bugs found. Confirmed
correct: RedriveExecutionOutput.RedriveDate decodes non-nil
(api_op_RedriveExecution.go, sfn@v1.49.0); DescribeExecutionOutput.RedriveCount
increments on redrive (used as the test's stable assertion instead of
Status, since a Fail-state re-run completes asynchronously and near-
instantly, making a RUNNING-status snapshot immediately after
RedriveExecution returns a race rather than a guarantee); SendTaskFailure/
SendTaskHeartbeat correctly resolve a task token obtained via
GetActivityTask and unblock the corresponding InvokeActivity caller with
the real error/cause. Gates: go build ./... (whole module), go vet,
go test -race -count=1, golangci-lint run --new-from-rev=HEAD (0 issues)
all clean. No persisted struct fields changed; no version bump.
6 tier-1 fields: 4 fixed, 2 recorded. Fixed:
GetExecutionHistory.IncludeExecutionData (strips per-event input/output on
an explicit false) and ValidateStateMachineDefinition.severity/.type/
.maxResults (enum-validated, maxResults capping + Truncated wired, though
diagnostics never exceed one item today). Recorded: TestState. InspectionLevel/.RevealSecrets (no InspectionData subsystem to back
either). Tier-1: 6 -> 2. Gates green (build/vet/race-test/persistence/lint
0-new).
CreateStateMachineAlias's handler read a "stateMachineArn" field
CreateStateMachineAliasInput (sfn@v1.49.0) does not declare -- the real API
derives the target state machine from RoutingConfiguration's
StateMachineVersionArn entries instead. Since no real client could ever send
it, every real CreateStateMachineAlias call previously 404'd
(ErrStateMachineAliasDoesNotExist against an always-empty ARN) -- this was a
genuine, previously-undiscovered bug, not a dead path. Fixed: the backend
now resolves the owning state machine from routing[0].StateMachineVersionArn
and validates any second entry belongs to the same state machine (matching
the real API's documented constraint). Proven end-to-end through the real
SDK client: Test_SDKRoundTrip_StateMachineAlias_UpdateDate and
TestUpdateStateMachineAlias_InvalidRoutingConfig_RealClient now call
client.CreateStateMachineAlias directly instead of the previous
backend-only workaround documented in both tests' prior comments.
leak_main_test.go and Destroy() (cancels execution goroutines) already
existed from a prior pass; re-ran go test -race -count=2, still clean.
Implemented per AWS dev guide "Transforming data with JSONata in Step Functions"
and "Passing data between states with variables": top-level and per-state
QueryLanguage; Arguments/Output/Assign; {% %} expressions (strict
wrapping, validated at CreateStateMachine/ValidateStateMachineDefinition);
$states.input/result/errorOutput/context with creation-time checks of where
result/errorOutput are readable; Choice Condition (+ rule Assign); Map
Items/ItemSelector/MaxConcurrency; Wait Seconds/Timestamp; Task
TimeoutSeconds/HeartbeatSeconds; Fail Error/Cause; Catch Output/Assign;
States.QueryEvaluationError (catchable) for failed/undefined/mistyped
expressions and the 1s evaluation timeout; $partition/$range/$hash/$random/ $uuid/$parse ($eval rejected). Variables (also in JSONPath states): evaluation
at state entry, new values visible from the next state, inner scopes (Parallel/
Map) read outer variables and may not redeclare outer names, variable-name syntax
and 80-char limit. Engine: github.com/recolabs/gnata v0.5.0 (JSONata 2.x, MIT).
Proof: jsonata_sdk_test.go (typed SDK) and asl/jsonata_test.go.
- Step Functions Local / LocalStack mock config (
StateMachines/TestCases/MockedResponses,Return/Throw,"N"and"N-M"invocation keys) loaded fromSFN_MOCK_CONFIG(orLOCALSTACK_SFN_MOCK_CONFIG); env only, no CLI flag; invalid file fails startup with a clear error. - Activated by
StartExecution/StartSyncExecutionwithstateMachineArn#TestCase; unknown test case or no config returnsInvalidArn. - Mocked Task states skip the real integration (incl.
.waitForTaskToken) in JSONPath and JSONata, inside Map/Parallel; Retry/Catch/ResultSelector apply. Invocation index is counted per state across the run; a missing index fails withStates.Runtime. - Not mocked: Distributed Map child executions and RedriveExecution. Sources: docs.aws.amazon.com/step-functions/latest/dg/sfn-local-mock-cfg-file.html, docs.localstack.cloud/aws/services/stepfunctions/.
arn:aws:states:::aws-sdk:<service>:<action>is served in-process for 55 services (sdk_services.go): the real aws-sdk-go-v2 client builds the protocol-correct request and the full in-process server (internal/app/cli.gowireStepFunctionsSDKIntegration) answers it; Parameters PascalCase in, SDK output PascalCase out (nil members omitted, blobs base64, S3 Body and Lambda Payload as strings). Sources: docs.aws.amazon.com/step-functions/latest/dg/supported-services-awssdk.html, integrate-services.html.- Errors are
<ServicePrefix>.<ErrorName>withExceptionappended when absent (DynamoDb.ResourceNotFoundException,S3.NoSuchKeyException,Sqs.QueueDoesNotExistException), so Catch/Retry on those names works; optimized resources useDynamoDB/SQS/SNS/StepFunctionsprefixes (best effort, not verified against AWS). - Optimized
dynamodb:*,sqs:*,sns:*,events:putEvents,states:startExecution(.syncstring Output,.sync:2JSON Output, nested failure =States.TaskFailed),athena:startQueryExecution(.sync),batch:submitJob(.sync),codebuild:startBuild(.sync)use the same path;lambda:invoke(and direct Lambda ARNs) honor Payload/Qualifier/InvocationType and fail with the functionerrorTypeon a Lambda error payload. SFN_MOCK_CONFIG mocks still win over every integration. - With
--enforce-iam, SDK/optimized calls are signed with temporary credentials for the state machine's RoleArn (sts.AssumeRoleForService, trust must allowstates.amazonaws.com; cached per role, refreshed a minute before expiry), so the role's IAM policies apply and a denial surfaces as<Service>.AccessDeniedException. A missing/untrusted role fails the taskStates.Permissions(documented: docs.aws.amazon.com/step-functions/latest/dg/concepts-error-handling.html). Enforcement off keeps static credentials. Direct Lambda/ECS/Glue legacy adapters are not role-scoped. - Also fixed:
.sync:2was mis-parsed as action2; a Catch ResultPath was applied to the post-Parameters input instead of the state input; StartExecution with no Name produced an empty-named execution. - Tests:
internal/app/sfn_sdk_integration_test.go(typed sfn StartSyncExecution against the full in-process server: success, Catch, Retry, JSONPath and JSONata),sdk_integration_mock_test.go,asl/sdk_integration_internal_test.go. Not covered: bedrockinvokeModel,http:invoke,eks.
arn:aws:states:::http:invoke(http_task.go, http_form.go): real outbound HTTP, 60 s client timeout (States.Http.Socketon timeout), 256 KiB response cap (States.DataLimitExceeded), http(s) redirects only. Parameters, header merge precedence (connection wins), forbidden-header list andStates.Runtimecases, URL_ENCODED Transform with INDICES/REPEAT/COMMAS/BRACKETS, andUser-Agent/Rangeheaders follow the AWS guidecall-https-apis; non-2xx fails asStates.Http.StatusCode.<code>, octet-stream/image/video/audio responses asStates.Runtime. The guide does not publish the Output keys or Cause text: Output is {Headers (arrays), ResponseBody, StatusCode, StatusText} and Cause is that object as JSON (unverified).- Connection credentials come from the EventBridge connection store (API_KEY, BASIC, OAuth client credentials; the emulator keeps the secret in memory, not Secrets Manager). Under
--enforce-iamthe role needsstates:InvokeHTTPEndpoint(denial:States.Permissions) andevents:RetrieveConnectionCredentials,secretsmanager:GetSecretValue,secretsmanager:DescribeSecret(denial:Events.ConnectionResource.AccessDenied). ecs:runTask(.sync)andglue:startJobRun(.sync)now use the SDK path (Ecs.*/Glue.*error names);.syncpolls DescribeTasks/GetJobRun. ECSFailureson.syncfail asAmazonECS.Unknown, Glue's response gainsJobName(guideconnect-ecs,connect-glue). The legacy asl adapters remain only for executors without an SDK integration.- DynamoDB optimized prefix
DynamoDBis confirmed byconnect-ddb; the guides for SQS, SNS and Step Functions state no error prefix, soSQS/SNS/StepFunctionsstay unverified.lambda:invokeagainst the real Lambda backend is untested: its only container runtime is Docker (tests inject a mock Docker API inside the lambda package). - An uncaught Task error now fails the execution with the task's own error name and Cause (DescribeExecution
error/cause: "The error string if the state machine execution failed"); non-AWS-shaped errors becomeStates.TaskFailed. It used to be the bare nameTaskFailed. - http:invoke reads credentials through
eventbridge.ResolveConnectionAuth(an AUTHORIZED connection only; otherwiseEvents.ConnectionResource.InvalidConnectionStateorResourceNotFound);TestStepFunctionsHTTPTaskEventBridgeConnectionsdrives it with connections made by the typed EventBridge client. - Tests:
internal/app/sfn_http_task_test.go(typed StartSyncExecution: HTTP success/non-2xx/auth/transform/authz, Glue and ECS.sync).
- Executions now run on a context carrying the state machine's region (
execContext), so the legacy ECS and Glue adapters resolve that region's backend. ECS also honours theCluster/TaskDefinitionARN region;.syncpolling follows the task's cluster ARN region. - Proof:
TestExecutionContextCarriesBackendRegion,TestInitializeServices_CrossServiceECSTargetsUseOriginRegion,TestInitializeServices_CrossServiceGlueAndTaggingUseOriginRegion.
Emits AWS/States (StateMachineArn; procedure-cw-metrics.html): ExecutionsStarted, ExecutionsSucceeded, ExecutionsFailed, ExecutionsAborted, ExecutionsTimedOut (Express sync timeout) and ExecutionTime (Milliseconds, second-resolution start/stop). Verified by internal/app/service_metrics_test.go (typed SDK, ListMetrics + GetMetricStatistics).
The SQS service integration sends to the queue in the execution's region and the EventBridge integration puts events in it. Proof: TestInitializeServices_SQSTargetsUseQueueARNRegion/stepfunctions-execution-region, TestInitializeServices_EventBridgePutEventsUseBusARNRegion/stepfunctions-execution-region.
InspectionLevel is validated (INFO, DEBUG, TRACE; otherwise ValidationException) and DEBUG/TRACE return InspectionData from the executor's stage hooks (asl EnableInspection): input, afterInputPath, afterParameters, result, afterResultSelector, afterResultPath, as JSON strings; INFO (the default) returns none. Proof: TestTestState_InspectionLevel_RealClient. JSONata states record only the raw input; arguments/variables stages are not recorded.
ListExecutions applies redriveFilter (REDRIVEN/NOT_REDRIVEN on RedriveCount; api_op_ListExecutions.go:80-92) to mapRunArn listings and returns ValidationException for it with a stateMachineArn or for an unknown value. Proof: TestListExecutions_RedriveFilter.
Removed dead ErrInvalidExecutionType (never raised) and its mapper row. TaskTokenAlreadyExists and ActivityTaskFailed come from the in-engine WaitForTaskToken path (activities.go:347,368), not from an API op, so no op deserializer applies. StateMachineVersionReferencedByAlias maps to ConflictException; TaskTokenNotFound maps to TaskDoesNotExist.
CreateStateMachine rejects transitions to missing states and Task/Pass/Wait/Parallel/Map states with neither Next nor End (InvalidDefinition); TestState keeps single-state definitions valid. Names reject whitespace, control and <>{}[]?*"#%\^|~$&,;:/rather than allowing space and/`. Malformed nextToken is InvalidToken (tokens are now opaque), a non-JSON execution input is InvalidExecutionInput, an unknown statusFilter is ValidationException. StopDate/CreationDate/UpdatedDate/history timestamps carry millisecond precision (stopDate was truncated below startDate). Error messages no longer repeat the code prefix. Lenient on purpose: StartExecution still runs to completion synchronously (no RUNNING window for fast definitions). Tests: realism_validation_test.go.
History: ExecutionStarted/Succeeded/Failed/Aborted/TimedOut now carry their EventDetails (input, roleArn, output, error, cause); Parallel and Map emit ParallelStateStarted/Succeeded/Failed, MapStateStarted/Succeeded/Failed (length) and MapIteration events; each Retry attempt records TaskScheduled/TaskStarted/TaskFailed. RedriveExecution resumes at the failed top-level state with its entered input, keeps the history and appends ExecutionRedriven (it used to restart from the beginning and reset history). Left lenient: previousEventId is linear (not the triggering event for branches/iterations); Lambda-specific LambdaFunction* event types remain unmodeled; ListExecutions on an EXPRESS machine still returns a list (the dashboard relies on it).
eks:createCluster/deleteCluster/createNodegroup/deleteNodegroup (+ .sync, polling DescribeCluster/DescribeNodegroup; delete polls until ResourceNotFoundException) and eks:call (Kubernetes request to the cluster endpoint with an aws eks get-token style bearer and the supplied CA) run through the generic SDK integration; bedrock:invokeModel encodes Body and returns it parsed. S3 ItemReader/ResultWriter calls are authorized as the execution role (s3:GetObject/ListBucket/PutObject). The IAM enforcement middleware now answers REST-JSON services (eks, bedrock, lambda, apigateway) with X-Amzn-Errortype: AccessDeniedException JSON instead of query XML. Proof: internal/app/sfn_eks_bedrock_test.go, eks_call_test.go, s3_role_authz_test.go.