Skip to content

parity: per-service AWS emulation deepening (DO NOT auto-merge — for review)#2334

Open
agbishop wants to merge 133 commits into
mainfrom
parity-deepen
Open

parity: per-service AWS emulation deepening (DO NOT auto-merge — for review)#2334
agbishop wants to merge 133 commits into
mainfrom
parity-deepen

Conversation

@agbishop

@agbishop agbishop commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Large accumulating PR of genuine per-service AWS emulation/fidelity improvements (match-and-exceed LocalStack). One verified commit per service (build + golangci 0 + tests). Held open for human review — NOT auto-merged. Real behavioral fixes only; honest 'already-accurate' reports where applicable; no padding. CI on this PR backstops any lint missed under concurrent golangci locks.

Landed (verified) — 15 services

  • eventbridge — anything-but nested matchers matched everything; ci prefix/suffix
  • secretsmanager — missing X-Amzn-Errortype; DeleteSecret force/recovery validation
  • stepfunctions — StringMatches Choice comparator absent (glob)
  • cloudwatchlogs — filter ?term was negation (AWS=OR); FilterLogEvents shape; prefix ignored
  • kinesis — EndingSequenceNumber wrongly set on open shards
  • sns — 6 missing filter operators ($or/cidr/wildcard/nested anything-but/String.Array/body)
  • route53 — ChangeResourceRecordSets DELETE exact TTL+value match
  • apigateway — Import/PutRestApi were canned stubs → real Swagger/OpenAPI import + routing
  • ecs — RegisterTaskDefinition validation; bare error-code fidelity
  • sqs — MD5OfMessageAttributes over received subset
  • lambda — GetFunction/Config honor Qualifier; runtime validation
  • iam — policy-document grammar validation across identity paths
  • cloudwatch — TreatMissingData=ignore (alarm wrongly flipped to OK)
  • dynamodb — UpdateExpression SET beyond list-end appends; REMOVE out-of-range no-op
  • ses — SendTemplatedEmail/Bulk ignored TemplateData (sent raw placeholders); recipient cap

In progress

kms, ssm, s3 — more batching in (~120 services remaining).

🤖 Generated with Claude Code

mayor and others added 12 commits June 19, 2026 21:13
…header + DeleteSecret force/recovery conflict

- handleError now sets the X-Amzn-Errortype response header (awsJson1_1
  protocol) in addition to the body __type, matching real AWS so SDKs/CLI
  can construct the typed exception from the header.
- DeleteSecret now rejects ForceDeleteWithoutRecovery combined with
  RecoveryWindowInDays with InvalidParameterException, matching AWS's
  mutual-exclusivity rule (previously the recovery window was silently
  ignored).
- Adds table-driven tests for both behaviors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ce comparator

Add the AWS Step Functions StringMatches glob comparator to ChoiceRule.
'*' matches zero or more characters; backslash escapes the next character
so \* matches a literal asterisk and \\ a literal backslash. Matching is
anchored at both ends, implemented via a backtracking two-pointer algorithm
to avoid catastrophic backtracking. Adds white-box and end-to-end Choice
table tests covering AWS doc examples, escaping, and anchoring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hape + correct filter-pattern semantics

- Fix filter-pattern term semantics to match AWS: '?' is now OR (optional),
  '-' is exclusion, plain/quoted terms are AND. Previously '?' was wrongly
  treated as negation. '?' terms are ignored when combined with required/exclude
  terms, mirroring documented AWS behaviour. Shared by FilterLogEvents,
  subscription filters and metric filters.
- FilterLogEvents now returns FilteredLogEvent objects (logStreamName + eventId)
  instead of bare OutputLogEvent, matching the AWS response shape; results are
  interleaved across streams and sorted ascending by timestamp.
- Add logStreamNamePrefix support and enforce InvalidParameterException when
  logStreamNames and logStreamNamePrefix are both set.
- Return searchedLogStreams (empty list, per AWS deprecation contract).
- Table-driven tests for OR/exclude patterns, event shape, prefix filtering,
  and mutual-exclusivity error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… nested anything-but, String.Array filter matching
