Skip to content

Latest commit

 

History

History
1575 lines (1447 loc) · 119 KB

PARITY.md

File metadata and controls

1575 lines (1447 loc) · 119 KB
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
CreateStateMachine UpdateStateMachine DeleteStateMachine DescribeStateMachine ListStateMachines DescribeStateMachineForExecution PublishStateMachineVersion DeleteStateMachineVersion ListStateMachineVersions CreateStateMachineAlias UpdateStateMachineAlias DeleteStateMachineAlias DescribeStateMachineAlias ListStateMachineAliases TagResource UntagResource ListTagsForResource ValidateStateMachineDefinition StartExecution StartSyncExecution StopExecution RedriveExecution DescribeExecution ListExecutions GetExecutionHistory CreateActivity DeleteActivity DescribeActivity ListActivities GetActivityTask SendTaskSuccess SendTaskFailure SendTaskHeartbeat DescribeMapRun ListMapRuns UpdateMapRun TestState
wire errors state persist note
fixed
ok
ok
ok
FIXED this pass: the response never echoed back stateMachineVersionArn when publish=true (AWS: "If you do not set the publish parameter to true, this field returns null value" -- implying it MUST be populated when publish=true; PublishStateMachineVersion's result was previously discarded). Also added versionDescription parsing + AWS's documented ValidationException when versionDescription is set with publish=false. STANDARD/EXPRESS, roleArn validation, tags, logging/tracing config unchanged and correct.
wire errors state persist note
fixed
ok
ok
ok
FIXED this pass: UpdateStateMachineOutput.RevisionId and .StateMachineVersionArn (publish=true only) were entirely absent -- the backend method returned only (updateDate, error). Added StateMachine.RevisionID (opaque crypto/rand-generated token, regenerated every update, matching AWS's "compare between versions ... without performing a diff of the properties" semantics), changed UpdateStateMachine's signature to (updateDate, revisionID, error), and wired both new output fields + the same versionDescription/publish ValidationException as CreateStateMachine.
wire errors state persist note
ok
fixed
ok
ok
FIXED (error-path sweep, 2026-08-29): raised a fabricated StateMachineDoesNotExist for a missing state machine; DeleteStateMachine's own deserializeOpError models only InvalidArn/ValidationException, so it is now idempotent on a missing state machine, matching AWS.
wire errors state persist note
fixed
ok
ok
ok
FIXED this pass: did not support version-qualified ARNs at all. AWS: "This API action returns the details for a state machine version if the stateMachineArn you specify is a state machine version ARN" (and echoes the version ARN back as StateMachineArn, unlike execution start which always normalizes to the base ARN). This is the REAL mechanism AWS uses for fetching version details -- there is no separate DescribeStateMachineVersion operation in the actual API (see notes; that op was fabricated in this emulator and has been deleted). Also now returns the new RevisionID field.
wire errors state persist note
fixed
ok
ok
ok
FIXED (gopherstack-dv4s): response marshaled the full StateMachine struct per item, leaking definition/roleArn/status/revisionId/updatedDate/encryptionConfiguration/tracingConfiguration/loggingConfiguration -- real StateMachineListItem (types.go, sfn@v1.45.4) declares only creationDate/name/stateMachineArn/type. Prior 'wire: ok' verified required-field presence, not absence of extras. Now marshals a new stateMachineListItem view; page.Page[T] pagination unchanged.
wire errors state persist
ok
ok
ok
ok
wire errors state persist
ok
ok
ok
ok
wire errors state persist note
ok
fixed
ok
ok
FIXED (error-path sweep, 2026-08-29): raised a fabricated StateMachineVersionDoesNotExist (names no type anywhere in this SDK) for a missing version; DeleteStateMachineVersion's own deserializeOpError models only ConflictException/InvalidArn/ValidationException, so it is now idempotent on a missing version.
wire errors state persist note
fixed
fixed
ok
ok
FIXED (gopherstack-dv4s): response marshaled the full StateMachineVersion struct per item, leaking stateMachineArn/name/definition/roleArn/type/status/description/revisionId -- real StateMachineVersionListItem (types.go, sfn@v1.45.4) declares only creationDate/stateMachineVersionArn. Now marshals a new stateMachineVersionListItem view. ERRORS FIXED (error-path sweep, 2026-08-29): unlike its ListExecutions/ListStateMachineAliases siblings, this op's own deserializeOpError models no StateMachineDoesNotExist -- it now returns an empty page for an unknown stateMachineArn instead of raising.
wire errors state persist note
ok
fixed
ok
ok
routingConfiguration weighted versions validated. ERRORS FIXED (error-path sweep, 2026-08-29): raised fabricated StateMachineDoesNotExist/StateMachineAliasAlreadyExists codes naming no type in this SDK; now emits the modelled ResourceNotFound/ConflictException. FIXED (2026-09-18, acceptguard, gopherstack-1ai8): CreateStateMachineAliasInput has no stateMachineArn field on the real wire -- AWS derives the target state machine from routingConfiguration's version ARNs -- but this backend required stateMachineArn explicitly, so it always 404'd through any conformant SDK client. Now resolved from routing[0].StateMachineVersionArn (validating any second entry belongs to the same state machine); proven via Test_SDKRoundTrip_StateMachineAlias_UpdateDate driving Create through the real client.
wire errors state persist note
ok
fixed
ok
ok
FIXED (error-path sweep, 2026-08-29): raised a fabricated StateMachineAliasDoesNotExist for a missing alias; now emits the modelled ResourceNotFound.
wire errors state persist note
ok
fixed
ok
ok
FIXED (error-path sweep, 2026-08-29): raised a fabricated StateMachineAliasDoesNotExist for a missing alias; now emits the modelled ResourceNotFound.
wire errors state persist note
ok
fixed
ok
ok
FIXED (error-path sweep, 2026-08-29): raised a fabricated StateMachineAliasDoesNotExist for a missing alias; now emits the modelled ResourceNotFound.
wire errors state persist note
fixed
ok
ok
ok
FIXED (gopherstack-dv4s): response reused stateMachineAliasEntry (the Describe/Create/Update shape), leaking name/description/routingConfiguration/updateDate -- real StateMachineAliasListItem (types.go, sfn@v1.45.4) declares only creationDate/stateMachineAliasArn. Now marshals a new, distinct stateMachineAliasListItem view; stateMachineAliasEntry stays as-is for Describe/Create/Update, which do carry all those fields.
wire errors state persist note
fixed
fixed
ok
ok
ERRORS FIXED (error-path sweep, 2026-08-29): the too-many-tags branch of validateTags raised a fabricated TagPolicyViolation; now emits the modelled TooManyTags. The key-too-long/empty-key/value-too-long branches still emit TagPolicyViolation, which also names no type in this SDK -- TagResource's own deserializeOpError models only InvalidArn/ResourceNotFound/TooManyTags, no exception matching a key/value length violation, so no replacement code is confirmed; left as-is per this sweep's restraint rule (report, don't invent). WIRE FIXED (gopherstack-2kph): sfnTagResourceInput.Tags was typed *tags.Tags (JSON object), but the real TagResourceInput.Tags field (sfn@v1.45.4 api_op_TagResource.go) is []types.Tag, serialized as an array of {key,value} objects (serializers.go:3140-3145, awsAwsjson10_serializeOpDocumentTagResourceInput). Every real client call 500'd ("cannot unmarshal array into ... map[string]string") and got retried 3x. Now typed []sfnTagEntry, matching the shape CreateStateMachine/CreateActivity's inline tags already used. UntagResource's TagKeys []string and ListTagsForResource's []types.Tag output were already correct and needed no change.
wire errors state persist
ok
ok
ok
ok
wire errors state persist
ok
ok
ok
ok
wire errors state persist note
fixed
ok
ok
n/a
FIXED this pass (partial): added Retry.JitterStrategy enum validation (recursively, including nested Map/Parallel Iterator/ItemProcessor/ Branches) -- AWS rejects anything other than "FULL"/"NONE"/omitted with ValidationException at Create/UpdateStateMachine time; this emulator previously accepted any string silently (bd: gopherstack-xtl, closed). Still JSON/structural validation only beyond that one check -- other deep ASL semantic checks (e.g. ToleratedFailure+INLINE combos) remain unimplemented, see gaps. FIXED 2026-08-21 (bd gopherstack-r80d, batch 10): the FAIL-path diagnostic map only ever set "message"/"code", leaving ValidateStateMachineDefinitionDiagnostic.Severity (types.go:1559-1586, required) as the client-side zero value on every invalid definition -- the common case this op exists to exercise, not an edge case. Fixed by adding "severity": "ERROR" (ValidateStateMachineDefinitionSeverity declares exactly ERROR/WARNING, enums.go:464-470; this handler only ever emits the FAIL/blocking case, never WARNING). FIXED 2026-09-18 (reqfielddiff, gopherstack-xhu2t): severity, type, and maxResults (all real ValidateStateMachineDefinitionInput members) were entirely undeclared -- the decode struct had only definition. Now declared; severity/type are enum-validated (ERROR/WARNING, STANDARD/EXPRESS) and rejected with ValidationException otherwise; maxResults (default/cap 100 per the doc) is applied to the diagnostics list with a real Truncated output field (previously absent from the wire entirely). Neither severity's WARNING/ERROR split nor maxResults truncation has an observable effect today beyond the enum check itself: asl.Parse can only ever produce zero or one ERROR diagnostic, so there is never more than one item to filter or truncate -- disclosed, not fabricated. Proven via TestValidateStateMachineDefinition_Options (real aws-sdk-go-v2 client).
wire errors state persist note
fixed
ok
ok
ok
FIXED this pass (severe): StartExecution never resolved version- or alias-qualified stateMachineArn ARNs -- AWS documents all three input shapes (unqualified / stateMachineArn:N version / stateMachineArn:name alias) as valid, with alias ARNs applying weighted routing across 1-2 versions. The pre-existing code did a direct, exact-match b.stateMachines.Get(stateMachineArn) lookup, so ANY qualified-ARN StartExecution call failed with StateMachineDoesNotExist even though CreateStateMachineAlias/PublishStateMachineVersion (the resource CRUD side) were fully implemented and previously marked "ok" -- the two halves of this feature were never connected. Added resolveExecutionTarget() (qualified_arn.go): resolves unqualified/ version/alias ARNs to the target version's frozen Definition/RoleArn/Type, keyed by the BASE (unqualified) ARN for execution-ARN construction (AWS never carries a qualifier into execution ARNs), with weighted random version selection for 2-entry alias routing configs. Added Execution.StateMachineVersionArn/ StateMachineAliasArn (AWS DescribeExecutionOutput fields, previously entirely absent), populated only when the qualifier was used, per AWS's documented null-when-unqualified semantics. FIXED 2026-09-11 (bd: gopherstack-1sf): StartExecutionInput has no ClientRequestToken member at all (checked sfn@v1.49.0, none of api_op_StartExecution.go's fields is one) -- the real semantic, per the Name field's doc, is name+input based: EXPRESS now skips the uniqueness check entirely (names reusable immediately); STANDARD reuse of a still-RUNNING execution's name with the same Input now returns that execution unchanged (idempotent), and with a different Input or a closed execution still raises ExecutionAlreadyExists. Not modeled: the 90-day name-reuse window after a STANDARD execution closes (see gaps) -- reuse of a closed execution's name conflicts unconditionally here.
wire errors state persist note
fixed
ok
ok
ok
FIXED this pass: same qualified-ARN resolution gap as StartExecution, fixed via the same resolveExecutionTarget() helper.
wire errors state persist note
fixed
ok
ok
fixed
cancels the execution's context via cancelFns; goroutine exits promptly. 2026-09-19 (gopherstack-r80d required-output-members re-check): required StopDate could decode as JSON null -- Snapshot's RUNNING->TIMED_OUT promotion (persistence.go, for surviving process restart) never set StopDate, so StopExecution's already-terminal no-op branch returned a nil StopDate for any execution that outlived a restart. This is the exact gap PARITY.md's own prior 'Reviewed, not a bug' note for StopDate missed -- it traced finalizeExecutionRecordLocked/StopExecution but not this third transition. Fixed: Snapshot now backfills StopDate to the snapshot time when promoting to TIMED_OUT. Proven via Test_SDKRoundTrip_StopExecution_StopDate_AfterSnapshotTimeout (wire_output_required_r80d_test.go), hand-confirmed failing pre-fix. Also fixed same pass: DescribeMapRun's required MaxConcurrency/ToleratedFailureCount/ToleratedFailurePercentage were tagged omitempty despite 0 being a common real value -- harmless for these non-pointer scalar SDK fields (decodes the same either way) but corrected for wire-shape hygiene; see families.MapRun / models.go.
wire errors state persist
ok
ok
ok
ok
wire errors state persist note
fixed
ok
ok
ok
FIXED this pass: StateMachineVersionArn/StateMachineAliasArn added (see StartExecution). RedriveStatus/RedriveStatusReason were fixed in the 2026-08-23 manifest-harvest pass (see notes below). MapRunArn (bd: gopherstack-f5dc, structural half tracked under gopherstack-8j8) is FIXED this pass (bd: gopherstack-zov6): a DISTRIBUTED Map state now spawns a real child Execution per item/batch (see asl_map family note), and that child's own DescribeExecution response carries a real MapRunArn. A non-child (top-level, or INLINE-map-iteration) Execution still has no MapRunArn, correctly -- AWS never sets one there either. FIXED 2026-09-11 (bd: gopherstack-f5dc): TraceHeader is now parsed from StartExecutionInput, echoed on DescribeExecutionOutput, and persisted across Snapshot/Restore. InputDetails/OutputDetails (CloudWatchEventsExecutionDataDetails) were wire-tagged/valued {truncated:false} -- the real type has no Truncated member, only Included bool (sfn@v1.49.0 types.go:159-166), so real clients always decoded neither field regardless of emulator output; now correctly {included:true}.
wire errors state persist note
fixed
fixed
ok
ok
FIXED (gopherstack-dv4s): response marshaled the full Execution struct per item, leaking input/output/error/cause -- real ExecutionListItem (types.go, sfn@v1.45.4) has no such fields. Now marshals a new executionListItem view. ERRORS FIXED (error-path sweep, 2026-08-29): ListExecutions models StateMachineDoesNotExist but the backend never checked stateMachineArn existence at all -- an unknown ARN silently returned an empty page (missing-error: success where AWS raises). Now raises StateMachineDoesNotExist for an unknown stateMachineArn. FIXED this pass (bd: gopherstack-zov6): the mapRunArn query mode (AWS: "you can specify either a mapRunArn or a stateMachineArn, but not both" -- api_op_ListExecutions.go) is now parsed and handled; supplying both raises ValidationException. ExecutionListItem's itemCount/mapRunArn (sfn@v1.49.0 types.go:308-317, both "returned only if mapRunArn was specified") are populated on the results of a mapRunArn-scoped query and correctly absent (omitempty) from a stateMachineArn-scoped one, since a non-child Execution's underlying MapRunArn/ItemCount fields stay at their zero values. Distributed Map child executions are excluded from the default stateMachineArn-scoped listing (ListExecutions(stateMachineArn=...)) even though they share the parent's StateMachineArn, matching AWS: they surface only via the mapRunArn query. See asl_map family note for how children are spawned.
wire errors state persist note
fixed
ok
ok
ok
FIXED 2026-08-21 (bd gopherstack-r80d, batch 10; closes the resource/region/parameters portion of gopherstack-996, open since 2026-07-05): TaskScheduledEventDetails.Region/Parameters (types.go: 1311-1339, both required) were never set at all -- RecordTaskScheduled only ever populated Resource/ResourceType. TaskSucceededEventDetails. Resource/ResourceType (types.go:1431-1450, required) and TaskFailedEventDetails.Resource/ResourceType (types.go:1289-1307, required) were also never set. All four are reachable on every normal Task-state execution, not an edge case. Fixed by threading state.Resource through RecordTaskSucceeded/RecordTaskFailed (asl/ executor.go's HistoryRecorder interface gained a resource param on both) and the resolved post-Parameters-template task input through RecordTaskScheduled for Parameters, with Region derived via the existing regionFromARN(resource, backend.region) helper (same one used for activity ARNs elsewhere in this package). gopherstack-996's remaining scope (TaskSubmitted/TaskStarted events for .sync/ waitForTaskToken integration patterns) is a structural gap, not a dropped-field bug -- this emulator never models those event kinds at all, so no HistoryEvent ever claims to be one; left open, see gaps. FIXED 2026-09-11 (bd: gopherstack-996), closing the timeout/heartbeat portion: TaskScheduledEventDetails.TimeoutInSeconds/HeartbeatInSeconds (types.go:1311-1339, both optional *int64) are now set from the Task state's own TimeoutSeconds/HeartbeatSeconds, nil (not a fabricated 0) when unset. Also fixed: Resource on TaskScheduled/TaskSucceeded/TaskFailed was the raw Task Resource ARN; the real field is "the action of the resource called by a task state" (types.go), so a States service-integration ARN (arn:aws:states:::lambda:invoke) is now split to just its action ("invoke"), matching AWS; direct-service/activity ARNs are unchanged. FIXED 2026-09-18 (reqfielddiff, gopherstack-xhu2t): includeExecutionData (real, documented default true) was entirely undeclared -- a caller asking to omit event input/output with includeExecutionData=false always got it back anyway. Now declared; an explicit false strips StateEnteredEventDetails.Input/StateExitedEventDetails.Output/TaskSubmittedEventDetails.Output/TaskSucceededEventDetails.Output from a copy of each returned event (the stored history itself is never mutated). Proven via TestGetExecutionHistory_IncludeExecutionData (real aws-sdk-go-v2 client).
wire errors state persist note
fixed
ok
ok
ok
FIXED this pass: encryptionConfiguration (real CreateActivityInput field, server-side KMS encryption) and tags (real CreateActivityInput field) were both entirely unparsed/unsupported. Added Activity.EncryptionConfiguration + SetActivityEncryptionConfiguration (mirrors SetStateMachineConfigurations' established pattern) and inline-tags handling (mirrors CreateStateMachine's h.setTags call). Kept CreateActivity(ctx, name)'s existing signature unchanged (~35 call sites) rather than adding required params.
wire errors state persist note
fixed
fixed
ok
ok
LEAK FIX this pass: DeleteActivity never cleaned up h.tags for the deleted activity ARN (DeleteStateMachine's handler already did this for state machines) -- a permanent per-deleted-activity tombstone entry in the handler's tags map. Added the same tagsMu-guarded cleanup DeleteStateMachine uses. ERRORS FIXED (error-path sweep, 2026-08-29): raised a fabricated ActivityDoesNotExist for a missing activity; DeleteActivity's own deserializeOpError models only InvalidArn, so it is now idempotent on a missing activity, matching AWS.
wire errors state persist note
fixed
ok
ok
ok
now returns EncryptionConfiguration (see CreateActivity)
wire errors state persist note
fixed
ok
ok
ok
FIXED (gopherstack-dv4s): response marshaled the full Activity struct per item, leaking encryptionConfiguration -- real ActivityListItem (types.go, sfn@v1.45.4) declares only activityArn/creationDate/name. Now marshals a new activityListItem view.
wire errors state persist note
ok
ok
ok
ok
long-poll with WaitTimeSeconds; task-token issuance
wire errors state persist
ok
ok
ok
ok
wire errors state persist
ok
ok
ok
ok
wire errors state persist note
ok
ok
ok
ok
States.HeartbeatTimeout enforced against HeartbeatSeconds
wire errors state persist note
fixed
fixed
ok
ok
ERRORS FIXED (error-path sweep, 2026-08-29): raised a fabricated MapRunDoesNotExist for a missing map run -- names no type anywhere in this SDK. DescribeMapRun's own deserializeOpError models InvalidArn/ResourceNotFound; now emits the modelled ResourceNotFound. REVERSED 2026-08-21 (bd gopherstack-r80d, batch 10): a prior pass concluded ExecutionCounts having no backing field was "correctly so" because this emulator has no distributed-map child-execution model (iterations run in-process, not as separate Execution records) -- true as far as it goes, but the conclusion drawn from it repeats the exact mistake this campaign's own candidates file documents and reverses for quicksight: "required-but-inapplicable means present-and-empty, not absent." ExecutionCounts is required on DescribeMapRunOutput (api_op_DescribeMapRun.go:57) and typed as a *types.MapRunExecutionCounts pointer client-side -- omitting the key entirely left that pointer nil, so a real client dereferencing out.ExecutionCounts.Total would nil-pointer panic, not just see a zero. Fixed by adding MapRunExecutionCounts (models.go, shape mirrors MapRunItemCounts) and an ExecutionCounts field on MapRun, always marshaled (no omitempty). FIXED this pass (bd: gopherstack-zov6): ExecutionCounts is no longer unconditionally zero. A DISTRIBUTED Map Run's ExecutionCounts is computed live, on every DescribeMapRun call, by tallying the real child Executions filed under the Run's MapRunArn (tallyMapRunExecutionCounts over the executionsByMapRun index) by their current Status -- so it reflects genuine in-flight state, not just a value frozen at Map-completion time. An INLINE Map Run (or a DISTRIBUTED one whose child-execution runner was somehow never wired) still reports genuinely zero ExecutionCounts, correctly: no child executions were ever filed under its MapRunArn to tally.
wire errors state persist note
fixed
fixed
ok
ok
FIXED (gopherstack-dv4s): response marshaled the full MapRun struct per item, leaking status/itemCounts/toleratedFailurePercentage/maxConcurrency/toleratedFailureCount/redriveCount/redriveDate -- real MapRunListItem (types.go, sfn@v1.45.4) declares only executionArn/mapRunArn/startDate/stateMachineArn/stopDate. Now marshals a new mapRunListItem view. ERRORS FIXED (error-path sweep, 2026-08-29): ListMapRuns models ExecutionDoesNotExist but the backend never checked executionArn existence -- an unknown ARN silently returned an empty page. Now raises ExecutionDoesNotExist for an unknown executionArn, with an OR-check against the mapRunsByExecution index so StartSyncExecution's EXPRESS executions -- never inserted into b.executions by design -- still list correctly.
wire errors state persist note
ok
fixed
ok
ok
ToleratedFailureCount/Percentage on the MapRun *resource* API were already real; the ASL-definition-level Map state fields were fixed in a prior pass. ERRORS FIXED (error-path sweep, 2026-08-29): raised a fabricated MapRunDoesNotExist for a missing map run -- names no type anywhere in this SDK; now emits the modelled ResourceNotFound.
wire errors state persist note
ok
ok
ok
n/a
InspectionLevel (INFO/DEBUG/TRACE), Mock (Task states: result or errorOutput), Context and StateConfiguration.RetrierRetryCount are honoured; Status is SUCCEEDED, FAILED, RETRIABLE (matching Retry, no wait) or CAUGHT_ERROR (matching Catch, NextState = Catch.Next). Proof: TestTestState_MockAndOutcomeStatus, TestTestState_MockAndContextValidation, TestTestState_InspectionLevel_RealClient. Remaining gaps are in items_still_open.
families
asl_task asl_choice asl_map asl_parallel asl_wait asl_pass_succeed_fail asl_intrinsics json_1_0_protocol timestamps
status note
fixed
FIXED (gopherstack-tdp6): a Task's ".sync" Resource suffix was recognized (ErrSyncPatternUnsupported already rejected it for the services AWS documents as Request-Response-only) but for ECS runTask.sync/Glue startJobRun.sync -- the two services AWS DOES support "Run a Job" for -- the task was dispatched fire-and-forget and the state advanced immediately with the start-call response, never observing whether the job actually finished or failed. Now, when an optional ECSSyncWaiter/GlueSyncWaiter is wired (asl/executor.go; internal/app/cli.go's wireStepFunctionsServiceIntegrations wires both whenever ECS/Glue are registered, via adapters in integrations.go polling ecs.DescribeTasks/glue.GetJobRun), invokeECSTask/invokeGlueTask poll every 25ms until the task reaches ECS's STOPPED or the job run reaches a terminal Glue JobRunState, bounded by ctx -- the same TimeoutSeconds-derived deadline every other Task type in this executor already honors, so a job that never completes doesn't hang the execution forever if TimeoutSeconds is set. On success the state's output is the service's own description of the completed job (ECS DescribeTasks shape / Glue GetJobRun's {"JobRun": ...} shape), not the start-call response, matching real AWS .sync. On failure (non-zero ECS container exit code; Glue JobRunState STOPPED/FAILED/TIMEOUT/ERROR/EXPIRED) the Task state fails via the existing FailError/Catch machinery. Unwired (no waiter set) stays exactly fire-and-forget, matching pre-fix behavior -- a silent no-op, never a hang. Batch, EMR, SageMaker, and nested Step Functions still have no completion signal and are out of scope; EMR/APIGateway .sync resources already fall through to a generic unsupported-integration TaskFailed (not ErrSyncPatternUnsupported specifically), which was true before this fix too and was left unchanged.
status note
ok
Unchanged this pass.
status note
fixed
SEVERE FIX this pass: asl.Executor's ItemReader path (resolveItemsFromReader, S3 CSV/JSON/JSONL decoding) was already correctly implemented and unit-tested at the executor level with mocks (asl/intrinsics_extras_test.go's stubS3), and the prior pass's PARITY.md carried it forward as "deferred ... spot-checked only, appeared correct" -- but nothing in services/stepfunctions/ or internal/app/cli.go ever called InMemoryBackend.SetS3Reader (which didn't even exist as a backend method). e.s3 was always nil in any real running gopherstack process, so EVERY Distributed Map ItemReader hard-failed with ErrS3ReaderNotConfigured -- an identical bug class to the ECS/Glue/EventBridge internal/app/cli.go wiring gap a prior pass found and fixed (see notes below). Fixed by: adding InMemoryBackend.s3Reader field + SetS3Reader() (store.go), threading it through startExecutionLocked/runParsedExecution/StartSyncExecution/ RedriveExecution alongside the other 6 integrations, adding s3Adapter/NewS3Integration (integrations.go, adapts services/s3.StorageBackend.GetObject to asl.S3Reader), and wiring internal/app/cli.go's wireStepFunctionsServiceIntegrations to call sfnBk.SetS3Reader(sfnbackend.NewS3Integration(s3H.Backend)) alongside the existing SQS/SNS/DynamoDB/ECS/Glue/EventBridge calls. Verified end-to-end with a real StartExecution against a Map+ItemReader definition and a fake S3Reader (services/stepfunctions/s3_item_reader_test.go). ResultWriter (S3 write-out, bd: gopherstack-8j8) is now implemented: State.ResultWriter is parsed (parser.go), and Executor.exportMapResults (asl/result_writer.go) writes SUCCEEDED_n.json/FAILED_n.json plus a manifest.json to the wired S3Writer on Map completion, returning {MapRunArn, ResultWriterDetails:{Bucket,Key}} in place of inline results -- verified against AWS docs (input-output-resultwriter.html) for the ResultWriter/ ResultWriterDetails/manifest.json shapes, since aws-sdk-go-v2's sfn client has no typed struct for any of these (they're ASL-JSON/ execution-output only, never part of the control-plane API surface). Wired the same way as S3Reader: internal/app/cli.go's wireStepFunctionsServiceIntegrations now also calls sfnBk.SetS3ResultWriter(sfnbackend.NewS3ResultWriterIntegration(s3H.Backend)). Known deviations: WriterConfig (Transformation/OutputType) is parsed but not applied -- only the plain S3-export shape is honored; per-item result records omit ExecutionArn/Name/StartDate/StopDate (real AWS backs each with a genuine child execution, gopherstack runs Map iterations as in-process sub-executors with no such resource to point to); the manifest folder segment uses gopherstack's own MapRunArn suffix (.../execName/stateName) rather than real AWS's Map:<uuid>, since gopherstack's MapRunArn was never shaped like AWS's to begin with. When no S3Writer is wired, or Parameters.Bucket is unset, the Map state degrades to its pre-existing inline-results behavior rather than failing. STALE, corrected this pass: this note's own DEFERRED line claimed "ItemProcessor.ProcessorConfig.Mode (bd: gopherstack-8im) remains unimplemented", but parser.go's validateSingleMapState/ validProcessorModes already parsed and validated Mode (INLINE/DISTRIBUTED) and ExecutionType (STANDARD/EXPRESS) by the time this pass started -- that half of gopherstack-8im was already done, just never reflected here. What genuinely remained unimplemented, and is FIXED this pass (bd: gopherstack-zov6), is Mode actually being *acted on*: DISTRIBUTED and INLINE parsed identically and both ran every iteration in-process. Now, when a Map state's ItemProcessor.ProcessorConfig.Mode is DISTRIBUTED, each item (or ItemBatcher batch -- batching already ran upstream of dispatch, so DISTRIBUTED mode gets it for free: one child per batch, itemCount == len(batch)) is run as a genuine child Execution: a real record in InMemoryBackend.executions with its own executionArn, Input, history, and MapRunArn, created and finalized through the exact same initializeExecutionRecord/finalizeExecutionRecordLocked machinery StartExecution's async path uses (services/stepfunctions/ distributed_map.go's runDistributedMapChild), not a parallel implementation. Children run concurrently, bounded by the Map state's already-existing resolveMaxConcurrency/semaphore machinery (asl/ distributed_map.go's runDistributedMapTasks mirrors runMapTasks), so MaxConcurrency was already correctly enforced for DISTRIBUTED mode with zero new code. ToleratedFailureCount/Percentage were likewise already applied uniformly to the per-item errs array regardless of mode, so they now bound genuine child-execution failures instead of in-process ones -- also free. The seam is a new asl.DistributedMapRunner interface (SetDistributedMapRunner), mirroring MapRunNotifier's existing shape; unset, a DISTRIBUTED Map falls back to running inline exactly as before this fix, so every prior asl-level unit test that builds an Executor directly (without wiring a runner) is unaffected. INLINE is untouched: isDistributedMapIterator gates strictly on ProcessorConfig.Mode == "DISTRIBUTED", so an INLINE Map (or a legacy Iterator-style Map, which has no ProcessorConfig at all) takes the pre-existing runMapTasks path unconditionally. FIXED 2026-09-26 (WriterConfig sweep), correcting the prior pass's "DISCLOSED, not modeled" note below: ResultWriter's per-item S3 export records omitting ExecutionArn/Name/StartDate/StopDate, and WriterConfig (Transformation/OutputType) being parsed but unapplied, are both fixed. asl.DistributedMapRunner.RunDistributedMapItem now returns a DistributedMapItemResult (Output plus ExecutionArn/Name/ StartDate/StopDate) instead of a bare `any`, threaded through runDistributedMapTasks into a new `meta []DistributedMapItemResult` slice that exportMapResults uses to populate Transformation: NONE records -- populated only for DISTRIBUTED Map items (real child Executions exist to source it from); INLINE Map iterations still correctly leave those fields empty, having no such resource. WriterConfig.Transformation (NONE: full metadata record with JSON-stringified Input/Output, matching a real DescribeExecution; COMPACT: raw per-item output; FLATTEN: COMPACT plus splicing any array output into the outer array) and OutputType (JSON: array; JSONL: newline-delimited, no enclosing array) are now applied to the S3-exported SUCCEEDED_n.json/FAILED_n.json files AND to the no-export preview output (ResultWriter with WriterConfig but no Resource/ Parameters, AWS's documented "preview the formatted output" shape). Per AWS's documented note ("If a child workflow execution fails, Step Functions returns its execution result unchanged"), a FAILED item's record is always the full NONE-shaped record regardless of Transformation -- verified via TestDistributedMapResultWriter_FailedItemsKeepFullRecord. Verified against input-output-resultwriter.html for the exact Transformation/ OutputType semantics; see TestDistributedMapResultWriter_TransformationOutputType (all 6 Transformation x OutputType combinations, real SDK client + wired in-process S3) and TestDistributedMapResultWriter_ DistributedChildIdentity. A DISTRIBUTED Map Run's parent MapRun *resource* record now persists (2026-10-01, see Notes). 2026-09-26 (WriterConfig sweep, new finding, not fixed this pass): re-reading input-output-itemreader.html surfaced that ItemReader only ever supports Resource=arn:aws:states:::s3:getObject with InputType JSON/JSON Lines/CSV against a single object -- Resource= arn:aws:states:::s3:listObjectsV2 (bucket/prefix metadata iteration, optionally with Transformation=LOAD_AND_FLATTEN), InputType=MANIFEST (ManifestType ATHENA_DATA/S3_INVENTORY), and InputType=PARQUET are all real, documented ItemReader shapes with no code path here at all -- resolveItemsFromReader never inspects ItemReader.Resource, and decodeReaderItems' InputType switch has no MANIFEST/PARQUET case. This was never previously documented in this file (grepped: no prior mention of ListObjectsV2/ManifestType/PARQUET anywhere in this PARITY.md's history). Not attempted that pass. FIXED 2026-09-26 (ItemReader Resource sweep), closing most of the above: resolveItemsFromReader now switches on ItemReader.Resource. arn:aws:states:::s3:listObjectsV2 lists the bucket/prefix (a new asl.S3ListReader interface, paginating via s3Adapter. ListObjectsV2Items against services/s3.StorageBackend.ListObjectsV2) and returns one item per object -- {Etag,Key,LastModified,Size, StorageClass}, LastModified as epoch seconds via pkgs/awstime.Epoch, matching the docs' example shape exactly. ReaderConfig.Transformation LOAD_AND_FLATTEN (new field) instead reads and decodes each listed object's content per InputType (JSON/JSONL/CSV; zero-byte trailing-slash "folder" keys are skipped, since they have no content to decode) and flattens every object's items into one array, per "Processing nested data sets" in the docs. ReaderConfig.ManifestType (new field) S3_INVENTORY -- and the legacy bare InputType=MANIFEST, which the docs' own example uses without ManifestType set, treated as identical -- reads a manifest.json (fileSchema, files[].key), fetches each listed CSV data file (gzip-decompressed when the key ends .gz, via compress/gzip), and decodes it with fileSchema's comma-separated column names as CSV headers, matching the docs' worked example's field names and values (TestItemReader_S3Manifest). All ItemReader failures (S3 NoSuchBucket/NoSuchKey, unsupported Resource, unsupported ManifestType/InputType) are now wrapped as a States.ItemReaderFailed FailError instead of falling back to the error's raw Go string as the Catch-match code -- AWS's own documented predefined error name for this failure class, so a Map state's Catch can now match it specifically instead of only via States.ALL. InputType=PARQUET is explicitly rejected with a dedicated sentinel error (ErrParquetUnsupported) rather than silently mis-parsed -- see the narrowed items_still_open entry. Verified via TestItemReader_S3ListObjectsV2/TestItemReader_S3Manifest/ TestItemReader_S3GetObject_Errors, all driven through the real aws-sdk-go-v2 sfn client with objects seeded in the in-process S3 backend.
status note
ok
Unchanged this pass.
status note
ok
Unchanged this pass.
status note
ok
Unchanged this pass.
status note
ok
Unchanged this pass. Non-AWS extras (informational, not a bug) still present -- see gaps.
status note
ok
Unchanged this pass.
status note
ok
Pattern-hunt pass (timestamp encoding class, 2026-08-29): protocol confirmed JSON-RPC 1.0 (awsAwsjson10_* serializer prefix, sfn@v1.45.4) and every *time.Time deserializer call in deserializers.go is smithytime.ParseEpochSeconds -- 30 occurrences across types/types.go + api_op_*.go, 6 distinct member names (CreationDate, StartDate, RedriveDate, StopDate, Timestamp, UpdateDate), no per-field trait override. gopherstack already stores every one of these as a raw float64 (Unix epoch seconds) end to end -- models.go's own header comment documents this explicitly -- and every write site uses float64(time.Now().Unix()) or an equivalent, never a time.Time marshalled through encoding/json. 0 new wrong-format bugs found. This class was already the subject of a prior fix (Test_SDKRoundTrip_ StateMachineAlias_UpdateDate / Test_SDKRoundTrip_ DescribeStateMachineForExecution_UpdateDate in wire_updatedate_test.go, gopherstack-1ai8): DescribeStateMachineAlias/ UpdateStateMachineAlias/DescribeStateMachineForExecution's UpdateDate wire tag was wrong and decoded nil through the real SDK client; both tests still pass against current code, reconfirming the fix holds. No Input struct in this SDK carries a *time.Time member, so there is no request-side parse direction to check for this service.
filter_semantics
status note
ok
gopherstack-uox6 (value-semantics sweep, 2026-08-30): this service establishes no prior sweep of this kind. First, its protocol: aws-sdk-go-v2/service/sfn@v1.45.4's types package has NO Filter struct at all (grep of types/types.go) -- this API surface has almost no server-side filtering. The one real filter is ListExecutionsInput.StatusFilter (types.ExecutionStatus, a single-value equality field, not a list), applied at executions.go:643 via an exact bucket lookup -- no documented modifier to get wrong. Everything else this service's ~14 hand-rolled 'match' helpers implement is Amazon States Language Choice-state comparators (asl/executor.go), which decide whether a state's input satisfies a rule, not an SDK list filter, but the same right-field-wrong-algorithm risk applies: evaluateChoiceRule's And/Or/Not (correct all/any/negate), IsPresent/IsNull/IsString/IsNumeric/IsBoolean/IsTimestamp (each compares a computed bool against *rule.IsX with ==, correctly honoring both true and false rather than only checking truthiness), and the String/Numeric/Boolean/Timestamp -Equals/-LessThan/-GreaterThan/-LessThanEquals/-GreaterThanEquals families (each Path and literal variant) were all read and are correct. stringMatchesPattern/globMatch (StringMatches) is the one genuine wildcard comparator in this family -- verified against the ASL spec's documented semantics (its own doc comment: '*' matches zero or more chars, backslash escapes the next character, anchored both ends) via a real two-pointer backtracking implementation; correct, including the escape case. No bugs found -- clean verdict.
gaps
structural_gaps
ExecutionThrottled is never emitted: the emulator applies no account-level StartExecution throttling.
ItemReader InputType=PARQUET (asl.ErrParquetUnsupported) fails with a sentinel error: no pure-Go Parquet reader is in go.mod.
TestState RevealSecrets has no effect and TRACE adds no HTTP request/response: the executor runs without SDK integrations, so HTTP Tasks and EventBridge connection secrets are unavailable.
items_still_open
TestState StateConfiguration.ErrorCausedByState/MapIterationFailureCount/MapItemReaderData are accepted but unused (the pinned SDK documents the fields but not their observable effect), and MockInput.fieldValidationMode is enum-checked but mocks are not validated against service API models (no per-service response schemas). The pinned SDK models no StateConfiguration.Variables.
JSONata (gopherstack-iisrz) gaps (ToleratedFailureCount/Percentage expressions are supported: TestJSONata_ToleratedFailureExpressions): Items given as a JSON object (AWS accepts array or object but the docs checked, state-map-distributed and input-output-itemspath, do not give the per-pair item shape; objects are rejected with States.QueryEvaluationError); ItemReader/ItemBatcher/ResultWriter expressions; Retry Output/Assign; the Expression-evaluation memory limit is not enforced; the AWS wording of JSONPath-field-in-JSONata validation errors is undocumented, so a plain InvalidDefinition message is used; omitted Task Arguments passes the state input (unverified against AWS).
Service integrations not implemented (bd gopherstack-wdw): bedrock optimized integrations other than invokeModel (createModelCustomizationJob.sync etc.; only InvokeModel is served by bedrockruntime), eks:call against a cluster without the Docker EKS engine (fails the task: the synthetic endpoint is not reachable), and aws-sdk integrations for services outside the 55-service table in sdk_services.go. Not documented by AWS and unverified: States.Http.StatusCode.<n> Cause format, SQS/SNS/StepFunctions/Bedrock optimized error prefixes, ECS/Glue/EKS .sync output (Glue keeps GetJobRun {JobRun}; EKS returns DescribeCluster/DescribeNodegroup, deletes return the delete response).
Service-initiated calls not yet authorized against a customer role under --enforce-iam (2026-10-03): Firehose delivery role (S3/Redshift/HTTP destinations), CloudWatch Logs subscription-filter RoleArn (Kinesis/Firehose destinations), IoT rule action roleArn, SNS SubscriptionRoleArn Firehose subscriptions, Step Functions activity paths, Scheduler DLQ sqs:SendMessage under the schedule role, and EventBridge Lambda/SQS/SNS targets (AWS authorizes them by the target's resource policy, which gopherstack does not evaluate for these in-process deliveries).
deferred
leaks
status note
clean
StopExecution/DeleteStateMachine cancel the execution's context via b.cancelFns; Wait/waitForRetry/execSem/semaphore all select on ctx.Done(); Map/Parallel goroutines (wg.Go) all respect ctx cancellation. FIXED this pass: DeleteActivity leaked a permanent h.tags tombstone entry per deleted activity (see ops.DeleteActivity). No new goroutines introduced this pass (resolveExecutionTarget/S3Reader wiring are synchronous, no new goroutines).

2026-10-05 (gap burn-down pass 3, gopherstack-9x62)

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.

Notes

2026-10-07: TaskStarted / TaskSubmitted history events

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.

2026-10-03: legacy integrations run as the execution role under --enforce-iam

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.

2026-10-26 workflow variables: limits, JSONPath references, Distributed Map scope

  • Limits (variables guide, "Limits"): setVars (asl/variables.go) fails the state with States.DataLimitExceeded when 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.DataLimitExceeded is 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 $name references now also resolve in InputPath, OutputPath, Choice Variable and the *Path comparison fields, ItemsPath, SecondsPath/TimestampPath and the Task/Map *Path fields (Executor.pathArg). ResultPath rejects them, as documented.
  • Distributed Map: a child workflow reading an outer-scope variable is rejected at CreateStateMachine/ValidateStateMachineDefinition (checkDistributedScope, heuristic textual scan for $name of 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).

2026-10-01 items_still_open burn-down

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).

2026-09-26 ItemReader gap-closure sweep (CSVDelimiter, ItemsPointer)

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 a States.ItemReaderFailed error rather than silently falling back to comma. Wired into decodeCSVItems via csv.Reader.Comma, and threaded through to S3_INVENTORY manifest data files too (the docs: "You can specify this field when InputType is CSV or MANIFEST") -- previously resolveS3InventoryManifest built its own fileCfg with no way to carry the outer ReaderConfig's delimiter through, so it's now passed the full cfg and copies CSVDelimiter onto fileCfg.
  • ReaderConfig.ItemsPointer (new field): an RFC 6901 JSON Pointer (/data/items, forward-slash-separated, numeric array indices, ~1/~0 escapes) selecting a nested array within a JSON InputType file, 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 a States.ItemReaderFailed error. Only applies to InputType: JSON (or omitted, its default); JSONL/CSV are unaffected and still auto-detect as before when ItemsPointer is 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.

2026-09-26 ItemReader Resource sweep (listObjectsV2, MANIFEST, LOAD_AND_FLATTEN)

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 new asl.S3ListReader interface (ListObjectsV2Items), implemented by the existing s3Adapter against services/s3.StorageBackend.ListObjectsV2, paginating via ContinuationToken until exhausted. Default mode returns one item per object: {"Etag","Key","LastModified","Size","StorageClass"}, matching the docs' example exactly (LastModified as epoch seconds via pkgs/awstime.Epoch).
  • ReaderConfig.Transformation: LOAD_AND_FLATTEN (new field): reads and decodes each listed object's content per InputType (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 bare InputType: MANIFEST the docs' own worked example uses without ManifestType set (treated identically): reads a manifest.json (fileSchema, files[].key), fetches each listed CSV data file (gzip-decompressed when the key ends .gz), and decodes it using fileSchema's comma-separated column names as CSV headers.
  • Error behavior: every ItemReader failure (missing bucket/key, unsupported Resource, unsupported ManifestType/InputType) is now wrapped as a States.ItemReaderFailed FailError -- 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's Catch can now match States.ItemReaderFailed specifically, not only via States.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.

2026-09-26 Distributed Map ResultWriter WriterConfig sweep

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.

2026-09-24 perf sweep

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.

2026-09-19 (required-output-members re-check, gopherstack-r80d)

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.

Prior-pass notes (unchanged, retained for history)

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.

2026-08-21 pass (bd gopherstack-r80d, batch 10): required OUTPUT members never populated

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).

4 bugs found and fixed, all proven via real aws-sdk-go-v2/service/sfn

client round-trip tests (wire_output_required_r80d_test.go)

  1. TaskScheduledEventDetails.Region/.Parameters (types.go:1311-1339, both required). RecordTaskScheduled (execution_history.go) only ever set Resource/ResourceType; Region/Parameters were never assigned at all, decoding as empty strings on every real client's GetExecutionHistory call for any Task-state execution -- the common case, not an edge case. Fixed by threading the resolved (post-Parameters- template) task input through as Parameters, and deriving Region via the same regionFromARN(resource, backend.region) helper already used for activity ARNs (activities.go:138), applied here to the Task state's resource ARN instead.
  2. TaskSucceededEventDetails.Resource/.ResourceType (types.go:1431-1450, both required). RecordTaskSucceeded never set either field. Fixed by threading state.Resource through from executeTask's already-in-scope state (asl/executor.go).
  3. TaskFailedEventDetails.Resource/.ResourceType (types.go:1289-1307, both required). RecordTaskFailed never set either field on either of its two call sites (the direct-failure path in executeTask and the Catcher path in checkCatchers), both of which already have state in scope. Fixed the same way. asl.HistoryRecorder's RecordTaskSucceeded/RecordTaskFailed (an exported interface) both gained a resource string parameter 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.
  4. DescribeMapRun's ExecutionCounts (api_op_DescribeMapRun.go:57, required; types.MapRunExecutionCounts, types.go:841-906) had no backing field on MapRun at all -- see the REVERSED note on DescribeMapRun above 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 for DescribeMapRun (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).

Reviewed, not a bug

  • ValidateStateMachineDefinitionDiagnostic.Severity counted above as fixed (added to the map), but two adjacent fields on the same struct were checked and found fine: Code/Message are both already populated ("SCHEMA_VALIDATION_FAILED"/err.Error()) -- Code is a free-form *string on 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-supplied EncryptionConfiguration on CreateStateMachine/UpdateStateMachine/CreateActivity -- reviewed and left as-is: a real aws-sdk-go-v2 client round trip constructing this input always sets Type (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 key updateDate, required on UpdateStateMachineAlias) is tagged omitempty but UpdateStateMachineAlias (aliases.go) unconditionally sets alias.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, no omitempty) -- STALE, CORRECTED 2026-09-19 (gopherstack-r80d): this note previously claimed every place exec.Status transitions off RUNNING (finalizeExecutionRecordLocked/StopExecution itself) sets StopDate in the same statement, so the no-op branch could never observe nil. That trace missed a third transition: Snapshot (persistence.go) promotes any execution still RUNNING at snapshot time to TIMED_OUT without setting StopDate. StopExecution called on such an execution after a restore hit the no-op branch with a genuinely nil StopDate. Fixed -- see ops.StopExecution above.
  • RoutingConfigurationListItem (StateMachineVersionArn/Weight, both required) matches AliasRoutingConfig exactly, both fields already non-omitempty. Clean.
  • ActivityListItem/ExecutionListItem/MapRunListItem/ StateMachineAliasListItem/StateMachineListItem/ StateMachineVersionListItem -- all six re-verified against types.go field-by-field (see "trusted vs re-derived" above); all required members present, non-omitempty. Clean, matches this file's existing dv4s-pass notes.

Structurally unmodeled event kinds (disclosed, not fixed)

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 on Execution and already assigned real values at every relevant transition (initializeExecutionRecord/finalizeExecutionRecordLocked/ StopExecution/resetExecutionForRedrive, executions.go) -- the note was simply wrong. RedriveStatusReason was the one real gap: declared (models.go) but never assigned anywhere, so a real client always decoded an empty string regardless of redriveStatus. 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 every NOT_REDRIVABLE transition, and clearing it at every REDRIVABLE transition. MapRunArn remains genuinely unset -- confirmed via grep that no code path ever assigns Execution.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/parameters were fixed by the 2026-08-21 batch-10 pass above, which never updated this older note. TimeoutInSeconds/HeartbeatInSeconds and 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.

Findings

  1. CreateActivity / ActivityDoesNotExist (activities.go:119, handler_activities.go:113) -- false positive, class 4 (guard cannot fire). handleCreateActivity calls SetActivityEncryptionConfiguration(a.ActivityArn, ...) with the ARN just returned by CreateActivity in the same request; the guard it trips (ErrActivityDoesNotExist) requires the activity to be absent, which is impossible moments after b.activities.Put(a). Ground truth: CreateActivity declares ActivityAlreadyExists, ActivityLimitExceeded, InvalidEncryptionConfiguration, InvalidName, KmsAccessDeniedException, KmsThrottlingException, TooManyTags, UnknownError -- no ActivityDoesNotExist; correctly declared instead by DescribeActivity/ GetActivityTask. Regression coverage already exists and asserts 200: TestCreateActivity_EncryptionConfiguration (handler_activities_test.go).
  2. CreateStateMachine / StateMachineDoesNotExist (handler_state_machines.go:117, state_machines.go:51, state_machine_versions.go:18) -- false positive, class 4, same shape: createStateMachineAction calls SetStateMachineConfigurations(sm.StateMachineArn, ...) and (when publish=true) PublishStateMachineVersion(sm.StateMachineArn, ...) with the ARN just created in the same request; both guards trip ErrStateMachineDoesNotExist, which cannot fire on an ARN that was Put moments earlier under the same lock region. Ground truth: CreateStateMachine declares ConflictException, InvalidArn, InvalidDefinition, InvalidEncryptionConfiguration, InvalidLoggingConfiguration, InvalidName, InvalidTracingConfiguration, KmsAccessDeniedException, KmsThrottlingException, StateMachineAlreadyExists, StateMachineDeleting, StateMachineLimitExceeded, StateMachineTypeNotSupported, TooManyTags, UnknownError, ValidationException -- no StateMachineDoesNotExist; correctly declared instead by DescribeStateMachine/ListExecutions/ ListStateMachineAliases/PublishStateMachineVersion/StartExecution. Regression coverage already exists and asserts 200: TestHandler_CreateStateMachine_PropagatesTracingAndLogging (handler_tracing_logging_test.go) and the publish_true_creates_version subtest of TestHandler_CreateStateMachine_Publish (handler_state_machines_test.go).
  3. DescribeStateMachineForExecution / StateMachineDoesNotExist (executions.go:776, now :790) -- real bug, left unfixed. Ground truth: this op declares only ExecutionDoesNotExist, InvalidArn, KmsAccessDeniedException, KmsInvalidStateException, KmsThrottlingException, UnknownError -- no StateMachineDoesNotExist, and no code in that set semantically fits "the execution exists but its state machine's record is gone" (ExecutionDoesNotExist would be false: the execution does exist). Reachable, not theoretical: the function's !hasSnapshot fallback fires for any execution started before the last persistence restore, because executionDefinitions is deliberately excluded from backendSnapshot (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 (persist executionDefinitions, bump sfnSnapshotVersion) 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.
  4. 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"). StartSyncExecution re-parses sm.Definition with asl.Parse after pulling it from the store; but CreateStateMachine (state_machines.go:86-88) and UpdateStateMachine (state_machines.go:259-262) both already reject any definition that fails asl.Parse before it is ever stored, and every path that hands a definition to StartSyncExecution -- unqualified ARN, version ARN (stateMachineForVersionLocked copies a PublishStateMachineVersion snapshot, itself copied from an already-validated sm.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: StartSyncExecution declares InvalidArn, InvalidExecutionInput, InvalidName, KmsAccessDeniedException, KmsInvalidStateException, KmsThrottlingException, StateMachineDeleting, StateMachineDoesNotExist, StateMachineTypeNotSupported, UnknownError -- no InvalidDefinition; correctly declared instead by CreateStateMachine/TestState/ UpdateStateMachine. The identical asl.Parse(definition) -> ErrInvalidDefinition pattern also appears, unflagged by the tool (coverage gap, not a clean bill), in startExecutionLocked (executions.go:305, backs StartExecution) and redriveExecutionLocked (executions.go:688, backs RedriveExecution) -- same reasoning applies to both; neither op's declared set includes InvalidDefinition either. Regression coverage already exists and asserts success: TestStartSyncExecution_Express_Succeeds/ TestStartSyncExecution_Express_InputPayload (executions_asl_test.go) plus the broader StartExecution/ RedriveExecution happy-path suites in executions_test.go.

A related, unflagged structural finding: deleting vs. missing collapsed

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.

Verification

  • 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.go for ActivityDoesNotExist/StateMachineDoesNotExist/InvalidDefinition near CreateActivity/CreateStateMachine/StartSyncExecution/ DescribeStateMachineForExecution); 0 corrected.
  • Only change: a landmine comment on finding 3 (executions.go, DescribeStateMachineForExecution) -- no functional line touched. Verified with git stash on just that file: builds and go test -run TestDescribeStateMachineForExecution pass identically with and without it, confirming it's documentation-only.
  • No persisted struct was touched; pkgs/persistence guard 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.0 types/errors.go:683-684: // The specified state machine is being deleted. / type StateMachineDeleting struct { Message *string; ... }.
  • types/enums.go:362-375: StateMachineStatus has exactly two values, ACTIVE and DELETING.
  • deserializers.go -- found via each op's awsAwsjson10_deserializeOpError<Op> function boundaries (not grep -n 'case "', which returns nothing for this shape): StateMachineDeleting is declared by CreateStateMachine (:287), CreateStateMachineAlias (:428), ListStateMachineAliases (:2381), PublishStateMachineVersion (:2858), StartExecution (:3509), StartSyncExecution (:3647), UpdateStateMachine (:4406), UpdateStateMachineAlias (:4535). Not declared by DeleteStateMachine itself, DescribeStateMachine, ListStateMachines, either Delete* op, ListStateMachineVersions, or the tagging ops.
  • botocore stepfunctions/2016-11-23/service-2.json.gz (gzip+json): identical 8-operation errors list for StateMachineDeleting, 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." StateMachineStatus enum: ["ACTIVE", "DELETING"], no httpStatusCode override 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 a Wait(3600s) state machine, deletes it while running, asserts DescribeStateMachine still succeeds with Status == "DELETING" (pre-fix: require.NoError on ErrStateMachineDoesNotExist failed immediately), asserts CreateStateMachine on the same name returns ErrStateMachineDeleting, then stops the execution and asserts SweepDeletingStateMachines completes 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 assert errors.Is(gotErr, ErrStateMachineDeleting).
  • TestHandler_StartExecution_StateMachineDeleting: wire-level check through the real HTTP handler -- asserts the actual emitted resp["__type"] == "StateMachineDeleting" and rec.Code == http.StatusConflict (409), not merely that an error occurred.

Verification

  • GOTOOLCHAIN=go1.27.0 go test -race -count=1 ./services/stepfunctions/...: pass (both stepfunctions and stepfunctions/asl packages).
  • 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 exported SweepDeletingStateMachines/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 need task 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.

2026-09-12 (gopherstack-n3zi)

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.

2026-09-18 (reqfielddiff, gopherstack-xhu2t, tier-1 re-pass)

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).

2026-09-18 invented-field census (acceptguard, gopherstack-1ai8)

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.

2026-09-19 goleak TestMain re-verification (gopherstack-1x2u0)

leak_main_test.go and Destroy() (cancels execution goroutines) already existed from a prior pass; re-ran go test -race -count=2, still clean.

2026-10-01 JSONata query language (gopherstack-iisrz)

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.

2026-10-01 Mocked service integrations (gopherstack-pu3k0)

  • Step Functions Local / LocalStack mock config (StateMachines/TestCases/MockedResponses, Return/Throw, "N" and "N-M" invocation keys) loaded from SFN_MOCK_CONFIG (or LOCALSTACK_SFN_MOCK_CONFIG); env only, no CLI flag; invalid file fails startup with a clear error.
  • Activated by StartExecution/StartSyncExecution with stateMachineArn#TestCase; unknown test case or no config returns InvalidArn.
  • 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 with States.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/.

2026-10-03 AWS SDK and optimized service integrations (gopherstack-wdw)

  • 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.go wireStepFunctionsSDKIntegration) 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> with Exception appended when absent (DynamoDb.ResourceNotFoundException, S3.NoSuchKeyException, Sqs.QueueDoesNotExistException), so Catch/Retry on those names works; optimized resources use DynamoDB/SQS/SNS/StepFunctions prefixes (best effort, not verified against AWS).
  • Optimized dynamodb:*, sqs:*, sns:*, events:putEvents, states:startExecution (.sync string Output, .sync:2 JSON 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 function errorType on 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 allow states.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 task States.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:2 was mis-parsed as action 2; 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: bedrock invokeModel, http:invoke, eks.

2026-10-03 HTTP Task and ECS/Glue on the SDK path

  • arn:aws:states:::http:invoke (http_task.go, http_form.go): real outbound HTTP, 60 s client timeout (States.Http.Socket on timeout), 256 KiB response cap (States.DataLimitExceeded), http(s) redirects only. Parameters, header merge precedence (connection wins), forbidden-header list and States.Runtime cases, URL_ENCODED Transform with INDICES/REPEAT/COMMAS/BRACKETS, and User-Agent/Range headers follow the AWS guide call-https-apis; non-2xx fails as States.Http.StatusCode.<code>, octet-stream/image/video/audio responses as States.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-iam the role needs states:InvokeHTTPEndpoint (denial: States.Permissions) and events:RetrieveConnectionCredentials, secretsmanager:GetSecretValue, secretsmanager:DescribeSecret (denial: Events.ConnectionResource.AccessDenied).
  • ecs:runTask(.sync) and glue:startJobRun(.sync) now use the SDK path (Ecs.*/Glue.* error names); .sync polls DescribeTasks/GetJobRun. ECS Failures on .sync fail as AmazonECS.Unknown, Glue's response gains JobName (guide connect-ecs, connect-glue). The legacy asl adapters remain only for executors without an SDK integration.
  • DynamoDB optimized prefix DynamoDB is confirmed by connect-ddb; the guides for SQS, SNS and Step Functions state no error prefix, so SQS/SNS/StepFunctions stay unverified. lambda:invoke against 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 become States.TaskFailed. It used to be the bare name TaskFailed.
  • http:invoke reads credentials through eventbridge.ResolveConnectionAuth (an AUTHORIZED connection only; otherwise Events.ConnectionResource.InvalidConnectionState or ResourceNotFound); TestStepFunctionsHTTPTaskEventBridgeConnections drives 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).

2026-10-03 Region-correct cross-service targets

  • 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 the Cluster/TaskDefinition ARN region; .sync polling follows the task's cluster ARN region.
  • Proof: TestExecutionContextCarriesBackendRegion, TestInitializeServices_CrossServiceECSTargetsUseOriginRegion, TestInitializeServices_CrossServiceGlueAndTaggingUseOriginRegion.

2026-10-04: in-process metric inventory (gopherstack-4m1qr)

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).

2026-10-04 integration regions

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.

2026-10-04 (reqfielddiff tier-1: TestState.InspectionLevel)

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.

2026-10-05 (reqfielddiff tier-1/2 pass 8)

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.

2026-10-05 errcodeaudit needs-review triage (gopherstack-r3pr)

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.

2026-10-09 realism pass

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.

2026-10-11 deep realism pass

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).

2026-10-22 EKS and Bedrock integrations, S3 role authorization

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.