Skip to content

Frontend Deployment Pipeline — Design Analysis

Design analysis for transitioning the Arda frontend deployment from Amplify branch-sync to a GitHub Actions–controlled pipeline using StartJob.

Each Amplify app is connected to a specific branch of the arda-frontend-app repository. On push, Amplify pulls the code, runs the build defined in amplify.yml, and deploys the .next/ artifacts.

BranchAmplify App IdAccountInfrastructurePartitionURL
devd38w5m1ngjza76139852620346Alpha002devdev.alpha002.app.arda.cards
staged1kbrvra79y8sc139852620346Alpha002stagestage.alpha002.app.arda.cards
mainduhexavnwh88g009765408297Alpha001prodlive.app.arda.cards
  1. Pre-build: npm ci, extract environment variables (NEXT_PUBLIC_*, ARDA_API_KEY, COGNITO_*, HUBSPOT_*, PYLON_WIDGET_SECRET) into .env
  2. Build: npm run build (Next.js production build with NEXT_TELEMETRY_DISABLED=1)
  3. Artifacts: .next/ directory
  4. Cache: .next/cache/, .npm/

Code flows through branches: feature branches merge into dev, dev promotes to stage, stage promotes to main. This is enforced by validate-pr-source.yml:

  • PRs targeting stage must come from dev
  • PRs targeting main must come from stage

Each promotion step requires review, approval, and passing CI checks (lint, build, unit tests, changelog validation).

Triggered on every push and non-draft PR:

  • lint: npm run lint
  • build: make build (Next.js production build)
  • test: make test (Jest unit tests)
  • validate-release: Extracts version tag from CHANGELOG.md via clq-action (only on protected branches)
  • tag: Creates a GitHub release on push to protected branches

The build requires partition-specific environment variables injected by Amplify:

  • Server-side (no NEXT_PUBLIC_ prefix): BASE_URL, ARDA_API_KEY, COGNITO_REGION, COGNITO_USER_POOL_ID, HUBSPOT_*, PYLON_WIDGET_SECRET
  • Client-side (NEXT_PUBLIC_ prefix): NEXT_PUBLIC_COGNITO_USER_POOL_ID, NEXT_PUBLIC_COGNITO_CLIENT_ID, NEXT_PUBLIC_COGNITO_REGION, NEXT_PUBLIC_API_URL, NEXT_PUBLIC_MOCK_MODE

These differ per partition (different Cognito pools, API endpoints, etc.) and are configured via CloudFormation.

The Amplify app and its environment variables are defined in two CloudFormation templates in the infrastructure repository:

  • infrastructure/src/main/cfn/amplify.cfn.yaml — Creates the AWS::Amplify::App resource with all environment variables. Values are resolved at deploy time from:
    • CloudFormation exports (cross-stack references): BASE_URL, COGNITO_USER_POOL_ID, NEXT_PUBLIC_COGNITO_CLIENT_ID, NEXT_PUBLIC_FRONTEND_URL — via !ImportValue with the pattern ${Infrastructure}-${Partition}-API-* or ${Infrastructure}-${Partition}-I-*
    • AWS Secrets Manager (dynamic references): ARDA_API_KEY, ARDA_SIGNUP_SECRET_KEY, HUBSPOT_CLIENT_SECRET, HUBSPOT_PRIVATE_ACCESS_TOKEN, NEXT_PUBLIC_COGNITO_CLIENT_SECRET, PYLON_WIDGET_SECRET — via {{resolve:secretsmanager:...}}
    • Static values: HUBSPOT_API_BASE, NEXT_PUBLIC_PYLON_APP_ID, NEXT_PUBLIC_DEPLOY_ENV, COGNITO_REGION
  • infrastructure/src/main/cfn/amplifyBranch.cfn.yaml — Creates the AWS::Amplify::Branch and AWS::Amplify::Domain resources, linking a branch to the Amplify app and associating the custom domain.

These stacks are deployed by the infrastructure/amm.sh script as part of the partition provisioning step. For Alpha001 and Alpha002, the Amplify apps were provisioned earlier and are managed outside amm.sh. For newer environments (SandboxKyle002), the CloudFormation templates are used directly. See Amplify App Provisioning — How It Works for the full step-by-step breakdown.

The key implication is that Amplify manages all environment variable resolution — the GitHub Actions pipeline does not need to resolve secrets or CloudFormation exports. It only needs to trigger the build.