…ation

ChangeResourceRecordSets DELETE now enforces AWS's exact-match rule: a
DELETE must supply the same TTL and the same (unordered) set of resource
record values (or matching AliasTarget) as the existing record set.
Mismatches return InvalidChangeBatch with the AWS message 'Tried to delete
resource record set [...] but the values provided do not match the current
values' instead of silently deleting. Adds table-driven backend tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…import (ImportRestApi/PutRestApi)

Replace the canned ImportRestApi/PutRestApi stubs (which returned a fixed
{id:stub0000,name:imported-api} and created zero resources) with a real
importer that parses Swagger 2.0 and OpenAPI 3.0 documents and materialises
the full resource tree (parentId/pathPart/path), methods (authorization,
requestParameters, requestModels, operationName, apiKeyRequired from security),
integrations from x-amazon-apigateway-integration (type AWS/AWS_PROXY/HTTP/
HTTP_PROXY/MOCK, integrationHttpMethod, uri, requestTemplates, passthrough,
timeout), integration responses (default vs selectionPattern), method responses
and models (definitions / components.schemas).

Also fixes routing: POST /restapis?mode=import now dispatches to ImportRestApi
(previously always CreateRestApi) and PUT /restapis/{id} to PutRestApi
(previously unrouted) with merge/overwrite semantics, passing the raw spec body
through verbatim instead of corrupting it with path/query field injection.

Errors use AWS codes (BadRequestException/NotFoundException). Table-driven
tests cover Swagger 2.0, OAS 3.0, integration/response/model fidelity, REST
routing, merge vs overwrite, and error cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iner/network validation

Add the structural validation real AWS ECS applies to RegisterTaskDefinition,
surfaced as ClientException (matching the AWS error code):
- containerDefinitions must be non-empty
- each container requires a well-formed name and an image
- container names must be unique within a task definition
- networkMode must be one of bridge/host/awsvpc/none
- awsvpc mode: a port mapping hostPort (when set) must equal its containerPort
- FARGATE compatibility requires networkMode=awsvpc plus task-level cpu+memory

Also fix errorCode to return the bare AWS exception code (e.g. ClientException)
in __type / x-amzn-errortype instead of the full human-readable message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…utes over received subset

ReceiveMessage now computes MD5OfMessageAttributes over only the message
attributes actually returned to the consumer (per MessageAttributeNames
filtering), matching real AWS. Previously the send-time digest over the
full attribute set was echoed, which fails SDK-side checksum validation
when a consumer requests a subset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nfiguration Qualifier resolution + runtime enum validation

- GetFunction and GetFunctionConfiguration now honor the ?Qualifier= query
  param (version number, alias name, or $LATEST), returning the immutable
  version snapshot with the version/alias-suffixed ARN and resolved Version,
  matching real AWS. Previously both always returned the live $LATEST config.
- Added InMemoryBackend.GetFunctionByQualifier + complete versionToConfig
  mapping (preserves Layers/VpcConfig/TracingConfig/Version etc., unlike the
  lossy invocation-path versionToFn).
