Skip to content

ci(auth): AmplifyCognitoClient CI and integration-test infrastructure follow-ups - #4402

Merged
harsh62 merged 94 commits into
mainfrom
ci/cognito-client-infra-followups
Oct 29, 2026
Merged

harsh62 merged 94 commits into
mainfrom
ci/cognito-client-infra-followups

Conversation

@harsh62

@harsh62 harsh62 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-ups for the AmplifyCognitoClient beta's CI and integration-test infrastructure: the change selectors, the
client's own CI resources (a new MFA-required pool, a 365-day code sink API key, a Lambda drift check), fixes to the sandbox scripts, and new and corrected integration tests. No library source and no public API
changes.

CI selector

  • scripts/python/affected_targets.py no longer skips a host app's README when an Xcode project in the checkout copies
    it into a test bundle (twelve host-app READMEs are Copy Bundle Resources). Deleting or renaming one broke the build
    of the target that copies it while the PR selected nothing. Such a README is now attributed like any other host-app
    file, in every group file. With the integration groups it selects its host app's group. That catches the break only
    where the group's PR jobs build the copying target: six of the twelve are copied only by stress-test targets
    (GraphQLAPIStressTests, AnalyticsStressTests, AuthStressTests, DatastoreStressTests, GeoStressTests,
    StorageStressTests), which no PR job builds, so for those the break still shows only in stress_test.yml after
    the merge. With the unit groups it selects AWSPluginsCore, whose AmplifyPlugins/ path covers every plugin host
    app, as for any other host-app file: unit_test.yml runs AWSPluginsCore's unit tests, and the three build_*
    workflows, which only check that something is selected, build the whole package on all their platforms. None of
    those builds a host app. That is accepted. Documentation no project bundles is still skipped, and a project the
    selector can't read makes the docs under it run their group. New cases are in test_affected_targets.py, the unit
    groups' included.
  • The selector's --repo-root now defaults to the checkout that holds the script rather than the current directory,
    and a root with no Xcode project at all is treated as unread (its docs count as bundled), so a run from another
    directory no longer skips a bundled README. CI ran from the root already.
  • ci_script_self_tests.yml also runs on a change to any project.pbxproj, since the selector's self-test reads the
    real Xcode projects.
  • The seven workflows that feed the selector (integ_test.yml, integ_test_api.yml, integ_test_datastore.yml,
    unit_test.yml, build_amplify_swift_platforms.yml, build_minimum_supported_swift_platforms.yml,
    build_xcode_preview.yml) now also pass the renames' old paths, through a new --renamed-from file
    (git diff --name-status -M --diff-filter=R, first path); the changed list keeps rename detection. The selector
    attributes an old path like any changed file, so a renamed bundled README still selects its group and a file moved
    out of a target still selects that target's group, but it skips an old path that nothing owns, covers or ignores
    instead of failing closed, since the new path is already in the list. So a move that changes a target's directory
    selects what the new paths select: moving RecordValidationTests.swift into AmplifyRecordCache (c6bc9e3)
    selects 7 unit groups, not all 22. A plain deletion still fails closed.
  • auth_engine_gates.yml and scripts/cognito-engine/error_match_gate.sh: the headers now name only the two error
    matches the gate enforces, and state the risk for AuthErrorConvertible correctly: a cast on it matches AuthError,
    KeychainStoreError and the eight engine error types the plugin conforms, and silently misses the errors that are
    only EngineAuthErrorConvertible, so only AuthError(converting:) may make one.

api-dump