The operations repository deploys to EKS via Helm charts using a three-workflow architecture.

  1. cicd.yaml (automatic) — triggered on push and PR:

    • build job: Uses Arda-cards/gradle-build-pipeline-action@v1 to build and publish the Helm chart. Outputs chart_name, chart_version, module_name.
    • deploy job: Matrix strategy over [dev, stage, demo, prod] with max-parallel: 1 (sequential). Calls the reusable deployment workflow for each environment.
  2. deploy.yaml (manual) — workflow_dispatch with inputs for chart_name, chart_version, component_name, purpose. Allows one-off deployments of specific versions to specific environments.

  3. reusable_deployment.yaml (shared logic):

    • Constructs a CloudFormation parameter file with secret values (ArdaApiKey, DocumintKey, GhcrPullKey)
    • Analyzes chart version to determine cleanup behavior (persistent vs. temporary deployment)
    • Deploys via Arda-cards/helm-deploy-pipeline-action@v4
    • Retrieves runtime configuration from the Purpose Locator service
  • Matrix-based sequential deployment: All environments are deployed from the same workflow run, in order
  • Reusable workflow: Shared deployment logic is extracted into a callable workflow
  • Manual override: A separate workflow allows deploying any version to any environment
  • Environment discovery: The Purpose Locator service provides runtime configuration per partition
  • Authorization gates: GitHub environments with required_reviewers for stage and prod; deployment_gate=manual in purpose-configuration

GitHub Actions OIDC is configured in each AWS account via the GhOidcProvider CDK construct (infrastructure/src/main/cdk/constructs/oam/gh-oidc-provider.ts).

Three IAM roles are created per account:

RoleName PatternScopePermissions
Infrastructure${prefix}-I-GitHubActionInfrastructurerepo:Arda-cards/infrastructure:ref:*CloudFormation, ECR, IAM, SSM, S3, EKS, Amplify, Secrets Manager
Management${prefix}-API-GitHubActionManagementrepo:Arda-cards/management:ref:*Cognito operations
Service${prefix}-API-GitHubActionrepo:Arda-cards/* (except infra/mgmt)API Gateway, CloudFormation, CloudWatch, EKS, IAM, Secrets Manager

The Infrastructure role has Amplify permissions (amplify:StartJob, amplify:GetApp, etc.) but is scoped to repo:Arda-cards/infrastructure:ref:*. The Service role has no Amplify permissions. The frontend deployment workflow needs a dedicated frontend role with Amplify permissions scoped to repo:Arda-cards/arda-frontend-app.

The StartDeployment API allows uploading a pre-built zip artifact to Amplify, bypassing the branch-sync mechanism. This was initially selected as the deployment method because it would allow full isolation via a test-deploy branch — GitHub Actions would build the app, upload the artifact to S3, and call StartDeployment without touching any existing Amplify branch connections.

Why it was rejected: AWS Amplify does not support StartDeployment for SSR applications using the WEB_COMPUTE platform. From the AWS documentation: “Amplify Hosting does not support manual deploys for server-side rendered (SSR) apps.” This is confirmed by GitHub issue #3195 (49+ upvotes, no resolution). Since the frontend app uses Platform: "WEB_COMPUTE" (required for Next.js 14+), StartDeployment is not viable.

Consequences of rejection:

  • No S3 deployment bucket needed
  • No environment variable resolution in GitHub Actions needed (Amplify manages env vars)
  • No s3:PutObject/GetObject or secretsmanager:GetSecretValue permissions needed on the frontend role
  • The workflow is much simpler: just StartJob + poll GetJob (plus cloudformation:ListExports to read Amplify parameters)

Direct Modification of Existing Amplify Apps

Section titled “Direct Modification of Existing Amplify Apps”

The initial analysis recommended reconfiguring the existing dev/stage/prod Amplify apps (change branch connections to main, disable auto-build) and deploying via StartJob. This was rejected for the development phase because it would disrupt the existing pipeline before the new one is validated. It remains the approach for the production cutover phase.

  1. Keep the safeguards of the current pipeline — no changes to validate-pr-source.yml, no changes to existing Amplify app branch connections, no disabling auto-build during development.
  2. Temporary manual triggers are acceptable — both for the new pipeline (which starts as manual-only) and for the existing pipeline. During the transition, it is acceptable to temporarily “downgrade” steps in the current pipeline from automatic to manual so that deployments across both pipelines can be coordinated by the operator.
  3. The new pipeline is a second path — it deploys from main (or a stand-in branch) into a dedicated test partition, coexisting with the current branch-sync model.
  4. A dedicated test Amplify app provides isolation — a new Alpha001-demo Amplify app connected to a demo branch, with EnableAutoBuild: false, serves as the testing ground for the new pipeline without touching any existing Amplify apps.

Chosen Approach: StartJob with Demo Amplify App

Section titled “Chosen Approach: StartJob with Demo Amplify App”

StartJob with jobType: RELEASE triggers Amplify’s own build from the connected branch. Amplify pulls the code, runs the amplify.yml build spec, resolves environment variables from CloudFormation exports and Secrets Manager, and deploys the .next/ artifacts. This is the same mechanism used by amm.sh (line 510) for initial deployments.

The commitId parameter allows deploying a specific commit on the connected branch:

Terminal window
aws amplify start-job \
--app-id "${APP_ID}" \
--branch-name "${BRANCH}" \
--job-type "RELEASE" \
--commit-id "abc123def456" \
--job-reason "Deploy specific commit"

This enables targeted deployments and rollbacks without any artifact storage — Amplify pulls directly from the Git repository.

To test the new pipeline without touching existing Amplify apps, a new Amplify app is created in the demo partition (Alpha001, account 009765408297):

  • Amplify app: Alpha001-demo-Amplify CloudFormation stack, using the existing amplify.cfn.yaml template with Repo=Arda-cards/arda-frontend-app
  • Branch: Alpha001-demo-AmplifyBranch CloudFormation stack, connected to a demo branch with EnableAutoBuild: false
  • Domain: demo.app.arda.cards (already provisioned by the demo partition’s CDK infrastructure)
  • Environment variables: Resolved from Alpha001-demo-* CloudFormation exports and Secrets Manager entries (already provisioned)

The demo partition has full CDK infrastructure (Cognito, Aurora DB, API Gateway, DNS) but currently no Amplify app and no frontend deployment. Creating the Amplify app requires no template changes — only CloudFormation stack deployment with the correct parameters. See Amplify App Provisioning — How It Works for the exact commands.

The amplifyBranch.cfn.yaml template needs a minor update: add an EnableAutoBuild parameter (currently hardcoded to true) so it can be passed as false for the demo app while remaining true for the existing Kyle app.

The workflow is simple — GitHub Actions only orchestrates the StartJob call. Amplify handles the build, environment variable resolution, and deployment.

PlantUML diagram

The existing pipeline continues to operate independently and unchanged.

Once validated on the demo app, the existing Amplify apps (dev, stage, prod) are migrated to the new pipeline:

  1. Reconfigure each existing Amplify app via CLI:
    • Change branch connection to main
    • Disable auto-build (EnableAutoBuild: false)
  2. The GH Actions workflow deploys to all partitions sequentially (devstageprod) with authorization gates

PlantUML diagram

All Amplify apps are connected to main and build the same commit (via commitId), but each has its own environment variables configured in CloudFormation (different Cognito pools, API endpoints, etc.).

A separate redeploy.yaml workflow allows deploying a specific commit SHA to a single partition:

.github/workflows/redeploy.yaml
name: "Redeploy"
on:
workflow_dispatch:
inputs:
partition:
type: choice
options: [dev, stage, demo, prod]
description: "Target partition"
commit_sha:
type: string
description: "Commit SHA on main to deploy"

The workflow:

  1. Fetches purpose-configuration for the target partition
  2. Assumes the OIDC role
  3. Verifies CI passed for the SHA (commit status API). If CI never ran or failed, runs CI inline and aborts if it fails.
  4. Calls StartJob with the commitId parameter
  5. Polls GetJob until complete

Uses environment: ${{ inputs.partition }} for authorization gates. No S3 lookup, no artifact management — Amplify pulls the commit directly from Git.

No deployment without green CI.

  • deploy.yaml: Uses workflow_run trigger, firing only after ci.yaml completes successfully on main.
  • redeploy.yaml: Checks commit status API as a first step. If CI never ran for the SHA, runs CI inline before deploying.

Multi-Account Strategy — Derived from Existing Configuration

Section titled “Multi-Account Strategy — Derived from Existing Configuration”

The pipeline leverages the existing purpose-configuration repository without adding new properties. Each {purpose}.properties file already provides the aws_role ARN for the correct account. The frontend workflow derives all additional parameters:

  1. Frontend IAM role ARN: Derived by appending FrontEnd to the existing aws_role role name. For example, arn:aws:iam::009765408297:role/Alpha001-API-GitHubActionarn:aws:iam::009765408297:role/Alpha001-API-GitHubActionFrontEnd. This follows the same convention used by the backend (operations, accounts-component) where the infrastructure identity is embedded in the role ARN.
  2. Infrastructure prefix: Parsed from the aws_role role name (extract text before -API-GitHubAction). Used to construct CloudFormation export names.
  3. Amplify App ID: Read from CloudFormation export ${Infrastructure}-${Partition}-I-AmplifyAppId after assuming the frontend role. For the demo app (created via CloudFormation), this export is created by amplify.cfn.yaml. For existing manually-created apps, a lightweight CloudFormation stack creates the export (see Infrastructure Requirements).
  4. Amplify Branch Name: Read from CloudFormation export ${Infrastructure}-${Partition}-I-AmplifyBranchName. Same mechanism — created by amplifyBranch.cfn.yaml for new apps, by the lightweight stack for existing apps.

This approach requires no changes to purpose-configuration and no versioning coordination (v2 tag is not needed). The backend workflows continue to use purpose-configuration identically.

Defined in infrastructure/src/main/cdk/constructs/oam/gh-oidc-provider.ts, alongside the existing roles. Created at the infrastructure level (once per account), shared across all partitions.

  • Name: ${prefix}-API-GitHubActionFrontEnd
  • OIDC scope: repo:Arda-cards/arda-frontend-app limited to refs/heads/main, refs/heads/patch, and refs/heads/demo (the demo scope is removed after production cutover)
  • Permissions:
amplify:StartJob
amplify:GetJob
amplify:GetApp
amplify:GetBranch
cloudformation:ListExports
  • Resource scoping: Amplify: arn:aws:amplify:${region}:${account}:apps/*. CloudFormation: * (ListExports does not support resource-level restrictions).

The cloudformation:ListExports permission is needed because the workflow reads Amplify App ID and Branch Name from CloudFormation exports after assuming this role.

The amplifyBranch.cfn.yaml template needs two changes:

  1. A new EnableAutoBuild parameter (default true, backward-compatible with existing Kyle deployment; passed as false for the demo app)
  2. A new CloudFormation export ${Infrastructure}-${Partition}-I-AmplifyBranchName that publishes the branch name so the workflow can read it

Two CloudFormation stacks are deployed to Alpha001:

  • Alpha001-demo-Amplify — the Amplify app with demo partition env vars
  • Alpha001-demo-AmplifyBranch — connected to the demo branch with auto-build disabled

See Amplify App Provisioning — How It Works for the exact commands.

Lightweight Export Stacks for Existing Amplify Apps

Section titled “Lightweight Export Stacks for Existing Amplify Apps”

The existing dev, stage, and prod Amplify apps were created manually — they have no CloudFormation stacks and therefore no exports. At cutover, amm.sh deploys a lightweight CloudFormation stack per partition that creates the AmplifyAppId and AmplifyBranchName exports with the known values. This provides the workflow with a uniform lookup mechanism regardless of how the app was originally created.

The stack is minimal:

amplifyExports.cfn.yaml
Parameters:
Infrastructure: { Type: String }
Partition: { Type: String }
AmplifyAppId: { Type: String }
AmplifyBranchName: { Type: String }
Outputs:
AmplifyAppId:
Value: !Ref AmplifyAppId
Export: { Name: !Sub "${Infrastructure}-${Partition}-I-AmplifyAppId" }
AmplifyBranchName:
Value: !Ref AmplifyBranchName
Export: { Name: !Sub "${Infrastructure}-${Partition}-I-AmplifyBranchName" }

The Amplify branch resource names are a legacy of the original branch-sync model. The prod app’s branch is named main (not prod) because it was originally connected to the main git branch. Rather than special-casing this, amm.sh maintains a uniform partition-to-branch-name mapping for all partitions:

PartitionAmplify Branch Resource Name
devdev
stagestage
demodemo
prodmain

This mapping is defined as a constant in amm.sh and used both when deploying CloudFormation stacks and when creating the lightweight export stacks. The mapping is the single source of truth — the workflow never derives or assumes branch names.

The following decisions from earlier design rounds remain applicable to the current approach:

IdSummaryDecision
Q3IAM permission changes — extend Service role vs. dedicated frontend roleDedicated frontend role (${prefix}-API-GitHubActionFrontEnd)
Q4validate-pr-source.yml transitionRelax incrementally. Keep unchanged during development; begin relaxation after production cutover.
Q6aFrontend role OIDC scoperefs/heads/main, refs/heads/patch (break-the-glass), and refs/heads/demo (removed after cutover)
Q6cFrontend role name${prefix}-API-GitHubActionFrontEnd (derived as ${aws_role}FrontEnd)
Q7validate-pr-source.yml relaxation sequenceKeep unchanged during development. Concrete sequence deferred to cutover phase.
Q8Infrastructure deployment sequencingInfrastructure first, then workflow development.
A6Purpose config role referenceExplicit frontend_aws_role property Superseded: frontend role derived from existing aws_role by appending FrontEnd. No new purpose-config properties.
A7GitHub environmentsCreate dev, stage, prod on arda-frontend-app: dev = no gate, stage = required_reviewers, prod = required_reviewers. Reviewers: denisa, jmpicnic, danmerb, davequinta
A8Sequential deploymentMatrix with max-parallel: 1, consistent with backend pattern
A11Purpose-configuration versioningUse main during development; tag v2 before production cutover Superseded: no purpose-config changes needed, so no versioning coordination required.
A15Shared deployment logicReusable workflow for job-level concerns (environment gates, permissions)
A16CI check gatedeploy.yaml: workflow_run trigger on CI success. redeploy.yaml: commit status API check; inline CI if never ran.
B7Purpose-configuration fetch mechanismUse existing Arda-cards/purpose-configuration-action
B8Validation scopeSign-in and sample page navigation. User logs in first, then agent can verify via Playwright MCP or reusing E2E tests.
  1. Demo environment setup:

    • Create the demo branch off main in arda-frontend-app
    • Update amplifyBranch.cfn.yaml: add EnableAutoBuild parameter (default true) and AmplifyBranchName export
    • Add partition-to-branch-name mapping and lightweight export stack support to amm.sh
    • Deploy Alpha001-demo-Amplify and Alpha001-demo-AmplifyBranch CloudFormation stacks with EnableAutoBuild=false
    • Verify the demo Amplify app exists, is connected to the demo branch, and CloudFormation exports are available
  2. Infrastructure (in infrastructure repo):

    • Add dedicated frontend IAM role (${prefix}-API-GitHubActionFrontEnd) to the GhOidcProvider CDK construct
    • Deploy to both Alpha001 and Alpha002 accounts via amm.sh
    • Verify the role can be assumed from arda-frontend-app demo branch
  3. Workflow development (on demo branch in arda-frontend-app):

    • Create .github/workflows/deploy.yaml with workflow_dispatch trigger
    • Create .github/workflows/redeploy.yaml for targeted single-partition redeployment
    • Create .github/workflows/reusable_deployment.yaml for shared job-level logic
    • Implement: purpose-config fetch → derive frontend role → OIDC assume → read CloudFormation exports → StartJob → poll GetJob
    • Create GitHub environments on arda-frontend-app with appropriate protection rules
    • Test against the demo partition — verify StartJob triggers a build and the site at demo.app.arda.cards is functional
  4. Validation:

    • Manually trigger the workflow on demo branch
    • Verify the deployed application works correctly at demo.app.arda.cards (sign-in, page navigation)
    • Test redeploy.yaml with a specific commit SHA
    • Test the CI check gate (attempt to deploy a SHA without green CI)
  5. Production cutover — migrate existing Amplify apps:

    • Create a rollback plan document
    • Deploy lightweight export stacks for existing apps (dev, stage, prod) via amm.sh
    • For each existing Amplify app (dev, stage, prod):
      • Disable auto-build via CLI
    • Enable enablePullRequestPreview: true on the dev Amplify app’s branch resource
    • Update Amplify build spec to include npm run test (Jest) before npm run build — quality gate for PR previews
    • No Cognito callback URL changes needed (app uses direct password auth, not OAuth/OIDC)
    • Update deploy.yaml trigger from workflow_dispatch to workflow_run on CI success on main
    • First full sequential deployment from main (dev → stage → demo → prod with gates)
    • Merge workflow to main
    • Remove demo from the OIDC trust scope
    • Create post-cutover instructions document for validate-pr-source.yml relaxation and dev/stage branch cleanup
    • Create “How to Develop in the Front End” guide (documentation/src/content/docs/process/craft/implementation/frontend-development.md)
    • Delete demo branch and Alpha001-demo Amplify stacks
IdQuestionDecision
C1demo.properties in purpose-configurationSuperseded: no new properties in purpose-configuration. Frontend role is derived from aws_role; Amplify App ID and Branch Name come from CloudFormation exports.
C2Alpha002 IAM role timing: The IAM role is per-account. During development, only Alpha001 (demo partition) needs the role. Alpha002 (dev/stage) needs it only at production cutover (Step 5). Should the role be deployed to Alpha002 upfront or deferred?Upfront. It is part of the infrastructure.
C3Existing Amplify app reconfiguration (Step 6): The dev/stage/prod apps were created outside CloudFormation. Changing their branch connection to main and disabling auto-build via CLI is straightforward. Should they also be brought under CloudFormation management (like the demo app) as part of this project, or is that a separate follow-up?No change to existing applications other than to disable self-deploy.
C4Matrix partitions during development: The deploy.yaml workflow uses a matrix over partitions. During development on the demo branch, the matrix should be [demo] only. After production cutover on main, it becomes [dev, stage, prod]. How should the matrix be parameterized — hardcoded per branch, or a workflow input?After cutover the matrix value should be dev, stage, demo, prod following the pattern in the backend deployment (e.g. see operations component)
C5demo branch maintenance: The demo branch is created off main. During development, main may receive new commits (via the existing promotion pipeline). Should demo be periodically rebased/merged from main, or is it acceptable for it to drift since the workflow code (not the app code) is what’s being tested?dev, stage and demo branches in github will be removed after the new pipeline is stable. (Cleanup out of scope) no need to keep them up-to-date.
C6Reusable workflow vs. composite action: With StartJob, the workflow logic is minimal (fetch config → assume role → StartJob → poll). Is a reusable workflow sufficient, or is the hybrid pattern (reusable workflow + composite action) still warranted for this simpler case?Keep it simple. Reusable workflos whould be enough.
FileRepositoryAction
src/main/cfn/amplifyBranch.cfn.yamlinfrastructureModify — add EnableAutoBuild parameter (default true) and AmplifyBranchName export
src/main/cfn/amplifyExports.cfn.yamlinfrastructureCreate — lightweight stack to publish AmplifyAppId and AmplifyBranchName exports for existing manually-created apps
amm.shinfrastructureModify — update Amplify gate to allow-list, add explicit Repo/AppName mapping constant, add partition-to-branch-name mapping, deploy lightweight export stacks for existing apps
.github/workflows/amm.ymlinfrastructureModify — use secrets[format('ARDA_API_KEY_{0}', partition)] for per-environment secrets
constructs/oam/gh-oidc-provider.tsinfrastructureModify — add dedicated frontend IAM role ${prefix}-API-GitHubActionFrontEnd (deploy to both Alpha001 and Alpha002 upfront)
.github/workflows/deploy.yamlarda-frontend-appCreate — main deployment workflow (matrix [dev, stage, demo, prod], workflow_run trigger)
.github/workflows/redeploy.yamlarda-frontend-appCreate — manual redeploy for a specific SHA to a single partition
.github/workflows/reusable_deployment.yamlarda-frontend-appCreate — reusable workflow for shared deployment logic
.github/workflows/validate-pr-source.ymlarda-frontend-appKeep unchanged during development; relax after production cutover
GitHub environmentsarda-frontend-appCreate — dev (no gate), stage (required reviewers), prod (required reviewers)
CloudFormation stacksAlpha001 (AWS)Create — Alpha001-demo-Amplify and Alpha001-demo-AmplifyBranch
Rollback planWorking directoryCreate — manual rollback procedures document
Post-cutover instructionsWorking directoryCreate — validate-pr-source.yml relaxation and branch cleanup procedures
Dev Amplify branch — PR previewsAlpha002 (AWS)Enable enablePullRequestPreview: true on the dev branch resource at cutover
Amplify build specarda-frontend-appModify — add npm run test (Jest) before npm run build as quality gate for PR previews
process/craft/implementation/frontend-development.mddocumentationCreate — “How to Develop in the Front End” guide covering the PR preview workflow, deployment pipeline, and local development
Cognito callback URLsAlpha002 (AWS)No changes needed — app uses direct password auth (USER_PASSWORD_AUTH), not OAuth/OIDC