- CreateFunction/UpdateFunctionConfiguration now reject unknown runtimes with
  InvalidParameterValueException (enum-value-set message), accepting current +
  AWS-recognized deprecated runtimes; unknown identifiers are rejected. Docker
  run path untouched (validation is control-plane only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n identity policies

Previously identity policy documents (managed policies, policy versions,
inline user/role/group policies) were only checked with json.Valid, so
structurally valid JSON that violated the IAM policy grammar was silently
accepted, diverging from real AWS and LocalStack which return
MalformedPolicyDocument. Added validateIdentityPolicyDocument enforcing:
Version must be 2012-10-17/2008-10-17, Statement required and non-empty,
Effect exactly Allow/Deny, exactly one of Action/NotAction and
Resource/NotResource, and no Principal in identity policies. Wired into
CreatePolicy, CreatePolicyVersion, PutUserPolicy, PutRolePolicy,
PutGroupPolicy. Updated placeholder test docs to valid documents.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@agbishop

agbishop commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator Author

📊 Code Coverage Report

Metric Value Status
Total Coverage 0.0%
82.2%
New Code Coverage N/A (0/0 stmts)

Tip

This project maintains a minimum coverage threshold of 85%. Maintain or improve coverage on new code to ensure long-term stability.


Last updated: Sat, 20 Jun 2026 04:57:12 GMT

mayor and others added 17 commits June 19, 2026 22:00
…list SET

UpdateExpression SET on a list index beyond the current end now appends the
value to the end of the list (no NULL padding, no error), and REMOVE on an
out-of-range list index is a silent no-op. Previously both returned
ErrIndexOutOfRange, failing the whole UpdateItem. This matches documented AWS
DynamoDB behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stitution, recipient limits

- SendTemplatedEmail now substitutes {{var}} placeholders from TemplateData
  JSON into stored subject/text/html (previously stored raw placeholders).
- SendBulkTemplatedEmail merges request-level DefaultTemplateData with
  per-destination ReplacementTemplateData and renders each message; validates
  template existence up front (TemplateDoesNotExist for missing template even
  on empty batch).
- Enforce AWS 50-recipient-per-message cap (To+Cc+Bcc) on SendEmail and
  SendTemplatedEmail with MessageRejected.
- Reject malformed TemplateData JSON with InvalidParameterValue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…role ARN

AWS drops any IAM path on the role when forming the assumed-role principal
ARN: a role at arn:aws:iam::ACCT:role/team/dev/MyRole yields
arn:aws:sts::ACCT:assumed-role/MyRole/SESSION (only the bare role name is
carried over). buildAssumedRoleArn previously retained the full path,
producing assumed-role/team/dev/MyRole/SESSION which diverges from real STS
and from AWS CloudTrail/IAM policy matching. Add roleNameFromResource to
strip the path; covers AssumeRole, AssumeRoleWithWebIdentity, and
AssumeRoleWithSAML which share the construction. Add table-driven tests.

Also remove an accidentally-committed goimports atomic-write temp file
(.models.go5239108928845688131), a malformed duplicate of models.go.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ables, GetPartitions

Real AWS Glue enforces MaxResults (1-100 for catalogs, 1-1000 for partitions)
and returns NextToken when more results are available. Without this, SDK clients
that automatically paginate would silently receive truncated or unbounded results.

Adds paginateSlice[T] generic helper, MaxResults validation, and table-driven
tests covering first page, continuation, and out-of-range MaxResults. (go-ossso)
- Fix GetSessionEndpoint hardcoded us-east-1 region (now uses b.region)
- Validate empty QueryString in StartQueryExecution (→ InvalidRequestException)
- Validate WorkGroup State (only ENABLED/DISABLED accepted, not arbitrary strings)
- Omit empty NextToken from ListDataCatalogs response (field fidelity)
- Reject GetQueryResults on non-SUCCEEDED queries (CANCELLED/FAILED → 400)
- Add workGroupStateEnabled/Disabled constants; replace raw string literals

Tests: parity_deepen_test.go covers all five fixes with table-driven cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…reateVolume/DescribeVolumes

Real AWS sets IOPS/throughput defaults per volume type (gp3: 3000/125, gp2:
derived from size), requires Iops for io1/io2, and returns these fields in
CreateVolume and DescribeVolumes responses. Previously all three were ignored
and omitted.

- Add Iops and Throughput fields to Volume struct
- Add SetVolumePerformance backend method (parallel to SetVolumeEncryption)
- Extract parsePositiveInt and defaultIOPSForType helpers to satisfy gocognit
- Add volTypeDefaultGP2/gp3DefaultIOPS/etc constants (no magic numbers)
- Update volumeItem, createVolumeResponse, and toVolumeItem to return the fields
- Table-driven tests: gp3 defaults, custom IOPS/throughput, gp2 size-derived,
  io1/io2 rejection without Iops, DescribeVolumes round-trip

Fixes: go-evvxi
…Id (go-8dvrq)

Two genuine AWS parity gaps fixed:

1. Error responses for 400s were missing __type. AWS SDK clients parse __type
   to deserialize errors into typed structs (ResourceInUseException,
   InvalidArgumentException, UnknownOperationException). Only 404s had __type;
   all 400s returned a bare {message:...} body. Now each error case sets the
   correct __type matching real Firehose behavior.

2. PutRecordBatch RequestResponses contained empty structs instead of per-record
   {RecordId} entries. Real AWS returns one entry per input record with a unique
   RecordId on success. Callers use RecordId for tracking and deduplication.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lvhj)