This PR changes no public API. The "Public Interface Breakage Detection" workflow
(api-breaking-changes-detection.yml), which would regenerate the api-dump/*.json files and push an "Update API
dumps for new version" commit, is disabled in this repository, so no such commit comes to this branch.

Sandbox guard and scripts (AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra)

  • A first sandbox provision is refused while AWS access keys, a web identity role (AWS_ROLE_ARN,
    AWS_WEB_IDENTITY_TOKEN_FILE) or container credentials (AWS_CONTAINER_CREDENTIALS_*) are exported, not only when
    AWS_PROFILE is unset. The first two would take over the profile; container credentials are used only when the
    profile holds none, and are refused to be safe. It doesn't cover a profile with no credentials of its own: the CLI
    then falls back to an instance role or a legacy credentials file, and the first run records that account. Closing
    that is a follow-up. A provision is also refused in a region other than the one the state directory records, and,
    on every run, when the sandbox's global role already trusts an identity pool in another region (another state
    directory's sandbox; the IAM roles are global). The region refusal no longer suggests another state directory,
    because that would build the second sandbox it warns about.
  • plugin-configs.py --dir recognizes the owner's and the configured testconfiguration directory however the path is
    spelled (case, symlinks; os.path.samefile on the target and its ancestors). A test pins the samefile walk with
    the /System/Volumes/Data alias of the temporary directories, which only the walk recognizes (skipped where there
    is no such alias).
  • The two parity identity pools are recorded in state.json as soon as they exist, before their roles, so a teardown
    after a failure there removes them.
  • Overlapping prepare-run.sh runs now finish: a bounded retry, "already exists", "already forgotten" and an expired
    code sink key the other run deleted first (rotate-key) are tolerated, and the lock waits. The report shows erin
    as not found only while another run resets her; any other admin-get-user error stops the run, as on main.
  • Ctrl-\ during self-sign-up.sh turns self sign-up off again (bash 3.2 skips the EXIT trap on QUIT).
  • An interrupted npm ci no longer leaves a half-built custom sender cached.
  • ensure_domain reuses an adopted pool's own hosted-UI domain when no prefix is recorded.
  • Comment and doc fixes: the CI default pool refuses ccit-confirm- sign-ups too; preflight goes by the live self
    sign-up lease (an unused constant removed); the sandbox has seven parity pools, and teardown deletes identity pools
    by recorded ID only; the resource table has rows for P-13 and P-14.
  • The self-tests now cover the cases that were missing: a write into the testconfiguration directory fails in the
    leftover tmp file test, and the first signal forward is lost in the self sign-up test, so its re-send is checked.

CI resources (infra/ci)

  • New, additive pool ccit-ci-mfa-req-totp-sms, made from infra/pools/mfa-req-totp-sms.json on Essentials. Its
    ci app client allows SRP and USER_AUTH, with no USER_PASSWORD_AUTH. MFA is required, with TOTP and SMS. Its
    trigger is the CI pre-sign-up trigger, and both custom senders are the discard sender, so Cognito sends nothing.
    ci-overlay.sh has a new mfa-req-totp-sms role. It puts that pool's outputs in place of the plugin's
    MFA-required file for the client suite only, and checks first that the file requires MFA and that its
    mfa_methods is an array of exactly TOTP and SMS (not a string, not a superset). Until the file is uploaded, the
    role stays the plugin's. integ_test_auth_client.yml passes email-alias extended mfa-req-totp-sms, and
    run_integration_tests.yml's usage error names the new role. The overlay's self-test pins both halves of the MFA
    check and the fallback when the file is missing (the email-alias and extended files are still overlaid, and the
    plugin's own MFA-required file stays).
  • A run that makes either SMS pool opens the SNS caller role's trust to the account's pools while the pool is made
    (still held to the external ID and aws:SourceAccount), as it opens the key policy, and narrows it again after.
    The plan now says both, and the provisioner's header lists the trust among what a rerun updates.
  • Replacing a pool or a Lambda. A rerun never updates a pool or a Lambda's code, so either is deleted by hand, after
    its tag check, and made again by infra/ci/provision-ci.sh --apply --scope all (with --upload for a pool). The
    provisioner's header and every message that advises a redeploy now say --scope all:
    a plain --apply is phase 1, which makes only two of the eight functions again and adds invoke permissions for
    the device-alias pool only, while lambda-drift and the baseline check still pass. The pool steps include the
    uploaded files that name the pool (--upload never overwrites), and, for ccit-ci-default, the rotation file, the
    identity pool and the reset Lambda.
  • Code sink API key. provision-ci.sh creates the key, and extends it in place, for 365 days, AppSync's maximum
    (CreateApiKey and UpdateApiKey take 1 to 365 days from the call). The expiry is rounded down to the hour, as
    AppSync stores it, after a 10-minute allowance for a local clock ahead of AppSync's; the fake AWS CLI now refuses an
    expiry outside AppSync's bounds, as AppSync does. A rerun reuses the key only while it lives more than 120 days, and
    otherwise extends it, keeping its id, so the two uploaded files that name it
    (ccit-ci-default-amplify_outputs.json, ccit-ci-email-alias-codes-amplify_outputs.json) stay valid. Each run
    prints the key's expiry after it. An expired key is replaced by a new one, but --upload never rewrites those two
    files, so the run names them, says how to replace them (check their tag, delete them by hand, rerun with
    --upload) and refuses at the end.
  • Lambda drift. The CI Lambdas' zips are built deterministically (lambda_code.py). provision-ci.sh lambda-drift
    (read-only) checks each ccit-ci- Lambda's name and purpose tag, calls GetFunction, downloads the deployed zip
    from its presigned Code.Location into a private temporary directory, and compares its files, by path and
    content hash, with the checkout's zip. So a function made from the older zip -X zips of the same files, or
    zipped by another zlib, is in sync, and CodeSha256 is only a note. npm's node_modules/.bin/ links count on
    neither side, so the custom sender that main's zip -q -X -r built (which holds node_modules/.bin/uuid as a
    file) is in sync. A DRIFT names the function and the files that changed, are only deployed, or are only in the
    checkout. A function the baseline records that is not deployed is DRIFT too. lambda-drift --record prints that
    comparison and records nothing; only --record --accept-deployed writes the deployed files (paths and content
    hashes) with the sources' digest to infra/ci/lambda-baseline.json (not committed yet), which then counts as the
    accepted deployment until the sources change. A record needs every Lambda: --record writes nothing unless all
    eight CI Lambdas are deployed (the first record too), and --accept-deployed refuses while a function was not
    compared (the custom sender without npm). lambda-drift refuses another region than the CI resources' (as
    --apply does), and with no ccit-ci- function at all (another account) it and --record fail; an empty
    baseline counts as none (exit 3). RuntimeVersionConfig is never compared. The integration README says that,
    once a baseline is committed, a Lambda source edit needs a new record by someone with CI-account access, after a
    redeploy or after accepting the deployed files. The download is guarded:
    • The URL is never printed or put on a command line. It reaches lambda_code.py in a deployed.json that only
      its owner can read; any other mode is refused (exit 4) before anything is downloaded.
    • Only an https:// URL on the S3 host of a Lambda code bucket (<bucket>-tasks.s3.<region>.amazonaws.com, the
      shape of Code.Location in Lambda's documentation) is fetched. That keeps out other AWS endpoints and
      path-style S3. Anyone can name a bucket …-tasks, so what makes the URL Lambda's is that it comes from
      GetFunction. A bracketed host is refused on every python3, not only on those with the 2025 urlsplit fixes.
    • A redirect is never followed, and a download over 50 MB (the largest zip --zip-file uploads) is refused.
    • A malformed URL, and a corrupt or encrypted member of a deployed zip, count as unreadable (exit 1), not as a
      traceback.
    • File names from the deployed zip are printed escaped, so none can end its line, start a workflow command or hold
      one of the runner's legacy ##[ commands.
  • snapshot and lambda-drift refuse --apply, which would turn their read-only guard off.
  • The account snapshot leaves out the Lambdas' RuntimeVersionConfig, which AWS changes by itself and which made
    verify-unchanged report a false CHANGED.
  • provision-ci.sh checks the account's headroom only for what a run creates. The AppSync quota can be overridden
    (CCIT_CI_APPSYNC_API_QUOTA). Before this, a rerun that only extends the key was refused at 23 APIs.
  • provision-ci.sh stops when the custom sender can't be built, and its dry run checks for npm. The guidance it
    prints for stale uploaded files no longer runs its sentences together.

Integration tests (CognitoClientHostApp)

  • RequiredMFATests has two new tests: a USER_AUTH sign-in with the PASSWORD or PASSWORD_SRP first factor that
    must set up TOTP (MFA_SETUP). From the requests the client sent (RecordingHTTPClient), each checks that the
    sign-in was one InitiateAuth USER_AUTH preferring that factor, and that the challenges answered were
    MFA_SETUP (PASSWORD) or PASSWORD_VERIFIER then MFA_SETUP (PASSWORD_SRP), since an SRP sign-in reaches
    the same steps on that pool. Each test first checks, with one raw InitiateAuth, that the app client allows
    USER_AUTH. If it doesn't, the test fails, naming the sandbox's and CI's remedies, or skips only under the
    harness's CI-only skip rule (a new CISkipReason, mfaRequiredUserAuth). The pool template's client and ci
    app clients now allow ALLOW_USER_AUTH, with PASSWORD as an allowed first factor.
  • PluginCarriedAndSharedGroupLoginTests (interop target) adds two live checks:
    • The plugin carries a login the client made under a user-pool-only configuration, is signed in from it, and
      deletes it on a user pool change.
    • On the host app's shared access group, a sign-in and a sign-out by a .default client are what a relaunched
      plugin sees.
  • The email-update attribute tests (AT-2, AT-4) run on the role with the default backend's extras. On CI that is the
    client's own extended role, whose senders send no email, so they no longer spend the shared default backend's
    daily email limit.
  • Cleanup fixes and checks:
    • The new-password test cleans its session up against its own pool.
    • The several-pools cleanup test gives each session a signed-in row first.
    • The resent-code fallback for unconfirmed users is now exercised.
  • Doc fixes in the suites: which skips apply on CI, which backends the suites describe, the stress test's evidence, and
    the interop suite's missing-fixture messages, which now point at CI's file set.
  • The integration README has these new and corrected rows:
    • the new pool, its overlay role and what CI needs from the plugin's MFA-required backend, and the new skip reason;
    • the new interop tests, the attribute tests' role and the cleanup tests;
    • a shorter section on the client's CI resources: what they give the tests, how CI overlays them, and what a
      Lambda source edit needs; CI-account maintenance is in the team's internal runbook;
    • the sandbox's first-run guard and region rule, and the overlap cases prepare-run.sh tolerates;
    • -skipPackagePluginValidation on the guide's host app builds and on the plugin host-app commands, as CI passes
      it. On a fresh DerivedData the build otherwise stops at validating smithy-swift's code generator plug-in.

Testing

Offline, with every AWS_* variable unset, AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE set to /dev/null,
and IMDS off:

  • actionlint on every changed workflow: each reports only what it reports on main.
  • Selector self-tests: python3 -m unittest discover -s scripts/python -p 'test_*.py' (12 tests).
  • Infra self-tests, as auth_client_infra_self_tests.yml runs them:
    • python3 -m unittest discover -s infra -p 'test_*.py' (173 tests: test_parity.py 115,
      test_plugin_configs.py 45, test_enroll_totp.py 7, test_free_disk.py 6);
    • every infra/test_*.sh and infra/ci/test_*.sh, including test_lambda_code.sh (also on macOS's
      /usr/bin/python3, 3.9) and test_provision_ci.sh with its rerun check over the CI resources as first
      applied;
    • node --test on the Lambda tests (11).
  • scripts/cognito-engine/error_match_gate.sh self-test.
  • swift build --build-tests.
  • bash scripts/cognito-engine/check_fixture_lock.sh HEAD.
  • xcodebuild build-for-testing -skipPackagePluginValidation for the two client host-app test schemes
    (CognitoClientIntegrationTests, CognitoClientPluginInteropTests), with an empty COGNITO_CLIENT_INTEG_DIR.
  • SwiftFormat 0.60.1 on the changed Swift files.

Live: on 2026-10-27 the CI account got the new MFA-required pool and its uploaded outputs file, and
verify-unchanged showed that nothing outside ccit-ci- changed. The client's CI run on this PR passed with no
skip: its overlay mapped ccit-ci-mfa-req-totp-sms onto the MFA-required file, and the zero-skip gate passed. The
next client CI run confirms the challenge lists the two USER_AUTH MFA-setup tests now check. The CI-account steps
that remain are in the team's internal runbook.

The optional sandbox gets the template change (ALLOW_USER_AUTH and the allowed first factor) on its next
infra/provision.sh. Until then, the two USER_AUTH MFA-setup tests fail there, naming that remedy (a sandbox run
never skips).

Follow-ups

  • Treat a rename's old path as skippable only when its new path is itself attributed: today a file the selector
    doesn't know, moved into an ignored location such as scripts/ or .github/, selects nothing, while deleting it
    selects every group. Also pin the selector's default --repo-root (assert no "found no Xcode project" note).
  • Extend a code sink key that has already expired instead of replacing it, if AppSync's documentation holds that an
    expired key is kept for 60 days and can still be extended (to be checked once on a non-CI API first). The
    replacement steps then become the last resort.
  • Close the sandbox's first-run gap for a profile with no credentials of its own: refuse a first run unless
    aws configure list shows credentials from the profile (no API call).
  • Pin the SNS caller role's trust opening with a check that runs on CI: the fake AWS refuses to create or configure
    an SMS pool unless the trust admits it, and each Lambda's invoke statement is checked on its own, instead of only
    the rerun check that needs a commit outside the public history.
  • Move the selector's self-test into its own workflow, so a project.pbxproj change no longer starts the other
    script self-tests (first check that it isn't a required check).
  • Make a plain --apply refuse while any phase-2 ccit-ci- resource exists, naming --scope all.
  • Add a guarded provision-ci.sh replace <pool>: the tag checks and deletes the replacement steps list, through the
    script's own guards, then the --scope all --upload rerun.
  • Replace the bundled-README attribution with a detect-step check: a bundled file missing from the checkout (deleted,
    or a rename's old side) fails detect with an error naming the project, and an edit runs nothing. That covers the
    six READMEs only stress-test targets copy, and ends the unit and build workflows' over-selection for them.

@harsh62
harsh62 requested review from a team as code owners October 27, 2026 16:41
@harsh62
harsh62 requested a review from thisisabhash October 27, 2026 16:41
@harsh62
harsh62 deployed to IntegrationTest October 27, 2026 17:18 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 27, 2026 17:18 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 27, 2026 17:18 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 27, 2026 17:18 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 27, 2026 17:18 — with GitHub Actions Active
@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.86%. Comparing base (c335398) to head (e9074d1).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #4402       +/-   ##
===========================================
+ Coverage   75.69%   87.86%   +12.16%     
===========================================
  Files        1360      486      -874     
  Lines       57392    25183    -32209     
===========================================
- Hits        43444    22126    -21318     
+ Misses      13948     3057    -10891     
Flag Coverage Δ
API_plugin_unit_test ?
AWSPluginsCore ?
Amplify ?
Amplify_Foundation_Bridge_unit_test ?
Amplify_Foundation_unit_test ?
Amplify_Keychain_unit_test ?
Analytics_plugin_unit_test ?
Auth_plugin_unit_test 83.53% <ø> (+0.79%) ⬆️
CloudWatchClient_unit_test ?
CognitoClient_unit_test 95.60% <ø> (+0.06%) ⬆️
Connect_unit_test ?
DataStore_plugin_unit_test ?
EventEnrichment_unit_test ?
Firehose_plugin_unit_test ?
Geo_plugin_unit_test ?
Kinesis_plugin_unit_test ?
Logging_plugin_unit_test ?
Predictions_plugin_unit_test ?
PushNotifications_plugin_unit_test ?
RecordCache_unit_test ?
Storage_plugin_unit_test ?
unit_tests 87.86% <ø> (+12.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread .github/workflows/integ_test.yml Resolved
Comment thread scripts/python/affected_targets.py Resolved
Comment thread scripts/python/affected_targets.py Resolved
Comment thread scripts/python/test_affected_targets.py Resolved
Comment thread .github/workflows/auth_engine_gates.yml Resolved
The test selector ignored every .md file, but twelve integration host-app
READMEs are Copy Bundle Resources of their test targets, so a change that
deleted one selected nothing and broke that host app's build. The selector
now reads the checkout's Xcode projects and treats a file a project copies
into a bundle like any other host-app file; other documentation is still
skipped. A project it can't read makes the docs under it run their group.
The workflow and script headers said the gate rejects matches on every error
type it lists, but check enforces two rules, on AuthErrorConvertible and
KeychainStoreError. The headers now name those rules and say the
AWSCognitoAuthError and AuthError sites are only listed.
@harsh62
harsh62 force-pushed the ci/cognito-client-infra-followups branch from eff3a58 to e62d191 Compare October 28, 2026 20:47
@harsh62
harsh62 deployed to IntegrationTest October 28, 2026 22:58 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 28, 2026 22:58 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 28, 2026 22:58 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 28, 2026 22:58 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 28, 2026 22:58 — with GitHub Actions Active
@harsh62

harsh62 commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Update: the scheduled reminder workflow (auth_client_ci_reminders.yml) is removed from this PR, along with the committed key-expiry record and code_sink_key.py. CI-account maintenance doesn't belong in the public repo, so it now lives in the team's internal runbook. The replies above about the reminder's gh issue list handling, its label, its header and the issue text therefore no longer apply.

The code sink's API key is now created and extended for AppSync's 365-day maximum, and a rerun extends it once 120 or fewer days are left (e9074d1 and the three commits before it). The integration README keeps what's needed to run the tests.

@harsh62
harsh62 deployed to IntegrationTest October 29, 2026 02:49 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 29, 2026 02:49 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 29, 2026 02:49 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 29, 2026 02:49 — with GitHub Actions Active
@harsh62
harsh62 deployed to IntegrationTest October 29, 2026 02:49 — with GitHub Actions Active
@harsh62
harsh62 merged commit 5f7a95d into main Oct 29, 2026
84 checks passed
@harsh62
harsh62 deleted the ci/cognito-client-infra-followups branch October 29, 2026 14:50

This branch was successfully deployed

2 active deployments
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