AWS ECR DescribeImages accepts filter: { tagStatus: "TAGGED"|"UNTAGGED"|"ANY" }.
Previously the filter field was absent and silently ignored — all images were
returned regardless. ListImages already had this filtering via passesTagFilter;
DescribeImages now uses the same function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ListUpdates was always returning [], DescribeUpdate was synthesizing
a fake record for any ID. Add updates map to InMemoryBackend, store
Update objects in UpdateClusterConfig, UpdateClusterVpcEndpoint,
UpdateClusterVersion, UpdateNodegroupVersion, and UpdateNodegroupConfig
handler. Fix ListUpdates to return sorted stored IDs. Fix DescribeUpdate
to look up real records and return 404 for unknown IDs.
…-2vbyo)

Two genuine AWS behavioral gaps fixed:

1. MountTargetArn missing from mtToResponse — AWS always returns this field
   in CreateMountTarget and DescribeMountTargets responses. The backend
   already tracked it; just wasn't included in the JSON shape.

2. DescribeMountTargets ?AccessPointId= filter — real AWS supports resolving
   mount targets via an access point ID. The handler now looks up the access
   point to obtain its FileSystemId, then delegates to the existing
   DescribeMountTargets backend with that filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flint and others added 5 commits June 20, 2026 12:07
…ip tabs

Expand the S3 dashboard to surface backend-supported bucket features that had no
UI: a functional Public Access Block editor in the Permissions tab plus five new
tabs — Object Lock (default retention), Event Notifications (read-only summary),
Replication (view + delete), Server Access Logging (target bucket/prefix), and
Object Ownership controls. Each lazily loads via switchTab using the existing tab
pattern and the AWS SDK. Lint (oxlint), svelte-check (0 errors) and the s3 vitest
suite pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197MBJdH1bdve4Z3RR9pffn
Complete the comprehensive S3 dashboard expansion: a canned-ACL selector with
current grant count in the Permissions tab (GetBucketAcl/PutBucketAcl), and four
configuration tabs — Analytics, Metrics, Inventory, and Intelligent-Tiering —
that list stored configurations by Id with delete (List*/Delete* commands; the
backend stores these configs and round-trips them). Lint, svelte-check (0 errors)
and the s3 vitest suite pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197MBJdH1bdve4Z3RR9pffn

Copy link
Copy Markdown
Collaborator Author

S3 deep-dive (parity / emulation / perf / leaks / UI) + cross-service context consistency

Follow-up pass on S3 (and a related identity-consistency fix touching DynamoDB). Each commit builds, is golangci/gofmt clean, and is test-covered. UI changes pass oxlint --deny-warnings, svelte-check (0 errors) and the s3 vitest suite.

S3 behavioral parity

  • Delete-marker GET/HEAD: unversioned GET/HEAD where the latest version is a delete marker now returns 404 with x-amz-delete-marker: true; a versioned GET of a delete-marker version returns 405 + Allow: DELETE (was a bare NoSuchKey). New ErrLatestDeleteMarker sentinel.
  • encoding-type=url was accepted/echoed but never applied — Key/Prefix/Delimiter/markers are now URL-encoded across ListObjects(V1/V2)/Versions/MultipartUploads, so keys with &/spaces/non-ASCII round-trip through the SDK.
  • Object tag limits enforced (≤10 tags, key ≤128, value ≤256) on PutObject/CopyObject/CreateMultipartUpload/POST/PutObjectTagging → 400 BadRequest/InvalidTag.
  • Self-copy guard: copying an object onto itself with no attribute change → 400 InvalidRequest (AWS parity).
  • If-Range now honored on range GETs (full 200 when the validator doesn't match); ListObjectVersions honors max-keys=0.

Resource leaks / performance

  • Replication goroutines were parented to the request context (cancelled when the handler returns, and carrying the source SSE key). They're now parented to a service context wired into the backend from StartWorker — cancellable on shutdown (drained by replicationWg) and carrying only the request logger, never the SSE key or request cancellation; falls back to context.WithoutCancel (never context.Background()) when no service ctx is wired. Broader leak audit was clean.

Request-context consistency (S3 + DynamoDB)

Both services bypassed the central awsMetaMiddleware identity. They now source region from awsmeta (falling back to the shared extractor when the middleware isn't in play), and DynamoDB additionally honors a per-request X-Amz-Account-Id when building ARNs. Long-running background work derives from the service context; per-request work uses the request context (logger/metadata) — never context.Background().

UI — comprehensive S3 expansion (ui/src/routes/s3/+page.svelte)

New/enhanced tabs, all backend-backed and lazily loaded: functional Public Access Block + Bucket ACL (in Permissions), Object Lock (default retention), Event Notifications (summary), Replication (view + delete), Server Access Logging, Object Ownership, and config tabs for Analytics / Metrics / Inventory / Intelligent-Tiering (list + delete).

Honest notes / accepted simplifications

  • Analytics/Metrics/Inventory/Intelligent-Tiering remain store-only in the backend (config round-trips; no analysis) — the UI manages those configs accordingly.
  • CopyObjectResult.LastModified uses write-time (the SDK PutObjectOutput carries no LastModified) — cosmetic sub-second only.
  • Notifications/Replication UI is view/delete (PUT of those structured configs is left to the SDK/CLI).

Generated by Claude Code

Witness Patrol and others added 24 commits June 20, 2026 12:22
…lds and CallerReference idempotency (go-kfe9z)
…tern (lowercase, no trailing/consecutive hyphens) (go-p12p3)
Resolve the open Dependabot alerts in the ui dependency tree:
- undici 7.25.0 → 7.28.0 via overrides (jsdom's transitive dep; ^7.25.0). 7.28.0
  is the security-only 7.x release backporting all six advisories: SOCKS5 proxy
  pool reuse, TLS cert-validation bypass in the SOCKS5 ProxyAgent, Set-Cookie
  header injection, shared-cache cross-user disclosure, SameSite downgrade, and
  keep-alive response-queue poisoning.
- fast-xml-parser override 5.6.0 → 5.7.3 (XMLBuilder comment/CDATA injection).
- cookie override stays 1.0.2 (already patched).
- Remove ui/pnpm-lock.yaml: vestigial (CI/Makefile use npm exclusively via
  `npm --prefix ui ci`), and the sole source of the cookie@0.6.0 alert.

package-lock.json regenerated; `npm ci`, oxlint, svelte-check (0 errors) and the
vitest suites pass on undici 7.28.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197MBJdH1bdve4Z3RR9pffn
Per-commit CI runs piled up on rapid commit bursts. Add a concurrency group
keyed by PR number (falling back to workflow_run head branch / ref) with
cancel-in-progress, so a new push cancels the still-running CI for the same PR
and CI settles on the latest commit — which is the only one that matters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197MBJdH1bdve4Z3RR9pffn
Follow-up to the prior commit, whose `git add` aborted on an already-removed
pnpm-lock.yaml pathspec and therefore staged only the lockfile deletion — the
package.json override bump and regenerated package-lock.json were left out. This
commit applies them: undici 7.25.0 → 7.28.0 and fast-xml-parser 5.6.0 → 5.7.3,
closing the remaining Dependabot advisories.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197MBJdH1bdve4Z3RR9pffn
…, sync lifecycle, 1000+ test lines (go-dfjaa)

- Add input validation: connection/host name format (regex, 32-char max), tag
  key/value limits (128/256 chars), tag count max (200), ProviderEndpoint required
  and max 512 chars for CreateHost, ProviderType enum enforcement
- Add ResourceInUseException: DeleteHost blocked by active connections,
  DeleteRepositoryLink blocked by active sync configs
- Paginate all List operations (ListConnections, ListHosts, ListRepositoryLinks,
  ListSyncConfigurations) using pkgs/page with opaque NextToken round-trip
- Add PublishDeploymentStatus and TriggerResourceUpdateOn on
  CreateSyncConfiguration/UpdateSyncConfiguration with validation
- Add real sync status tracking via SetRepositorySyncStatus/SetResourceSyncStatus;
  auto-seed SUCCEEDED status on sync config creation
- Add real sync blocker CRUD: CreateSyncBlocker, GetSyncBlockerSummary returns
  live blockers sorted by CreatedAt desc, UpdateSyncBlocker resolves with
  ResolvedAt/ResolvedReason, DeleteSyncConfiguration cleans up blockers/statuses
- Fix fieldalignment on all list output structs
- handler_audit2_test.go: 1700+ lines of table-driven tests covering all new behaviors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… response fidelity, tests (go-337u7)

- Pagination for GetGroups, GetSamplingRules, GetTraceSummaries, GetInsightEvents,
  GetInsightSummaries, GetIndexingRules, GetSamplingStatisticSummaries,
  GetServiceGraph, GetTimeSeriesServiceStatistics, GetTraceGraph, ListTagsForResource
- PutTraceSegments: max 50 segments per call, max 64KB per document validation
- PutEncryptionConfig: Type must be NONE or KMS; KMS requires KeyId; NONE rejects KeyId
- GetTraceSummaries: TimeRangeType validation (TraceId/Event/Service)
- Insight struct: add EndTime time.Time field
- insightView: add EndTime and Categories fields for response fidelity
- Comprehensive table-driven tests covering all new behaviors (1000+ lines)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ugn2z)

- Fix struct field alignment (govet/fieldalignment): move non-pointer fields
  to after pointer-containing fields in createWorkspaceSpec, dirResp, WorkspaceBundle
- Remove copyloopvar variable captures (Go 1.22+ handles loop var capture)
- Fix nonamedreturns: convert named returns in describeWorkspacesPage to unnamed
- Preallocate slices with make() (prealloc): createdIDs, collected, ids
- Fix gocritic: use make+append instead of append-result-not-assigned
- Rename cap→capacity to avoid redefining builtin (revive)
- Use assert.Empty instead of assert.Equal with "" (testifylint)
- Inline bundleID param to eliminate unparam violation
- Add nolint explanations (nolintlint)
- Fix double blank line in backend.go (goimports)
- Shorten struct tag alignment to fit 120-char line limit (lll)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ifecycle, ACE rule eval, response fidelity (go-kjmmw)

- Input validation: CreateUser (name required, role enum), CreateGroup (name required),
  CreateResource (name required, type must be ROOM/EQUIPMENT)
- Entity state lifecycle: DeleteUser/Group/Resource returns EntityStateException if ENABLED;
  must DeregisterFromWorkMail first
- GetAccessControlEffect: implement real rule evaluation with IP CIDR matching,
  action/notAction filtering, userID/notUserID filtering; first matching rule wins
- Response fidelity: DescribeGroup now returns HiddenFromGlobalAddressList;
  ListUsers now returns UserRole; ListAccessControlRules now returns DateCreated/DateModified
- UpdatePrimaryEmailAddress: now handles groups and resources (was user-only)
- Pagination: replace fragile fmt.Sprintf token comparison with index-based strconv tokens
- Error handling: EntityStateException mapped to 400 in handleError
- 783 lines of new table-driven tests in handler_parity_test.go

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants