@openhi/constructs 0.0.148 → 0.0.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +3 -1
- package/lib/index.mjs.map +1 -1
- package/package.json +10 -10
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../config/src/open-hi-config.ts","../../config/src/index.ts","../src/app/compute-branch-hash.ts","../src/app/open-hi-app.ts","../src/app/open-hi-environment.ts","../src/app/open-hi-stage.ts","../src/app/open-hi-service.ts","../src/components/acm/root-wildcard-certificate.ts","../src/components/api-gateway/root-http-api.ts","../src/components/app-sync/root-graphql-api.ts","../src/components/ssm/discoverable-string-parameter.ts","../src/components/cognito/cognito-user-pool.ts","../src/components/cognito/cognito-user-pool-client.ts","../src/components/cognito/cognito-user-pool-domain.ts","../src/components/cognito/cognito-user-pool-kms-key.ts","../src/components/cognito/post-authentication-lambda.ts","../src/components/cognito/post-confirmation-lambda.ts","../src/components/cognito/pre-token-generation-lambda.ts","../src/components/dynamodb/data-store-historical-archive.ts","../src/components/dynamodb/dynamo-db-data-store.ts","../src/components/dynamodb/workflow-dedup-table.ts","../src/components/event-bridge/data-event-bus.ts","../src/components/event-bridge/ops-event-bus.ts","../src/components/event-bridge/control-event-bus.ts","../src/components/postgres/data-store-postgres-replica.ts","../src/components/route-53/child-hosted-zone.ts","../src/components/route-53/root-hosted-zone.ts","../src/components/static-hosting/per-branch-hostname.ts","../src/components/static-hosting/static-hosting.ts","../src/components/static-hosting/static-content.ts","../src/services/open-hi-auth-service.ts","../src/services/open-hi-data-service.ts","../src/services/open-hi-global-service.ts","../src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge.ts","../src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge-lambda.ts","../src/workflows/control-plane/seed-demo-data/seed-demo-data-lambda.ts","../src/workflows/control-plane/seed-demo-data/seed-demo-data-workflow.ts","../src/workflows/control-plane/seed-system-data/seed-system-data-lambda.ts","../src/workflows/control-plane/seed-system-data/seed-system-data-workflow.ts","../src/services/open-hi-website-service.ts","../src/services/open-hi-rest-api-service.ts","../src/data/lambda/cors-options-lambda.ts","../src/data/lambda/rest-api-lambda.ts","../src/workflows/control-plane/user-onboarding/provision-default-workspace-lambda.ts","../src/workflows/control-plane/user-onboarding/user-onboarding-workflow.ts","../src/services/open-hi-graphql-service.ts","../src/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-lambdas.ts","../src/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-workflow.ts","../src/workflows/control-plane/rename-cascade/rename-cascade-lambdas.ts","../src/workflows/control-plane/rename-cascade/rename-cascade-workflow.ts"],"sourcesContent":["/*******************************************************************************\n *\n * OpenHi Config\n *\n * These types are kept in their own package to prevent dependency conflicts and\n * conditions between @openhi/constructs and @openhi/platform..\n *\n ******************************************************************************/\n\n/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const OPEN_HI_STAGE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type OpenHiStageType =\n (typeof OPEN_HI_STAGE)[keyof typeof OPEN_HI_STAGE];\n\n/**\n * Deployment Target Role\n *\n * Is this (account, region) the primary or a secondary deployment target for the stage?\n * Works for both multi-region (different regions) and cellular (same region, different accounts).\n */\nexport const OPEN_HI_DEPLOYMENT_TARGET_ROLE = {\n /**\n * The primary deployment target for this stage (main account/region).\n * For example, the base DynamoDB region for global tables.\n */\n PRIMARY: \"primary\",\n\n /**\n * A secondary deployment target for this stage (additional account/region).\n * For example, a replica region for a global DynamoDB table, or another cell in the same region.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type OpenHiDeploymentTargetRoleType =\n (typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE)[keyof typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE];\n\nexport interface OpenHiEnvironmentConfig {\n account: string;\n region: string;\n /**\n * Route53 zone containing DNS for this service.\n */\n hostedZoneId?: string;\n zoneName?: string;\n}\n\n/**\n * Represents the configuration for OpenHi services across different stages and\n * deployment targets.\n */\nexport interface OpenHiConfig {\n versions?: {\n cdk?: {\n cdkLibVersion?: string;\n cdkCliVersion?: string;\n };\n };\n deploymentTargets?: {\n [OPEN_HI_STAGE.DEV]?: {\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY]?: OpenHiEnvironmentConfig;\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY]?: Array<OpenHiEnvironmentConfig>;\n /**\n * Additional client origins trusted by this stage beyond the\n * stage-owned admin/website hosts that auto-injection derives from\n * branch context. Each entry is a full `<scheme>://<host>` string\n * with no path and no trailing slash (e.g.\n * `https://main.onsitedev.codedrifters.com`). Consumed by both the\n * REST API CORS allow-list and the Auth OAuth callback list at the\n * service layer.\n */\n additionalTrustedClientOrigins?: ReadonlyArray<string>;\n };\n [OPEN_HI_STAGE.STAGE]?: {\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY]?: OpenHiEnvironmentConfig;\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY]?: Array<OpenHiEnvironmentConfig>;\n /**\n * Additional client origins trusted by this stage beyond the\n * stage-owned admin/website hosts that auto-injection derives from\n * branch context. Each entry is a full `<scheme>://<host>` string\n * with no path and no trailing slash (e.g.\n * `https://main.onsitestage.codedrifters.com`). Consumed by both\n * the REST API CORS allow-list and the Auth OAuth callback list\n * at the service layer.\n */\n additionalTrustedClientOrigins?: ReadonlyArray<string>;\n };\n [OPEN_HI_STAGE.PROD]?: {\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY]?: OpenHiEnvironmentConfig;\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY]?: Array<OpenHiEnvironmentConfig>;\n /**\n * Additional client origins trusted by this stage beyond the\n * stage-owned admin/website hosts that auto-injection derives from\n * branch context. Each entry is a full `<scheme>://<host>` string\n * with no path and no trailing slash. Consumed by both the REST\n * API CORS allow-list and the Auth OAuth callback list at the\n * service layer.\n */\n additionalTrustedClientOrigins?: ReadonlyArray<string>;\n };\n };\n}\n","export * from \"./open-hi-config\";\n","import { hashString } from \"@codedrifters/utils\";\n\n/**\n * Inputs required to compute the deterministic branch hash that\n * scopes per-branch resources within an OpenHI deployment target.\n *\n * @public\n */\nexport interface ComputeBranchHashOptions {\n /** Application name (e.g. \"openhi\"). */\n readonly appName: string;\n /** Deployment target role identifier (e.g. \"primary\", \"secondary\"). */\n readonly deploymentTargetRole: string;\n /** AWS account id the deployment targets. */\n readonly account: string;\n /** AWS region the deployment targets. */\n readonly region: string;\n /** Git branch name driving this deployment. */\n readonly branchName: string;\n}\n\n/**\n * Compute the deterministic branch hash used by `OpenHiService` to scope\n * per-branch resources. Every `DiscoverableStringParameter` published by\n * an OpenHI service uses this hash as the branch segment of its SSM\n * path: `/{version}/{branchHash}/{serviceType}/{account}/{region}/{paramName}`.\n *\n * Exporting the helper lets external tooling compute the same SSM\n * prefix the CDK stacks publish to without re-implementing — and silently\n * drifting from — the inlined math.\n *\n * @public\n */\nexport const computeBranchHash = (\n options: ComputeBranchHashOptions,\n): string => {\n const { appName, deploymentTargetRole, account, region, branchName } =\n options;\n return hashString(\n [appName, deploymentTargetRole, account, region, branchName].join(\"-\"),\n 6,\n );\n};\n","import {\n OPEN_HI_DEPLOYMENT_TARGET_ROLE,\n OPEN_HI_STAGE,\n OpenHiConfig,\n OpenHiEnvironmentConfig,\n} from \"@openhi/config\";\nimport { App, AppProps } from \"aws-cdk-lib\";\nimport { IConstruct } from \"constructs\";\nimport { OpenHiEnvironment } from \"./open-hi-environment\";\nimport { OpenHiStage } from \"./open-hi-stage\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-app.md\n */\n\n/**\n * Symbol used for runtime type checking to identify OpenHiApp instances.\n *\n * @internal\n */\nconst OPEN_HI_APP_SYMBOL = Symbol.for(\"@openhi/constructs/core.OpenHiApp\");\n\n/**\n * Properties for creating an OpenHiApp instance.\n */\nexport interface OpenHiAppProps extends AppProps {\n /**\n * Optional name for the application.\n */\n readonly appName?: string;\n\n /**\n * The OpenHi configuration object that defines stages, environments, and\n * their associated AWS account and region settings.\n */\n readonly config: OpenHiConfig;\n}\n\n/**\n * Root application construct for OpenHi CDK applications.\n */\nexport class OpenHiApp extends App {\n /**\n * Finds the OpenHiApp instance that contains the given construct in its\n * construct tree.\n */\n public static of(construct: IConstruct): OpenHiApp | undefined {\n return construct.node.scopes.reverse().find(OpenHiApp.isOpenHiApp);\n }\n\n /**\n * Type guard that checks if a value is an OpenHiApp instance.\n */\n public static isOpenHiApp(this: void, x: any): x is OpenHiApp {\n return x !== null && typeof x === \"object\" && OPEN_HI_APP_SYMBOL in x;\n }\n\n /**\n * Name for the application.\n */\n readonly appName: string;\n\n /**\n * The OpenHi configuration object for this application.\n */\n readonly config: OpenHiConfig;\n\n /**\n * Creates a new OpenHiApp instance.\n */\n constructor(props: OpenHiAppProps) {\n super(props);\n\n // Set runtime symbol for type checking\n Object.defineProperty(this, OPEN_HI_APP_SYMBOL, { value: true });\n\n // Store app name, defaulting to \"openhi\" if not provided\n this.appName = props.appName ?? \"openhi\";\n\n // Store configuration for use by child constructs\n this.config = props.config;\n\n // Create stages and environments based on configuration\n // Iterate through all possible stage types (dev, stage, prod)\n Object.values(OPEN_HI_STAGE).forEach((stageType) => {\n // Only create a stage if it's configured in the config\n if (this.config.deploymentTargets?.[stageType]) {\n const stage = new OpenHiStage(this, { stageType });\n\n // Create primary deployment target if configured\n // Each stage can have at most one primary deployment target\n if (\n this.config.deploymentTargets?.[stageType]?.[\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY\n ]\n ) {\n const envConfig =\n this.config.deploymentTargets[stageType][\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY\n ]!;\n new OpenHiEnvironment(stage, {\n deploymentTargetRole: OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY,\n config: envConfig,\n env: { account: envConfig.account, region: envConfig.region },\n });\n }\n\n // Create secondary deployment targets if configured\n // Each stage can have zero or more secondary deployment targets\n if (\n this.config.deploymentTargets?.[stageType]?.[\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY\n ]\n ) {\n this.config.deploymentTargets[stageType][\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY\n ]!.forEach((envConfig: OpenHiEnvironmentConfig) => {\n new OpenHiEnvironment(stage, {\n deploymentTargetRole: OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY,\n config: envConfig,\n env: { account: envConfig.account, region: envConfig.region },\n });\n });\n }\n }\n });\n }\n\n /*****************************************************************************\n *\n * Stages\n *\n ****************************************************************************/\n\n /**\n * Gets all OpenHiStage instances that are direct children of this app.\n\n */\n public get stages(): Array<OpenHiStage> {\n return this.node.children.filter(OpenHiStage.isOpenHiStage);\n }\n\n /**\n * Gets the development stage, if it exists.\n */\n public get devStage(): OpenHiStage | undefined {\n return this.stages.find((stage) => stage.stageType === OPEN_HI_STAGE.DEV);\n }\n\n /**\n * Gets the staging stage, if it exists.\n */\n public get stageStage(): OpenHiStage | undefined {\n return this.stages.find((stage) => stage.stageType === OPEN_HI_STAGE.STAGE);\n }\n\n /**\n * Gets the production stage, if it exists.\n */\n public get prodStage(): OpenHiStage | undefined {\n return this.stages.find((stage) => stage.stageType === OPEN_HI_STAGE.PROD);\n }\n\n /*****************************************************************************\n *\n * Environments\n *\n ****************************************************************************/\n\n /**\n * Gets all OpenHiEnvironment instances across all stages in this app.\n */\n public get environments(): Array<OpenHiEnvironment> {\n return this.stages.flatMap((stage) => stage.environments);\n }\n\n /**\n * Gets all primary environments across all stages in this app.\n */\n public get primaryEnvironments(): Array<OpenHiEnvironment> {\n return this.environments.filter(\n (env) => env.deploymentTargetRole === \"primary\",\n );\n }\n\n /**\n * Gets all secondary environments across all stages in this app.\n */\n public get secondaryEnvironments(): Array<OpenHiEnvironment> {\n return this.environments.filter(\n (env) => env.deploymentTargetRole === \"secondary\",\n );\n }\n}\n","import {\n OPEN_HI_DEPLOYMENT_TARGET_ROLE,\n OpenHiEnvironmentConfig,\n} from \"@openhi/config\";\nimport { Stage, StageProps } from \"aws-cdk-lib\";\nimport { IConstruct } from \"constructs\";\nimport { OpenHiStage } from \"./open-hi-stage\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-environment.md\n */\n\n/**\n * Symbol used to identify OpenHiEnvironment instances at runtime.\n */\nconst OPEN_HI_ENVIRONMENT_SYMBOL = Symbol.for(\n \"@openhi/constructs/core.OpenHiEnvironment\",\n);\n\n/**\n * Properties for creating an OpenHiEnvironment.\n */\nexport interface OpenHiEnvironmentProps extends StageProps {\n /**\n * The deployment target role for this (account, region).\n */\n readonly deploymentTargetRole: (typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE)[keyof typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE];\n\n /**\n * Configuration for this specific environment.\n */\n readonly config: OpenHiEnvironmentConfig;\n}\n\n/**\n * Represents an OpenHi environment within an AWS CDK stage.\n */\nexport class OpenHiEnvironment extends Stage {\n /**\n * Finds the OpenHiEnvironment that contains the given construct.\n */\n public static of(construct: IConstruct): OpenHiEnvironment | undefined {\n return construct.node.scopes\n .reverse()\n .find(OpenHiEnvironment.isOpenHiEnvironment);\n }\n\n /**\n * Type guard to check if a value is an OpenHiEnvironment instance.\n */\n public static isOpenHiEnvironment(\n this: void,\n x: any,\n ): x is OpenHiEnvironment {\n return (\n x !== null && typeof x === \"object\" && OPEN_HI_ENVIRONMENT_SYMBOL in x\n );\n }\n\n /**\n * Configuration for this specific environment.\n */\n readonly config: OpenHiEnvironmentConfig;\n\n /**\n * The deployment target role for this (account, region).\n */\n public readonly deploymentTargetRole: (typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE)[keyof typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE];\n\n /**\n * Creates a new OpenHiEnvironment.\n */\n constructor(\n /**\n * The OpenHiStage that contains this environment.\n */\n public ohStage: OpenHiStage,\n /**\n * Properties for creating the environment.\n */\n public props: OpenHiEnvironmentProps,\n ) {\n // Copy account and region from config into env, if provided.\n // This allows all resources in this environment to default to the correct\n // account and region without having to specify it on each stack or resource.\n if (props.config.account && props.config.region) {\n props = {\n ...props,\n env: {\n account: props.config.account,\n region: props.config.region,\n },\n };\n }\n\n // Determine the stage name:\n // - Primary environments use the environment type as the name\n // - Secondary deployment targets use \"{deploymentTargetRole}-{index}\" format\n const stageName =\n props.deploymentTargetRole === OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY\n ? props.deploymentTargetRole\n : [props.deploymentTargetRole, ohStage.environments.length].join(\"-\");\n\n super(ohStage, stageName, {\n env: props.env ?? ohStage.props.env,\n ...props,\n });\n\n // Mark this instance as an OpenHiEnvironment for runtime type checking\n Object.defineProperty(this, OPEN_HI_ENVIRONMENT_SYMBOL, { value: true });\n\n this.deploymentTargetRole = props.deploymentTargetRole;\n this.config = props.config;\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport { Stage, StageProps } from \"aws-cdk-lib\";\nimport { IConstruct } from \"constructs\";\nimport { OpenHiApp } from \"./open-hi-app\";\nimport { OpenHiEnvironment } from \"./open-hi-environment\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-stage.md\n */\n\n/**\n * Symbol used to identify OpenHiStage instances at runtime.\n *\n * @internal\n */\nconst OPEN_HI_STAGE_SYMBOL = Symbol.for(\"@openhi/constructs/core.OpenHiStage\");\n\n/**\n * Properties for creating an OpenHiStage instance.\n */\nexport interface OpenHiStageProps extends StageProps {\n /**\n * The type of the OpenHi stage.\n */\n readonly stageType: (typeof OPEN_HI_STAGE)[keyof typeof OPEN_HI_STAGE];\n}\n\n/**\n * Represents a deployment stage in the OpenHi infrastructure hierarchy.\n */\nexport class OpenHiStage extends Stage {\n /**\n * Finds the OpenHiStage that contains the given construct.\n */\n public static of(construct: IConstruct): OpenHiStage | undefined {\n return construct.node.scopes.reverse().find(OpenHiStage.isOpenHiStage);\n }\n\n /**\n * Type guard to check if a value is an OpenHiStage instance.\n */\n public static isOpenHiStage(this: void, x: any): x is OpenHiStage {\n return x !== null && typeof x === \"object\" && OPEN_HI_STAGE_SYMBOL in x;\n }\n\n /**\n * The type of this OpenHi stage.\n */\n public readonly stageType: (typeof OPEN_HI_STAGE)[keyof typeof OPEN_HI_STAGE];\n\n /**\n * Creates a new OpenHiStage instance.\n */\n constructor(\n /**\n * The OpenHiApp that this stage belongs to.\n *\n * @public\n */\n public ohApp: OpenHiApp,\n\n /**\n * Properties for configuring the stage.\n *\n * @public\n */\n public props: OpenHiStageProps,\n ) {\n super(ohApp, props.stageType, props);\n\n Object.defineProperty(this, OPEN_HI_STAGE_SYMBOL, { value: true });\n\n this.stageType = props.stageType;\n }\n\n /**\n * Gets all OpenHiEnvironment instances contained within this stage.\n */\n public get environments(): Array<OpenHiEnvironment> {\n return this.node.children.filter(OpenHiEnvironment.isOpenHiEnvironment);\n }\n\n /**\n * Gets the primary OpenHiEnvironment for this stage, if one exists.\n */\n public get primaryEnvironment(): OpenHiEnvironment | undefined {\n return this.environments.find(\n (env) => env.deploymentTargetRole === \"primary\",\n );\n }\n\n /**\n * Gets all secondary OpenHiEnvironment instances for this stage.\n */\n public get secondaryEnvironments(): Array<OpenHiEnvironment> {\n return this.environments.filter(\n (env) => env.deploymentTargetRole === \"secondary\",\n );\n }\n}\n","import {\n findGitBranch,\n findGitRepoName,\n hashString,\n} from \"@codedrifters/utils\";\nimport { OPEN_HI_STAGE, OpenHiEnvironmentConfig } from \"@openhi/config\";\nimport { RemovalPolicy, Stack, StackProps, Tags } from \"aws-cdk-lib\";\nimport { paramCase } from \"change-case\";\nimport { computeBranchHash } from \"./compute-branch-hash\";\nimport { OpenHiEnvironment } from \"./open-hi-environment\";\n\n/**\n * Default release branch used when no override is supplied.\n */\nconst DEFAULT_RELEASE_BRANCH = \"main\";\n\n/**\n * Max length of the `childZonePrefix` segment. See\n * {@link OpenHiService.childZonePrefix} for the byte-budget reasoning.\n */\nconst CHILD_ZONE_PREFIX_MAX_LENGTH = 56;\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-service.md\n */\n\n/**\n * Known OpenHI service type strings. Each service class defines its own\n * static SERVICE_TYPE (e.g. OpenHiAuthService.SERVICE_TYPE === \"auth\").\n *\n * @public\n */\nexport type OpenHiServiceType =\n | \"auth\"\n | \"rest-api\"\n | \"data\"\n | \"global\"\n | \"graphql-api\"\n | \"website\";\n\n/**\n * Inputs to {@link OpenHiService.composeServiceDomain}. All fields are\n * supplied by the caller — the helper itself reads no environment state.\n *\n * @public\n */\nexport interface ComposeServiceDomainOptions {\n /** Sub-domain prefix (e.g. `\"api\"`, `\"admin\"`). */\n readonly domainPrefix: string;\n /** Branch name being deployed. */\n readonly branchName: string;\n /** Release branch name (e.g. `\"main\"`). */\n readonly defaultReleaseBranch: string;\n /** Per-branch child-zone prefix (typically `paramCase(branchName)` truncated). */\n readonly childZonePrefix: string;\n /** DNS zone name (e.g. `\"dev.openhi.org\"`). */\n readonly zoneName: string;\n}\n\n/**\n * Tag-key suffixes applied by every OpenHiService stack via Tags.of().\n * Full keys are composed `${appName}:${suffix}` — see {@link openHiTagKey}.\n * Consumers that filter or project these tags (e.g. the platform-deploy\n * bridge) import these suffixes rather than redeclaring the strings.\n *\n * @public\n */\nexport const OPENHI_TAG_SUFFIX_REPO_NAME = \"repo-name\";\n/** @public */\nexport const OPENHI_TAG_SUFFIX_BRANCH_NAME = \"branch-name\";\n/** @public */\nexport const OPENHI_TAG_SUFFIX_SERVICE_TYPE = \"service-type\";\n/** @public */\nexport const OPENHI_TAG_SUFFIX_STAGE_TYPE = \"stage-type\";\n\n/**\n * Compose a full stack-tag key from an `appName` and a suffix from\n * {@link OPENHI_TAG_SUFFIX_REPO_NAME} et al.\n *\n * @public\n */\nexport const openHiTagKey = (appName: string, suffix: string): string =>\n `${appName}:${suffix}`;\n\n/**\n * Properties for creating an {@link OpenHiService} stack.\n *\n * @public\n */\nexport interface OpenHiServiceProps extends StackProps {\n /**\n * Optional branch name override.\n */\n readonly branchName?: string;\n\n /**\n * Optional repository name override.\n */\n readonly repoName?: string;\n\n /**\n * Optional application name override.\n */\n readonly appName?: string;\n\n /**\n * Default release branch name.\n */\n readonly defaultReleaseBranch?: string;\n\n /**\n * The removal policy for persistent stack resources.\n */\n readonly removalPolicy?: RemovalPolicy;\n\n /**\n * Environment configuration for this service.\n */\n readonly config?: OpenHiEnvironmentConfig;\n\n /**\n * A constant that identifies the service type.\n */\n readonly serviceType?: OpenHiServiceType;\n}\n\n/**\n * Represents an OpenHI service stack within the OpenHI platform.\n * Subclasses must override {@link serviceType} to return their static SERVICE_TYPE.\n */\nexport abstract class OpenHiService extends Stack {\n /**\n * Compose the full per-deploy domain for an OpenHI service.\n *\n * On the release branch (`branchName === defaultReleaseBranch`), the\n * full domain is `<domainPrefix>.<zoneName>`. On every other branch\n * the per-PR preview hostname is\n * `<domainPrefix>-<childZonePrefix>.<zoneName>`.\n *\n * Pure helper — reads no environment state. Subclasses expose thin\n * statics (`composeFullDomain`) that fill in `domainPrefix` from their\n * own service constant and delegate here.\n */\n public static composeServiceDomain(\n opts: ComposeServiceDomainOptions,\n ): string {\n const isRelease = opts.branchName === opts.defaultReleaseBranch;\n const subDomain = isRelease\n ? opts.domainPrefix\n : `${opts.domainPrefix}-${opts.childZonePrefix}`;\n return `${subDomain}.${opts.zoneName}`;\n }\n\n /**\n * Compute the `childZonePrefix` segment for a given branch — kebab-cased\n * and truncated to {@link CHILD_ZONE_PREFIX_MAX_LENGTH}. Matches the\n * per-instance {@link OpenHiService.childZonePrefix} getter so consumers\n * can compose hostnames identical to the service's own without\n * instantiating it.\n */\n public static computeChildZonePrefix(branchName: string): string {\n return paramCase(branchName).slice(0, CHILD_ZONE_PREFIX_MAX_LENGTH);\n }\n\n /**\n * Resolve the branch context the service would compute internally given\n * an environment and optional overrides. Mirrors the same defaulting\n * (props override → JEST sentinel → `GIT_BRANCH_NAME` env → git\n * detection on DEV → release branch on stage/prod) the\n * {@link OpenHiService} constructor uses.\n *\n * Consumers (e.g. sibling stack entries) call this to predict the\n * branch values a service will see at synth time so they can compose\n * hostnames against the same inputs.\n */\n public static resolveBranchContext(\n ohEnv: OpenHiEnvironment,\n overrides: { branchName?: string; defaultReleaseBranch?: string } = {},\n ): {\n branchName: string;\n defaultReleaseBranch: string;\n childZonePrefix: string;\n } {\n const defaultReleaseBranch =\n overrides.defaultReleaseBranch ?? DEFAULT_RELEASE_BRANCH;\n const branchName =\n overrides.branchName ??\n (process.env.JEST_WORKER_ID\n ? \"test-branch\"\n : process.env.GIT_BRANCH_NAME?.trim() ||\n (ohEnv.ohStage.stageType === OPEN_HI_STAGE.DEV\n ? findGitBranch()\n : defaultReleaseBranch));\n const childZonePrefix = OpenHiService.computeChildZonePrefix(branchName);\n return { branchName, defaultReleaseBranch, childZonePrefix };\n }\n\n /**\n * The service/stack ID that was passed to the constructor.\n */\n public readonly serviceId: string;\n\n /**\n * The deployment target role identifier.\n */\n public readonly deploymentTargetRole: string;\n\n /**\n * Repository name used in resource tagging.\n */\n public readonly repoName: string;\n\n /**\n * Application name identifier.\n */\n public readonly appName: string;\n\n /**\n * Default release branch name.\n */\n public readonly defaultReleaseBranch: string;\n\n /**\n * Branch name used when calculating resource names and hashes.\n */\n public readonly branchName: string;\n\n /**\n * Short hash unique to the deployment target (app name, deployment target role, account, region).\n */\n public readonly environmentHash: string;\n\n /**\n * Short hash unique to the environment and branch combination.\n */\n public readonly branchHash: string;\n\n /**\n * Branch hash computed against {@link defaultReleaseBranch} rather than\n * {@link branchName}. On the release branch this equals {@link branchHash};\n * on every other branch it identifies the namespace the release-branch\n * deploy of this service writes to. Use when looking up SSM parameters\n * that only the release-branch stack publishes (e.g. shared static-hosting\n * bucket ARN).\n */\n public readonly releaseBranchHash: string;\n\n /**\n * Short hash unique to the specific stack/service.\n */\n public readonly stackHash: string;\n\n /**\n * The removal policy for persistent stack resources.\n */\n public readonly removalPolicy: RemovalPolicy;\n\n /**\n * Environment configuration for this service.\n * This is either the value passed in or the default config\n */\n public readonly config: OpenHiEnvironmentConfig;\n\n /**\n * Service type identifier. Override in subclasses to return the class's static SERVICE_TYPE.\n * Used for parameter names, tags, and service discovery.\n */\n abstract get serviceType(): OpenHiServiceType | string;\n\n /**\n * Creates a new OpenHI service stack.\n *\n * @param ohEnv - The OpenHI environment (stage) this service belongs to\n * @param id - Unique identifier for this service stack (e.g., \"user-service\")\n * @param props - Optional properties for configuring the service\n *\n * @throws \\{Error\\} If account and region are not defined in props or environment\n *\n */\n constructor(\n public ohEnv: OpenHiEnvironment,\n id: string,\n public props: OpenHiServiceProps = {},\n ) {\n // Determine the account and region based on environment or user passed props.\n // This must be done before calling super() as it's needed for stack naming.\n const { account, region } = props.env || ohEnv;\n if (!account || !region) {\n throw new Error(\n \"Account and region must be defined in OpenHiServiceProps or OpenHiEnvironment\",\n );\n }\n\n // Get app name from the app in the hierarchy (via environment -> stage -> app)\n const appName = props.appName ?? ohEnv.ohStage.ohApp.appName ?? \"openhi\";\n\n // Initialize deployment context properties\n // Repo name is used in tagging. This tag value is important for tracking\n // when tearing preview stacks back down. If not provided, detect from git.\n const repoName = props.repoName ?? findGitRepoName();\n\n // Resolve branch name + defaultReleaseBranch via the public helper so\n // sibling stacks can reproduce the same values without instantiating\n // a service. Detection logic (in resolveBranchContext):\n // - If explicitly provided in props, use that value\n // - If Jest is running, use \"test-branch\" to avoid snapshot test issues\n // - If GIT_BRANCH_NAME env is set (e.g. by CI), use it\n // - If in dev stage, detect from git using findGitBranch()\n // - Otherwise (stage/prod), default to defaultReleaseBranch\n const { branchName, defaultReleaseBranch } =\n OpenHiService.resolveBranchContext(ohEnv, {\n ...(props.branchName !== undefined && { branchName: props.branchName }),\n ...(props.defaultReleaseBranch !== undefined && {\n defaultReleaseBranch: props.defaultReleaseBranch,\n }),\n });\n\n // Compute environment hash: unique to deployment target (app name, role, account, region)\n // Mainly used for DNS names and deployment-target-scoped resources\n const environmentHash = hashString(\n [appName, ohEnv.deploymentTargetRole, account, region].join(\"-\"),\n 6,\n );\n\n // Compute branch hash: unique to deployment target and branch combination\n // Useful for resources shared across stacks within the same branch\n const branchHash = computeBranchHash({\n appName,\n deploymentTargetRole: ohEnv.deploymentTargetRole,\n account,\n region,\n branchName,\n });\n\n // Compute release-branch hash: same formula as branchHash but with\n // defaultReleaseBranch in place of branchName. Lets feature-branch stacks\n // address resources published into the release-branch namespace (e.g. the\n // shared static-hosting bucket ARN written by the release-branch website\n // deploy).\n const releaseBranchHash = hashString(\n [\n appName,\n ohEnv.deploymentTargetRole,\n account,\n region,\n defaultReleaseBranch,\n ].join(\"-\"),\n 6,\n );\n\n // Compute stack hash: unique to the specific stack/service\n // Useful for stack-specific resources like S3 buckets, KMS key aliases\n // This ensures two PR builds or different services don't collide\n const stackHash = hashString(\n [\n appName,\n ohEnv.deploymentTargetRole,\n account,\n region,\n branchName,\n id,\n ].join(\"-\"),\n 6,\n );\n\n // Set the removal policy for this stack based on the deployment target role.\n // Production stages retain resources, others destroy them on stack deletion.\n const removalPolicy =\n props.removalPolicy ??\n (ohEnv.ohStage.stageType === OPEN_HI_STAGE.PROD\n ? RemovalPolicy.RETAIN\n : RemovalPolicy.DESTROY);\n Object.assign(props, { removalPolicy });\n\n // Description to use for the stack and all resources within it.\n // Includes service ID, branch name, and hash for easy identification.\n const description = `OpenHi Service: ${id} [${branchName}] - ${branchHash}`;\n\n // Call the super constructor of Stack.\n // This initializes the AWS CDK Stack with:\n // - Scope: the OpenHI environment\n // - ID: unique stack name including branch hash\n // - Props: stack properties including description and removal policy\n super(ohEnv, [branchHash, id, account, region].join(\"-\"), {\n ...props,\n description,\n });\n\n // Store the service ID for use in deployment context and other operations.\n this.serviceId = id;\n\n // Set the removal policy for this stack based on the deployment target role.\n this.removalPolicy = removalPolicy;\n\n /**\n * Explicit config or use the environment config as a backup,\n */\n this.config = props.config ?? ohEnv.props.config;\n\n // Initialize deployment context properties directly on the service\n this.deploymentTargetRole = ohEnv.deploymentTargetRole;\n this.repoName = repoName;\n this.appName = appName;\n this.defaultReleaseBranch = defaultReleaseBranch;\n this.branchName = branchName;\n this.environmentHash = environmentHash;\n this.branchHash = branchHash;\n this.releaseBranchHash = releaseBranchHash;\n this.stackHash = stackHash;\n\n // Pre-populate the AZ context cache for this stack so any construct that\n // calls `stack.availabilityZones` (notably RDS DatabaseCluster, ELBs, and\n // anything else that fans out across AZs) gets concrete values without\n // triggering a CDK context lookup. Without this, CI synth records the\n // lookup as \"missing\" and deploy fails because the GitHubOpenHiDeployer\n // role can't assume `cdk-…-lookup-role-…` (only granted to dev machines\n // by default). AZ names follow the stable `<region>a/b/c…` pattern across\n // all current commercial AWS regions; if a deployment ever targets a\n // region where this assumption breaks, override here per region.\n this.node.setContext(\n `availability-zones:account=${account}:region=${region}`,\n [`${region}a`, `${region}b`, `${region}c`],\n );\n\n // Standard tagging across all resources in the stack.\n // Use id (the service type string passed to super) since abstract serviceType cannot be accessed in constructor.\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_REPO_NAME),\n repoName.slice(0, 255),\n );\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_BRANCH_NAME),\n branchName.slice(0, 255),\n );\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_SERVICE_TYPE),\n id.slice(0, 255),\n );\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_STAGE_TYPE),\n ohEnv.ohStage.stageType.slice(0, 255),\n );\n }\n\n /**\n * DNS prefix for this branche's child zone. Capped at 56 chars so\n * that a `<service>-<prefix>` hostname segment stays under the 63-byte\n * DNS label limit even for the longest current prefix (`admin-`, 6\n * bytes; the matching API uses `api-`, 4 bytes). 56 leaves 1 byte of\n * headroom on the longer side.\n */\n public get childZonePrefix(): string {\n return OpenHiService.computeChildZonePrefix(this.branchName);\n }\n}\n","import {\n Certificate,\n CertificateProps,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/acm/root-wildcard-certificate.md\n */\n\nexport class RootWildcardCertificate extends Certificate {\n /**\n * Used when storing the Certificate ARN in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"ROOT_WILDCARD_CERT_ARN\";\n\n /**\n * Using a special name here since this will be shared and used among many\n * stacks and services. Use with OpenHiGlobalService.rootWildcardCertificateFromConstruct.\n */\n public static ssmParameterName(): string {\n return (\n \"/\" +\n [\"GLOBAL\", RootWildcardCertificate.SSM_PARAM_NAME].join(\"/\").toUpperCase()\n );\n }\n\n constructor(scope: Construct, props: CertificateProps) {\n super(scope, \"root-wildcard-certificate\", { ...props });\n\n /**\n * Generate the SSM Parameter used to store this Certificate's ARN.\n */\n new StringParameter(this, \"wildcard-cert-param\", {\n parameterName: RootWildcardCertificate.ssmParameterName(),\n stringValue: this.certificateArn,\n });\n }\n}\n","import { HttpApi, HttpApiProps } from \"aws-cdk-lib/aws-apigatewayv2\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/api-gateway/root-http-api.md\n */\n\nexport interface RootHttpApiProps extends HttpApiProps {}\n\nexport class RootHttpApi extends HttpApi {\n /**\n * Used when storing the API ID in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"ROOT_HTTP_API\";\n\n constructor(scope: Construct, props: RootHttpApiProps = {}) {\n const stack = OpenHiService.of(scope) as OpenHiService;\n\n const origins = props.corsPreflight?.allowOrigins;\n if (origins?.length) {\n const withTrailingSlash = origins.filter((o) => o.endsWith(\"/\"));\n if (withTrailingSlash.length > 0) {\n throw new Error(\n `CORS allowOrigins must not include a trailing slash. The browser Origin header is scheme + host + port only (no path), so API Gateway will not match origins like \"https://example.com/\" and CORS can fail. Invalid: ${withTrailingSlash.join(\", \")}. Use e.g. \"https://example.com\" instead.`,\n );\n }\n }\n\n super(scope, \"http-api\", {\n /**\n * User provided props\n */\n ...props,\n\n /**\n * Required\n */\n apiName: [\"root\", \"http\", \"api\", stack.branchHash].join(\"-\"),\n });\n }\n}\n","import {\n Definition,\n GraphqlApi,\n GraphqlApiProps,\n IGraphqlApi,\n} from \"aws-cdk-lib/aws-appsync\";\nimport { CodeFirstSchema, GraphqlType, ObjectType } from \"awscdk-appsync-utils\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/app-sync/root-graphql-api.md\n */\n\nexport interface RootGraphqlApiProps extends GraphqlApiProps {}\n\nexport class RootGraphqlApi extends GraphqlApi {\n /**\n * Used when storing the GraphQl API ID in SSM\n */\n public static readonly SSM_PARAM_NAME = \"ROOT_GRAPHQL_API\";\n\n public static fromConstruct(scope: Construct): IGraphqlApi {\n const graphqlApiId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: RootGraphqlApi.SSM_PARAM_NAME,\n serviceType: \"graphql-api\",\n });\n\n return GraphqlApi.fromGraphqlApiAttributes(scope, \"root-graphql-api\", {\n graphqlApiId,\n });\n }\n\n constructor(scope: Construct, props?: Omit<RootGraphqlApiProps, \"name\">) {\n const stack = OpenHiService.of(scope) as OpenHiService;\n\n const schema = new CodeFirstSchema();\n schema.addType(\n new ObjectType(\"Query\", {\n definition: { HelloWorld: GraphqlType.string() },\n }),\n );\n\n super(scope, \"root-graphql-api\", {\n /**\n * Defaults\n */\n queryDepthLimit: 2,\n resolverCountLimit: 50,\n definition: Definition.fromSchema(schema),\n\n /**\n * Overrideable props\n */\n ...props,\n\n /**\n * Required\n */\n name: [\"root\", \"graphql\", \"api\", stack.branchHash].join(\"-\"),\n });\n\n /**\n * Generate the SSM Parameter used to store this GraphQL API's ID.\n */\n new DiscoverableStringParameter(this, \"graphql-api-param\", {\n ssmParamName: RootGraphqlApi.SSM_PARAM_NAME,\n serviceType: \"graphql-api\",\n stringValue: this.apiId,\n });\n }\n}\n","import { Tags } from \"aws-cdk-lib\";\nimport {\n StringParameter,\n type StringParameterProps,\n} from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/ssm/discoverable-string-parameter.md\n */\n\n/*******************************************************************************\n *\n * DiscoverableStringParameterProps: props for creating or looking up SSM\n * parameters. Includes StringParameterProps (minus parameterName) plus\n * name-building fields used by buildParameterName.\n *\n ******************************************************************************/\n\nexport interface DiscoverableStringParameterProps extends Omit<\n StringParameterProps,\n \"parameterName\"\n> {\n /**\n * SSM param name used to build the SSM parameter name via buildParameterName\n * and stored as a tag on the parameter for discoverability.\n */\n readonly ssmParamName: string;\n\n /**\n * The environment hash the parameter belongs to.\n * @default - the current stack's environment hash\n */\n readonly branchHash?: string;\n\n /**\n * The service type the parameter belongs to.\n * @default - the current stack's service type\n */\n readonly serviceType?: string;\n\n /**\n * The AWS account the parameter belongs to.\n * @default - the current stack's account\n */\n readonly account?: string;\n\n /**\n * The AWS region the parameter belongs to.\n * @default - the current stack's region\n */\n readonly region?: string;\n}\n\n/**\n * Props for buildParameterName and valueForLookupName.\n * Includes ssmParamName (required) and optional overrides (branchHash, serviceType, account, region).\n */\nexport type BuildParameterNameProps = Pick<\n DiscoverableStringParameterProps,\n \"ssmParamName\" | \"branchHash\" | \"serviceType\" | \"account\" | \"region\"\n>;\n\n/**\n * Discoverable SSM string parameter construct. Extends CDK StringParameter:\n * builds parameterName from the given name via buildParameterName and tags\n * the parameter with the name constant.\n */\nexport class DiscoverableStringParameter extends StringParameter {\n /**\n * Version of the parameter name format / discoverability schema.\n * Bump when buildParameterName or tagging semantics change.\n * Also used to drive replacement of parameters during CloudFormation deploys.\n */\n public static readonly version = \"v1\";\n\n /**\n * Build a param name based on predictable attributes found in services and\n * constructs. Used for storage and retrieval of SSM values across services.\n */\n public static buildParameterName(\n scope: Construct,\n props: BuildParameterNameProps,\n ): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return (\n \"/\" +\n [\n DiscoverableStringParameter.version,\n props.branchHash ?? stack.branchHash,\n props.serviceType ?? stack.serviceType,\n props.account ?? stack.account,\n props.region ?? stack.region,\n props.ssmParamName,\n ]\n .join(\"/\")\n .toUpperCase()\n );\n }\n\n /**\n * Read the string value of an SSM parameter created with DiscoverableStringParameter,\n * using props that include ssmParamName and optional overrides (e.g. serviceType).\n */\n public static valueForLookupName(\n scope: Construct,\n props: BuildParameterNameProps,\n ): string {\n const paramName = DiscoverableStringParameter.buildParameterName(\n scope,\n props,\n );\n return StringParameter.valueForStringParameter(scope, paramName);\n }\n\n constructor(\n scope: Construct,\n id: string,\n props: DiscoverableStringParameterProps,\n ) {\n const { ssmParamName, branchHash, serviceType, account, region, ...rest } =\n props;\n\n const parameterName = DiscoverableStringParameter.buildParameterName(\n scope,\n props,\n );\n\n super(scope, id + \"-\" + DiscoverableStringParameter.version, {\n ...rest,\n parameterName,\n });\n\n const { appName } = OpenHiService.of(scope) as OpenHiService;\n Tags.of(this).add(`${appName}:param-name`, ssmParamName);\n }\n}\n","import {\n FeaturePlan,\n UserPool,\n UserPoolProps,\n VerificationEmailStyle,\n} from \"aws-cdk-lib/aws-cognito\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool.md\n */\n\nexport class CognitoUserPool extends UserPool {\n /**\n * Used when storing the User Pool ID in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL\";\n\n constructor(scope: Construct, props: UserPoolProps = {}) {\n const service = OpenHiService.of(scope) as OpenHiService;\n\n super(scope, \"user-pool\", {\n /**\n * Defaults\n */\n selfSignUpEnabled: true,\n signInAliases: {\n email: true,\n },\n userVerification: {\n emailSubject: \"Verify your email!\",\n emailBody: \"Your verification code is {####}.\",\n emailStyle: VerificationEmailStyle.CODE,\n },\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n // Plus is required for access-token V2 claim customization in the\n // pre-token-generation Lambda. Essentials silently drops\n // claimsAndScopeOverrideDetails.accessTokenGeneration.claimsToAddOrOverride.\n featurePlan: FeaturePlan.PLUS,\n\n /**\n * Over-rideable props\n */\n ...props,\n\n /**\n * Required\n */\n userPoolName: [\"cognito\", \"user\", \"pool\", service.branchHash].join(\"-\"),\n });\n }\n}\n","import { UserPoolClient, UserPoolClientProps } from \"aws-cdk-lib/aws-cognito\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool-client.md\n */\n\nexport class CognitoUserPoolClient extends UserPoolClient {\n /**\n * Used when storing the User Pool Client ID in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL_CLIENT\";\n\n constructor(scope: Construct, props: UserPoolClientProps) {\n super(scope, \"user-pool-client\", {\n // Default: SPA client (no secret). OAuth flow + callback/logout URL\n // composition is the owning service's responsibility — pass via\n // `props.oAuth` (see `OpenHiAuthService.resolveOAuthRedirectUrls`).\n generateSecret: false,\n ...props,\n });\n }\n}\n","import { UserPoolDomain, UserPoolDomainProps } from \"aws-cdk-lib/aws-cognito\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool-domain.md\n */\n\nexport class CognitoUserPoolDomain extends UserPoolDomain {\n /**\n * Used when storing the User Pool Domain in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL_DOMAIN\";\n\n constructor(scope: Construct, props: UserPoolDomainProps) {\n /**\n * This supports both custom and native Cognito domains, but we need to\n * name them uniquely so that swap outs work and don't cause conflicts\n * when cloudformation does it's deploy.\n */\n const id = props.cognitoDomain?.domainPrefix\n ? \"cognito-domain\"\n : \"custom-domain\";\n\n super(scope, id, {\n ...props,\n });\n }\n}\n","import { Key, KeyProps } from \"aws-cdk-lib/aws-kms\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool-kms-key.md\n */\n\nexport class CognitoUserPoolKmsKey extends Key {\n /**\n * Used when storing the KMS Key in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL_KMS_KEY\";\n\n constructor(scope: Construct, props: KeyProps = {}) {\n const service = OpenHiService.of(scope) as OpenHiService;\n\n super(scope, \"kms-key\", {\n ...props,\n // alias: [\"alias\", \"cognito\", service.branchHash].join(\"/\"),\n description: `KMS Key for Cognito User Pool - ${service.branchHash}`,\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/post-authentication-lambda.md\n */\n\nconst HANDLER_NAME = \"post-authentication.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\n/**\n * Lambda used as Cognito Post Authentication trigger.\n */\nexport class PostAuthenticationLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct) {\n super(scope, \"post-authentication-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/post-confirmation-lambda.md\n */\n\nconst HANDLER_NAME = \"post-confirmation.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nconst resolveHandlerEntry = (dirname: string): string => {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n};\n\nexport interface PostConfirmationLambdaProps {\n /**\n * Control-plane EventBridge bus name. Passed to the Lambda as\n * CONTROL_EVENT_BUS_NAME so it can publish onboarding workflow events.\n */\n readonly controlEventBusName: string;\n}\n\n/**\n * Lambda used as Cognito Post Confirmation trigger. It publishes a control\n * event and returns quickly; workflow Lambdas own provisioning.\n */\nexport class PostConfirmationLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, props: PostConfirmationLambdaProps) {\n super(scope, \"post-confirmation-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n CONTROL_EVENT_BUS_NAME: props.controlEventBusName,\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/pre-token-generation-lambda.md\n */\n\nconst HANDLER_NAME = \"pre-token-generation.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\nexport interface PreTokenGenerationLambdaProps {\n /**\n * DynamoDB data store table name. Passed to the Lambda as DYNAMO_TABLE_NAME\n * so the control-plane ElectroDB service reads the User by Cognito `sub`\n * (GSI2) and the user's first active Membership (fallback path).\n */\n readonly dynamoTableName: string;\n}\n\n/**\n * Lambda used as Cognito Pre Token Generation trigger. Resolves the OpenHI\n * User from the request's Cognito `sub` and injects `ohi_tid`, `ohi_wid`,\n * `ohi_uid`, `ohi_uname` into both the ID token and the access token\n * (ADR 2026-03-17-01).\n */\nexport class PreTokenGenerationLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, props: PreTokenGenerationLambdaProps) {\n super(scope, \"pre-token-generation-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n DYNAMO_TABLE_NAME: props.dynamoTableName,\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, RemovalPolicy, Size, Tags } from \"aws-cdk-lib\";\nimport * as events from \"aws-cdk-lib/aws-events\";\nimport * as kinesis from \"aws-cdk-lib/aws-kinesis\";\nimport * as kinesisfirehose from \"aws-cdk-lib/aws-kinesisfirehose\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport * as s3 from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService, openHiTagKey } from \"../../app\";\n\nconst HANDLER_NAME = \"firehose-archive-transform.handler.js\";\n\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n return path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface DataStoreHistoricalArchiveProps {\n /**\n * Kinesis stream that receives DynamoDB item-level changes (table Kinesis destination).\n */\n readonly kinesisStream: kinesis.IStream;\n /**\n * Removal policy for the archive bucket and related resources.\n */\n readonly removalPolicy: RemovalPolicy;\n /**\n * Short hash for unique stream/bucket naming within the deployment.\n */\n readonly stackHash: string;\n /**\n * When set, the Firehose transform Lambda publishes qualifying changes to\n * this bus via PutEvents (ADR 2026-03-02-01).\n */\n readonly dataEventBus?: events.IEventBus;\n}\n\n/**\n * DynamoDB change stream → Kinesis → Firehose → S3 with a transform Lambda for\n * scope filtering and dynamic partitioning (ADR 2026-03-11-02). The same Lambda\n * publishes qualifying current-resource changes to the data event bus (ADR 2026-03-02-01)\n * when {@link DataStoreHistoricalArchiveProps.dataEventBus} is set.\n */\nexport class DataStoreHistoricalArchive extends Construct {\n public readonly archiveBucket: s3.Bucket;\n /**\n * Receives PutEvents payloads that still fail after in-Lambda retries when\n * {@link DataStoreHistoricalArchiveProps.dataEventBus} is configured.\n */\n public readonly putEventsFailureDlqBucket?: s3.Bucket;\n public readonly deliveryStream: kinesisfirehose.IDeliveryStream;\n public readonly transformFunction: NodejsFunction;\n\n constructor(\n scope: Construct,\n id: string,\n props: DataStoreHistoricalArchiveProps,\n ) {\n super(scope, id);\n\n this.archiveBucket = new s3.Bucket(this, \"ArchiveBucket\", {\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n removalPolicy: props.removalPolicy,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n versioned: true,\n });\n\n const putEventsFailureDlqBucket = props.dataEventBus\n ? new s3.Bucket(this, \"PutEventsFailureDlq\", {\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n removalPolicy: props.removalPolicy,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n versioned: false,\n })\n : undefined;\n if (putEventsFailureDlqBucket) {\n const appName = (OpenHiService.of(this) as OpenHiService).appName;\n Tags.of(putEventsFailureDlqBucket).add(\n openHiTagKey(appName, \"resource-role\"),\n \"dead-letter-queue\",\n );\n Tags.of(putEventsFailureDlqBucket).add(\n openHiTagKey(appName, \"pipeline\"),\n \"data-replication\",\n );\n }\n this.putEventsFailureDlqBucket = putEventsFailureDlqBucket;\n\n this.transformFunction = new NodejsFunction(this, \"FirehoseTransform\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n description:\n \"Firehose transform: filter CURRENT resource rows, S3 keys, EventBridge PutEvents\",\n environment:\n props.dataEventBus && putEventsFailureDlqBucket\n ? {\n DATA_EVENT_BUS_NAME: props.dataEventBus.eventBusName,\n DATA_STORE_PUT_EVENTS_DLQ_BUCKET:\n putEventsFailureDlqBucket.bucketName,\n }\n : undefined,\n bundling: {\n minify: true,\n sourceMap: false,\n },\n });\n\n props.dataEventBus?.grantPutEventsTo(this.transformFunction);\n putEventsFailureDlqBucket?.grantPut(this.transformFunction);\n\n const processor = new kinesisfirehose.LambdaFunctionProcessor(\n this.transformFunction,\n {\n bufferInterval: Duration.seconds(60),\n bufferSize: Size.mebibytes(3),\n retries: 3,\n },\n );\n\n const destination = new kinesisfirehose.S3Bucket(this.archiveBucket, {\n compression: kinesisfirehose.Compression.GZIP,\n bufferingInterval: Duration.seconds(300),\n // Firehose requires SizeInMBs ≥ 64 when dynamic partitioning is enabled.\n bufferingSize: Size.mebibytes(64),\n processors: [processor],\n errorOutputPrefix:\n \"errors/!{firehose:error-output-type}/!{timestamp:yyyy/MM/dd/HH}/\",\n loggingConfig: new kinesisfirehose.EnableLogging(),\n });\n\n this.deliveryStream = new kinesisfirehose.DeliveryStream(\n this,\n \"ArchiveDeliveryStream\",\n {\n deliveryStreamName: `openhi-dstore-arch-${props.stackHash}`,\n source: new kinesisfirehose.KinesisStreamSource(props.kinesisStream),\n destination,\n },\n );\n\n const cfn = this.deliveryStream.node\n .defaultChild as kinesisfirehose.CfnDeliveryStream;\n cfn.addPropertyOverride(\n \"ExtendedS3DestinationConfiguration.DynamicPartitioningConfiguration\",\n {\n Enabled: true,\n RetryOptions: { DurationInSeconds: 300 },\n },\n );\n cfn.addPropertyOverride(\n \"ExtendedS3DestinationConfiguration.Prefix\",\n \"!{partitionKeyFromLambda:tenantId}/!{partitionKeyFromLambda:workspaceId}/!{partitionKeyFromLambda:resourceType}/!{partitionKeyFromLambda:resourceId}/!{partitionKeyFromLambda:version}/\",\n );\n }\n}\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n AttributeType,\n BillingMode,\n ProjectionType,\n Table,\n TableProps,\n} from \"aws-cdk-lib/aws-dynamodb\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/dynamodb/dynamo-db-data-store.md\n */\n\n/**\n * DynamoDB table name for the data store. Used for cross-stack reference and\n * deterministic naming per branch. The table backs the app data store.\n */\nexport function getDynamoDbDataStoreTableName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `data-store-${stack.branchHash}`;\n}\n\nexport interface DynamoDbDataStoreProps extends Omit<\n TableProps,\n \"tableName\" | \"removalPolicy\"\n> {\n /**\n * Optional removal policy override. If not set, uses the service's default\n * removal policy (RETAIN for prod, DESTROY otherwise).\n */\n readonly removalPolicy?: RemovalPolicy;\n}\n\n/**\n * DynamoDB table implementing the single-table design for app data (FHIR\n * resources data plane and platform control plane), per planning ADR-011 and\n * DR-004.\n *\n * @see {@link https://github.com/codedrifters/openhi/blob/main/sites/www-docs/content/architecture/dynamodb-single-table-design.md | DynamoDB Single-Table Design}\n *\n * Primary key: PK (String), SK (String).\n *\n * GSIs:\n * - **GSI1 — Unified Sharded List** (`GSI1PK`/`GSI1SK`, INCLUDE projection per\n * DR-004). Primary list/lookup index for both data-plane FHIR resources and\n * control-plane entities (User, Tenant, Workspace, Membership, Role,\n * RoleAssignment, Configuration). PK shape:\n * `TID#<tid>#WID#<wid>#RT#<Type>#SHARD#<n>` with 4 shards\n * (`n = hash(id) mod 4`). SK shape per `extractSortKey`: labeled types use\n * `<normalizedLabel>#<id>`; unlabeled use `<ISO-8601 lastUpdated>#<id>`.\n * - **GSI2 — Sub-Lookup** (`GSI2PK`/`GSI2SK`, INCLUDE projection). Resolves\n * `UserEntity` from a Cognito `sub` for the Pre Token Generation Lambda.\n * PK shape: `USER#SUB#<cognitoSub>`. SK shape: `CURRENT`.\n *\n * For historical archive to S3, pass `kinesisStream` and `stream` (e.g.\n * `StreamViewType.NEW_AND_OLD_IMAGES`) on the table props per ADR 2026-03-11-02.\n */\nexport class DynamoDbDataStore extends Table {\n constructor(\n scope: Construct,\n id: string,\n props: DynamoDbDataStoreProps = {},\n ) {\n const service = OpenHiService.of(scope) as OpenHiService;\n\n super(scope, id, {\n ...props,\n tableName: getDynamoDbDataStoreTableName(scope),\n partitionKey: {\n name: \"PK\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"SK\",\n type: AttributeType.STRING,\n },\n billingMode: BillingMode.PAY_PER_REQUEST,\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n });\n\n // GSI1 — Unified Sharded List (data plane + control plane) per ADR-011 and DR-004.\n this.addGlobalSecondaryIndex({\n indexName: \"GSI1\",\n partitionKey: {\n name: \"GSI1PK\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"GSI1SK\",\n type: AttributeType.STRING,\n },\n projectionType: ProjectionType.INCLUDE,\n nonKeyAttributes: [\n \"id\",\n \"summary\",\n \"vid\",\n \"lastUpdated\",\n \"createdDate\",\n \"modifiedDate\",\n \"createdById\",\n \"modifiedById\",\n // ElectroDB filters every query result through `ownsItem`, which\n // verifies `__edb_e__` (entity name) and `__edb_v__` (version) match\n // the entity. Without these projected, every GSI1 query returns 0\n // results — list endpoints silently return empty bundles.\n \"__edb_e__\",\n \"__edb_v__\",\n ],\n });\n\n // GSI2 — Sub-Lookup: Cognito sub → UserEntity (Pre Token Generation Lambda).\n this.addGlobalSecondaryIndex({\n indexName: \"GSI2\",\n partitionKey: {\n name: \"GSI2PK\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"GSI2SK\",\n type: AttributeType.STRING,\n },\n projectionType: ProjectionType.INCLUDE,\n nonKeyAttributes: [\n \"id\",\n \"currentTenant\",\n \"currentWorkspace\",\n \"displayName\",\n // See GSI1 above: ElectroDB's `ownsItem` filter rejects items\n // without these, so any query against GSI2 returns 0 results\n // unless they're projected.\n \"__edb_e__\",\n \"__edb_v__\",\n ],\n });\n }\n}\n","import {\n WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH,\n WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR,\n} from \"@openhi/workflows\";\nimport { Annotations, RemovalPolicy } from \"aws-cdk-lib\";\nimport { AttributeType, BillingMode, Table } from \"aws-cdk-lib/aws-dynamodb\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Function } from \"aws-cdk-lib/aws-lambda\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService, type OpenHiServiceType } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/dynamodb/workflow-dedup-table.md\n */\n\n/**\n * Deterministic table name for the shared workflow dedup table.\n * Mirrors `getDynamoDbDataStoreTableName` naming: `workflow-dedup-${branchHash}`.\n */\nexport function getWorkflowDedupTableName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `workflow-dedup-${stack.branchHash}`;\n}\n\n/** Props for `WorkflowDedupTable`. */\nexport interface WorkflowDedupTableProps {\n /**\n * Optional removal policy override. Defaults to the service's default\n * (RETAIN for prod, DESTROY otherwise).\n */\n readonly removalPolicy?: RemovalPolicy;\n}\n\n/** Options for `WorkflowDedupTable.grantConsumer`. */\nexport interface GrantConsumerOptions {\n /**\n * Override the default TTL applied by the runtime client. The 14-day\n * default lives in `@openhi/workflows`; per-consumer overrides clamp\n * shorter per TR-015. Stored in the consumer's environment so the\n * `WorkflowDedupClient` factory can pick it up.\n */\n readonly defaultTtlSeconds?: number;\n}\n\n/**\n * Shared platform-level dedup table every retryable workflow consumer\n * dedupes against. Provisioned exactly once at the platform stack.\n *\n * Schema (per TR-015):\n * - Partition key `consumerName` (S)\n * - Sort key `sk` (S) — encodes `<eventId>#<attempt>`\n * - TTL attribute `expiresAt` (N, Unix epoch seconds)\n * - On-demand billing\n *\n * @see https://github.com/codedrifters/openhi-planning/blob/main/docs/src/content/docs/requirements/technical-requirements/TR-015-workflow-dedup-table.md\n */\nexport class WorkflowDedupTable extends Construct {\n /** SSM param name (short) used by `DiscoverableStringParameter` for the table name lookup. */\n public static readonly TABLE_NAME_SSM_PARAM_NAME =\n \"workflow-dedup-table-name\";\n /** SSM param name (short) used by `DiscoverableStringParameter` for the table ARN lookup. */\n public static readonly TABLE_ARN_SSM_PARAM_NAME = \"workflow-dedup-table-arn\";\n\n /** Cross-stack lookup for the table name. */\n public static tableNameFromLookup(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: WorkflowDedupTable.TABLE_NAME_SSM_PARAM_NAME,\n serviceType: WorkflowDedupTable.PUBLISHER_SERVICE_TYPE,\n });\n }\n\n /** Cross-stack lookup for the table ARN. */\n public static tableArnFromLookup(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: WorkflowDedupTable.TABLE_ARN_SSM_PARAM_NAME,\n serviceType: WorkflowDedupTable.PUBLISHER_SERVICE_TYPE,\n });\n }\n\n /**\n * Cross-stack equivalent of {@link grantConsumer}. Use when the dedup\n * table is on a different stack than the consumer Lambda — the\n * grant resolves the table name + ARN via SSM at synth time, so the\n * consumer stack does not pick up a CloudFormation export dependency\n * on the global stack.\n *\n * Inverts the singleton-guard semantics of `grantConsumer`: there is\n * no synth-time check that the same `consumerName` was registered\n * twice across stacks. Consumer names are agreed by convention\n * (see TR-015); double-registration is operator error caught at\n * design time, not synth time.\n */\n public static grantConsumerFromLookup(\n scope: Construct,\n fn: Function,\n consumerName: string,\n options: GrantConsumerOptions = {},\n ): void {\n WorkflowDedupTable.assertConsumerNameStatic(consumerName);\n const tableName = WorkflowDedupTable.tableNameFromLookup(scope);\n const tableArn = WorkflowDedupTable.tableArnFromLookup(scope);\n\n fn.addEnvironment(WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR, tableName);\n if (options.defaultTtlSeconds !== undefined) {\n fn.addEnvironment(\n \"OPENHI_WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS\",\n String(options.defaultTtlSeconds),\n );\n }\n\n fn.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n ],\n resources: [tableArn],\n conditions: {\n \"ForAllValues:StringEquals\": {\n \"dynamodb:LeadingKeys\": [consumerName],\n },\n },\n }),\n );\n }\n\n /**\n * Service-type the publishing stack runs under. The cross-stack lookups\n * pin to this value so consumer stacks on a different service-type\n * (e.g. `data`, `auth`) resolve the parameter at the publisher's SSM\n * path instead of their own. Typed against `OpenHiServiceType` so a\n * future rename of the literal triggers a compile error; not pulled\n * from `OpenHiGlobalService.SERVICE_TYPE` because\n * `OpenHiGlobalService` already imports `WorkflowDedupTable` — a\n * back-import would create a circular dependency.\n */\n private static readonly PUBLISHER_SERVICE_TYPE: OpenHiServiceType = \"global\";\n\n /**\n * Standalone consumer-name validator shared by the instance method\n * and `grantConsumerFromLookup` so the two grants enforce identical\n * invariants.\n */\n private static assertConsumerNameStatic(consumerName: string): void {\n if (consumerName.length === 0) {\n throw new WorkflowDedupConsumerNameInvalidError(\n \"consumerName must be non-empty.\",\n );\n }\n if (consumerName.length > WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {\n throw new WorkflowDedupConsumerNameInvalidError(\n `consumerName must be at most ${WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`,\n );\n }\n if (/\\s/.test(consumerName)) {\n throw new WorkflowDedupConsumerNameInvalidError(\n \"consumerName must not contain whitespace.\",\n );\n }\n }\n\n /** The underlying DynamoDB table. */\n public readonly table: Table;\n\n private readonly registeredConsumers = new Set<string>();\n\n constructor(\n scope: Construct,\n id: string,\n props: WorkflowDedupTableProps = {},\n ) {\n super(scope, id);\n\n const service = OpenHiService.of(scope) as OpenHiService;\n\n // Synth-time singleton guard: refuse a second WorkflowDedupTable in\n // the same owning service (one stack per deploy target). The check is\n // intentionally scoped to the host service rather than to the CDK\n // app root: openhi/global/src/app.ts maps over `app.environments` and\n // instantiates one `OpenHiGlobalService` per environment, and each of\n // those is a separate deployment target that owns its own dedup\n // table per TR-015.\n const others = service.node\n .findAll()\n .filter(\n (c): c is WorkflowDedupTable =>\n c instanceof WorkflowDedupTable && c !== this,\n );\n if (others.length > 0) {\n throw new WorkflowDedupTableDuplicateError(\n `WorkflowDedupTable already exists at ${others[0].node.path}; ` +\n \"only one shared dedup table is allowed per service stack (TR-015).\",\n );\n }\n\n this.table = new Table(this, \"Table\", {\n tableName: getWorkflowDedupTableName(scope),\n partitionKey: {\n name: \"consumerName\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"sk\",\n type: AttributeType.STRING,\n },\n billingMode: BillingMode.PAY_PER_REQUEST,\n timeToLiveAttribute: \"expiresAt\",\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n });\n\n // Publish the table name and ARN so consumer stacks can discover\n // them without a cross-stack prop dependency.\n new DiscoverableStringParameter(this, \"table-name-param\", {\n ssmParamName: WorkflowDedupTable.TABLE_NAME_SSM_PARAM_NAME,\n stringValue: this.table.tableName,\n });\n new DiscoverableStringParameter(this, \"table-arn-param\", {\n ssmParamName: WorkflowDedupTable.TABLE_ARN_SSM_PARAM_NAME,\n stringValue: this.table.tableArn,\n });\n }\n\n /**\n * Wire a Lambda consumer to this table. Injects the table-name env var\n * so the runtime `WorkflowDedupClient` can resolve it, then attaches a\n * per-consumer IAM grant scoped by `dynamodb:LeadingKeys` so the\n * consumer can only read/write its own partition.\n */\n public grantConsumer(\n fn: Function,\n consumerName: string,\n options: GrantConsumerOptions = {},\n ): void {\n this.assertConsumerName(consumerName);\n\n if (this.registeredConsumers.has(consumerName)) {\n Annotations.of(this).addWarning(\n `WorkflowDedupTable: consumerName \"${consumerName}\" registered more than once; ` +\n \"subsequent grantConsumer calls add policy statements but do not re-inject the env var.\",\n );\n }\n this.registeredConsumers.add(consumerName);\n\n fn.addEnvironment(WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR, this.table.tableName);\n if (options.defaultTtlSeconds !== undefined) {\n fn.addEnvironment(\n \"OPENHI_WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS\",\n String(options.defaultTtlSeconds),\n );\n }\n\n fn.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n ],\n resources: [this.table.tableArn],\n conditions: {\n \"ForAllValues:StringEquals\": {\n \"dynamodb:LeadingKeys\": [consumerName],\n },\n },\n }),\n );\n }\n\n private assertConsumerName(consumerName: string): void {\n WorkflowDedupTable.assertConsumerNameStatic(consumerName);\n }\n}\n\n/** Thrown when a second `WorkflowDedupTable` is instantiated in the same app. */\nexport class WorkflowDedupTableDuplicateError extends Error {\n /** @param message - human-readable description of the duplicate. */\n constructor(message: string) {\n super(message);\n this.name = \"WorkflowDedupTableDuplicateError\";\n }\n}\n\n/** Thrown when a consumerName violates the TR-015 invariants. */\nexport class WorkflowDedupConsumerNameInvalidError extends Error {\n /** @param message - human-readable description of the invariant violation. */\n constructor(message: string) {\n super(message);\n this.name = \"WorkflowDedupConsumerNameInvalidError\";\n }\n}\n","import { Duration, Stack } from \"aws-cdk-lib\";\nimport { Archive, EventBus, EventBusProps } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/event-bridge/data-event-bus.md\n */\n\n/** Default retention for the archive — 7 days. */\nconst DEFAULT_ARCHIVE_RETENTION = Duration.days(7);\n\nexport interface DataEventBusOptions {\n /**\n * Retention for the bus's event archive. Defaults to 7 days. Pass\n * `Duration.days(0)` (or omit and override the archive in a subclass)\n * to disable archiving entirely.\n */\n readonly archiveRetention?: Duration;\n}\n\nexport class DataEventBus extends EventBus {\n /*****************************************************************************\n *\n * Return a name for this EventBus based on the stack environment hash. This\n * name is common across all stacks since it's using the environment hash in\n * it's name.\n *\n ****************************************************************************/\n\n public static getEventBusName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `datav1${stack.branchHash}`;\n }\n\n /**\n * Replay archive of every event written to this bus, retained for the\n * configured TTL (default 7 days). Enables EventBridge `StartReplay`\n * for incident response and ad-hoc backfill.\n *\n * Named `replayArchive` rather than `archive` to avoid shadowing the\n * inherited `EventBus.archive(id, options)` instance method.\n */\n public readonly replayArchive: Archive;\n\n constructor(\n scope: Construct,\n props: (EventBusProps & DataEventBusOptions) | undefined = undefined,\n ) {\n const { archiveRetention, ...busProps } = props ?? {};\n super(scope, \"data-event-bus-v1\", {\n ...busProps,\n eventBusName: DataEventBus.getEventBusName(scope),\n });\n\n // Archive everything on this bus. EventBridge archive requires an\n // eventPattern; the `account` filter scoped to this stack's account is\n // the canonical \"match every event on this bus\" form (the bus only\n // accepts events from within the account, so this is a no-op filter that\n // satisfies the API contract).\n this.replayArchive = new Archive(this, \"Archive\", {\n sourceEventBus: this,\n archiveName: `${DataEventBus.getEventBusName(scope)}-archive`,\n description:\n \"Replay archive for the OpenHI data event bus (data-store change notifications).\",\n eventPattern: { account: [Stack.of(this).account] },\n retention: archiveRetention ?? DEFAULT_ARCHIVE_RETENTION,\n });\n }\n}\n","import { EventBus, EventBusProps } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/event-bridge/ops-event-bus.md\n */\n\nexport class OpsEventBus extends EventBus {\n /*****************************************************************************\n *\n * Return a name for this EventBus based on the stack environment hash. This\n * name is common across all stacks since it's using the environment hash in\n * it's name.\n *\n ****************************************************************************/\n\n public static getEventBusName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `opsv1${stack.branchHash}`;\n }\n\n constructor(scope: Construct, props?: EventBusProps) {\n super(scope, \"ops-event-bus-v1\", {\n ...props,\n eventBusName: OpsEventBus.getEventBusName(scope),\n });\n }\n}\n","import { EventBus, EventBusProps } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/event-bridge/control-event-bus.md\n */\n\nexport class ControlEventBus extends EventBus {\n /*****************************************************************************\n *\n * Return a name for this EventBus based on the stack environment hash. This\n * name is common across all stacks since it's using the environment hash in\n * its name.\n *\n ****************************************************************************/\n\n public static getEventBusName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `controlv1${stack.branchHash}`;\n }\n\n constructor(scope: Construct, props?: EventBusProps) {\n super(scope, \"control-event-bus-v1\", {\n ...props,\n eventBusName: ControlEventBus.getEventBusName(scope),\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, RemovalPolicy, Stack } from \"aws-cdk-lib\";\nimport * as ec2 from \"aws-cdk-lib/aws-ec2\";\nimport * as kinesis from \"aws-cdk-lib/aws-kinesis\";\nimport { Runtime, StartingPosition } from \"aws-cdk-lib/aws-lambda\";\nimport { KinesisEventSource } from \"aws-cdk-lib/aws-lambda-event-sources\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport * as rds from \"aws-cdk-lib/aws-rds\";\nimport { Construct } from \"constructs\";\nimport { DiscoverableStringParameter } from \"../ssm/discoverable-string-parameter\";\n\nconst HANDLER_NAME = \"data-store-postgres-replication.handler.js\";\nconst DEFAULT_DATABASE_NAME = \"openhi\";\nconst SCHEMA_NAME_PATTERN = /^[a-z_][a-z0-9_]{0,62}$/;\n\n/**\n * SSM parameter names that publish the Postgres replica's coordinates so other\n * stacks (notably the REST API stack) can discover them without a direct CDK\n * cross-stack reference. The schema name is intentionally NOT published — it\n * is a deterministic function of `branchHash` and consumers compute it locally\n * via {@link getPostgresReplicaSchemaName}.\n */\nexport const POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME =\n \"POSTGRES_REPLICA_CLUSTER_ARN\";\nexport const POSTGRES_REPLICA_SECRET_ARN_SSM_NAME =\n \"POSTGRES_REPLICA_SECRET_ARN\";\nexport const POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME =\n \"POSTGRES_REPLICA_DATABASE_NAME\";\n\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n return path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\n/**\n * Derive the per-branch Postgres schema name from a branch hash. The `b_`\n * prefix guarantees a leading letter (Postgres identifier rule). Branch hashes\n * are 6 hex chars from {@link OpenHiService.branchHash} so the resulting\n * `b_xxxxxx` is well within the 63-byte identifier limit.\n */\nexport function getPostgresReplicaSchemaName(branchHash: string): string {\n const candidate = `b_${branchHash.toLowerCase()}`;\n if (!SCHEMA_NAME_PATTERN.test(candidate)) {\n throw new Error(\n `Branch hash ${JSON.stringify(branchHash)} produces an invalid Postgres ` +\n `schema name ${JSON.stringify(candidate)}; expected /[a-z_][a-z0-9_]{0,62}/.`,\n );\n }\n return candidate;\n}\n\nexport interface DataStorePostgresReplicaProps {\n /**\n * Kinesis stream that receives DynamoDB item-level changes (the same stream\n * that backs {@link DataStoreHistoricalArchive}). The replication Lambda is\n * registered as a parallel consumer.\n */\n readonly kinesisStream: kinesis.IStream;\n /**\n * Removal policy for the cluster, secret, and dependent resources.\n */\n readonly removalPolicy: RemovalPolicy;\n /**\n * Short hash unique to the stack — used in the cluster identifier.\n */\n readonly stackHash: string;\n /**\n * Short hash unique to the branch — used to derive the per-branch schema\n * name (`b_<branchHash>`) inside the Postgres database.\n */\n readonly branchHash: string;\n /**\n * Optional VPC override. If absent, the construct creates a minimal isolated\n * VPC (2 AZs, no NAT gateways) just for the cluster and replication Lambda.\n */\n readonly vpc?: ec2.IVpc;\n /**\n * Optional database name override.\n * @default \"openhi\"\n */\n readonly databaseName?: string;\n /**\n * Aurora Serverless v2 minimum capacity in ACUs. Defaults to 1 so the\n * writer stays warm — avoids the ~10–20s scale-up wait that a cold\n * (0 ACU) cluster imposes on the next request. Set explicitly to 0 to\n * opt back into scale-to-zero if idle cost becomes the dominant concern.\n */\n readonly minCapacity?: number;\n /**\n * Aurora Serverless v2 maximum capacity in ACUs. Defaults to 2 — adequate\n * for the PoC's replication-only workload.\n */\n readonly maxCapacity?: number;\n}\n\n/**\n * DynamoDB change stream → Postgres replication tier (ADR 2026-04-17-01,\n * phase 1). Provisions an Aurora Serverless v2 PostgreSQL cluster and a\n * Lambda consumer on the existing change-stream that projects each current\n * FHIR resource into a JSONB `resources` table under a per-branch schema.\n *\n * Phase 1 is replication-only; query routing and SearchParameter-specific\n * indexes are intentionally deferred. Per-branch *clusters* (rather than the\n * shared cluster suggested by the ADR) are an explicit PoC simplification —\n * see the ADR's \"Operational notes\" section for the long-term direction.\n *\n * @see sites/www-docs/content/architecture/adr/2026-04-17-01-ad-hoc-query-support-fhir-api.md\n */\nexport class DataStorePostgresReplica extends Construct {\n /**\n * Resolve the cluster ARN published by an upstream {@link DataStorePostgresReplica}.\n * Use from any stack that needs to grant `rds-data:ExecuteStatement` against\n * the cluster.\n */\n public static clusterArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME,\n serviceType: \"data\",\n });\n }\n\n /**\n * Resolve the credentials secret ARN published by an upstream\n * {@link DataStorePostgresReplica}. Use from any stack that needs to grant\n * `secretsmanager:GetSecretValue` against the secret.\n */\n public static secretArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: POSTGRES_REPLICA_SECRET_ARN_SSM_NAME,\n serviceType: \"data\",\n });\n }\n\n /**\n * Resolve the database name published by an upstream\n * {@link DataStorePostgresReplica}.\n */\n public static databaseNameFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME,\n serviceType: \"data\",\n });\n }\n\n public readonly vpc: ec2.IVpc;\n public readonly cluster: rds.DatabaseCluster;\n public readonly replicationFunction: NodejsFunction;\n public readonly databaseName: string;\n public readonly schemaName: string;\n\n constructor(\n scope: Construct,\n id: string,\n props: DataStorePostgresReplicaProps,\n ) {\n super(scope, id);\n\n this.databaseName = props.databaseName ?? DEFAULT_DATABASE_NAME;\n this.schemaName = getPostgresReplicaSchemaName(props.branchHash);\n\n // Pass explicit AZ names (derived from the stack region) instead of using\n // `maxAzs`, which triggers a CDK availability-zones context lookup. CI's\n // synth step doesn't have full deploy-account creds, so an unresolved AZ\n // lookup gets recorded as \"missing\" in the cdk.out manifest and the deploy\n // step then refuses to proceed. AWS region AZ names follow a stable\n // `<region>a/b/c…` pattern across all current commercial regions.\n const region = Stack.of(this).region;\n const ownsVpc = props.vpc === undefined;\n this.vpc =\n props.vpc ??\n new ec2.Vpc(this, \"Vpc\", {\n availabilityZones: [`${region}a`, `${region}b`],\n natGateways: 0,\n subnetConfiguration: [\n {\n name: \"isolated\",\n subnetType: ec2.SubnetType.PRIVATE_ISOLATED,\n cidrMask: 24,\n },\n ],\n });\n\n // Secrets Manager interface endpoint. The replication Lambda runs in\n // PRIVATE_ISOLATED subnets with no NAT (per the cost-of-cluster trade-off\n // in this construct), so every AWS API call from the function code needs\n // a corresponding VPC endpoint. The handler's only AWS SDK call on the\n // cold-start path is `GetSecretValue` to fetch the cluster credentials —\n // without this endpoint, that call hangs at TCP connect and the Lambda\n // returns `ETIMEDOUT` for every Kinesis batch.\n //\n // `privateDnsEnabled: true` is the bit that lets the function resolve\n // the standard `secretsmanager.<region>.amazonaws.com` DNS name to the\n // endpoint's private IP — without it the SDK would need a custom endpoint\n // override, which we'd rather not bake into the handler.\n //\n // Only provisioned when this construct owns its VPC. If a VPC is passed\n // in via `props.vpc`, the caller is expected to add the endpoint there\n // (or supply a NAT path) so we don't accidentally double-provision the\n // endpoint on a shared VPC.\n if (ownsVpc) {\n new ec2.InterfaceVpcEndpoint(this, \"SecretsManagerEndpoint\", {\n vpc: this.vpc,\n service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,\n subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },\n privateDnsEnabled: true,\n });\n }\n\n this.cluster = new rds.DatabaseCluster(this, \"Cluster\", {\n clusterIdentifier: `openhi-dstore-pg-${props.stackHash}`,\n engine: rds.DatabaseClusterEngine.auroraPostgres({\n version: rds.AuroraPostgresEngineVersion.VER_16_4,\n }),\n vpc: this.vpc,\n vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },\n writer: rds.ClusterInstance.serverlessV2(\"writer\"),\n serverlessV2MinCapacity: props.minCapacity ?? 1,\n serverlessV2MaxCapacity: props.maxCapacity ?? 2,\n defaultDatabaseName: this.databaseName,\n credentials: rds.Credentials.fromGeneratedSecret(\"openhi_admin\"),\n storageEncrypted: true,\n removalPolicy: props.removalPolicy,\n // Phase 2 of ADR 2026-04-17-01: the REST API Lambda queries Postgres\n // via the RDS Data API (HTTPS) so it can stay out of the cluster's VPC.\n // Direct `pg` from the replication Lambda continues to work in parallel.\n enableDataApi: true,\n });\n\n this.publishCoordinatesToSsm();\n\n this.replicationFunction = new NodejsFunction(this, \"ReplicationFunction\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n vpc: this.vpc,\n vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },\n description:\n \"Replicates DynamoDB current-resource changes into the Postgres `resources` JSONB table (ADR 2026-04-17-01).\",\n environment: {\n OPENHI_PG_HOST: this.cluster.clusterEndpoint.hostname,\n OPENHI_PG_PORT: this.cluster.clusterEndpoint.port.toString(),\n OPENHI_PG_DATABASE: this.databaseName,\n OPENHI_PG_SCHEMA: this.schemaName,\n OPENHI_PG_SECRET_ARN: this.cluster.secret!.secretArn,\n OPENHI_PG_SSL: \"true\",\n },\n bundling: {\n minify: true,\n sourceMap: false,\n // pg's conditional optional deps (pg-native, pg-cloudflare) are\n // marked external so esbuild does not try to resolve them — pg's\n // runtime code wraps the requires in try/catch and falls back to\n // the pure-JS client when they are not present.\n externalModules: [\"pg-native\", \"pg-cloudflare\"],\n },\n });\n\n this.cluster.secret!.grantRead(this.replicationFunction);\n this.cluster.connections.allowDefaultPortFrom(this.replicationFunction);\n\n this.replicationFunction.addEventSource(\n new KinesisEventSource(props.kinesisStream, {\n startingPosition: StartingPosition.LATEST,\n batchSize: 100,\n maxBatchingWindow: Duration.seconds(5),\n retryAttempts: 10,\n bisectBatchOnError: true,\n parallelizationFactor: 2,\n reportBatchItemFailures: true,\n }),\n );\n }\n\n /**\n * Publishes the cluster ARN, secret ARN, and database name as discoverable\n * SSM parameters so the REST API stack (and any future read-side consumer)\n * can wire RDS Data API access without a direct CDK cross-stack reference.\n */\n private publishCoordinatesToSsm(): void {\n new DiscoverableStringParameter(this, \"cluster-arn-param\", {\n ssmParamName: POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME,\n stringValue: this.cluster.clusterArn,\n description:\n \"ARN of the Aurora Serverless v2 cluster backing the Postgres replication tier (ADR 2026-04-17-01).\",\n });\n new DiscoverableStringParameter(this, \"secret-arn-param\", {\n ssmParamName: POSTGRES_REPLICA_SECRET_ARN_SSM_NAME,\n stringValue: this.cluster.secret!.secretArn,\n description:\n \"ARN of the Secrets Manager secret with credentials for the Postgres replication tier.\",\n });\n new DiscoverableStringParameter(this, \"database-name-param\", {\n ssmParamName: POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME,\n stringValue: this.databaseName,\n description: \"Database name within the Postgres replication cluster.\",\n });\n }\n}\n","import { Duration } from \"aws-cdk-lib\";\nimport {\n HostedZone,\n HostedZoneProps,\n IHostedZone,\n NsRecord,\n} from \"aws-cdk-lib/aws-route53\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/route-53/child-hosted-zone.md\n */\n\nexport interface ChildHostedZoneProps extends HostedZoneProps {\n /**\n * The root zone we will attach this sub-zone to.\n */\n readonly parentHostedZone: IHostedZone;\n}\n\nexport class ChildHostedZone extends HostedZone {\n /**\n * Used when storing the child zone ID in SSM. Use {@link OpenHiGlobalService.childHostedZoneFromConstruct} to look up.\n */\n public static readonly SSM_PARAM_NAME = \"CHILDHOSTEDZONE\";\n\n constructor(scope: Construct, id: string, props: ChildHostedZoneProps) {\n super(scope, id, { ...props });\n\n /**\n * Chain the child zone to the parent zone using NS record.\n */\n new NsRecord(this, \"child-ns-record\", {\n zone: props.parentHostedZone,\n recordName: this.zoneName,\n values: this.hostedZoneNameServers || [],\n ttl: Duration.minutes(5),\n });\n }\n}\n","import { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/route-53/root-hosted-zone.md\n */\n\n/**\n * Placeholder for root hosted zone. Use {@link OpenHiGlobalService.rootHostedZoneFromConstruct}\n * to obtain an IHostedZone from attributes (e.g. from config). The root zone is always\n * created manually and imported via config.\n */\nexport class RootHostedZone extends Construct {}\n","import { Distribution } from \"aws-cdk-lib/aws-cloudfront\";\nimport {\n ARecord,\n type IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { Construct } from \"constructs\";\nimport { StaticHosting, STATIC_HOSTING_SERVICE_TYPE } from \"./static-hosting\";\nimport { OpenHiService } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/static-hosting/per-branch-hostname.md\n */\n\n/**\n * Props for the PerBranchHostname construct.\n */\nexport interface PerBranchHostnameProps {\n /**\n * Fully-qualified hostname to alias to the release-branch distribution\n * (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`). Used as the ARecord's\n * `recordName`.\n */\n readonly hostname: string;\n\n /**\n * Hosted zone the ARecord is created in. The zone's `zoneName` must be\n * a suffix of `hostname`.\n */\n readonly hostedZone: IHostedZone;\n\n /**\n * Service type used to address the release-branch StaticHosting SSM\n * parameters. Defaults to {@link STATIC_HOSTING_SERVICE_TYPE} so the\n * construct resolves the same `\"website\"` namespace `StaticHosting`\n * writes to.\n *\n * @default STATIC_HOSTING_SERVICE_TYPE (\"website\")\n */\n readonly serviceType?: string;\n}\n\n/**\n * Creates a single Route53 `ARecord` that aliases a per-PR hostname (e.g.\n * `admin-feat-1093-patient-migration.dev.openhi.org`) to the release-branch CloudFront\n * distribution. The distribution domain and ID are resolved from SSM\n * parameters published by {@link StaticHosting}, addressed against the\n * containing service's {@link OpenHiService.releaseBranchHash} so a\n * feature-branch stack reads the values the release-branch stack wrote.\n *\n * No per-PR CloudFront distribution is created — the release-branch\n * distribution's wildcard SAN (`*.dev.openhi.org`) covers every per-PR\n * hostname.\n */\nexport class PerBranchHostname extends Construct {\n public readonly record: ARecord;\n\n constructor(scope: Construct, id: string, props: PerBranchHostnameProps) {\n super(scope, id);\n\n const stack = OpenHiService.of(scope) as OpenHiService;\n const serviceType = props.serviceType ?? STATIC_HOSTING_SERVICE_TYPE;\n\n // Resolve the release-branch distribution from SSM. The parameters are\n // published by StaticHosting on the release-branch deploy; on every\n // other branch the values are read cross-namespace via\n // releaseBranchHash. Same pattern as\n // OpenHiWebsiteService.resolveStaticHostingBucket.\n const distributionDomain = DiscoverableStringParameter.valueForLookupName(\n this,\n {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_DOMAIN,\n serviceType,\n branchHash: stack.releaseBranchHash,\n },\n );\n const distributionId = DiscoverableStringParameter.valueForLookupName(\n this,\n {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ID,\n serviceType,\n branchHash: stack.releaseBranchHash,\n },\n );\n\n const distribution = Distribution.fromDistributionAttributes(\n this,\n \"imported-distribution\",\n {\n domainName: distributionDomain,\n distributionId,\n },\n );\n\n // Embed the hostname in the construct id so a rename produces a\n // CloudFormation Add + Remove pair rather than an in-place\n // Replacement on AWS::Route53::RecordSet.Name (which is\n // UpdateRequiresReplacement). Avoids the TtyNotAttached error CDK\n // raises on `--no-rollback` deploys in CI when the hostname\n // changes — mirrors the StaticHosting ARecord fix from #1131.\n this.record = new ARecord(this, `alias-record-${props.hostname}`, {\n zone: props.hostedZone,\n recordName: props.hostname,\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ICertificate } from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n type BehaviorOptions,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n type CachePolicyProps,\n CacheQueryStringBehavior,\n Distribution,\n type DistributionProps,\n Function as CloudFrontFunction,\n FunctionCode,\n FunctionEventType,\n type IOrigin,\n LambdaEdgeEventType,\n OriginProtocolPolicy,\n OriginRequestPolicy,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { HttpOrigin, S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n type IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { Bucket, type BucketProps, type IBucket } from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\nimport type { HostingMode } from \"./static-hosting.viewer-request-handler\";\nimport { OpenHiService } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/static-hosting/static-hosting.md\n */\n\n/**\n * Service type for the website service. Used in SSM parameter paths and by\n * OpenHiWebsiteService for fromConstruct() lookups.\n */\nexport const STATIC_HOSTING_SERVICE_TYPE = \"website\";\n\n/**\n * Shared prefix for every per-PR preview hostname / S3 key. Per-PR\n * uploads land under `admin-<branch-prefix>.<zone>/...` and the\n * lifecycle rule below expires every object whose key starts with\n * `admin-`. Release-branch content lands under `admin.<zone>/...`\n * (note byte 5 is `.`, not `-`); S3 prefix matching is byte-exact from\n * byte 0, so `admin-` and `admin.` are disjoint — the rule never\n * matches release content. The constant is locked to the admin-console\n * domain prefix because no other website service currently consumes\n * per-PR previews; parameterize on `domainPrefix` when a second\n * consumer appears.\n */\nexport const PER_BRANCH_PREVIEW_PREFIX = \"admin-\";\n\n/**\n * Default TTL applied to objects matching the `prefixPattern` lifecycle\n * rule when `enablePreviewLifecycle` is `true` and `previewExpiration`\n * is omitted. Per-PR preview content is small and short-lived; 14 days\n * is long enough that a forgotten preview can still be opened during a\n * code review and short enough that abandoned content does not\n * accumulate.\n */\nexport const DEFAULT_PREVIEW_EXPIRATION_DAYS = 14;\n\n/**\n * Props for the StaticHosting construct.\n */\nexport interface StaticHostingProps {\n /**\n * Optional S3 bucket props. Bucket name must not be set statically.\n */\n readonly bucketProps?: Omit<BucketProps, \"bucketName\">;\n\n /**\n * Optional CloudFront distribution props. Defaults wire a custom cache\n * policy (60s/300s with gzip+brotli), `REDIRECT_TO_HTTPS`, and\n * `ALLOW_GET_HEAD_OPTIONS` on the default behavior; overrides apply on top.\n */\n readonly distributionProps?: Omit<\n DistributionProps,\n \"defaultBehavior\" | \"defaultRootObject\"\n >;\n\n /**\n * Optional cache policy overrides. Defaults: `defaultTtl=60s`, `maxTtl=300s`,\n * `minTtl=0s`, gzip+brotli enabled, no headers/cookies/query strings cached.\n */\n readonly cachePolicyProps?: Omit<CachePolicyProps, \"cachePolicyName\">;\n\n /**\n * Wildcard certificate to attach to the CloudFront distribution. When\n * supplied together with `hostedZone` and `domainNames`, CloudFront serves\n * the listed domains and Route53 ARecords are created in the zone.\n *\n * @default - no custom certificate; CloudFront default domain is served\n */\n readonly certificate?: ICertificate;\n\n /**\n * Hosted zone to create Route53 ARecords in. Required together with\n * `certificate` and `domainNames` to attach a custom domain.\n */\n readonly hostedZone?: IHostedZone;\n\n /**\n * Domain names to attach to the CloudFront distribution. Each name also\n * gets an ARecord in `hostedZone`, except for wildcard entries\n * (e.g. `*.dev.openhi.org`) which are added as CloudFront alternate\n * domain names (so the cert covers them) but skipped by the ARecord\n * loop — Route53 cannot create an `A` record at a wildcard apex, and\n * per-host ARecords are created later by downstream stacks.\n */\n readonly domainNames?: ReadonlyArray<string>;\n\n /**\n * Selects how path-like URIs are rewritten by the viewer-request\n * Lambda@Edge handler.\n *\n * - `spa` (default): path-like URIs rewrite to `/index.html`.\n * - `static`: path-like URIs append `/index.html`.\n *\n * @default \"spa\"\n */\n readonly hostingMode?: HostingMode;\n\n /**\n * Service type for SSM parameter paths.\n *\n * @default STATIC_HOSTING_SERVICE_TYPE (\"website\")\n */\n readonly serviceType?: string;\n\n /**\n * Optional human-readable description used in distribution comment and\n * SSM parameter descriptions.\n */\n readonly description?: string;\n\n /**\n * When supplied, the distribution proxies API traffic to the supplied\n * REST API custom domain (e.g. `api.example.com`). Three CloudFront\n * behaviors are added — `/config.json`, `/api/control/runtime-config`,\n * and `/api/*` — and each is wired with two edge stages so per-PR\n * websites reach their own per-PR API gateway:\n *\n * 1. **Viewer-request CloudFront Function** copies the viewer `Host`\n * header into `x-viewer-host` before CloudFront strips `Host` per\n * `ALL_VIEWER_EXCEPT_HOST_HEADER`. The `/config.json` function\n * additionally rewrites the URI to the configured\n * `runtimeConfigPath` (defaults to `/control/runtime-config`).\n * 2. **Origin-request Lambda@Edge** reads `x-viewer-host`, computes\n * the matching API host by swapping `hostMapping.viewerPrefix` for\n * `hostMapping.apiPrefix` at the start of the host, and overrides\n * both `request.origin.custom.domainName` and the upstream `Host`\n * header so the upstream's custom-domain mapping resolves to the\n * correct per-PR stack.\n *\n * `runtimeConfigPath` and `hostMapping` default to the OpenHI\n * admin-console / REST API values; downstream consumers (e.g. a\n * marketing site with its own bootstrap path and origin pair) can\n * override them per-site without touching the construct.\n *\n * Behavior cache keys:\n * - `/config.json` and `/api/control/runtime-config` share a cache\n * policy whose cache key includes the `v` query string AND the\n * `Host` header. The `Host` partition prevents PR-A's runtime\n * config from being served from PR-B's cache slot.\n * - `/api/*` uses `CachePolicy.CACHING_DISABLED`, so no cache-key\n * partitioning is needed.\n *\n * None of these behaviors are wired through the default-behavior\n * viewer-request edge Lambda — SPA path rewriting only applies to\n * the default S3 origin.\n *\n * @default - no REST API proxy; the distribution serves S3 only\n */\n readonly restApi?: {\n /**\n * REST API custom-domain hostname (no scheme — e.g. `api.example.com`).\n */\n readonly domainName: string;\n\n /**\n * Default / max TTL for the cached `/api/control/runtime-config` response.\n *\n * @default 5 minutes default, 1 hour max\n */\n readonly runtimeConfigCacheTtl?: {\n readonly defaultTtl?: Duration;\n readonly maxTtl?: Duration;\n };\n\n /**\n * Path the viewer-request CloudFront Function rewrites `/config.json`\n * to on the REST API origin. Override when a downstream consumer\n * bootstraps its SPA from a different endpoint.\n *\n * @default \"/control/runtime-config\" — the OpenHI admin-console\n * runtime-config endpoint.\n */\n readonly runtimeConfigPath?: string;\n\n /**\n * Host-prefix substitution applied by the origin-request Lambda@Edge.\n * `viewerPrefix` is matched at the start of the viewer's `Host`\n * header; when it matches, the upstream `Host` (and the custom\n * origin's `domainName`) becomes `apiPrefix + viewerHost.slice(viewerPrefix.length)`.\n *\n * The prefixes are baked into the Lambda@Edge bundle via esbuild\n * `define` at synth time (Lambda@Edge forbids runtime env vars), so\n * a change requires re-deploying the website stack.\n *\n * @default `{ viewerPrefix: \"admin\", apiPrefix: \"api\" }` — maps\n * `admin[-<branch>].<zone>` -> `api[-<branch>].<zone>` for the\n * OpenHI admin-console / REST API pair.\n */\n readonly hostMapping?: {\n readonly viewerPrefix: string;\n readonly apiPrefix: string;\n };\n };\n\n /**\n * S3 key prefix that the per-PR preview lifecycle rule matches. Must\n * be the same value the per-PR deployment construct writes under\n * (see `PER_BRANCH_PREVIEW_PREFIX`). Required even when\n * `enablePreviewLifecycle` is `false` so the contract between the\n * deployment side and the lifecycle side is single-sourced.\n */\n readonly prefixPattern: string;\n\n /**\n * When `true`, add an S3 lifecycle rule to the bucket that expires\n * objects matching `prefixPattern` after `previewExpiration`. **No\n * default** — every caller must make the stage decision explicitly\n * because adding the rule in production could silently delete real\n * content if the prefix ever overlapped with release content.\n *\n * Callers gate this against the stage (e.g.\n * `ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD`) before passing\n * the prop.\n */\n readonly enablePreviewLifecycle: boolean;\n\n /**\n * TTL applied to objects under `prefixPattern` when the lifecycle\n * rule is created.\n *\n * @default Duration.days(14)\n */\n readonly previewExpiration?: Duration;\n}\n\n/**\n * Static hosting: S3 bucket (private) + CloudFront distribution with Origin\n * Access Control (OAC) + Lambda@Edge viewer-request handler. Publishes\n * bucket ARN, distribution ARN, distribution domain, and distribution ID\n * via DiscoverableStringParameter for cross-stack lookup.\n */\nexport class StaticHosting extends Construct {\n /**\n * SSM parameter name for the S3 bucket ARN.\n */\n public static readonly SSM_PARAM_NAME_BUCKET_ARN =\n \"STATIC_HOSTING_BUCKET_ARN\";\n\n /**\n * SSM parameter name for the CloudFront distribution ARN.\n */\n public static readonly SSM_PARAM_NAME_DISTRIBUTION_ARN =\n \"STATIC_HOSTING_DISTRIBUTION_ARN\";\n\n /**\n * SSM parameter name for the CloudFront distribution domain\n * (e.g. dXXXXX.cloudfront.net).\n */\n public static readonly SSM_PARAM_NAME_DISTRIBUTION_DOMAIN =\n \"STATIC_HOSTING_DISTRIBUTION_DOMAIN\";\n\n /**\n * SSM parameter name for the CloudFront distribution ID.\n */\n public static readonly SSM_PARAM_NAME_DISTRIBUTION_ID =\n \"STATIC_HOSTING_DISTRIBUTION_ID\";\n\n /**\n * Returns true when `domainName` begins with a wildcard label (`*.`),\n * indicating it is an alt-name on the CloudFront cert but cannot be\n * created as a Route53 ARecord.\n */\n private static isWildcardDomain(domainName: string): boolean {\n return domainName.startsWith(\"*.\");\n }\n\n public readonly bucket: IBucket;\n public readonly distribution: Distribution;\n public readonly viewerRequestHandler: NodejsFunction;\n /**\n * Viewer-request CloudFront Function attached to the `/config.json`\n * behavior. Rewrites the URI to `/control/runtime-config` and copies\n * the viewer `Host` header into `x-viewer-host` so the origin-request\n * Lambda@Edge can pick the matching per-PR API origin. Only present\n * when the `restApi` prop is supplied.\n */\n public readonly configJsonRewriteFunction?: CloudFrontFunction;\n /**\n * Viewer-request CloudFront Function attached to the `/api/*`\n * behavior. Copies the viewer `Host` header into `x-viewer-host` so\n * the origin-request Lambda@Edge can route to the matching per-PR\n * API origin (the `ALL_VIEWER_EXCEPT_HOST_HEADER` origin-request\n * policy strips the literal `Host` header). Only present when the\n * `restApi` prop is supplied.\n */\n public readonly hostCopyFunction?: CloudFrontFunction;\n /**\n * Origin-request Lambda@Edge attached to the `/config.json`,\n * `/api/control/runtime-config`, and `/api/*` behaviors. Reads\n * `x-viewer-host` and overrides the upstream origin's `domainName`\n * (and the `Host` header) to the matching per-PR API host so PR-A's\n * website calls PR-A's API. Only present when the `restApi` prop is\n * supplied.\n */\n public readonly originRequestHandler?: NodejsFunction;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps) {\n super(scope, id);\n\n const stack = OpenHiService.of(scope) as OpenHiService;\n const serviceType = props.serviceType ?? STATIC_HOSTING_SERVICE_TYPE;\n const hostingMode: HostingMode = props.hostingMode ?? \"spa\";\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * Per-PR preview content under `prefixPattern` self-expires on a\n * bucket-wide lifecycle rule when `enablePreviewLifecycle` is true.\n * The rule never runs in production — the caller gates it on\n * stage. See PER_BRANCH_PREVIEW_PREFIX for the shared constant.\n *\n **************************************************************************/\n\n const previewLifecycleRules = props.enablePreviewLifecycle\n ? [\n {\n id: \"expire-pr-previews\",\n enabled: true,\n prefix: props.prefixPattern,\n expiration:\n props.previewExpiration ??\n Duration.days(DEFAULT_PREVIEW_EXPIRATION_DAYS),\n },\n ]\n : undefined;\n\n this.bucket = new Bucket(this, \"bucket\", {\n blockPublicAccess: {\n blockPublicAcls: true,\n blockPublicPolicy: true,\n ignorePublicAcls: true,\n restrictPublicBuckets: true,\n },\n ...(previewLifecycleRules !== undefined && {\n lifecycleRules: previewLifecycleRules,\n }),\n ...props.bucketProps,\n });\n\n /***************************************************************************\n *\n * LAMBDA@EDGE VIEWER-REQUEST HANDLER\n *\n * Rewrites path-like URIs and prepends the Host header as a folder so\n * each domain maps to its own bucket prefix.\n *\n **************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n this.viewerRequestHandler = new NodejsFunction(\n this,\n \"viewer-request-handler\",\n {\n entry: handlerEntry,\n handler: hostingMode === \"static\" ? \"staticHandler\" : \"spaHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_LATEST,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n },\n );\n\n /***************************************************************************\n *\n * CLOUDFRONT DISTRIBUTION\n *\n **************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cache-policy\", {\n cachePolicyName: `static-hosting-${stack.branchHash}`,\n comment: \"Static hosting default: 60s default / 300s max, gzip+brotli.\",\n defaultTtl: Duration.seconds(60),\n minTtl: Duration.seconds(0),\n maxTtl: Duration.seconds(300),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n ...props.cachePolicyProps,\n });\n\n const oac = new S3OriginAccessControl(this, \"origin-access-control\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(this.bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const hasCustomDomain =\n props.certificate !== undefined &&\n props.hostedZone !== undefined &&\n props.domainNames !== undefined &&\n props.domainNames.length > 0;\n\n const restApiWiring = this.buildRestApiBehaviors(\n stack.branchHash,\n props.restApi,\n );\n const additionalBehaviors = restApiWiring?.behaviors;\n this.configJsonRewriteFunction = restApiWiring?.configJsonRewriteFunction;\n this.hostCopyFunction = restApiWiring?.hostCopyFunction;\n this.originRequestHandler = restApiWiring?.originRequestHandler;\n\n this.distribution = new Distribution(this, \"distribution\", {\n comment: `Static hosting distribution for ${props.description ?? id}`,\n ...(hasCustomDomain\n ? {\n certificate: props.certificate,\n domainNames: [...props.domainNames!],\n }\n : {}),\n defaultRootObject: \"index.html\",\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: this.viewerRequestHandler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n includeBody: false,\n },\n ],\n },\n ...(additionalBehaviors !== undefined && { additionalBehaviors }),\n ...props.distributionProps,\n });\n\n /***************************************************************************\n *\n * DNS RECORDS\n *\n * Only created when a hosted zone, certificate, and domain names are\n * all supplied. One ARecord per supplied domain name.\n *\n **************************************************************************/\n\n if (hasCustomDomain) {\n props.domainNames!.forEach((domainName, index) => {\n // Wildcard entries (e.g. `*.dev.openhi.org`) are included in the\n // distribution's `domainNames` so the cert covers them, but\n // Route53 cannot create an `A` record at a wildcard apex.\n // Per-host ARecords for each concrete subdomain are created\n // later by downstream stacks.\n if (StaticHosting.isWildcardDomain(domainName)) {\n return;\n }\n new ARecord(this, `dns-record-${index}-${domainName}`, {\n zone: props.hostedZone!,\n recordName: domainName,\n target: RecordTarget.fromAlias(\n new CloudFrontTarget(this.distribution),\n ),\n });\n });\n }\n\n /***************************************************************************\n *\n * SSM PARAMETERS (4)\n *\n * Bucket ARN, distribution ARN, distribution domain, distribution ID.\n * Consumers (e.g. StaticContent uploader, DNS-aware tools) look these\n * up via DiscoverableStringParameter rather than reconstructing them.\n *\n **************************************************************************/\n\n new DiscoverableStringParameter(this, \"bucket-arn-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,\n serviceType,\n stringValue: this.bucket.bucketArn,\n description: `Static hosting bucket ARN (${props.description ?? id})`,\n });\n\n new DiscoverableStringParameter(this, \"distribution-arn-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ARN,\n serviceType,\n stringValue: this.distribution.distributionArn,\n description: `Static hosting distribution ARN (${props.description ?? id})`,\n });\n\n new DiscoverableStringParameter(this, \"distribution-domain-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_DOMAIN,\n serviceType,\n stringValue: this.distribution.domainName,\n description: `Static hosting distribution domain (${props.description ?? id})`,\n });\n\n new DiscoverableStringParameter(this, \"distribution-id-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ID,\n serviceType,\n stringValue: this.distribution.distributionId,\n description: `Static hosting distribution ID (${props.description ?? id})`,\n });\n }\n\n /**\n * Builds the `/config.json`, `/api/*`, and `/api/control/runtime-config`\n * behaviors backed by the REST API custom-domain origin, plus the\n * viewer-request CloudFront Functions and the origin-request\n * Lambda@Edge that route each request to the matching per-PR API\n * origin. Returns `undefined` when no `restApi` prop is supplied so\n * the Distribution stays S3-only.\n */\n protected buildRestApiBehaviors(\n branchHash: string,\n restApi: StaticHostingProps[\"restApi\"],\n ):\n | {\n behaviors: Record<string, BehaviorOptions>;\n configJsonRewriteFunction: CloudFrontFunction;\n hostCopyFunction: CloudFrontFunction;\n originRequestHandler: NodejsFunction;\n }\n | undefined {\n if (restApi === undefined) {\n return undefined;\n }\n // Caller-overridable defaults for the OpenHI admin-console use case.\n // The construct stays generic — a second website consumer passes\n // different values to point the same wiring at a different SPA.\n const runtimeConfigPath =\n restApi.runtimeConfigPath ?? \"/control/runtime-config\";\n const viewerHostPrefix = restApi.hostMapping?.viewerPrefix ?? \"admin\";\n const apiHostPrefix = restApi.hostMapping?.apiPrefix ?? \"api\";\n const apiOrigin: IOrigin = new HttpOrigin(restApi.domainName, {\n protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,\n });\n const runtimeConfigCachePolicy = new CachePolicy(\n this,\n \"runtime-config-cache-policy\",\n {\n cachePolicyName: `static-hosting-runtime-config-${branchHash}`,\n comment:\n \"/config.json: cache key keyed on Host + `v` so per-PR responses cannot leak across hosts.\",\n defaultTtl:\n restApi.runtimeConfigCacheTtl?.defaultTtl ?? Duration.minutes(5),\n minTtl: Duration.seconds(0),\n maxTtl: restApi.runtimeConfigCacheTtl?.maxTtl ?? Duration.hours(1),\n // `Host` keys the cache per-PR — the origin-request edge Lambda\n // forwards each PR's request to its own API origin, and two\n // PRs' /config.json payloads must not share a cache slot.\n headerBehavior: CacheHeaderBehavior.allowList(\"Host\"),\n queryStringBehavior: CacheQueryStringBehavior.allowList(\"v\"),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n },\n );\n\n // CloudFront Function (JS 1.0) attached to /config.json at the\n // viewer-request stage. Copies the viewer Host header into\n // x-viewer-host before CloudFront strips Host (per\n // ALL_VIEWER_EXCEPT_HOST_HEADER), then rewrites the URI so the\n // upstream sees /control/runtime-config. ES5.1-compatible body —\n // CloudFront Functions JS 1.0 forbids `const`/`let`, arrow\n // functions, and template literals.\n // JSON.stringify gives a properly-escaped JS string literal even\n // for paths containing quotes — safe to splice into the inline\n // function source.\n const runtimeConfigPathLiteral = JSON.stringify(runtimeConfigPath);\n const configJsonRewriteFunction = new CloudFrontFunction(\n this,\n \"config-json-rewrite-function\",\n {\n functionName: `static-hosting-config-json-rewrite-${branchHash}`,\n // CloudFront caps `Comment` at 128 chars; the full rationale is in\n // the preceding code comment.\n comment:\n \"Rewrites /config.json to the runtime-config path; copies Host into x-viewer-host.\",\n code: FunctionCode.fromInline(\n [\n \"function handler(event) {\",\n \" var request = event.request;\",\n \" if (request.headers.host && request.headers.host.value) {\",\n \" request.headers['x-viewer-host'] = { value: request.headers.host.value };\",\n \" }\",\n ` request.uri = ${runtimeConfigPathLiteral};`,\n \" return request;\",\n \"}\",\n ].join(\"\\n\"),\n ),\n },\n );\n\n // CloudFront Function (JS 1.0) attached to /api/* at the\n // viewer-request stage. Same Host-copy step as the rewrite function\n // above, without the URI rewrite (the API path is forwarded\n // verbatim).\n const hostCopyFunction = new CloudFrontFunction(\n this,\n \"host-copy-function\",\n {\n functionName: `static-hosting-host-copy-${branchHash}`,\n // CloudFront caps `Comment` at 128 chars; the full rationale is in\n // the preceding code comment.\n comment:\n \"Copies viewer Host into x-viewer-host for the origin-request Lambda@Edge.\",\n code: FunctionCode.fromInline(\n [\n \"function handler(event) {\",\n \" var request = event.request;\",\n \" if (request.headers.host && request.headers.host.value) {\",\n \" request.headers['x-viewer-host'] = { value: request.headers.host.value };\",\n \" }\",\n \" return request;\",\n \"}\",\n ].join(\"\\n\"),\n ),\n },\n );\n\n // Lambda@Edge attached at the origin-request stage. Reads\n // x-viewer-host and overrides the upstream origin's domainName +\n // Host header so PR-A's website calls PR-A's API gateway.\n const originHandlerJs = path.join(\n __dirname,\n \"static-hosting.origin-request-handler.js\",\n );\n const originHandlerTs = path.join(\n __dirname,\n \"static-hosting.origin-request-handler.ts\",\n );\n const originHandlerEntry = fs.existsSync(originHandlerJs)\n ? originHandlerJs\n : originHandlerTs;\n const originRequestHandler = new NodejsFunction(\n this,\n \"origin-request-handler\",\n {\n entry: originHandlerEntry,\n handler: \"originRequestHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_LATEST,\n logGroup: new LogGroup(this, \"origin-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n // Lambda@Edge forbids runtime env vars, so the host-prefix\n // mapping is inlined into the bundle at synth time via esbuild\n // `define`. The handler reads `process.env.VIEWER_HOST_PREFIX`\n // and `process.env.API_HOST_PREFIX` at module load; esbuild\n // replaces those identifiers with literal strings during\n // bundling so the shipped code carries no env-var lookups.\n bundling: {\n define: {\n \"process.env.VIEWER_HOST_PREFIX\": JSON.stringify(viewerHostPrefix),\n \"process.env.API_HOST_PREFIX\": JSON.stringify(apiHostPrefix),\n },\n },\n },\n );\n\n const originRequestEdgeLambda = {\n functionVersion: originRequestHandler.currentVersion,\n eventType: LambdaEdgeEventType.ORIGIN_REQUEST,\n includeBody: false,\n };\n\n return {\n configJsonRewriteFunction,\n hostCopyFunction,\n originRequestHandler,\n behaviors: {\n \"/config.json\": {\n origin: apiOrigin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n cachePolicy: runtimeConfigCachePolicy,\n originRequestPolicy:\n OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,\n functionAssociations: [\n {\n function: configJsonRewriteFunction,\n eventType: FunctionEventType.VIEWER_REQUEST,\n },\n ],\n edgeLambdas: [originRequestEdgeLambda],\n },\n \"/api/control/runtime-config\": {\n origin: apiOrigin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n cachePolicy: runtimeConfigCachePolicy,\n originRequestPolicy:\n OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,\n functionAssociations: [\n {\n function: hostCopyFunction,\n eventType: FunctionEventType.VIEWER_REQUEST,\n },\n ],\n edgeLambdas: [originRequestEdgeLambda],\n },\n \"/api/*\": {\n origin: apiOrigin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n allowedMethods: AllowedMethods.ALLOW_ALL,\n cachePolicy: CachePolicy.CACHING_DISABLED,\n originRequestPolicy:\n OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,\n functionAssociations: [\n {\n function: hostCopyFunction,\n eventType: FunctionEventType.VIEWER_REQUEST,\n },\n ],\n edgeLambdas: [originRequestEdgeLambda],\n },\n },\n };\n }\n}\n","import { IBucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/static-hosting/static-content.md\n */\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/<sub-domain>.<full-domain>/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket):\n *\n * /www.stage.openhi.org/* -\\> serves content to www.stage.openhi.org\n * /feature-7.stage.openhi.org/* -\\> serves content to feature-7.stage.openhi.org\n * /pr-123.stage.openhi.org/* -\\> serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\n/**\n * Props for the StaticContent construct.\n */\nexport interface StaticContentProps {\n /**\n * Destination bucket the content is uploaded to. Callers resolve this\n * reference themselves so the construct doesn't need to know whether the\n * bucket was created in the same stack or imported across branches.\n */\n readonly bucket: IBucket;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n *\n * @default \"/\"\n */\n readonly contentDestinationDirectory?: string;\n\n /**\n * The sub domain prefix (e.g. \"feature-7\"). Used as the per-branch folder\n * name in the bucket so each branch deploys to its own prefix.\n *\n * @default the current stack's branch name (kebab-cased)\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (e.g. \"stage.openhi.org\"). Used together with\n * `subDomain` to form the destination prefix\n * `<sub-domain>.<full-domain>`.\n */\n readonly fullDomain: string;\n}\n\n/**\n * Static content uploader: deploys a local directory to a static-hosting\n * S3 bucket under `<sub-domain>.<full-domain>/<dest>` so each branch\n * deploys to its own prefix without clobbering siblings. The destination\n * bucket is supplied by the caller, which lets the same construct run in\n * both same-stack (release-branch) and cross-stack/cross-branch\n * (feature-branch) contexts.\n */\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n const stack = OpenHiService.of(scope) as OpenHiService;\n\n const {\n bucket,\n contentSourceDirectory,\n contentDestinationDirectory = \"/\",\n subDomain = stack.branchName,\n fullDomain,\n } = props;\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. Use an empty source list when\n * running under Jest so the asset bundle (which is built every time and\n * embeds machine-specific paths) doesn't bork test snapshots.\n *\n **************************************************************************/\n\n const isTestEnv = process.env.JEST_WORKER_ID !== undefined;\n const sources = isTestEnv ? [] : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport {\n IUserPool,\n IUserPoolClient,\n IUserPoolDomain,\n LambdaVersion,\n UserPool,\n UserPoolClient,\n UserPoolDomain,\n UserPoolOperation,\n UserPoolProps,\n} from \"aws-cdk-lib/aws-cognito\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { IKey, Key } from \"aws-cdk-lib/aws-kms\";\nimport { IFunction } from \"aws-cdk-lib/aws-lambda\";\nimport { Stack } from \"aws-cdk-lib/core\";\nimport { Construct } from \"constructs\";\nimport { OpenHiDataService } from \"./open-hi-data-service\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport {\n ADMIN_DOMAIN_PREFIX,\n OpenHiWebsiteService,\n} from \"./open-hi-website-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { CognitoUserPool } from \"../components/cognito/cognito-user-pool\";\nimport { CognitoUserPoolClient } from \"../components/cognito/cognito-user-pool-client\";\nimport { CognitoUserPoolDomain } from \"../components/cognito/cognito-user-pool-domain\";\nimport { CognitoUserPoolKmsKey } from \"../components/cognito/cognito-user-pool-kms-key\";\nimport { PostAuthenticationLambda } from \"../components/cognito/post-authentication-lambda\";\nimport { PostConfirmationLambda } from \"../components/cognito/post-confirmation-lambda\";\nimport { PreTokenGenerationLambda } from \"../components/cognito/pre-token-generation-lambda\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport { UserOnboardingWorkflow } from \"../workflows/control-plane/user-onboarding\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-auth-service.md\n */\n\n/**\n * Localhost OAuth callback URLs auto-injected into the Cognito User Pool\n * Client on every non-prod (`stageType !== \"prod\"`) deploy. Both schemes\n * (`http`, `https`) on `localhost:3000` are covered so admin-console\n * previews running on `localhost` can complete the redirect flow.\n *\n * Excluded from prod on purpose — a prod Cognito client that accepts\n * `localhost` redirects is a phishing vector.\n */\nexport const LOCALHOST_OAUTH_CALLBACK_URLS: ReadonlyArray<string> = [\n \"http://localhost:3000/oauth/callback\",\n \"https://localhost:3000/oauth/callback\",\n];\n\n/**\n * Localhost OAuth logout URLs auto-injected alongside\n * {@link LOCALHOST_OAUTH_CALLBACK_URLS} on non-prod deploys so the hosted\n * UI sign-out flow can redirect back to a local dev server.\n */\nexport const LOCALHOST_OAUTH_LOGOUT_URLS: ReadonlyArray<string> = [\n \"http://localhost:3000/oauth/logout\",\n \"https://localhost:3000/oauth/logout\",\n];\n\nexport interface OpenHiAuthServiceProps extends OpenHiServiceProps {\n /**\n * Optional props for the Cognito User Pool.\n */\n readonly userPoolProps?: UserPoolProps;\n}\n\n/**\n * OpenHI Auth Service stack.\n *\n * @remarks\n * The Auth service manages authentication infrastructure including:\n * - Cognito User Pool for user management and authentication\n * - User Pool Client for application integration\n * - User Pool Domain for hosting the Cognito hosted UI\n * - KMS Key for Cognito User Pool encryption\n *\n * Resources are created in protected methods; subclasses may override to customize.\n * Other stacks obtain auth by calling **OpenHiAuthService.userPoolFromConstruct(scope)**,\n * **OpenHiAuthService.userPoolClientFromConstruct(scope)**,\n * **OpenHiAuthService.userPoolDomainFromConstruct(scope)**,\n * and **OpenHiAuthService.userPoolKmsKeyFromConstruct(scope)** for each resource needed.\n *\n * Only one instance of the auth service should exist per environment.\n *\n * @public\n */\nexport class OpenHiAuthService extends OpenHiService {\n static readonly SERVICE_TYPE = \"auth\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns an IUserPool by looking up the Auth stack's User Pool ID from SSM.\n */\n static userPoolFromConstruct(scope: Construct): IUserPool {\n const userPoolId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPool.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n return UserPool.fromUserPoolId(scope, \"user-pool\", userPoolId);\n }\n\n /**\n * Returns an IUserPoolClient by looking up the Auth stack's User Pool Client ID from SSM.\n */\n static userPoolClientFromConstruct(scope: Construct): IUserPoolClient {\n const userPoolClientId = DiscoverableStringParameter.valueForLookupName(\n scope,\n {\n ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n },\n );\n return UserPoolClient.fromUserPoolClientId(\n scope,\n \"user-pool-client\",\n userPoolClientId,\n );\n }\n\n /**\n * Returns an IUserPoolDomain by looking up the Auth stack's User Pool Domain from SSM.\n */\n static userPoolDomainFromConstruct(scope: Construct): IUserPoolDomain {\n const domainName = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n return UserPoolDomain.fromDomainName(scope, \"user-pool-domain\", domainName);\n }\n\n /**\n * Returns the full Cognito Hosted UI base URL (e.g.\n * `https://auth-abc.auth.us-east-2.amazoncognito.com`) by looking up\n * the Auth stack's User Pool Domain from SSM and composing it with the\n * calling stack's region.\n *\n * Equivalent to `UserPoolDomain.baseUrl()` on the concrete construct,\n * but works across stacks where the looked-up `IUserPoolDomain` is an\n * `Import` and does not carry the `baseUrl()` method. Assumes the\n * domain was created as a Cognito-managed prefix domain (the only\n * variant `OpenHiAuthService.createUserPoolDomain` produces).\n */\n static userPoolDomainBaseUrlFromConstruct(scope: Construct): string {\n const domainName = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n const region = Stack.of(scope).region;\n return `https://${domainName}.auth.${region}.amazoncognito.com`;\n }\n\n /**\n * Returns an IKey (KMS) by looking up the Auth stack's User Pool KMS Key ARN from SSM.\n */\n static userPoolKmsKeyFromConstruct(scope: Construct): IKey {\n const keyArn = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPoolKmsKey.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n return Key.fromKeyArn(scope, \"kms-key\", keyArn);\n }\n\n get serviceType(): string {\n return OpenHiAuthService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options (e.g. userPoolProps). */\n public override props: OpenHiAuthServiceProps;\n\n public readonly userPoolKmsKey: IKey;\n public readonly preTokenGenerationLambda: IFunction;\n public readonly postAuthenticationLambda: IFunction;\n public readonly postConfirmationLambda: IFunction;\n public readonly userOnboardingWorkflow: UserOnboardingWorkflow;\n public readonly userPool: IUserPool;\n public readonly userPoolClient: IUserPoolClient;\n public readonly userPoolDomain: IUserPoolDomain;\n\n /**\n * Cross-stack reference to the data store table. Cached so repeated\n * lookups share a single CDK construct id (\"dynamo-db-data-store\") in\n * this stack — a second `Table.fromTableName` call under the same scope\n * would collide.\n */\n private _dataStoreTable: ReturnType<\n typeof OpenHiDataService.dynamoDbDataStoreFromConstruct\n > | null = null;\n private _controlEventBus: IEventBus | null = null;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiAuthServiceProps = {}) {\n super(ohEnv, OpenHiAuthService.SERVICE_TYPE, props);\n this.props = props;\n\n this.userPoolKmsKey = this.createUserPoolKmsKey();\n this.preTokenGenerationLambda = this.createPreTokenGenerationLambda();\n this.postAuthenticationLambda = this.createPostAuthenticationLambda();\n this.postConfirmationLambda = this.createPostConfirmationLambda();\n this.userOnboardingWorkflow = this.createUserOnboardingWorkflow();\n this.userPool = this.createUserPool();\n this.grantPreTokenGenerationPermissions();\n this.grantPostAuthenticationPermissions();\n this.grantPostConfirmationPermissions();\n this.userPoolClient = this.createUserPoolClient();\n this.userPoolDomain = this.createUserPoolDomain();\n }\n\n /**\n * Creates the KMS key for the Cognito User Pool and exports its ARN to SSM.\n * Look up via {@link OpenHiAuthService.userPoolKmsKeyFromConstruct}.\n * Override to customize.\n */\n protected createUserPoolKmsKey(): IKey {\n const key = new CognitoUserPoolKmsKey(this);\n new DiscoverableStringParameter(this, \"kms-key-param\", {\n ssmParamName: CognitoUserPoolKmsKey.SSM_PARAM_NAME,\n stringValue: key.keyArn,\n description:\n \"KMS key ARN for Cognito User Pool (e.g. custom sender); cross-stack reference\",\n });\n return key;\n }\n\n /**\n * Creates the Pre Token Generation Lambda (Cognito trigger). On every\n * sign-in and token refresh the Lambda resolves the User by Cognito `sub`\n * (GSI2) and injects `ohi_tid`, `ohi_wid`, `ohi_uid`, `ohi_uname` into\n * both the ID token and the access token (ADR 2026-03-17-01).\n */\n protected createPreTokenGenerationLambda(): IFunction {\n const construct = new PreTokenGenerationLambda(this, {\n dynamoTableName: this.dataStoreTable().tableName,\n });\n return construct.lambda;\n }\n\n /**\n * Creates the Post Authentication Lambda (Cognito trigger). Calls\n * AdminUserGlobalSignOut on every sign-in to enforce single-device-per-user\n * sessions per ADR 2026-03-17-01.\n */\n protected createPostAuthenticationLambda(): IFunction {\n const construct = new PostAuthenticationLambda(this);\n return construct.lambda;\n }\n\n /**\n * Creates the Post Confirmation Lambda (Cognito trigger). On sign-up\n * confirmation, publishes a control-plane workflow event; provisioning lives\n * behind EventBridge.\n */\n protected createPostConfirmationLambda(): IFunction {\n const construct = new PostConfirmationLambda(this, {\n controlEventBusName: this.controlEventBus().eventBusName,\n });\n return construct.lambda;\n }\n\n protected createUserOnboardingWorkflow(): UserOnboardingWorkflow {\n return new UserOnboardingWorkflow(this, {\n controlEventBus: this.controlEventBus(),\n dataStoreTable: this.dataStoreTable(),\n });\n }\n\n private dataStoreTable() {\n if (this._dataStoreTable === null) {\n this._dataStoreTable =\n OpenHiDataService.dynamoDbDataStoreFromConstruct(this);\n }\n return this._dataStoreTable;\n }\n\n private controlEventBus() {\n if (this._controlEventBus === null) {\n this._controlEventBus =\n OpenHiGlobalService.controlEventBusFromConstruct(this);\n }\n return this._controlEventBus;\n }\n\n /**\n * Creates the Cognito User Pool and exports its ID to SSM.\n * Look up via {@link OpenHiAuthService.userPoolFromConstruct}.\n * Override to customize.\n */\n protected createUserPool(): IUserPool {\n const userPool = new CognitoUserPool(this, {\n ...this.props.userPoolProps,\n customSenderKmsKey: this.userPoolKmsKey,\n });\n // Access-token-only claims require Pre Token Generation V2_0.\n userPool.addTrigger(\n UserPoolOperation.PRE_TOKEN_GENERATION_CONFIG,\n this.preTokenGenerationLambda,\n LambdaVersion.V2_0,\n );\n userPool.addTrigger(\n UserPoolOperation.POST_AUTHENTICATION,\n this.postAuthenticationLambda,\n );\n userPool.addTrigger(\n UserPoolOperation.POST_CONFIRMATION,\n this.postConfirmationLambda,\n );\n new DiscoverableStringParameter(this, \"user-pool-param\", {\n ssmParamName: CognitoUserPool.SSM_PARAM_NAME,\n stringValue: userPool.userPoolId,\n description:\n \"Cognito User Pool ID for this Auth stack; cross-stack reference\",\n });\n return userPool;\n }\n\n /**\n * Grants the Pre Token Generation Lambda read-only access on the data\n * store table and its GSIs. The Lambda needs:\n * - `Query` on GSI2 to resolve a User by Cognito `sub`\n * - `GetItem` on the base table for direct User reads (canonical row hydration\n * after the GSI2 hit, per #1175)\n * - `BatchGetItem` on the base table for ElectroDB `batchGetWithRetry`\n * hydration used by `listMembershipsOperation` and\n * `listRoleAssignmentsOperation` when resolving the\n * `ohi_organization_roles` / `ohi_platform_roles` claims\n *\n * No write or scan access: a User missing `currentTenant`/`currentWorkspace`\n * falls into the absent-claims path; repair belongs in a separate backfill.\n */\n protected grantPreTokenGenerationPermissions(): void {\n const dataStoreTable = this.dataStoreTable();\n const dynamoActions = [\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n \"dynamodb:BatchGetItem\",\n ] as const;\n dataStoreTable.grant(this.preTokenGenerationLambda, ...dynamoActions);\n this.preTokenGenerationLambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [...dynamoActions],\n resources: [`${dataStoreTable.tableArn}/index/*`],\n }),\n );\n }\n\n /**\n * Grants the Post Authentication Lambda permission to call\n * `cognito-idp:AdminUserGlobalSignOut`.\n *\n * Scoped via `Stack.of(this).formatArn` rather than `userPool.userPoolArn`\n * because the User Pool registers this Lambda as a Post Authentication\n * trigger, creating the cycle:\n * userPool → lambda (trigger ARN) → role policy → userPool ARN.\n * Using `formatArn` avoids referencing the User Pool resource directly\n * while still scoping to user pools in this account+region. The Lambda\n * is invoked only by Cognito with a Cognito-provided `event.userPoolId`,\n * so the runtime target is constrained by the trigger contract.\n */\n protected grantPostAuthenticationPermissions(): void {\n this.postAuthenticationLambda.addToRolePolicy(\n new PolicyStatement({\n actions: [\"cognito-idp:AdminUserGlobalSignOut\"],\n resources: [\n Stack.of(this).formatArn({\n service: \"cognito-idp\",\n resource: \"userpool\",\n resourceName: \"*\",\n }),\n ],\n }),\n );\n }\n\n /**\n * Grants the Post Confirmation Lambda publish-only access to the\n * control-plane event bus. Workflow Lambdas own DynamoDB writes.\n */\n protected grantPostConfirmationPermissions(): void {\n this.controlEventBus().grantPutEventsTo(this.postConfirmationLambda);\n }\n\n /**\n * Creates the User Pool Client and exports its ID to SSM (AUTH service type).\n * OAuth flows are enabled with auto-injected callback/logout URLs derived\n * from this stack's branch context (see {@link resolveOAuthRedirectUrls}).\n * Look up via {@link OpenHiAuthService.userPoolClientFromConstruct}.\n * Override to customize.\n */\n protected createUserPoolClient(): IUserPoolClient {\n const { callbackUrls, logoutUrls } = this.resolveOAuthRedirectUrls();\n const client = new CognitoUserPoolClient(this, {\n userPool: this.userPool,\n oAuth: {\n flows: {\n authorizationCodeGrant: true,\n implicitCodeGrant: true,\n },\n callbackUrls,\n logoutUrls,\n },\n });\n new DiscoverableStringParameter(this, \"user-pool-client-param\", {\n ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,\n stringValue: client.userPoolClientId,\n description:\n \"Cognito User Pool Client ID for this Auth stack; cross-stack reference\",\n });\n return client;\n }\n\n /**\n * Returns the OAuth `callbackUrls` and `logoutUrls` the Cognito User Pool\n * Client should accept. Composed from the same branch context the website\n * service will see at synth time:\n *\n * - `https://admin{,-<childZonePrefix>}.<zone>/oauth/{callback,logout}`\n * - `https://www{,-<childZonePrefix>}.<zone>/oauth/{callback,logout}`\n *\n * Both deployed-host pairs are auto-injected on every stage. The stage's\n * `additionalTrustedClientOrigins` entries (e.g. on-site customer SPA\n * hosts) are filtered to `https://`-prefix values and contribute\n * `/oauth/callback` + `/oauth/logout` URLs to the merge — Cognito rejects\n * non-localhost http callbacks, so `http://` entries are silently dropped.\n * On non-prod stages the localhost dev URLs from\n * {@link LOCALHOST_OAUTH_CALLBACK_URLS} /\n * {@link LOCALHOST_OAUTH_LOGOUT_URLS} join the merge; on prod they are\n * deliberately excluded.\n *\n * If `zoneName` is absent (no-DNS test configurations), the deployed-host\n * pairs are skipped — only the localhost set and any configured\n * additional `https://` origins survive (the latter on every stage).\n * Override to customize.\n */\n protected resolveOAuthRedirectUrls(): {\n callbackUrls: Array<string>;\n logoutUrls: Array<string>;\n } {\n const isNonProd = this.ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD;\n const zoneName = this.props.config?.zoneName;\n const deployedOrigins: Array<string> = [];\n if (zoneName !== undefined) {\n const adminHost = OpenHiWebsiteService.composeFullDomain({\n domainPrefix: ADMIN_DOMAIN_PREFIX,\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n const websiteHost = OpenHiWebsiteService.composeFullDomain({\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n deployedOrigins.push(`https://${adminHost}`, `https://${websiteHost}`);\n }\n const stageType = this.ohEnv.ohStage.stageType;\n const additionalHttpsOrigins =\n this.ohEnv.ohStage.ohApp.config.deploymentTargets?.[\n stageType\n ]?.additionalTrustedClientOrigins?.filter((o) =>\n o.startsWith(\"https://\"),\n ) ?? [];\n const localhostCallbacks = isNonProd ? LOCALHOST_OAUTH_CALLBACK_URLS : [];\n const localhostLogouts = isNonProd ? LOCALHOST_OAUTH_LOGOUT_URLS : [];\n return {\n callbackUrls: [\n ...deployedOrigins.map((o) => `${o}/oauth/callback`),\n ...additionalHttpsOrigins.map((o) => `${o}/oauth/callback`),\n ...localhostCallbacks,\n ],\n logoutUrls: [\n ...deployedOrigins.map((o) => `${o}/oauth/logout`),\n ...additionalHttpsOrigins.map((o) => `${o}/oauth/logout`),\n ...localhostLogouts,\n ],\n };\n }\n\n /**\n * Creates the User Pool Domain (Cognito hosted UI) and exports domain name to SSM.\n * Look up via {@link OpenHiAuthService.userPoolDomainFromConstruct}.\n * Override to customize.\n */\n protected createUserPoolDomain(): IUserPoolDomain {\n const domain = new CognitoUserPoolDomain(this, {\n userPool: this.userPool,\n cognitoDomain: {\n domainPrefix: `auth-${this.branchHash}`,\n },\n });\n new DiscoverableStringParameter(this, \"user-pool-domain-param\", {\n ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,\n stringValue: domain.domainName,\n description:\n \"Cognito User Pool Domain (hosted UI) for this Auth stack; cross-stack reference\",\n });\n return domain;\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport { StreamViewType, ITable, Table } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport * as kinesis from \"aws-cdk-lib/aws-kinesis\";\nimport { Construct } from \"constructs\";\nimport { OpenHiAuthService } from \"./open-hi-auth-service\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { DataStoreHistoricalArchive } from \"../components/dynamodb/data-store-historical-archive\";\nimport {\n DynamoDbDataStore,\n getDynamoDbDataStoreTableName,\n} from \"../components/dynamodb/dynamo-db-data-store\";\nimport { DataStorePostgresReplica } from \"../components/postgres/data-store-postgres-replica\";\nimport { SeedDemoDataWorkflow } from \"../workflows/control-plane/seed-demo-data\";\nimport { SeedSystemDataWorkflow } from \"../workflows/control-plane/seed-system-data\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-data-service.md\n */\n\nexport type OpenHiDataServiceProps = OpenHiServiceProps;\n\n/**\n * Data storage service stack: centralizes DynamoDB, S3, and other persistence\n * resources for OpenHI. Creates the single-table data store in a protected\n * method; subclasses may override to customize. EventBridge event buses\n * (data, ops, control) are owned by {@link OpenHiGlobalService} so they deploy\n * ahead of regional services.\n */\nexport class OpenHiDataService extends OpenHiService {\n static readonly SERVICE_TYPE = \"data\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns the data store table by name. Use from other stacks (e.g. REST API Lambda) to obtain an ITable reference.\n */\n static dynamoDbDataStoreFromConstruct(\n scope: Construct,\n id = \"dynamo-db-data-store\",\n ): ITable {\n return Table.fromTableName(scope, id, getDynamoDbDataStoreTableName(scope));\n }\n\n get serviceType(): string {\n return OpenHiDataService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options. */\n public override props: OpenHiDataServiceProps;\n\n /**\n * The single-table DynamoDB data store. Use {@link OpenHiDataService.dynamoDbDataStoreFromConstruct}\n * from other stacks to obtain an ITable reference by name.\n */\n public readonly dataStore: ITable;\n\n /**\n * Kinesis stream receiving DynamoDB item-level changes for the data store table.\n */\n public readonly dataStoreChangeStream: kinesis.IStream;\n\n /**\n * Historical archive pipeline (Kinesis → Firehose → S3) and data-event-bus\n * notifications for current FHIR resources (ADRs 2026-03-11-02, 2026-03-02-01).\n */\n public readonly dataStoreHistoricalArchive: DataStoreHistoricalArchive;\n\n /**\n * Postgres replication tier (ADR 2026-04-17-01, phase 1). A second consumer\n * on the change stream that projects current FHIR resources into a JSONB\n * `resources` table on Aurora Serverless v2. Phase 1 is replication-only;\n * the read path is not wired up yet.\n */\n public readonly dataStorePostgresReplica: DataStorePostgresReplica;\n\n /**\n * Deploy-triggered workflow that idempotently re-asserts the\n * platform-singleton control-plane records (today: the three canonical\n * Roles via `PLATFORM_ROLE_CONCEPTS`; future: additional system\n * data). Subscribes to `platform.deployment-completed.v1` on the\n * control event bus and dedups via the shared `WorkflowDedupTable`.\n */\n public readonly seedSystemDataWorkflow: SeedSystemDataWorkflow;\n\n /**\n * Deploy-triggered workflow that idempotently re-asserts the demo\n * data graph (placeholder + 3 demo Tenants + 5 Workspaces; per\n * dev-user Cognito users with their DynamoDB User records,\n * Memberships, and RoleAssignments). **Non-prod only** —\n * `undefined` on prod stages. The synth-time stage gate in\n * {@link createSeedDemoDataWorkflow} is the only guarantee\n * separating prod stacks from the workflow's IAM grants and rule\n * target; the construct itself never checks the stage.\n */\n public readonly seedDemoDataWorkflow?: SeedDemoDataWorkflow;\n\n /**\n * Cached control-event-bus lookup. `OpenHiGlobalService.controlEventBusFromConstruct`\n * registers a child `EventBus.fromEventBusName` construct with a\n * fixed id under the scope it is passed, so calling it twice on the\n * same `OpenHiDataService` instance collides. The cache mirrors the\n * `private controlEventBus()` pattern already used in\n * `OpenHiAuthService`. Use {@link controlEventBus} from this class\n * — never call the static lookup from inside `OpenHiDataService`.\n */\n private _controlEventBus: IEventBus | null = null;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiDataServiceProps = {}) {\n super(ohEnv, OpenHiDataService.SERVICE_TYPE, props);\n this.props = props;\n\n this.dataStoreChangeStream = new kinesis.Stream(\n this,\n \"data-store-change-stream\",\n {\n streamName: `openhi-dstore-cdc-${this.branchHash}`,\n streamMode: kinesis.StreamMode.ON_DEMAND,\n // CDK default for kinesis.Stream is RETAIN, which strands the stream\n // when a non-prod stack is destroyed. Use the service's policy so\n // non-prod tears down cleanly while prod retains.\n removalPolicy: this.removalPolicy,\n },\n );\n\n this.dataStore = this.createDataStore();\n\n this.dataStoreHistoricalArchive = new DataStoreHistoricalArchive(\n this,\n \"data-store-historical-archive\",\n {\n kinesisStream: this.dataStoreChangeStream,\n removalPolicy: this.removalPolicy,\n stackHash: this.stackHash,\n dataEventBus: OpenHiGlobalService.dataEventBusFromConstruct(this),\n },\n );\n\n this.dataStorePostgresReplica = new DataStorePostgresReplica(\n this,\n \"data-store-postgres-replica\",\n {\n kinesisStream: this.dataStoreChangeStream,\n removalPolicy: this.removalPolicy,\n stackHash: this.stackHash,\n branchHash: this.branchHash,\n },\n );\n\n this.seedSystemDataWorkflow = this.createSeedSystemDataWorkflow();\n this.seedDemoDataWorkflow = this.createSeedDemoDataWorkflow();\n }\n\n /**\n * Lazily looks up the control event bus exactly once per\n * `OpenHiDataService` instance and caches the reference. Every\n * workflow that consumes the bus must read it through this method\n * — see {@link _controlEventBus} for the underlying collision risk.\n */\n private controlEventBus(): IEventBus {\n if (this._controlEventBus === null) {\n this._controlEventBus =\n OpenHiGlobalService.controlEventBusFromConstruct(this);\n }\n return this._controlEventBus;\n }\n\n /**\n * Creates the seed-system-data workflow. Override to customize.\n */\n protected createSeedSystemDataWorkflow(): SeedSystemDataWorkflow {\n return new SeedSystemDataWorkflow(this, {\n controlEventBus: this.controlEventBus(),\n dataStoreTable: this.dataStore,\n });\n }\n\n /**\n * Creates the seed-demo-data workflow — but only on non-prod\n * stages. Returns `undefined` on prod so the workflow literally\n * does not exist in prod stacks. Override to customize.\n */\n protected createSeedDemoDataWorkflow(): SeedDemoDataWorkflow | undefined {\n if (this.ohEnv.ohStage.stageType === OPEN_HI_STAGE.PROD) {\n return undefined;\n }\n return new SeedDemoDataWorkflow(this, {\n controlEventBus: this.controlEventBus(),\n dataStoreTable: this.dataStore,\n userPool: OpenHiAuthService.userPoolFromConstruct(this),\n });\n }\n\n /**\n * Creates the single-table DynamoDB data store.\n * Override to customize.\n */\n protected createDataStore(): ITable {\n return new DynamoDbDataStore(this, \"dynamo-db-data-store\", {\n kinesisStream: this.dataStoreChangeStream,\n stream: StreamViewType.NEW_AND_OLD_IMAGES,\n });\n }\n}\n","import {\n Certificate,\n CertificateValidation,\n ICertificate,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport { EventBus, IEventBus } from \"aws-cdk-lib/aws-events\";\nimport {\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n} from \"aws-cdk-lib/aws-route53\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { RootWildcardCertificate } from \"../components/acm/root-wildcard-certificate\";\nimport { WorkflowDedupTable } from \"../components/dynamodb/workflow-dedup-table\";\nimport { ControlEventBus } from \"../components/event-bridge/control-event-bus\";\nimport { DataEventBus } from \"../components/event-bridge/data-event-bus\";\nimport { OpsEventBus } from \"../components/event-bridge/ops-event-bus\";\nimport { ChildHostedZone } from \"../components/route-53/child-hosted-zone\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport { PlatformDeployBridge } from \"../workflows/control-plane/platform-deploy-bridge\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-global-service.md\n */\n\nexport interface OpenHiGlobalServiceProps extends OpenHiServiceProps {}\n\n/**\n * Global Infrastructure stack: owns global DNS, certificates, and the\n * cross-region EventBridge buses (data, ops, control). Resources (root zone,\n * optional child zone, wildcard cert, data/ops/control buses) are created in\n * protected methods; subclasses may override to customize.\n */\nexport class OpenHiGlobalService extends OpenHiService {\n static readonly SERVICE_TYPE = \"global\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns an IHostedZone from the given attributes (no SSM). Use when the zone is imported from config.\n */\n static rootHostedZoneFromConstruct(\n scope: Construct,\n props: HostedZoneAttributes,\n ): IHostedZone {\n return HostedZone.fromHostedZoneAttributes(scope, \"root-zone\", props);\n }\n\n /**\n * Returns an ICertificate by looking up the Global stack's wildcard cert ARN from SSM.\n */\n static rootWildcardCertificateFromConstruct(scope: Construct): ICertificate {\n const certificateArn = StringParameter.valueForStringParameter(\n scope,\n RootWildcardCertificate.ssmParameterName(),\n );\n return Certificate.fromCertificateArn(\n scope,\n \"wildcard-certificate\",\n certificateArn,\n );\n }\n\n /**\n * Returns an IHostedZone by looking up the child hosted zone ID from SSM. Defaults to GLOBAL service type.\n */\n static childHostedZoneFromConstruct(\n scope: Construct,\n props: { zoneName: string; serviceType?: OpenHiServiceType },\n ): IHostedZone {\n const hostedZoneId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: ChildHostedZone.SSM_PARAM_NAME,\n serviceType: props.serviceType ?? OpenHiGlobalService.SERVICE_TYPE,\n });\n return HostedZone.fromHostedZoneAttributes(scope, \"child-zone\", {\n hostedZoneId,\n zoneName: props.zoneName,\n });\n }\n\n /**\n * Returns the data event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.\n */\n static dataEventBusFromConstruct(scope: Construct): IEventBus {\n return EventBus.fromEventBusName(\n scope,\n \"data-event-bus\",\n DataEventBus.getEventBusName(scope),\n );\n }\n\n /**\n * Returns the ops event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.\n */\n static opsEventBusFromConstruct(scope: Construct): IEventBus {\n return EventBus.fromEventBusName(\n scope,\n \"ops-event-bus\",\n OpsEventBus.getEventBusName(scope),\n );\n }\n\n /**\n * Returns the control-plane event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.\n */\n static controlEventBusFromConstruct(scope: Construct): IEventBus {\n return EventBus.fromEventBusName(\n scope,\n \"control-event-bus\",\n ControlEventBus.getEventBusName(scope),\n );\n }\n\n /**\n * Returns the workflow dedup table by name (deterministic per branch).\n * Use from other stacks to obtain an ITable reference. Consumer Lambdas\n * are typically wired via `WorkflowDedupTable.grantConsumer(fn, name)`\n * on the owning service's `workflowDedupTable` reference; the\n * `tableNameFromLookup` / `tableArnFromLookup` SSM helpers on the\n * construct cover cross-stack consumers that need only the name/ARN.\n */\n static workflowDedupTableNameFromLookup(scope: Construct): string {\n return WorkflowDedupTable.tableNameFromLookup(scope);\n }\n\n get serviceType(): string {\n return OpenHiGlobalService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options. */\n public override props: OpenHiGlobalServiceProps;\n\n public readonly rootHostedZone: IHostedZone;\n public readonly childHostedZone?: IHostedZone;\n public readonly rootWildcardCertificate: ICertificate;\n\n /**\n * Event bus for data-related events (ingestion, transformation, storage).\n * Other stacks obtain it via {@link OpenHiGlobalService.dataEventBusFromConstruct}.\n */\n public readonly dataEventBus: IEventBus;\n\n /**\n * Event bus for operational events (monitoring, alerting, system health).\n * Other stacks obtain it via {@link OpenHiGlobalService.opsEventBusFromConstruct}.\n */\n public readonly opsEventBus: IEventBus;\n\n /**\n * Event bus for control-plane lifecycle and command events.\n * Other stacks obtain it via {@link OpenHiGlobalService.controlEventBusFromConstruct}.\n */\n public readonly controlEventBus: IEventBus;\n\n /**\n * Bridge that watches CloudFormation Stack Status Change events on the\n * default AWS bus and republishes terminal-success events for OpenHi-tagged\n * stacks onto {@link controlEventBus} as `platform.deployment-completed.v1`.\n */\n public readonly platformDeployBridge: PlatformDeployBridge;\n\n /**\n * Shared dedup table every retryable workflow consumer dedupes against\n * (TR-015). Singleton per deployment — provisioned here on the global\n * stack so consumer stacks reach it via SSM lookups, not props.\n */\n public readonly workflowDedupTable: WorkflowDedupTable;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiGlobalServiceProps = {}) {\n super(ohEnv, OpenHiGlobalService.SERVICE_TYPE, props);\n this.props = props;\n\n this.validateConfig(props);\n\n this.rootHostedZone = this.createRootHostedZone();\n this.childHostedZone = this.createChildHostedZone();\n this.rootWildcardCertificate = this.createRootWildcardCertificate();\n this.dataEventBus = this.createDataEventBus();\n this.opsEventBus = this.createOpsEventBus();\n this.controlEventBus = this.createControlEventBus();\n this.workflowDedupTable = this.createWorkflowDedupTable();\n this.platformDeployBridge = this.createPlatformDeployBridge();\n }\n\n /**\n * Validates that config required for the Global stack is present.\n */\n protected validateConfig(props: OpenHiGlobalServiceProps): void {\n const { config } = props;\n if (!config) {\n throw new Error(\"Config is required\");\n }\n if (!config.zoneName) {\n throw new Error(\"Zone name is required to import the root zone\");\n }\n if (!config.hostedZoneId) {\n throw new Error(\"Hosted zone ID is required to import the root zone\");\n }\n }\n\n /**\n * Creates the root hosted zone (imported via attributes from config).\n * Override to customize or create the zone.\n */\n protected createRootHostedZone(): IHostedZone {\n return OpenHiGlobalService.rootHostedZoneFromConstruct(this, {\n zoneName: this.config.zoneName!,\n hostedZoneId: this.config.hostedZoneId!,\n });\n }\n\n /**\n * Creates the optional child hosted zone (e.g. branch subdomain).\n * Override to create a child zone when config provides childHostedZoneAttributes.\n * If you create a ChildHostedZone, also create a DiscoverableStringParameter\n * with ChildHostedZone.SSM_PARAM_NAME and the zone's hostedZoneId.\n */\n protected createChildHostedZone(): IHostedZone | undefined {\n return undefined;\n }\n\n /**\n * Creates the root wildcard certificate. On main branch, creates a new cert\n * with DNS validation; otherwise imports from SSM.\n * Override to customize certificate creation.\n */\n protected createRootWildcardCertificate(): ICertificate {\n if (this.branchName === \"main\") {\n return new RootWildcardCertificate(this, {\n domainName: `*.${this.rootHostedZone.zoneName}`,\n subjectAlternativeNames: [this.rootHostedZone.zoneName],\n validation: CertificateValidation.fromDns(this.rootHostedZone),\n });\n }\n return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);\n }\n\n /**\n * Creates the data event bus.\n * Override to customize.\n */\n protected createDataEventBus(): IEventBus {\n return new DataEventBus(this);\n }\n\n /**\n * Creates the ops event bus.\n * Override to customize.\n */\n protected createOpsEventBus(): IEventBus {\n return new OpsEventBus(this);\n }\n\n /**\n * Creates the control-plane event bus.\n * Override to customize.\n */\n protected createControlEventBus(): IEventBus {\n return new ControlEventBus(this);\n }\n\n /**\n * Creates the platform deploy bridge that republishes CloudFormation\n * Stack Status Change events onto the control event bus.\n * Override to customize.\n */\n protected createPlatformDeployBridge(): PlatformDeployBridge {\n return new PlatformDeployBridge(this, {\n controlEventBus: this.controlEventBus,\n });\n }\n\n /**\n * Creates the shared workflow dedup table (TR-015 singleton).\n * Override to customize.\n */\n protected createWorkflowDedupTable(): WorkflowDedupTable {\n return new WorkflowDedupTable(this, \"workflow-dedup-table\");\n }\n}\n","import { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { PlatformDeployBridgeLambda } from \"./platform-deploy-bridge-lambda\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/platform-deploy-bridge/index.md\n */\n\nexport interface PlatformDeployBridgeProps {\n /** Destination control event bus the bridge republishes onto. */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Source-side reactor that watches CloudFormation Stack Status Change\n * events on the default AWS bus and republishes terminal-success events\n * (`CREATE_COMPLETE` / `UPDATE_COMPLETE`) for OpenHi-tagged stacks onto\n * the control event bus as `platform.deployment-completed.v1`.\n *\n * Implements row 4 of the workflow placement matrix\n * (codedrifters/openhi#953): ops-plane reactor → republishes to\n * control event bus.\n */\nexport class PlatformDeployBridge extends Construct {\n public readonly bridgeLambda: PlatformDeployBridgeLambda;\n\n constructor(scope: Construct, props: PlatformDeployBridgeProps) {\n super(scope, \"platform-deploy-bridge\");\n\n this.bridgeLambda = new PlatformDeployBridgeLambda(this, {\n controlEventBus: props.controlEventBus,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, Stack } from \"aws-cdk-lib\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n BRIDGED_STATUSES,\n CLOUDFORMATION_EVENT_SOURCE,\n CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,\n CONTROL_EVENT_BUS_NAME_ENV_VAR,\n OPENHI_REPO_TAG_KEY_ENV_VAR,\n OPENHI_TAG_KEY_PREFIX_ENV_VAR,\n} from \"./events\";\nimport {\n OPENHI_TAG_SUFFIX_REPO_NAME,\n OpenHiService,\n openHiTagKey,\n} from \"../../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/platform-deploy-bridge/index.md\n */\n\nconst HANDLER_NAME = \"platform-deploy-bridge.handler.js\";\n\n/**\n * Resolve handler entry so it works from src/ (tests) or lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface PlatformDeployBridgeLambdaProps {\n /** Destination control event bus the bridge republishes onto. */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Lambda that bridges CloudFormation Stack Status Change events from the\n * default AWS bus into typed `platform.deployment-completed.v1` envelopes on\n * the OpenHI control event bus.\n *\n * Owns its EventBridge Rule (on the default AWS bus) and the IAM\n * permissions it needs — colocating routing + permissions with the\n * function they target.\n *\n * The EventBridge rule pre-filters by stack-id prefix so the rule (and\n * therefore the Lambda) only fires on the host stack's own branch deploys.\n * This prevents cross-branch leak when multiple branches are deployed into\n * the same account.\n */\nexport class PlatformDeployBridgeLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: PlatformDeployBridgeLambdaProps) {\n super(scope, \"platform-deploy-bridge-lambda\");\n\n // Resolve the host OpenHiService so we can pin the rule to this\n // branch's stacks and project the appName-aware tag key.\n const service = OpenHiService.of(this) as OpenHiService;\n const repoTagKey = openHiTagKey(\n service.appName,\n OPENHI_TAG_SUFFIX_REPO_NAME,\n );\n const tagKeyPrefix = `${service.appName}:`;\n\n // Derive the shared sibling-stack prefix from this stack's own\n // synthesized name. `service.branchHash` alone (e.g. `4e4512-`)\n // is wrong because CDK prepends the Stage hierarchy\n // (`<stage>-<environment>-…`) to every stack name, so the\n // CloudFormation events carry stack-ids like\n // `dev-primary-4e4512-data-…`. Stripping the known\n // `-<serviceId>-<account>-<region>` suffix off the bridge's own\n // stack name yields the prefix every sibling stack shares.\n const ownStackName = Stack.of(this).stackName;\n const ownSuffix = `-${service.serviceId}-${Stack.of(this).account}-${\n Stack.of(this).region\n }`;\n const sharedPrefix = ownStackName.endsWith(ownSuffix)\n ? ownStackName.slice(0, -ownSuffix.length)\n : service.branchHash;\n const stackIdPrefix = `arn:aws:cloudformation:${Stack.of(this).region}:${\n Stack.of(this).account\n }:stack/${sharedPrefix}-`;\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 256,\n timeout: Duration.seconds(30),\n environment: {\n [CONTROL_EVENT_BUS_NAME_ENV_VAR]: props.controlEventBus.eventBusName,\n [OPENHI_REPO_TAG_KEY_ENV_VAR]: repoTagKey,\n [OPENHI_TAG_KEY_PREFIX_ENV_VAR]: tagKeyPrefix,\n },\n });\n\n // Fetch stack tags so the handler can project them into the envelope.\n // Scope to stacks in the deploying account/region.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"cloudformation:DescribeStacks\"],\n resources: [\n `arn:aws:cloudformation:${Stack.of(this).region}:${\n Stack.of(this).account\n }:stack/*`,\n ],\n }),\n );\n\n // Republish the projected envelope onto the control event bus.\n props.controlEventBus.grantPutEventsTo(this.lambda);\n\n // Rule lives on the default AWS bus — that is where AWS publishes\n // its own CloudFormation Stack Status Change events. Omitting\n // `eventBus` defaults to the account's default bus.\n //\n // The `stack-id` prefix scopes the rule to this branch's own stacks\n // only (OpenHi stack names start with the deterministic per-branch\n // hash), so other branches' deploys never trigger this Lambda even\n // though every branch deploys its own bridge into the same account.\n this.rule = new Rule(this, \"rule\", {\n eventPattern: {\n source: [CLOUDFORMATION_EVENT_SOURCE],\n detailType: [CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE],\n detail: {\n \"stack-id\": [{ prefix: stackIdPrefix }],\n \"status-details\": {\n status: [...BRIDGED_STATUSES],\n },\n },\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, Stack } from \"aws-cdk-lib\";\nimport { IUserPool } from \"aws-cdk-lib/aws-cognito\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport { PlatformSystemDataSeededV1 } from \"./events\";\nimport { SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR } from \"./seed-demo-data.handler\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/seed-demo-data-lambda.md\n */\n\nconst HANDLER_NAME = \"seed-demo-data.handler.js\";\n\n/**\n * Resolve the bundled handler entry. Same dual-path lookup the\n * seed-system-data Lambda uses: src/ for tests (the file lives next\n * to this one) or lib/ for the compiled bundle.\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface SeedDemoDataLambdaProps {\n /**\n * Data-store table the workflow upserts demo-data records into.\n * Wired via `DYNAMO_TABLE_NAME` env var; granted `dynamodb:GetItem`\n * (pre-flight Role lookup) and `dynamodb:PutItem`/`dynamodb:UpdateItem`\n * (write phase). The grants are scoped to the table ARN only; the\n * handler itself is the scope guarantee for which records the\n * workflow touches (see the construct body for the previous\n * `LeadingKeys`-based grants and the reason they were dropped).\n */\n readonly dataStoreTable: ITable;\n\n /**\n * Control event bus that re-publishes\n * `platform.deployment-completed.v1` from the platform-deploy bridge.\n * The Rule mounts here.\n */\n readonly controlEventBus: IEventBus;\n\n /**\n * Cognito User Pool the workflow provisions dev users into. The\n * Lambda's IAM grant is scoped to this exact user-pool ARN — the\n * grant uses the user-pool ARN, **not** the wildcard formatArn\n * pattern used by `post-authentication-lambda` (that Lambda's\n * trigger-driven dependency cycle does not apply here, so the\n * tighter scope is safe).\n */\n readonly userPool: IUserPool;\n}\n\n/**\n * Lambda + EventBridge Rule pair for the seed-demo-data workflow.\n * Owns the routing (`source` / `detail-type` pattern), the scoped\n * DynamoDB grants, and the scoped Cognito Admin grant — co-locating\n * routing + permissions with the function they target. Wiring to the\n * workflow dedup table is the parent construct's job (it has the\n * singleton reference) and happens via `WorkflowDedupTable.grantConsumer`.\n *\n * Stage-gating is the parent's job too — this construct itself never\n * checks the stage. The CDK stage-router (`OpenHiDataService`)\n * decides whether to instantiate it at all on each stage.\n */\nexport class SeedDemoDataLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: SeedDemoDataLambdaProps) {\n super(scope, \"seed-demo-data-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(2),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n [SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR]: props.userPool.userPoolId,\n },\n });\n\n // Pre-flight Role check: read the three platform-singleton Role\n // PKs. The grant is scoped to the table ARN with no `LeadingKeys`\n // condition; the handler enumerates the exact Role ids it reads\n // and is the actual scope guarantee. The previous\n // `ForAllValues:StringEquals` / `dynamodb:LeadingKeys` condition\n // (one entry per Role id) was dropped because the rendered\n // managed-policy JSON — combined with the write grant below —\n // exceeded the 6,144-byte IAM managed-policy quota and broke every\n // `Deploy openhi-data dev/primary` run. The Lambda only runs on\n // stage-gated non-prod stages (no production data is at risk), so\n // the belt-and-suspenders IAM condition is not worth the deploy\n // breakage.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:GetItem\"],\n resources: [props.dataStoreTable.tableArn],\n }),\n );\n\n // Write grant: scoped to the table ARN with no `LeadingKeys`\n // condition. The handler writes a deterministic, hand-coded set\n // of demo Tenant / Workspace / Membership / RoleAssignment / User\n // records plus the OPS-009 v1 data-plane FHIR fixtures (Patient /\n // Practitioner / Observation / Encounter / Account) — that\n // enumeration is the scope guarantee, not the IAM policy. See the\n // pre-flight grant above for why the previous\n // `ForAllValues:StringEquals` condition was dropped.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:PutItem\", \"dynamodb:UpdateItem\"],\n resources: [props.dataStoreTable.tableArn],\n }),\n );\n\n // Cognito grant. The User Pool is imported by ARN from the auth\n // stack via SSM lookup, so reconstructing the ARN with\n // `Stack.of(this).formatArn` scopes the grant to this exact\n // account+region+pool without depending on the auth stack's\n // exported ARN.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"cognito-idp:AdminCreateUser\",\n \"cognito-idp:AdminGetUser\",\n \"cognito-idp:AdminSetUserPassword\",\n ],\n resources: [\n Stack.of(this).formatArn({\n service: \"cognito-idp\",\n resource: \"userpool\",\n resourceName: props.userPool.userPoolId,\n }),\n ],\n }),\n );\n\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.controlEventBus,\n eventPattern: {\n source: [PlatformSystemDataSeededV1.source],\n detailType: [PlatformSystemDataSeededV1.detailType],\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import { IUserPool } from \"aws-cdk-lib/aws-cognito\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { SEED_DEMO_DATA_CONSUMER_NAME } from \"./events\";\nimport { SeedDemoDataLambda } from \"./seed-demo-data-lambda\";\nimport { WorkflowDedupTable } from \"../../../components/dynamodb/workflow-dedup-table\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/seed-demo-data-workflow.md\n */\n\nexport interface SeedDemoDataWorkflowProps {\n /** Control event bus carrying `platform.system-data-seeded.v1`. */\n readonly controlEventBus: IEventBus;\n /** Data-store table the workflow upserts demo-data records into. */\n readonly dataStoreTable: ITable;\n /** Cognito User Pool the workflow provisions dev users into. */\n readonly userPool: IUserPool;\n}\n\n/**\n * Control-plane workflow that fires on every platform deploy and\n * idempotently re-asserts the demo-data graph: placeholder tenant +\n * workspace, 3 demo tenants + 4 workspaces, and per-dev-user Cognito\n * users with their DynamoDB User records, Memberships, and\n * RoleAssignments.\n *\n * Mounted on the data-service stack so the IAM grants against the\n * data-store table stay local. The control event bus and the workflow\n * dedup table reach in cross-stack via the SSM lookups\n * `OpenHiGlobalService.controlEventBusFromConstruct` and\n * `WorkflowDedupTable.grantConsumerFromLookup` respectively. The\n * Cognito User Pool similarly reaches in via\n * `OpenHiAuthService.userPoolFromConstruct`.\n *\n * Non-prod-only: the CDK stage-router (`OpenHiDataService`)\n * conditionally constructs this workflow only on non-prod stages.\n * The construct itself never checks the stage — its absence in prod\n * stacks is the gate.\n */\nexport class SeedDemoDataWorkflow extends Construct {\n public readonly seedDemoData: SeedDemoDataLambda;\n\n constructor(scope: Construct, props: SeedDemoDataWorkflowProps) {\n super(scope, \"seed-demo-data-workflow\");\n\n this.seedDemoData = new SeedDemoDataLambda(this, {\n controlEventBus: props.controlEventBus,\n dataStoreTable: props.dataStoreTable,\n userPool: props.userPool,\n });\n\n // Cross-stack grant resolves the dedup table's name + ARN via SSM\n // at synth time, so the data-service stack does not pick up a\n // CloudFormation export dependency on the global stack that owns\n // the dedup table.\n WorkflowDedupTable.grantConsumerFromLookup(\n this,\n this.seedDemoData.lambda,\n SEED_DEMO_DATA_CONSUMER_NAME,\n );\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { PLATFORM_ROLE_IDS } from \"@openhi/types\";\nimport { Duration, Stack } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n PlatformDeploymentCompletedV1,\n SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-system-data/seed-system-data-lambda.md\n */\n\nconst HANDLER_NAME = \"seed-system-data.handler.js\";\n\n/**\n * Resolve the bundled handler entry. Same dual-path lookup the user-onboarding\n * Lambda uses: src/ for tests (the file lives next to this one) or lib/ for\n * the compiled bundle.\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface SeedSystemDataLambdaProps {\n /**\n * Data-store table the workflow upserts platform-singleton control-plane\n * records into. Wired via `DYNAMO_TABLE_NAME` env var; granted scoped\n * write permission to the role records' partition keys only.\n */\n readonly dataStoreTable: ITable;\n\n /**\n * Control event bus that re-publishes\n * `platform.deployment-completed.v1` from the platform-deploy bridge.\n * The Rule mounts here.\n */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Lambda + EventBridge Rule pair for the seed-system-data workflow. Owns\n * the routing (`source` / `detail-type` pattern) and the scoped data-store\n * grants — co-locating routing + permissions with the function they\n * target. Wiring to the workflow dedup table is the parent construct's\n * job (it has the singleton reference) and happens via\n * `WorkflowDedupTable.grantConsumer`.\n */\nexport class SeedSystemDataLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: SeedSystemDataLambdaProps) {\n super(scope, \"seed-system-data-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n [SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR]:\n props.controlEventBus.eventBusName,\n },\n });\n\n // Least-privilege grant: only the three known role PKs are\n // writable. A regression that tried to write a non-Role record\n // (or a Role with an unrecognized id) would be rejected by IAM,\n // not by application code. Updating the role-id set means\n // amending the generator's `ID_PREFIX_BY_VALUE_SET_URL` table\n // and regenerating — the role-id values flow through.\n //\n // The leading-keys values must match what ElectroDB *actually\n // writes*, not the entity definition's pretty template. The\n // base-table PK template `ROLE#ID#${id}` has no\n // `casing: \"none\"`, so ElectroDB applies its default casing\n // (lowercase) at runtime and the on-the-wire PK is\n // `role#id#<id>`. Authoring the policy in the uppercase\n // template form silently produces an IAM denial on every\n // PutItem the seeder attempts.\n const roleArns = Object.values(PLATFORM_ROLE_IDS).map(\n (id) => `role#id#${id}`,\n );\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:PutItem\", \"dynamodb:UpdateItem\"],\n resources: [props.dataStoreTable.tableArn],\n conditions: {\n \"ForAllValues:StringEquals\": {\n \"dynamodb:LeadingKeys\": roleArns,\n },\n },\n }),\n );\n\n // Allow the handler to publish `platform.system-data-seeded.v1`\n // onto the control event bus after a successful seed. Downstream\n // consumers (`seed-demo-data`) subscribe to that event instead\n // of the raw deploy-completion event so the dependency is\n // enforced by a happens-before edge rather than by EventBridge\n // retry timing.\n props.controlEventBus.grantPutEventsTo(this.lambda);\n\n // Gate the rule on the host (data) stack's own completion event.\n // The data-store table is created by this stack, so seeding only\n // becomes safe once this stack reaches CREATE/UPDATE_COMPLETE.\n // Without this filter, sibling stacks (global, auth, rest-api,\n // graphql) completing before the data stack would trigger the\n // seeder against a not-yet-created table.\n //\n // The filter targets `detail.payload.stackName`: the outer\n // `detail` is EventBridge's envelope (which holds the whole\n // workflow envelope), and `payload` is the per-workflow payload\n // field on `WorkflowEvent` — i.e. the projection the bridge\n // produced from the CloudFormation Stack Status Change event.\n const hostStackName = Stack.of(this).stackName;\n\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.controlEventBus,\n eventPattern: {\n source: [PlatformDeploymentCompletedV1.source],\n detailType: [PlatformDeploymentCompletedV1.detailType],\n detail: {\n payload: {\n stackName: [hostStackName],\n },\n },\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { SEED_SYSTEM_DATA_CONSUMER_NAME } from \"./events\";\nimport { SeedSystemDataLambda } from \"./seed-system-data-lambda\";\nimport { WorkflowDedupTable } from \"../../../components/dynamodb/workflow-dedup-table\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-system-data/seed-system-data-workflow.md\n */\n\nexport interface SeedSystemDataWorkflowProps {\n /** Control event bus carrying `platform.deployment-completed.v1`. */\n readonly controlEventBus: IEventBus;\n /** Data-store table the workflow upserts platform-singleton records into. */\n readonly dataStoreTable: ITable;\n}\n\n/**\n * Control-plane workflow that fires on every platform deploy and\n * idempotently re-asserts the platform-singleton control-plane records\n * (today: the three canonical Roles; future: additional system data\n * slotted in as sibling steps under the same dedup record).\n *\n * Mounted on the data-service stack so the IAM grants against the\n * data-store table stay local. The control event bus and the\n * workflow dedup table reach in cross-stack via the SSM lookups\n * `OpenHiGlobalService.controlEventBusFromConstruct` and\n * `WorkflowDedupTable.grantConsumerFromLookup` respectively.\n */\nexport class SeedSystemDataWorkflow extends Construct {\n public readonly seedSystemData: SeedSystemDataLambda;\n\n constructor(scope: Construct, props: SeedSystemDataWorkflowProps) {\n super(scope, \"seed-system-data-workflow\");\n\n this.seedSystemData = new SeedSystemDataLambda(this, {\n controlEventBus: props.controlEventBus,\n dataStoreTable: props.dataStoreTable,\n });\n\n // Cross-stack grant resolves the dedup table's name + ARN via SSM\n // at synth time, so the data-service stack does not pick up a\n // CloudFormation export dependency on the global stack that owns\n // the dedup table.\n WorkflowDedupTable.grantConsumerFromLookup(\n this,\n this.seedSystemData.lambda,\n SEED_SYSTEM_DATA_CONSUMER_NAME,\n );\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport { ICertificate } from \"aws-cdk-lib/aws-certificatemanager\";\nimport { IHostedZone } from \"aws-cdk-lib/aws-route53\";\nimport { Bucket, IBucket } from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport { OpenHiRestApiService } from \"./open-hi-rest-api-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport {\n PER_BRANCH_PREVIEW_PREFIX,\n PerBranchHostname,\n StaticContent,\n StaticHosting,\n} from \"../components/static-hosting\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-website-service.md\n */\n\nexport interface OpenHiWebsiteServiceProps extends OpenHiServiceProps {\n /**\n * Sub-domain prefix attached to the child zone (e.g. \"www\" -\\> \"www.\\<zone\\>\").\n *\n * @default \"www\"\n */\n readonly domainPrefix?: string;\n\n /**\n * Absolute path to the local directory whose contents should be uploaded\n * to the static-hosting bucket. Required.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Path under the per-branch destination prefix to upload into. Should start\n * with a slash.\n *\n * @default \"/\"\n */\n readonly contentDestinationDirectory?: string;\n\n /**\n * Force the `StaticHosting` infrastructure (bucket + distribution +\n * Lambda@Edge + DNS + 4 SSM params) to be created on this branch even when\n * it is not the release branch. Useful for one-off bootstraps and tests.\n *\n * When omitted, hosting infrastructure is created only on\n * `defaultReleaseBranch`. The `StaticContent` uploader is always\n * created so feature branches can publish their content under their own\n * sub-domain folder against the release-branch bucket.\n *\n * @default - true on release branch, false otherwise\n */\n readonly createHostingInfrastructure?: boolean;\n\n /**\n * Whether to create the `StaticContent` uploader. Set to `false` to skip\n * it entirely on every branch — used as a one-shot bootstrap toggle while\n * the release-branch deploy of this service first creates the static-hosting\n * bucket and writes `STATIC_HOSTING_BUCKET_ARN` to SSM. Once that\n * parameter exists, flip back to `true` so feature-branch deploys can read\n * it and upload content under their per-branch sub-domain folder.\n *\n * @default true\n */\n readonly createStaticContent?: boolean;\n\n /**\n * When `true`, the website's CloudFront distribution proxies `/api/*` to\n * the REST API service deployed for this branch. The API custom-domain\n * hostname is resolved at synth time via SSM\n * ({@link OpenHiRestApiService.restApiDomainNameFromConstruct}), so the\n * REST API stack must have written its `REST_API_DOMAIN_NAME` SSM\n * parameter at least once before the website stack updates — the\n * workflow already orders these deploys (rest-api → website).\n *\n * Used together with the admin-console's runtime-config fetch (which\n * calls `/api/control/runtime-config` same-origin) so the React bundle\n * stays branch-agnostic.\n *\n * Only takes effect when `createHostingInfrastructure` is also true,\n * since the additional CloudFront behaviors live on the release-branch\n * distribution; feature-branch deploys share that distribution.\n *\n * @default false\n */\n readonly restApi?: boolean;\n}\n\n/**\n * SSM parameter name suffix for the website's full domain\n * (e.g. www.example.com).\n */\nexport const SSM_PARAM_NAME_FULL_DOMAIN = \"WEBSITE_FULL_DOMAIN\";\n\n/**\n * Sub-domain prefix the openhi admin console is deployed under. The\n * website-service deploys at `admin.<zone>` (release branch) or\n * `admin-<childZonePrefix>.<zone>` (per-PR), so any stack that needs to\n * reference the admin console's hostname — most notably the REST API\n * stack composing its CORS `allowOrigins` — should import this constant\n * rather than redeclaring the literal.\n *\n * @public\n */\nexport const ADMIN_DOMAIN_PREFIX = \"admin\";\n\n/**\n * Website service stack. Release-branch deploys compose `StaticHosting`\n * (bucket + CloudFront distribution with a wildcard SAN for per-PR\n * previews + Lambda@Edge + DNS) and `StaticContent` (content upload).\n * Non-release-branch deploys compose `PerBranchHostname` (a per-PR\n * Route53 alias aliased to the release distribution) and `StaticContent`\n * (per-PR upload to the shared bucket) so feature branches publish\n * previews without provisioning duplicate infrastructure.\n *\n * Resources are created in protected methods; subclasses may override to\n * customize.\n */\nexport class OpenHiWebsiteService extends OpenHiService {\n static readonly SERVICE_TYPE = \"website\" as const satisfies OpenHiServiceType;\n\n /**\n * Default `domainPrefix` for this service when none is supplied.\n * Release-branch hostname is `www.<zone>`; per-PR preview hostname is\n * `www-<childZonePrefix>.<zone>`.\n */\n static readonly DEFAULT_DOMAIN_PREFIX = \"www\";\n\n /**\n * Compose the website's full per-deploy domain. Thin wrapper over\n * {@link OpenHiService.composeServiceDomain} that fills in\n * {@link DEFAULT_DOMAIN_PREFIX} when `domainPrefix` is omitted.\n *\n * Use from sibling stacks that need to predict the website's hostname\n * before the website stack is synthesised — e.g. the REST API stack\n * computing its CORS `allowOrigins` for the admin-console.\n */\n static composeFullDomain(opts: {\n domainPrefix?: string;\n branchName: string;\n defaultReleaseBranch: string;\n childZonePrefix: string;\n zoneName: string;\n }): string {\n return OpenHiService.composeServiceDomain({\n ...opts,\n domainPrefix:\n opts.domainPrefix ?? OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX,\n });\n }\n\n /**\n * Looks up the static-hosting bucket ARN published by the release-branch\n * deploy of this service.\n */\n static bucketArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the CloudFront distribution ARN published by the release-branch\n * deploy of this service.\n */\n static distributionArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ARN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the CloudFront distribution domain\n * (e.g. dXXXXX.cloudfront.net) published by the release-branch deploy.\n */\n static distributionDomainFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_DOMAIN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the CloudFront distribution ID published by the release-branch\n * deploy of this service.\n */\n static distributionIdFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ID,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the website's full domain (e.g. www.example.com) published by\n * the release-branch deploy of this service.\n */\n static fullDomainFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: SSM_PARAM_NAME_FULL_DOMAIN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n get serviceType(): string {\n return OpenHiWebsiteService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options. */\n public override props: OpenHiWebsiteServiceProps;\n\n /**\n * Full domain served by this website. On the release branch this is\n * `<domainPrefix>.<zone>` (e.g. `admin.dev.openhi.org`); on every\n * other branch it is the per-PR preview hostname\n * `<domainPrefix>-<childZonePrefix>.<zone>`\n * (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`), where\n * `childZonePrefix` is `paramCase(branchName)` truncated to 56 chars.\n */\n public readonly fullDomain: string;\n\n /**\n * The hosting construct, only created on release-branch deploys (or when\n * `createHostingInfrastructure` is true).\n */\n public readonly staticHosting?: StaticHosting;\n\n /**\n * The content uploader. Created on every deploy unless\n * {@link OpenHiWebsiteServiceProps.createStaticContent} is `false`, in\n * which case the property is `undefined` and the stack ships no\n * `BucketDeployment`. Used during release-branch bootstrap, before the\n * shared static-hosting bucket has been written to SSM for the first time.\n */\n public readonly staticContent?: StaticContent;\n\n /**\n * Per-PR alias record. Created on non-release-branch deploys (when\n * `createHostingInfrastructure` is left at its default) so the PR\n * hostname `<domainPrefix>-<childZonePrefix>.<zone>` resolves to the\n * release-branch CloudFront distribution. `undefined` on release-branch\n * deploys and on bootstrap deploys that force hosting infra on.\n */\n public readonly perBranchHostname?: PerBranchHostname;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiWebsiteServiceProps) {\n super(ohEnv, OpenHiWebsiteService.SERVICE_TYPE, props);\n this.props = props;\n\n this.validateConfig(props);\n\n const isReleaseBranch = this.branchName === this.defaultReleaseBranch;\n\n const hostedZone = this.createHostedZone();\n this.fullDomain = this.computeFullDomain(hostedZone);\n\n const shouldCreateHostingInfra =\n props.createHostingInfrastructure ?? isReleaseBranch;\n\n if (shouldCreateHostingInfra) {\n const certificate = this.createCertificate();\n this.staticHosting = this.createStaticHosting({\n certificate,\n hostedZone,\n });\n this.createFullDomainParameter();\n } else if (!isReleaseBranch) {\n this.perBranchHostname = this.createPerBranchHostname(hostedZone);\n }\n\n if (props.createStaticContent !== false) {\n const bucket = this.resolveStaticHostingBucket();\n this.staticContent = this.createStaticContent(bucket);\n }\n }\n\n /**\n * Validates that config required for the website stack is present.\n */\n protected validateConfig(props: OpenHiWebsiteServiceProps): void {\n const { config } = props;\n if (!config) {\n throw new Error(\"Config is required\");\n }\n if (!config.zoneName) {\n throw new Error(\"Zone name is required\");\n }\n if (!config.hostedZoneId) {\n throw new Error(\"Hosted zone ID is required to import the website zone\");\n }\n }\n\n /**\n * Imports the website's hosted zone from config attributes (no SSM lookup).\n * The website attaches DNS records here on the release-branch deploy and\n * the same zone is imported on feature-branch deploys for any sub-domain\n * routing.\n * Override to customize.\n */\n protected createHostedZone(): IHostedZone {\n return OpenHiGlobalService.rootHostedZoneFromConstruct(this, {\n zoneName: this.config.zoneName!,\n hostedZoneId: this.config.hostedZoneId!,\n });\n }\n\n /**\n * Returns the wildcard certificate looked up from the Global service.\n * Override to customize.\n */\n protected createCertificate(): ICertificate {\n return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);\n }\n\n /**\n * Computes the full website domain from `domainPrefix`,\n * `childZonePrefix`, and the child zone name. Release-branch deploys\n * serve at `\\<domainPrefix\\>.\\<zone\\>` (e.g. `admin.dev.openhi.org`);\n * every other deploy serves a per-PR preview at\n * `\\<domainPrefix\\>-\\<childZonePrefix\\>.\\<zone\\>`\n * (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`).\n *\n * Delegates to {@link OpenHiWebsiteService.composeFullDomain} so the\n * release-vs-feature composition stays in one place.\n */\n protected computeFullDomain(hostedZone: IHostedZone): string {\n return OpenHiWebsiteService.composeFullDomain({\n domainPrefix: this.props.domainPrefix,\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName: hostedZone.zoneName,\n });\n }\n\n /**\n * Returns the sub-domain label (left of the zone) for the current\n * deploy. Used for the per-branch S3 key prefix passed to\n * {@link StaticContent} so the upload prefix always matches the\n * served hostname.\n *\n * Non-release deploys compose the per-PR slug as\n * `\\<domainPrefix\\>-\\<childZonePrefix\\>`, mirroring the REST API's\n * `api-\\<childZonePrefix\\>` convention. When `domainPrefix` is `admin`\n * (the only consumer today), the resulting sub-domain starts with\n * {@link PER_BRANCH_PREVIEW_PREFIX}, so the per-PR S3 key prefix\n * matches what `StaticHosting`'s lifecycle rule expires.\n */\n protected computeSubDomain(): string {\n const isReleaseBranch = this.branchName === this.defaultReleaseBranch;\n const domainPrefix =\n this.props.domainPrefix ?? OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX;\n if (isReleaseBranch) {\n return domainPrefix;\n }\n return `${domainPrefix}-${this.childZonePrefix}`;\n }\n\n /**\n * Creates the StaticHosting infrastructure (bucket + distribution +\n * Lambda@Edge + 4 SSM params + DNS). The release-branch distribution\n * adds `*.\\<zone\\>` as a wildcard alt-name on top of the canonical\n * hostname so per-PR previews resolve via the same distribution.\n *\n * The bucket carries an S3 lifecycle rule that expires per-PR\n * preview content (keys under {@link PER_BRANCH_PREVIEW_PREFIX})\n * on non-production stages. PROD never gets the rule — see\n * `enablePreviewLifecycle`.\n */\n protected createStaticHosting(deps: {\n certificate: ICertificate;\n hostedZone: IHostedZone;\n }): StaticHosting {\n const restApi =\n this.props.restApi === true ? this.resolveRestApi() : undefined;\n const wildcardSan = `*.${deps.hostedZone.zoneName}`;\n return new StaticHosting(this, \"static-hosting\", {\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n certificate: deps.certificate,\n hostedZone: deps.hostedZone,\n domainNames: [this.fullDomain, wildcardSan],\n description: `OpenHI website (${this.fullDomain})`,\n prefixPattern: PER_BRANCH_PREVIEW_PREFIX,\n enablePreviewLifecycle:\n this.ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD,\n ...(restApi !== undefined && { restApi }),\n });\n }\n\n /**\n * Resolves the REST API custom-domain hostname from the rest-api stack's\n * `REST_API_DOMAIN_NAME` SSM parameter. Wrapped in a private method so\n * it can be overridden / stubbed in subclasses and tests.\n */\n protected resolveRestApi(): { domainName: string } {\n return {\n domainName: OpenHiRestApiService.restApiDomainNameFromConstruct(this),\n };\n }\n\n /**\n * Creates the SSM parameter that publishes the website's full domain.\n * Look up via {@link OpenHiWebsiteService.fullDomainFromConstruct}.\n */\n protected createFullDomainParameter(): void {\n new DiscoverableStringParameter(this, \"full-domain-param\", {\n ssmParamName: SSM_PARAM_NAME_FULL_DOMAIN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n stringValue: this.fullDomain,\n description: \"Full website domain (e.g. www.example.com)\",\n });\n }\n\n /**\n * Creates the StaticContent uploader. Receives the resolved static-hosting\n * bucket from the constructor — on the release-branch deploy this is the\n * just-created {@link staticHosting} bucket (no SSM round-trip within a\n * single stack); on every other deploy it is imported from the bucket ARN\n * the release-branch deploy publishes to SSM, addressed against\n * {@link OpenHiService.releaseBranchHash}. See\n * {@link resolveStaticHostingBucket}.\n *\n * The S3 key prefix is `\\<sub-domain\\>.\\<zone\\>/\\<contentDest\\>` so the\n * upload location matches the Host-header-derived folder the Lambda@Edge\n * viewer-request handler prepends. Passing the zone name (rather than\n * `this.fullDomain`) for the `fullDomain` prop keeps the prefix flat —\n * `admin-feat-foo.dev.openhi.org/`, not\n * `admin-feat-foo.admin.dev.openhi.org/`.\n */\n protected createStaticContent(bucket: IBucket): StaticContent {\n const { contentSourceDirectory, contentDestinationDirectory } = this.props;\n return new StaticContent(this, \"static-content\", {\n bucket,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain: this.computeSubDomain(),\n fullDomain: this.config.zoneName!,\n });\n }\n\n /**\n * Creates the per-PR `PerBranchHostname` alias record on non-release\n * branch deploys. The record points\n * `\\<domainPrefix\\>-\\<childZonePrefix\\>.\\<zone\\>` at the release-branch\n * CloudFront distribution (resolved from SSM against\n * {@link OpenHiService.releaseBranchHash}).\n */\n protected createPerBranchHostname(\n hostedZone: IHostedZone,\n ): PerBranchHostname {\n return new PerBranchHostname(this, \"per-branch-hostname\", {\n hostname: this.fullDomain,\n hostedZone,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Returns an {@link IBucket} pointing at the static-hosting bucket the\n * uploaders write to. On the release-branch deploy this is the bucket\n * just provisioned by {@link staticHosting}; on every other deploy it's\n * imported from the bucket ARN the release-branch deploy publishes to\n * SSM, addressed against {@link OpenHiService.releaseBranchHash}.\n */\n protected resolveStaticHostingBucket(): IBucket {\n if (this.staticHosting) {\n return this.staticHosting.bucket;\n }\n const bucketArn = DiscoverableStringParameter.valueForLookupName(this, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n branchHash: this.releaseBranchHash,\n });\n return Bucket.fromBucketArn(this, \"shared-bucket\", bucketArn);\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport {\n CorsHttpMethod,\n CorsPreflightOptions,\n DomainName,\n HttpApi,\n HttpMethod,\n HttpNoneAuthorizer,\n HttpRoute,\n HttpRouteKey,\n IHttpApi,\n} from \"aws-cdk-lib/aws-apigatewayv2\";\nimport { HttpUserPoolAuthorizer } from \"aws-cdk-lib/aws-apigatewayv2-authorizers\";\nimport { HttpLambdaIntegration } from \"aws-cdk-lib/aws-apigatewayv2-integrations\";\nimport { ICertificate } from \"aws-cdk-lib/aws-certificatemanager\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport {\n ARecord,\n HostedZone,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { ApiGatewayv2DomainProperties } from \"aws-cdk-lib/aws-route53-targets\";\nimport { Duration } from \"aws-cdk-lib/core\";\nimport { Construct } from \"constructs\";\nimport { OpenHiAuthService } from \"./open-hi-auth-service\";\nimport { OpenHiDataService } from \"./open-hi-data-service\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport {\n ADMIN_DOMAIN_PREFIX,\n OpenHiWebsiteService,\n} from \"./open-hi-website-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport {\n RootHttpApi,\n RootHttpApiProps,\n} from \"../components/api-gateway/root-http-api\";\nimport {\n DataStorePostgresReplica,\n getPostgresReplicaSchemaName,\n} from \"../components/postgres/data-store-postgres-replica\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport { CorsOptionsLambda } from \"../data/lambda/cors-options-lambda\";\nimport { RestApiLambda } from \"../data/lambda/rest-api-lambda\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-rest-api-service.md\n */\n\nexport interface OpenHiRestApiServiceProps extends OpenHiServiceProps {\n /**\n * Optional props passed through to the RootHttpApi (API Gateway HTTP API) construct.\n * Use corsPreflight (CDK CorsPreflightOptions) for CORS; other HttpApiProps (e.g. description, disableExecuteApiEndpoint) apply as well.\n */\n readonly rootHttpApiProps?: RootHttpApiProps;\n}\n\n/**\n * SSM parameter name suffix for the REST API base URL.\n * Full parameter name is built via buildParameterName with serviceType REST_API.\n */\nexport const REST_API_BASE_URL_SSM_NAME = \"REST_API_BASE_URL\";\n\n/**\n * SSM parameter name suffix for the REST API's custom domain (bare hostname,\n * no scheme — e.g. `api.example.com`). Consumed by the website service as\n * the CloudFront `/api/*` origin host.\n */\nexport const REST_API_DOMAIN_NAME_SSM_NAME = \"REST_API_DOMAIN_NAME\";\n\n/**\n * Localhost / 127.0.0.1 dev origins auto-injected into CORS `allowOrigins`\n * on every non-prod (`stageType !== \"prod\"`) REST API deploy. Both schemes\n * (`http`, `https`) and both ports the local SPAs use (`3000`, `5173`) are\n * covered so admin-console / on-site previews running on `localhost` or\n * `127.0.0.1` can call the API direct cross-origin without per-consumer\n * boilerplate.\n */\nexport const DEV_CORS_ALLOW_ORIGINS: ReadonlyArray<string> = [\n \"http://localhost:3000\",\n \"https://localhost:3000\",\n \"http://localhost:5173\",\n \"https://localhost:5173\",\n \"http://127.0.0.1:3000\",\n \"https://127.0.0.1:3000\",\n \"http://127.0.0.1:5173\",\n \"https://127.0.0.1:5173\",\n];\n\n/**\n * REST API service stack: HTTP API, custom domain, and Lambda; exports base URL via SSM.\n * Resources are created in protected methods; subclasses may override to customize.\n */\nexport class OpenHiRestApiService extends OpenHiService {\n static readonly SERVICE_TYPE =\n \"rest-api\" as const satisfies OpenHiServiceType;\n\n /**\n * Sub-domain prefix used by the REST API. Release-branch hostname is\n * `api.<zone>`; per-PR preview hostname is `api-<childZonePrefix>.<zone>`.\n */\n static readonly API_DOMAIN_PREFIX = \"api\";\n\n /**\n * Compose the REST API's full per-deploy domain. Thin wrapper over\n * {@link OpenHiService.composeServiceDomain} that pins `domainPrefix`\n * to {@link API_DOMAIN_PREFIX}.\n *\n * Use from sibling stacks that need to predict the API's hostname\n * before the REST API stack is synthesised.\n */\n static composeFullDomain(opts: {\n branchName: string;\n defaultReleaseBranch: string;\n childZonePrefix: string;\n zoneName: string;\n }): string {\n return OpenHiService.composeServiceDomain({\n ...opts,\n domainPrefix: OpenHiRestApiService.API_DOMAIN_PREFIX,\n });\n }\n\n /**\n * Returns an IHttpApi by looking up the REST API stack's HTTP API ID from SSM.\n */\n static rootHttpApiFromConstruct(scope: Construct): IHttpApi {\n const httpApiId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: RootHttpApi.SSM_PARAM_NAME,\n serviceType: OpenHiRestApiService.SERVICE_TYPE,\n });\n return HttpApi.fromHttpApiAttributes(scope, \"http-api\", { httpApiId });\n }\n\n /**\n * Returns the REST API base URL (e.g. https://api.example.com) by looking it up from SSM.\n * Use in other stacks for E2E, scripts, or config.\n */\n static restApiBaseUrlFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: REST_API_BASE_URL_SSM_NAME,\n serviceType: OpenHiRestApiService.SERVICE_TYPE,\n });\n }\n\n /**\n * Returns the REST API's custom domain name (bare hostname, no scheme — e.g.\n * `api.example.com`) by looking it up from SSM. Use as the host for a\n * CloudFront `HttpOrigin` so the website's distribution can proxy `/api/*`\n * to this stack's API Gateway without per-branch DNS knowledge.\n */\n static restApiDomainNameFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,\n serviceType: OpenHiRestApiService.SERVICE_TYPE,\n });\n }\n\n get serviceType(): string {\n return OpenHiRestApiService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options (e.g. rootHttpApiProps). */\n public override props: OpenHiRestApiServiceProps;\n\n public readonly rootHttpApi: RootHttpApi;\n\n /**\n * REST API custom domain (bare hostname, no scheme — e.g.\n * `api.dev.openhi.org`). Captured so {@link resolveRuntimeConfigEnvVars}\n * can derive the public `apiBaseUrl` without callers having to hardcode it.\n */\n private readonly apiDomainName: string;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiRestApiServiceProps = {}) {\n super(ohEnv, OpenHiRestApiService.SERVICE_TYPE, props);\n this.props = props;\n\n this.validateConfig(props);\n\n const hostedZone = this.createHostedZone();\n const certificate = this.createCertificate();\n this.apiDomainName = this.createApiDomainNameString(hostedZone);\n this.createRestApiBaseUrlParameter(this.apiDomainName);\n this.createRestApiDomainNameParameter(this.apiDomainName);\n const domainName = this.createDomainName(hostedZone, certificate);\n this.rootHttpApi = this.createRootHttpApi(domainName);\n this.createRestApiLambdaAndRoutes(hostedZone, domainName);\n }\n\n /**\n * Validates that config required for the REST API stack is present.\n */\n protected validateConfig(props: OpenHiRestApiServiceProps): void {\n const { config } = props;\n if (!config) {\n throw new Error(\"Config is required\");\n }\n if (!config.hostedZoneId) {\n throw new Error(\"Hosted zone ID is required\");\n }\n if (!config.zoneName) {\n throw new Error(\"Zone name is required\");\n }\n }\n\n /**\n * Creates the hosted zone reference (imported from config).\n * Override to customize.\n */\n protected createHostedZone(): IHostedZone {\n const { config } = this.props;\n return HostedZone.fromHostedZoneAttributes(this, \"root-zone\", {\n hostedZoneId: config!.hostedZoneId!,\n zoneName: config!.zoneName!,\n });\n }\n\n /**\n * Creates the wildcard certificate (imported from Global stack via SSM).\n * Override to customize.\n */\n protected createCertificate() {\n return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);\n }\n\n /**\n * Returns the API domain name string (e.g. api.example.com or api-\\{prefix\\}.example.com).\n * Delegates to {@link OpenHiRestApiService.composeFullDomain} so the\n * release-vs-feature composition stays in one place; picks up\n * `this.defaultReleaseBranch` (not a hard-coded `\"main\"`).\n * Override to customize.\n */\n protected createApiDomainNameString(hostedZone: IHostedZone): string {\n return OpenHiRestApiService.composeFullDomain({\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName: hostedZone.zoneName,\n });\n }\n\n /**\n * Creates the SSM parameter for the REST API base URL.\n * Look up via {@link OpenHiRestApiService.restApiBaseUrlFromConstruct}.\n * Override to customize.\n */\n protected createRestApiBaseUrlParameter(apiDomainName: string): void {\n const restApiBaseUrl = `https://${apiDomainName}`;\n new DiscoverableStringParameter(this, \"rest-api-base-url-param\", {\n ssmParamName: REST_API_BASE_URL_SSM_NAME,\n stringValue: restApiBaseUrl,\n description: \"REST API base URL for this deployment (E2E, scripts)\",\n });\n }\n\n /**\n * Creates the SSM parameter exposing the REST API's custom domain (bare\n * hostname, no scheme). Consumed by the website service as the CloudFront\n * `/api/*` origin host.\n * Look up via {@link OpenHiRestApiService.restApiDomainNameFromConstruct}.\n * Override to customize.\n */\n protected createRestApiDomainNameParameter(apiDomainName: string): void {\n new DiscoverableStringParameter(this, \"rest-api-domain-name-param\", {\n ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,\n stringValue: apiDomainName,\n description:\n \"REST API custom domain name (bare hostname) for cross-stack CloudFront origin lookup\",\n });\n }\n\n /**\n * Creates the API Gateway custom domain name resource.\n * Override to customize.\n */\n protected createDomainName(\n _hostedZone: IHostedZone,\n certificate: ICertificate,\n ): DomainName {\n const apiDomainName = this.createApiDomainNameString(_hostedZone);\n return new DomainName(this, \"domain\", {\n domainName: apiDomainName,\n certificate,\n });\n }\n\n /**\n * Creates the Lambda integration, HTTP routes, and API DNS record.\n * Override to customize. Uses {@link rootHttpApi} set by the constructor.\n */\n protected createRestApiLambdaAndRoutes(\n hostedZone: IHostedZone,\n domainName: DomainName,\n ): void {\n const dataStoreTable =\n OpenHiDataService.dynamoDbDataStoreFromConstruct(this);\n\n // Phase 2 of ADR 2026-04-17-01: REST API Lambda queries Postgres via the\n // RDS Data API. Cluster ARN, secret ARN, and database name come from SSM\n // (cross-stack discovery); the per-branch schema name is deterministic\n // from `branchHash` and computed locally to avoid an extra SSM lookup.\n const postgresClusterArn =\n DataStorePostgresReplica.clusterArnFromConstruct(this);\n const postgresSecretArn =\n DataStorePostgresReplica.secretArnFromConstruct(this);\n const postgresDatabase =\n DataStorePostgresReplica.databaseNameFromConstruct(this);\n const postgresSchema = getPostgresReplicaSchemaName(this.branchHash);\n\n const extraEnvironment = this.resolveRuntimeConfigEnvVars();\n\n const { lambda } = new RestApiLambda(this, {\n dynamoTableName: dataStoreTable.tableName,\n branchTagValue: this.branchName,\n httpApiTagValue: RootHttpApi.SSM_PARAM_NAME,\n postgresClusterArn,\n postgresSecretArn,\n postgresDatabase,\n postgresSchema,\n extraEnvironment,\n });\n\n // Allow the Lambda to issue Data API statements against this cluster and\n // read the cluster credentials secret. These are scoped to the specific\n // ARNs published by the data stack — no wildcards.\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"rds-data:ExecuteStatement\",\n \"rds-data:BatchExecuteStatement\",\n ],\n resources: [postgresClusterArn],\n }),\n );\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"secretsmanager:GetSecretValue\"],\n resources: [postgresSecretArn],\n }),\n );\n const dynamoActions = [\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n \"dynamodb:BatchGetItem\",\n \"dynamodb:ConditionCheckItem\",\n \"dynamodb:DescribeTable\",\n \"dynamodb:BatchWriteItem\",\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:DeleteItem\",\n ] as const;\n dataStoreTable.grant(lambda, ...dynamoActions);\n // Query (and other operations) on GSIs require the index resource ARN\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [...dynamoActions],\n resources: [`${dataStoreTable.tableArn}/index/*`],\n }),\n );\n // Temporary: broad SSM read for dynamic config (test only)\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"ssm:GetParameter\",\n \"ssm:GetParameters\",\n \"ssm:DescribeParameters\",\n ],\n resources: [\"*\"],\n }),\n );\n const integration = new HttpLambdaIntegration(\"lambda-integration\", lambda);\n const { lambda: optionsLambda } = new CorsOptionsLambda(this);\n const optionsIntegration = new HttpLambdaIntegration(\n \"options-integration\",\n optionsLambda,\n );\n const noAuth = new HttpNoneAuthorizer();\n // OPTIONS routes use dedicated low-memory Lambda so main REST API Lambda is not invoked for preflight (#694).\n new HttpRoute(this, \"options-route-root\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/\", HttpMethod.OPTIONS),\n integration: optionsIntegration,\n authorizer: noAuth,\n });\n new HttpRoute(this, \"options-route-proxy\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/{proxy+}\", HttpMethod.OPTIONS),\n integration: optionsIntegration,\n authorizer: noAuth,\n });\n // `GET /control/runtime-config` is the admin-console's bootstrap endpoint —\n // the SPA fetches it *before* it has a Cognito JWT, so it must bypass the\n // default JWT authorizer applied to the catch-all `/{proxy+}` route below.\n // HTTP APIs match the most-specific route first, so these literal-path\n // routes preempt the proxy automatically. HEAD mirrors GET for parity\n // with the proxy route's HttpMethod.ANY (#1111).\n new HttpRoute(this, \"runtime-config-get-route\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/control/runtime-config\", HttpMethod.GET),\n integration,\n authorizer: noAuth,\n });\n new HttpRoute(this, \"runtime-config-head-route\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/control/runtime-config\", HttpMethod.HEAD),\n integration,\n authorizer: noAuth,\n });\n new HttpRoute(this, \"proxy-route-root\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/\", HttpMethod.ANY),\n integration,\n });\n new HttpRoute(this, \"proxy-route\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/{proxy+}\", HttpMethod.ANY),\n integration,\n });\n // Derive the sub-domain label from the already-composed full domain\n // (`this.apiDomainName`) so this block can't drift from\n // {@link createApiDomainNameString}.\n const apiPrefix = this.apiDomainName.slice(\n 0,\n -(hostedZone.zoneName.length + 1),\n );\n // Embed apiPrefix in the construct id so a recordName change\n // (e.g. when `childZonePrefix`'s truncation cap shifts and a\n // branch's paramCased name re-slices to a different value)\n // produces a CloudFormation Add + Remove pair rather than an\n // in-place Replacement on AWS::Route53::RecordSet.Name (which is\n // UpdateRequiresReplacement). Avoids the TtyNotAttached error CDK\n // raises on `--no-rollback` deploys — mirrors the StaticHosting\n // ARecord fix from #1131 and the PerBranchHostname fix on #1132.\n new ARecord(this, `api-a-record-${apiPrefix}`, {\n zone: hostedZone,\n recordName: apiPrefix,\n target: RecordTarget.fromAlias(\n new ApiGatewayv2DomainProperties(\n domainName.regionalDomainName,\n domainName.regionalHostedZoneId,\n ),\n ),\n });\n }\n\n /**\n * Creates the Root HTTP API with default domain mapping, Cognito JWT authorizer, and exports API ID to SSM.\n * Look up via {@link OpenHiRestApiService.rootHttpApiFromConstruct}.\n * Override to customize.\n */\n protected createRootHttpApi(domainName: DomainName): RootHttpApi {\n const userPool = OpenHiAuthService.userPoolFromConstruct(this);\n const userPoolClient = OpenHiAuthService.userPoolClientFromConstruct(this);\n const cognitoAuthorizer = new HttpUserPoolAuthorizer(\n \"cognito-authorizer\",\n userPool,\n { userPoolClients: [userPoolClient] },\n );\n const { corsPreflight: cors, ...restRootHttpApiProps } =\n this.props.rootHttpApiProps ?? {};\n const isNonProd = this.ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD;\n const callerOrigins = cors?.allowOrigins ?? [];\n // Auto-inject this stack's admin + website hostnames on every stage so\n // callers don't have to predict them; `DEV_CORS_ALLOW_ORIGINS` still\n // only joins on non-prod.\n const autoOrigins = this.resolveAutoInjectedCorsOrigins();\n const mergedOrigins = Array.from(\n new Set([\n ...callerOrigins,\n ...autoOrigins,\n ...(isNonProd ? DEV_CORS_ALLOW_ORIGINS : []),\n ]),\n );\n // CORS is always configured: `autoOrigins` is non-empty on every stage,\n // so the admin SPA has a working preflight without the caller passing\n // any `corsPreflight` block.\n const corsPreflight = this.buildCorsPreflightOptions(mergedOrigins, cors);\n const rootHttpApi = new RootHttpApi(this, {\n ...restRootHttpApiProps,\n corsPreflight,\n defaultDomainMapping: {\n domainName,\n mappingKey: undefined,\n },\n defaultAuthorizer: cognitoAuthorizer,\n });\n new DiscoverableStringParameter(this, \"http-api-url-param\", {\n ssmParamName: RootHttpApi.SSM_PARAM_NAME,\n stringValue: rootHttpApi.httpApiId,\n description:\n \"API Gateway HTTP API ID for this REST API stack (cross-stack reference)\",\n });\n return rootHttpApi;\n }\n\n /**\n * Returns the admin-console and marketing-website origins this REST API\n * stack should accept by default, composed from the same branch context\n * the website service will see at synth time. Both hostnames are\n * `https://`-only — they always resolve to real DNS records.\n *\n * The stage's `additionalTrustedClientOrigins` config entries (e.g. on-site\n * customer SPA hosts) are appended verbatim — both `http://` and `https://`\n * entries flow into CORS. Scheme filtering is OAuth-specific and happens\n * in `OpenHiAuthService.resolveOAuthRedirectUrls`.\n *\n * Auto-injected on every stage (no `isNonProd` gate) so the admin SPA can\n * call the API cross-origin without the caller having to predict the\n * per-deploy hostname. Override to customize the auto-injected set.\n */\n protected resolveAutoInjectedCorsOrigins(): ReadonlyArray<string> {\n const zoneName = this.props.config!.zoneName!;\n const adminHost = OpenHiWebsiteService.composeFullDomain({\n domainPrefix: ADMIN_DOMAIN_PREFIX,\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n const websiteHost = OpenHiWebsiteService.composeFullDomain({\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n const stageType = this.ohEnv.ohStage.stageType;\n const additional =\n this.ohEnv.ohStage.ohApp.config.deploymentTargets?.[stageType]\n ?.additionalTrustedClientOrigins ?? [];\n return [`https://${adminHost}`, `https://${websiteHost}`, ...additional];\n }\n\n /**\n * Builds the full `CorsPreflightOptions` from a merged origins array,\n * filling defaults for `allowMethods`/`allowHeaders`/`allowCredentials`/\n * `maxAge` from the caller-supplied block when present.\n */\n protected buildCorsPreflightOptions(\n allowOrigins: ReadonlyArray<string>,\n cors: CorsPreflightOptions | undefined,\n ): CorsPreflightOptions {\n return {\n allowOrigins: [...allowOrigins],\n allowMethods: cors?.allowMethods ?? [\n CorsHttpMethod.GET,\n CorsHttpMethod.HEAD,\n CorsHttpMethod.POST,\n CorsHttpMethod.PUT,\n CorsHttpMethod.PATCH,\n CorsHttpMethod.DELETE,\n CorsHttpMethod.OPTIONS,\n ],\n allowHeaders: cors?.allowHeaders ?? [\"Content-Type\", \"Authorization\"],\n allowCredentials: cors?.allowCredentials ?? true,\n maxAge: cors?.maxAge ?? Duration.days(1),\n ...(cors?.exposeHeaders !== undefined && {\n exposeHeaders: cors.exposeHeaders,\n }),\n };\n }\n\n /**\n * Builds the `OPENHI_RUNTIME_CONFIG_*` env-var map the REST API Lambda\n * exposes through `GET /control/runtime-config`. The four values are\n * always populated — the three Cognito IDs are resolved via SSM lookups\n * against the auth stack from a dedicated sub-scope (`runtime-config`)\n * so they don't collide with the user-pool / user-pool-client constructs\n * already created in {@link createRootHttpApi}, and `apiBaseUrl` is\n * derived from this stack's own custom domain. The OAuth callback URL\n * is no longer plumbed through the API — the admin-console derives it\n * client-side from `window.location.origin`.\n */\n protected resolveRuntimeConfigEnvVars(): Record<string, string> {\n const cognitoScope = new Construct(this, \"runtime-config\");\n const userPool = OpenHiAuthService.userPoolFromConstruct(cognitoScope);\n const userPoolClient =\n OpenHiAuthService.userPoolClientFromConstruct(cognitoScope);\n const cognitoDomainUrl =\n OpenHiAuthService.userPoolDomainBaseUrlFromConstruct(cognitoScope);\n return {\n OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_ID: userPool.userPoolId,\n OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_CLIENT_ID:\n userPoolClient.userPoolClientId,\n OPENHI_RUNTIME_CONFIG_COGNITO_DOMAIN_URL: cognitoDomainUrl,\n OPENHI_RUNTIME_CONFIG_API_BASE_URL: `https://${this.apiDomainName}`,\n };\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * Dedicated Lambda for CORS preflight (OPTIONS) requests. Returns 204 so API Gateway\n * can add CORS headers from the API's corsPreflight config. Low memory footprint.\n * @see #694\n */\n\nconst HANDLER_NAME = \"cors-options-lambda.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\nexport class CorsOptionsLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, id: string = \"cors-options-lambda\") {\n super(scope, id);\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 128,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/lambda/rest-api-lambda.md\n */\n\nconst HANDLER_NAME = \"rest-api-lambda.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\nexport interface RestApiLambdaProps {\n /**\n * DynamoDB table name for the data store. The Lambda receives it as the\n * environment variable DYNAMO_TABLE_NAME at runtime.\n */\n readonly dynamoTableName: string;\n\n /**\n * Branch name from the service. Passed as BRANCH_TAG_VALUE at runtime.\n */\n readonly branchTagValue: string;\n\n /**\n * SSM parameter name for the HTTP API (e.g. RootHttpApi.SSM_PARAM_NAME).\n * Passed as HTTP_API_TAG_VALUE at runtime.\n */\n readonly httpApiTagValue: string;\n\n /**\n * Aurora cluster ARN published by `DataStorePostgresReplica`. Passed as\n * `OPENHI_PG_CLUSTER_ARN` at runtime so the Lambda can target the cluster\n * via the RDS Data API (ADR 2026-04-17-01, phase 2).\n */\n readonly postgresClusterArn: string;\n\n /**\n * Secrets Manager ARN with the cluster credentials. Passed as\n * `OPENHI_PG_SECRET_ARN` at runtime; consumed by the RDS Data API client.\n */\n readonly postgresSecretArn: string;\n\n /**\n * Database name on the cluster. Passed as `OPENHI_PG_DATABASE` at runtime.\n */\n readonly postgresDatabase: string;\n\n /**\n * Per-branch schema name on the cluster (e.g. `b_<branchHash>`). Passed as\n * `OPENHI_PG_SCHEMA` at runtime.\n */\n readonly postgresSchema: string;\n\n /**\n * Additional environment variables to set on the Lambda. Each entry is\n * passed through verbatim — the key becomes the env-var name and the value\n * its string contents. Used by the runtime-config plumbing to expose\n * Cognito IDs etc. to the public `/control/runtime-config` route.\n */\n readonly extraEnvironment?: Record<string, string>;\n}\n\nexport class RestApiLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, props: RestApiLambdaProps) {\n super(scope, \"rest-api-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n DYNAMO_TABLE_NAME: props.dynamoTableName,\n BRANCH_TAG_VALUE: props.branchTagValue,\n HTTP_API_TAG_VALUE: props.httpApiTagValue,\n OPENHI_PG_CLUSTER_ARN: props.postgresClusterArn,\n OPENHI_PG_SECRET_ARN: props.postgresSecretArn,\n OPENHI_PG_DATABASE: props.postgresDatabase,\n OPENHI_PG_SCHEMA: props.postgresSchema,\n ...props.extraEnvironment,\n },\n bundling: {\n minify: true,\n sourceMap: false,\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE,\n USER_ONBOARDING_EVENT_SOURCE,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/user-onboarding/provision-default-workspace-lambda.md\n */\n\nconst HANDLER_NAME = \"provision-default-workspace.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface ProvisionDefaultWorkspaceLambdaProps {\n /**\n * DynamoDB data store table. Used for the Lambda's `DYNAMO_TABLE_NAME`\n * env var and for granting the Lambda the writes + GSI queries it needs\n * to provision default control-plane resources.\n */\n readonly dataStoreTable: ITable;\n\n /**\n * Control-plane event bus that the EventBridge Rule listens on.\n */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Lambda used by the user-onboarding workflow to create a user's default\n * Tenant, Workspace, Memberships, and RoleAssignment.\n *\n * Owns the EventBridge Rule that routes the default-workspace onboarding\n * event to itself, and the IAM permissions it needs on the data store\n * table — colocating routing + permissions with the function they target.\n */\nexport class ProvisionDefaultWorkspaceLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: ProvisionDefaultWorkspaceLambdaProps) {\n super(scope, \"provision-default-workspace-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n\n // Grant table writes for default resources and User repair.\n props.dataStoreTable.grant(\n this.lambda,\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n );\n\n // Table.grant(\"dynamodb:Query\") only covers the table itself, not its\n // GSIs, so an explicit policy is required to query GSI2 by cognitoSub.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:Query\"],\n resources: [`${props.dataStoreTable.tableArn}/index/*`],\n }),\n );\n\n // Route only the default-workspace onboarding event to this worker.\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.controlEventBus,\n eventPattern: {\n source: [USER_ONBOARDING_EVENT_SOURCE],\n detailType: [PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE],\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { ProvisionDefaultWorkspaceLambda } from \"./provision-default-workspace-lambda\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/user-onboarding/user-onboarding-workflow.md\n */\n\nexport interface UserOnboardingWorkflowProps {\n readonly controlEventBus: IEventBus;\n readonly dataStoreTable: ITable;\n}\n\n/**\n * Control-plane workflow for onboarding users after Cognito confirmation.\n */\nexport class UserOnboardingWorkflow extends Construct {\n public readonly provisionDefaultWorkspace: ProvisionDefaultWorkspaceLambda;\n\n constructor(scope: Construct, props: UserOnboardingWorkflowProps) {\n super(scope, \"user-onboarding-workflow\");\n\n this.provisionDefaultWorkspace = new ProvisionDefaultWorkspaceLambda(this, {\n dataStoreTable: props.dataStoreTable,\n controlEventBus: props.controlEventBus,\n });\n }\n}\n","import {\n AuthorizationType,\n IGraphqlApi,\n UserPoolDefaultAction,\n} from \"aws-cdk-lib/aws-appsync\";\nimport { Construct } from \"constructs\";\nimport { OpenHiAuthService } from \"./open-hi-auth-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { RootGraphqlApi } from \"../components/app-sync/root-graphql-api\";\n\nexport interface OpenHiGraphqlServiceProps extends OpenHiServiceProps {}\n\n/**\n * GraphQL API service stack: creates the AppSync API via {@link RootGraphqlApi}\n * and exports its ID via SSM. Look up from other stacks via\n * {@link OpenHiGraphqlService.graphqlApiFromConstruct}.\n */\nexport class OpenHiGraphqlService extends OpenHiService {\n static readonly SERVICE_TYPE =\n \"graphql-api\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns the GraphQL API by looking up the GraphQL stack's API ID from SSM.\n * Use from other stacks to obtain an IGraphqlApi reference.\n */\n static graphqlApiFromConstruct(scope: Construct): IGraphqlApi {\n return RootGraphqlApi.fromConstruct(scope);\n }\n\n get serviceType(): string {\n return OpenHiGraphqlService.SERVICE_TYPE;\n }\n\n /* Override so this.props is typed with this service's options */\n public override props: OpenHiGraphqlServiceProps;\n\n public readonly rootGraphqlApi: RootGraphqlApi;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiGraphqlServiceProps = {}) {\n super(ohEnv, OpenHiGraphqlService.SERVICE_TYPE, props);\n this.props = props;\n this.rootGraphqlApi = this.createRootGraphqlApi();\n }\n\n /** Creates the root GraphQL API with Cognito user pool. */\n protected createRootGraphqlApi(): RootGraphqlApi {\n const userPool = OpenHiAuthService.userPoolFromConstruct(this);\n return new RootGraphqlApi(this, {\n authorizationConfig: {\n defaultAuthorization: {\n authorizationType: AuthorizationType.USER_POOL,\n userPoolConfig: {\n userPool,\n defaultAction: UserPoolDefaultAction.ALLOW,\n },\n },\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport { OWNING_DELETE_OPS_EVENT_BUS_ENV_VAR } from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-lambdas.md\n */\n\ninterface ResolvedHandler {\n readonly entry: string;\n readonly handler: string;\n}\n\n/**\n * Resolve a Lambda entry under `src/` (tests) or `lib/` (compiled\n * bundle). Mirrors the dual-path lookup the seed-* lambdas use so the\n * same code path runs in unit tests, CDK snapshot tests, and prod.\n */\nfunction resolveHandlerEntry(\n dirname: string,\n handlerName: string,\n): ResolvedHandler {\n const sameDir = path.join(dirname, handlerName);\n if (fs.existsSync(sameDir)) {\n return { entry: sameDir, handler: \"handler\" };\n }\n const libDir = path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", handlerName);\n return { entry: libDir, handler: \"handler\" };\n}\n\nexport interface OwningDeleteCascadeLambdasProps {\n /** Data-store table the cascade reads (Query) and writes (DeleteItem / TransactWriteItems) against. */\n readonly dataStoreTable: ITable;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n}\n\n/**\n * The three Lambdas that power the TR-022 owning-entity hard-delete\n * cascade state machine. Bundled together because the state machine\n * wires them in a fixed topology and they share the same data-store\n * grant pattern.\n *\n * - `listChunks` — pages through the owner's adjacency-list partition\n * via ElectroDB Query, splits the page into \\<=100-item chunks for\n * the inline Map state.\n * - `deleteChunk` — Map-iteration handler; submits one chunk as a\n * single `TransactWriteItems` via `executeMultiWrite`. The state\n * machine's `MaxConcurrency = 8` runs up to eight of these in\n * parallel.\n * - `finalize` — deletes the owning canonical record at\n * `SK = \"CURRENT\"` conditional on `lifecycleState = \"deleting\"`,\n * then emits the `control-plane.owning-delete-complete.v1` terminal\n * event on the ops event bus.\n *\n * IAM grants are scoped per-Lambda: the read/write Lambdas get\n * table-level Query / TransactWriteItems on the data store; the\n * finalize Lambda gets a focused `DeleteItem` + `PutEvents` policy\n * (no Query, no broad writes).\n */\nexport class OwningDeleteCascadeLambdas extends Construct {\n public readonly listChunks: NodejsFunction;\n public readonly deleteChunk: NodejsFunction;\n public readonly finalize: NodejsFunction;\n\n constructor(scope: Construct, props: OwningDeleteCascadeLambdasProps) {\n super(scope, \"owning-delete-cascade-lambdas\");\n\n const listResolved = resolveHandlerEntry(\n __dirname,\n \"list-chunks.handler.js\",\n );\n this.listChunks = new NodejsFunction(this, \"list-chunks-handler\", {\n entry: listResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n props.dataStoreTable.grant(this.listChunks, \"dynamodb:Query\");\n\n const deleteResolved = resolveHandlerEntry(\n __dirname,\n \"delete-chunk.handler.js\",\n );\n this.deleteChunk = new NodejsFunction(this, \"delete-chunk-handler\", {\n entry: deleteResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n // TransactWriteItems requires both the resource-level action and\n // (implicitly) DeleteItem on the items it touches. AWS treats\n // `dynamodb:DeleteItem` as the per-item permission inside a\n // TransactWriteItems delete, so grant both.\n props.dataStoreTable.grant(\n this.deleteChunk,\n \"dynamodb:DeleteItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:PutItem\",\n );\n\n const finalizeResolved = resolveHandlerEntry(\n __dirname,\n \"finalize.handler.js\",\n );\n this.finalize = new NodejsFunction(this, \"finalize-handler\", {\n entry: finalizeResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n [OWNING_DELETE_OPS_EVENT_BUS_ENV_VAR]: props.opsEventBus.eventBusName,\n },\n });\n props.dataStoreTable.grant(this.finalize, \"dynamodb:DeleteItem\");\n this.finalize.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"events:PutEvents\"],\n resources: [props.opsEventBus.eventBusArn],\n }),\n );\n }\n}\n","import { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { SfnStateMachine } from \"aws-cdk-lib/aws-events-targets\";\nimport {\n Choice,\n Condition,\n CustomState,\n DefinitionBody,\n Pass,\n StateMachine,\n Succeed,\n TaskInput,\n Wait,\n WaitTime,\n} from \"aws-cdk-lib/aws-stepfunctions\";\nimport { LambdaInvoke } from \"aws-cdk-lib/aws-stepfunctions-tasks\";\nimport { Construct } from \"constructs\";\nimport {\n ControlPlaneOwningDeleteV1,\n OPENHI_DATA_SOURCE,\n OWNING_DELETE_CASCADE_DEFAULT_CONCURRENCY,\n} from \"./events\";\nimport { OwningDeleteCascadeLambdas } from \"./owning-delete-cascade-lambdas\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-workflow.md\n */\n\nexport interface OwningDeleteCascadeWorkflowProps {\n /**\n * Data event bus carrying `control-plane.owning-delete.v1`. The\n * workflow's EventBridge rule lives on this bus and starts a state\n * machine execution per matching event.\n */\n readonly dataEventBus: IEventBus;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n /** Data-store table the cascade reads from / writes deletes into. */\n readonly dataStoreTable: ITable;\n /**\n * Inline-Map max concurrency. Defaults to\n * {@link OWNING_DELETE_CASCADE_DEFAULT_CONCURRENCY} (8) per the\n * ADR-018 implementation guide section 4 — tunable per environment.\n * NOTE: this is an **inline** Map (NOT Distributed Map — TR-022\n * Choice 2A pins inline; TR-023 uses Distributed for renames).\n */\n readonly cascadeMapConcurrency?: number;\n}\n\n/**\n * Control-plane workflow that fans out the TR-022 owning-entity\n * hard-delete cascade.\n *\n * Pipeline (per the ADR-018 implementation guide section 4):\n *\n * 1. Synchronous API entry point flips the canonical owning record's\n * `lifecycleState: active -> deleting`. (Owned by the REST adapter,\n * not this construct.)\n * 2. DynamoDB stream / Firehose transform publishes\n * `control-plane.owning-delete.v1` on the data event bus.\n * 3. EventBridge rule (owned here) starts this state machine.\n * 4. State machine outer loop:\n * - `ListChunks` Lambda pages through the owner's adjacency-list\n * partition and emits chunks of up to 100 projection rows.\n * - `RewriteChunks` inline Map (MaxConcurrency = 8) deletes each\n * chunk in parallel via `executeMultiWrite`.\n * - `IsExhausted` Choice loops back to `ListChunks` until the page\n * query returns zero items and every per-entity cursor is `null`.\n * 5. `Finalize` Lambda deletes the canonical owning record\n * (conditional on `lifecycleState = \"deleting\"`) and emits\n * `control-plane.owning-delete-complete.v1` on the ops event bus.\n *\n * Idempotency: every Map iteration uses a per-chunk `ClientRequestToken`,\n * and the finalize step's canonical delete is conditional on\n * `lifecycleState = \"deleting\"`. A replayed execution finds no rows\n * to delete, no canonical to remove, and emits no terminal event.\n */\nexport class OwningDeleteCascadeWorkflow extends Construct {\n public readonly lambdas: OwningDeleteCascadeLambdas;\n public readonly stateMachine: StateMachine;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: OwningDeleteCascadeWorkflowProps) {\n super(scope, \"owning-delete-cascade-workflow\");\n\n this.lambdas = new OwningDeleteCascadeLambdas(this, {\n dataStoreTable: props.dataStoreTable,\n opsEventBus: props.opsEventBus,\n });\n\n const concurrency =\n props.cascadeMapConcurrency ?? OWNING_DELETE_CASCADE_DEFAULT_CONCURRENCY;\n\n // Step 1: Initialize cascade state from the EventBridge envelope.\n // Pulls `ownerType`, `ownerId`, `tenantId` from the workflow event\n // payload and seeds cursors / accumulators.\n const initState = new Pass(this, \"init-state\", {\n parameters: {\n \"ownerType.$\": \"$.detail.payload.ownerType\",\n \"ownerId.$\": \"$.detail.payload.ownerId\",\n \"tenantId.$\": \"$.detail.payload.tenantId\",\n cursors: {},\n projectionsRemoved: 0,\n chunkCount: 0,\n // Used by the finalize step to compute `durationMs`.\n \"startedAt.$\": \"$$.Execution.StartTime\",\n // Propagate envelope identity for ADR-016 causation chaining.\n \"eventId.$\": \"$.detail.eventId\",\n \"correlationId.$\": \"$.detail.correlationId\",\n \"causationId.$\": \"$.detail.eventId\",\n },\n });\n\n // Step 2: Query a page of child projection rows + split into chunks.\n // `payload` defaults to the entire task input (`$`), so the\n // listChunks handler receives the cascade state verbatim.\n const listChunks = new LambdaInvoke(this, \"list-chunks\", {\n lambdaFunction: this.lambdas.listChunks,\n resultPath: \"$.listResult\",\n retryOnServiceExceptions: true,\n });\n\n // Reconstitute the cascade state from the page response. Carry\n // every field forward so the loop's next iteration has the\n // accumulator + envelope identity intact.\n const updateAfterList = new Pass(this, \"update-after-list\", {\n parameters: {\n \"ownerType.$\": \"$.ownerType\",\n \"ownerId.$\": \"$.ownerId\",\n \"tenantId.$\": \"$.tenantId\",\n \"cursors.$\": \"$.listResult.Payload.cursors\",\n \"projectionsRemoved.$\": \"$.listResult.Payload.projectionsRemoved\",\n \"chunkCount.$\": \"$.listResult.Payload.chunkCount\",\n \"exhausted.$\": \"$.listResult.Payload.exhausted\",\n \"chunks.$\": \"$.listResult.Payload.chunks\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n },\n });\n\n // Step 3: Inline Map state — fan out chunk deletes.\n //\n // Step Functions L2 `Map` exists, but the L2 + `LambdaInvoke`\n // task-state combination does not surface inline-Map's `Retry` /\n // `Catch` blocks the implementation guide specifies. Drop to an\n // ASL `CustomState` so the retry / catch shapes match the guide\n // exactly. The state is INLINE (not Distributed) per TR-022\n // Choice 2A — `Mode` is omitted (inline is the default); the\n // sibling rename cascade (TR-023) is the one that uses\n // `Mode: DISTRIBUTED`.\n const rewriteChunks = new CustomState(this, \"rewrite-chunks\", {\n stateJson: {\n Type: \"Map\",\n ItemsPath: \"$.chunks\",\n MaxConcurrency: concurrency,\n ResultPath: \"$.rewriteResults\",\n ItemSelector: {\n // The handler receives `CascadeChunkInput` verbatim from the\n // `listChunks` output, no additional wrapping.\n \"ownerType.$\": \"$$.Map.Item.Value.ownerType\",\n \"ownerId.$\": \"$$.Map.Item.Value.ownerId\",\n \"tenantId.$\": \"$$.Map.Item.Value.tenantId\",\n \"rows.$\": \"$$.Map.Item.Value.rows\",\n \"chunkToken.$\": \"$$.Map.Item.Value.chunkToken\",\n },\n ItemProcessor: {\n ProcessorConfig: {\n // Inline mode (NOT Distributed). Per TR-022 Choice 2A.\n Mode: \"INLINE\",\n },\n StartAt: \"DeleteChunk\",\n States: {\n DeleteChunk: {\n Type: \"Task\",\n Resource: \"arn:aws:states:::lambda:invoke\",\n Parameters: {\n FunctionName: this.lambdas.deleteChunk.functionArn,\n \"Payload.$\": \"$\",\n },\n Retry: [\n {\n ErrorEquals: [\n \"DynamoDB.ProvisionedThroughputExceededException\",\n \"DynamoDB.ThrottlingException\",\n \"DynamoDB.TransactionConflictException\",\n \"Lambda.ServiceException\",\n \"Lambda.AWSLambdaException\",\n \"Lambda.SdkClientException\",\n ],\n IntervalSeconds: 1,\n MaxAttempts: 3,\n BackoffRate: 2.0,\n MaxDelaySeconds: 30,\n },\n ],\n Catch: [\n {\n // Replay path: the rows in this chunk are already\n // gone. Treat as a no-op success so the outer loop\n // keeps draining the partition.\n ErrorEquals: [\"DynamoDB.TransactionCanceledException\"],\n Next: \"ChunkAlreadyDeleted\",\n },\n ],\n End: true,\n },\n ChunkAlreadyDeleted: {\n Type: \"Succeed\",\n },\n },\n },\n },\n });\n\n // Outer loop control: a small wait between pages so retries do\n // not hammer DynamoDB on a partial throttle.\n const interPageWait = new Wait(this, \"inter-page-wait\", {\n time: WaitTime.duration(Duration.seconds(0)),\n });\n\n // Step 4: Choice — keep draining until the per-entity cursors all\n // return null AND the page is empty.\n const isExhausted = new Choice(this, \"is-exhausted\");\n\n // Step 5: Finalize — delete the canonical owning row and emit the\n // terminal event.\n const finalize = new LambdaInvoke(this, \"finalize\", {\n lambdaFunction: this.lambdas.finalize,\n payload: TaskInput.fromObject({\n \"ownerType.$\": \"$.ownerType\",\n \"ownerId.$\": \"$.ownerId\",\n \"tenantId.$\": \"$.tenantId\",\n \"projectionsRemoved.$\": \"$.projectionsRemoved\",\n \"chunkCount.$\": \"$.chunkCount\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n }),\n resultPath: \"$.finalizeResult\",\n retryOnServiceExceptions: true,\n });\n\n const success = new Succeed(this, \"success\");\n\n // Wire the state machine topology:\n // InitState\n // -> ListChunks\n // -> UpdateAfterList\n // -> RewriteChunks (inline Map, MaxConcurrency = 8)\n // -> InterPageWait\n // -> IsExhausted\n // ├── (exhausted) -> Finalize -> Success\n // └── (more) -> ListChunks (loop)\n const definition = initState\n .next(listChunks)\n .next(updateAfterList)\n .next(rewriteChunks)\n .next(interPageWait)\n .next(\n isExhausted\n .when(\n Condition.booleanEquals(\"$.exhausted\", true),\n finalize.next(success),\n )\n .otherwise(listChunks),\n );\n\n this.stateMachine = new StateMachine(this, \"state-machine\", {\n definitionBody: DefinitionBody.fromChainable(definition),\n // Long timeout because real-world cascades can run minutes when\n // a workspace has thousands of members. The stuck-cascade alarm\n // fires at 15 minutes; the state machine itself does not abort.\n timeout: Duration.hours(2),\n });\n\n // EventBridge rule — match the input detail-type on the data bus\n // and start the state machine for each event.\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.dataEventBus,\n eventPattern: {\n source: [OPENHI_DATA_SOURCE],\n detailType: [ControlPlaneOwningDeleteV1.detailType],\n },\n targets: [\n new SfnStateMachine(this.stateMachine, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport { RENAME_CASCADE_OPS_EVENT_BUS_ENV_VAR } from \"./events\";\n\ninterface ResolvedHandler {\n readonly entry: string;\n readonly handler: string;\n}\n\n/**\n * Resolve a Lambda entry under `src/` (tests) or `lib/` (compiled\n * bundle). Mirrors the dual-path lookup the sibling cascade lambdas use\n * so the same code path runs in unit tests, CDK snapshot tests, and prod.\n */\nfunction resolveHandlerEntry(\n dirname: string,\n handlerName: string,\n): ResolvedHandler {\n const sameDir = path.join(dirname, handlerName);\n if (fs.existsSync(sameDir)) {\n return { entry: sameDir, handler: \"handler\" };\n }\n const libDir = path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", handlerName);\n return { entry: libDir, handler: \"handler\" };\n}\n\nexport interface RenameCascadeLambdasProps {\n /** Data-store table the cascade reads (Query) and writes (TransactWriteItems) against. */\n readonly dataStoreTable: ITable;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n}\n\n/**\n * The three Lambdas that power the TR-023 rename cascade state machine.\n *\n * - `listTargets` — pages through the affected projection partitions\n * for a rename and emits chunks of up to 50 rewrite targets.\n * - `rewriteChunk` — Distributed-Map iteration handler; submits one\n * chunk as a single `TransactWriteItems` via `executeMultiWrite`. The\n * state machine's `MaxConcurrency = 10` runs up to ten of these in\n * parallel.\n * - `finalize` — emits `control-plane.rename-complete.v1` on the ops\n * event bus.\n *\n * IAM grants are scoped per-Lambda: read/write Lambdas get table-level\n * Query / TransactWriteItems on the data store; the finalize Lambda\n * gets only `events:PutEvents` on the ops event bus.\n */\nexport class RenameCascadeLambdas extends Construct {\n public readonly listTargets: NodejsFunction;\n public readonly rewriteChunk: NodejsFunction;\n public readonly finalize: NodejsFunction;\n\n constructor(scope: Construct, props: RenameCascadeLambdasProps) {\n super(scope, \"rename-cascade-lambdas\");\n\n const listResolved = resolveHandlerEntry(\n __dirname,\n \"rename-list-targets.handler.js\",\n );\n this.listTargets = new NodejsFunction(this, \"list-targets-handler\", {\n entry: listResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n props.dataStoreTable.grant(this.listTargets, \"dynamodb:Query\");\n\n const rewriteResolved = resolveHandlerEntry(\n __dirname,\n \"rename-rewrite-chunk.handler.js\",\n );\n this.rewriteChunk = new NodejsFunction(this, \"rewrite-chunk-handler\", {\n entry: rewriteResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n // TransactWriteItems with delete + put pairs requires both per-item\n // permissions.\n props.dataStoreTable.grant(\n this.rewriteChunk,\n \"dynamodb:DeleteItem\",\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n );\n\n const finalizeResolved = resolveHandlerEntry(\n __dirname,\n \"rename-finalize.handler.js\",\n );\n this.finalize = new NodejsFunction(this, \"finalize-handler\", {\n entry: finalizeResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n [RENAME_CASCADE_OPS_EVENT_BUS_ENV_VAR]: props.opsEventBus.eventBusName,\n },\n });\n this.finalize.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"events:PutEvents\"],\n resources: [props.opsEventBus.eventBusArn],\n }),\n );\n }\n}\n","import { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { SfnStateMachine } from \"aws-cdk-lib/aws-events-targets\";\nimport {\n Choice,\n Condition,\n CustomState,\n DefinitionBody,\n Pass,\n StateMachine,\n Succeed,\n TaskInput,\n} from \"aws-cdk-lib/aws-stepfunctions\";\nimport { LambdaInvoke } from \"aws-cdk-lib/aws-stepfunctions-tasks\";\nimport { Construct } from \"constructs\";\nimport {\n ControlPlaneRenameV1,\n OPENHI_DATA_SOURCE,\n RENAME_CASCADE_DEFAULT_CONCURRENCY,\n} from \"./events\";\nimport { RenameCascadeLambdas } from \"./rename-cascade-lambdas\";\n\nexport interface RenameCascadeWorkflowProps {\n /**\n * Data event bus carrying `control-plane.rename.v1`. The workflow's\n * EventBridge rule lives on this bus and starts a state machine\n * execution per matching event.\n */\n readonly dataEventBus: IEventBus;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n /** Data-store table the cascade reads from / writes rewrites into. */\n readonly dataStoreTable: ITable;\n /**\n * Distributed-Map max concurrency. Defaults to\n * {@link RENAME_CASCADE_DEFAULT_CONCURRENCY} (10) per the ADR-018\n * implementation guide section 5 — tunable per environment.\n *\n * NOTE: this is a **Distributed** Map (NOT inline — TR-022's\n * owning-delete cascade uses inline; TR-023's rename cascade uses\n * Distributed).\n */\n readonly cascadeMapConcurrency?: number;\n}\n\n/**\n * Control-plane workflow that fans out the TR-023 rename cascade.\n *\n * Pipeline (per the ADR-018 implementation guide section 5):\n *\n * 1. The Firehose transform Lambda publishes\n * `control-plane.rename.v1` on the data event bus when it observes\n * a stream record showing a display-name change on a canonical\n * Tenant / User / Role row.\n * 2. EventBridge rule (owned here) starts this state machine.\n * 3. State machine outer loop:\n * - `ListTargets` Lambda pages through the affected projection\n * partitions for the renamed entity and emits chunks of up to 50\n * rewrite targets.\n * - `RewriteChunks` Distributed Map (MaxConcurrency = 10) rewrites\n * each chunk in parallel via `executeMultiWrite`. Each target\n * maps to either a `delete oldKey` + `put newItem` pair (SK\n * rewrite) or a single `put newItem` overwrite (attr-only update).\n * - `IsExhausted` Choice loops back to `ListTargets` until every\n * per-stream cursor returns `null`.\n * 4. `Finalize` Lambda emits `control-plane.rename-complete.v1` on the\n * ops event bus.\n *\n * Idempotency: every Map iteration uses a per-chunk `ClientRequestToken`,\n * and the state machine's `Catch` block absorbs\n * `DynamoDB.TransactionCanceledException` as a no-op success — a\n * replayed chunk where every row is already at the new SK fails its\n * delete-old triple and the helper rolls back; the cascade keeps\n * draining the page until the outer loop terminates on exhaustion.\n * Lost-race writes (per the TR-023 idempotency rule) are accepted —\n * the renaming write loses to a later concurrent write on the same row.\n */\nexport class RenameCascadeWorkflow extends Construct {\n public readonly lambdas: RenameCascadeLambdas;\n public readonly stateMachine: StateMachine;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: RenameCascadeWorkflowProps) {\n super(scope, \"rename-cascade-workflow\");\n\n this.lambdas = new RenameCascadeLambdas(this, {\n dataStoreTable: props.dataStoreTable,\n opsEventBus: props.opsEventBus,\n });\n\n const concurrency =\n props.cascadeMapConcurrency ?? RENAME_CASCADE_DEFAULT_CONCURRENCY;\n\n // Step 1: Initialize cascade state from the EventBridge envelope.\n // Pulls `entityType`, `entityId`, `tenantId`, name/normalizedName\n // from the workflow event payload and seeds cursors / accumulators.\n const initState = new Pass(this, \"init-state\", {\n parameters: {\n \"entityType.$\": \"$.detail.payload.entityType\",\n \"entityId.$\": \"$.detail.payload.entityId\",\n \"tenantId.$\": \"$.detail.payload.tenantId\",\n \"oldName.$\": \"$.detail.payload.oldName\",\n \"newName.$\": \"$.detail.payload.newName\",\n \"oldNormalizedName.$\": \"$.detail.payload.oldNormalizedName\",\n \"newNormalizedName.$\": \"$.detail.payload.newNormalizedName\",\n cursors: {},\n itemsRewritten: 0,\n chunkCount: 0,\n \"startedAt.$\": \"$$.Execution.StartTime\",\n \"eventId.$\": \"$.detail.eventId\",\n \"correlationId.$\": \"$.detail.correlationId\",\n \"causationId.$\": \"$.detail.eventId\",\n },\n });\n\n // Step 2: Query one page of targets + split into chunks.\n const listTargets = new LambdaInvoke(this, \"list-targets\", {\n lambdaFunction: this.lambdas.listTargets,\n resultPath: \"$.listResult\",\n retryOnServiceExceptions: true,\n });\n\n // Reconstitute the cascade state from the page response.\n const updateAfterList = new Pass(this, \"update-after-list\", {\n parameters: {\n \"entityType.$\": \"$.entityType\",\n \"entityId.$\": \"$.entityId\",\n \"tenantId.$\": \"$.tenantId\",\n \"oldName.$\": \"$.oldName\",\n \"newName.$\": \"$.newName\",\n \"oldNormalizedName.$\": \"$.oldNormalizedName\",\n \"newNormalizedName.$\": \"$.newNormalizedName\",\n \"cursors.$\": \"$.listResult.Payload.cursors\",\n \"itemsRewritten.$\": \"$.listResult.Payload.itemsRewritten\",\n \"chunkCount.$\": \"$.listResult.Payload.chunkCount\",\n \"exhausted.$\": \"$.listResult.Payload.exhausted\",\n \"chunks.$\": \"$.listResult.Payload.chunks\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n },\n });\n\n // Step 3: Distributed Map state — fan out chunk rewrites.\n //\n // Step Functions L2 `Map` does not surface Distributed-Map's\n // `ItemProcessor.ProcessorConfig.Mode: DISTRIBUTED` cleanly with\n // the L2 + `LambdaInvoke` combo. Drop to an ASL `CustomState` so\n // the shape matches the implementation guide exactly. The state\n // is DISTRIBUTED (per TR-023 — distinguishes the rename cascade\n // from TR-022's INLINE owning-delete cascade) with MaxConcurrency\n // = 10.\n const rewriteChunks = new CustomState(this, \"rewrite-chunks\", {\n stateJson: {\n Type: \"Map\",\n ItemsPath: \"$.chunks\",\n MaxConcurrency: concurrency,\n ResultPath: \"$.rewriteResults\",\n ItemSelector: {\n \"entityType.$\": \"$$.Map.Item.Value.entityType\",\n \"entityId.$\": \"$$.Map.Item.Value.entityId\",\n \"tenantId.$\": \"$$.Map.Item.Value.tenantId\",\n \"targets.$\": \"$$.Map.Item.Value.targets\",\n \"chunkToken.$\": \"$$.Map.Item.Value.chunkToken\",\n },\n ItemProcessor: {\n ProcessorConfig: {\n // DISTRIBUTED mode — per TR-023 (NOT INLINE; that is\n // TR-022's territory).\n Mode: \"DISTRIBUTED\",\n ExecutionType: \"STANDARD\",\n },\n StartAt: \"RewriteChunk\",\n States: {\n RewriteChunk: {\n Type: \"Task\",\n Resource: \"arn:aws:states:::lambda:invoke\",\n Parameters: {\n FunctionName: this.lambdas.rewriteChunk.functionArn,\n \"Payload.$\": \"$\",\n },\n Retry: [\n {\n ErrorEquals: [\n \"DynamoDB.ProvisionedThroughputExceededException\",\n \"DynamoDB.ThrottlingException\",\n \"DynamoDB.TransactionConflictException\",\n \"Lambda.ServiceException\",\n \"Lambda.AWSLambdaException\",\n \"Lambda.SdkClientException\",\n ],\n IntervalSeconds: 1,\n MaxAttempts: 5,\n BackoffRate: 2.0,\n MaxDelaySeconds: 30,\n },\n ],\n Catch: [\n {\n // Replay path: the rewrite race \"lost to a later\n // write\" per TR-023 idempotency. Treat as a no-op\n // success so the outer loop keeps draining the page.\n ErrorEquals: [\"DynamoDB.TransactionCanceledException\"],\n Next: \"ChunkAlreadyRewritten\",\n },\n ],\n End: true,\n },\n ChunkAlreadyRewritten: {\n Type: \"Succeed\",\n },\n },\n },\n },\n });\n\n // Step 4: Choice — keep draining until every per-stream cursor\n // returns null.\n const isExhausted = new Choice(this, \"is-exhausted\");\n\n // Step 5: Finalize — emit the terminal event.\n const finalize = new LambdaInvoke(this, \"finalize\", {\n lambdaFunction: this.lambdas.finalize,\n payload: TaskInput.fromObject({\n \"entityType.$\": \"$.entityType\",\n \"entityId.$\": \"$.entityId\",\n \"tenantId.$\": \"$.tenantId\",\n \"newName.$\": \"$.newName\",\n \"itemsRewritten.$\": \"$.itemsRewritten\",\n \"chunkCount.$\": \"$.chunkCount\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n }),\n resultPath: \"$.finalizeResult\",\n retryOnServiceExceptions: true,\n });\n\n const success = new Succeed(this, \"success\");\n\n // Wire the state machine topology:\n // InitState\n // -> ListTargets\n // -> UpdateAfterList\n // -> RewriteChunks (Distributed Map, MaxConcurrency = 10)\n // -> IsExhausted\n // ├── (exhausted) -> Finalize -> Success\n // └── (more) -> ListTargets (loop)\n const definition = initState\n .next(listTargets)\n .next(updateAfterList)\n .next(rewriteChunks)\n .next(\n isExhausted\n .when(\n Condition.booleanEquals(\"$.exhausted\", true),\n finalize.next(success),\n )\n .otherwise(listTargets),\n );\n\n this.stateMachine = new StateMachine(this, \"state-machine\", {\n definitionBody: DefinitionBody.fromChainable(definition),\n // Long timeout — large renames may rewrite thousands of rows;\n // the `CascadeSlow` alarm fires at 300s p99 but the state\n // machine itself does not abort.\n timeout: Duration.hours(2),\n });\n\n // EventBridge rule — match the input detail-type on the data bus.\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.dataEventBus,\n eventPattern: {\n source: [OPENHI_DATA_SOURCE],\n detailType: [ControlPlaneRenameV1.detailType],\n },\n targets: [\n new SfnStateMachine(this.stateMachine, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n"],"mappings":"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","names":["import_config","Stage","Stage","import_config","hashString","hashString","Tags","StringParameter","StringParameter","Tags","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","fs","path","RemovalPolicy","Tags","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","RemovalPolicy","Tags","NodejsFunction","Runtime","import_workflows","AttributeType","BillingMode","Table","Construct","Construct","Table","AttributeType","BillingMode","Duration","Stack","Duration","Stack","EventBus","EventBus","EventBus","EventBus","fs","path","Duration","Stack","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","Stack","NodejsFunction","Runtime","Duration","Duration","Construct","Distribution","ARecord","RecordTarget","CloudFrontTarget","Construct","fs","path","Duration","Runtime","NodejsFunction","Bucket","Construct","Construct","Duration","Bucket","NodejsFunction","Runtime","Construct","Distribution","ARecord","RecordTarget","CloudFrontTarget","paramCase","Construct","Construct","paramCase","import_config","UserPool","UserPoolClient","UserPoolDomain","Effect","PolicyStatement","Key","Stack","import_config","Table","Certificate","EventBus","HostedZone","StringParameter","Construct","fs","path","Duration","Stack","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","Stack","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Construct","HostedZone","StringParameter","Certificate","EventBus","fs","path","Duration","Stack","Rule","LambdaFunction","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Stack","Rule","LambdaFunction","Construct","Construct","fs","path","Duration","Stack","Rule","LambdaFunction","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Stack","Rule","LambdaFunction","Construct","Construct","Table","import_config","Bucket","import_config","HttpApi","Effect","PolicyStatement","ARecord","HostedZone","RecordTarget","Duration","Construct","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","HttpApi","HostedZone","PolicyStatement","Effect","ARecord","RecordTarget","Duration","Construct","Bucket","fs","path","Duration","Rule","LambdaFunction","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","PolicyStatement","Effect","Rule","LambdaFunction","Duration","Construct","Construct","UserPool","UserPoolClient","UserPoolDomain","Stack","Key","PolicyStatement","Effect","fs","path","Duration","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Duration","Rule","Construct","Construct","Duration","Rule","fs","path","Duration","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Duration","Rule","SfnStateMachine","Choice","Condition","CustomState","DefinitionBody","Pass","StateMachine","Succeed","TaskInput","LambdaInvoke","Construct","Construct","Pass","LambdaInvoke","CustomState","Choice","TaskInput","Succeed","Condition","StateMachine","DefinitionBody","Duration","Rule","SfnStateMachine"]}
|
|
1
|
+
{"version":3,"sources":["../../config/src/open-hi-config.ts","../../config/src/index.ts","../src/app/compute-branch-hash.ts","../src/app/open-hi-app.ts","../src/app/open-hi-environment.ts","../src/app/open-hi-stage.ts","../src/app/open-hi-service.ts","../src/components/acm/root-wildcard-certificate.ts","../src/components/api-gateway/root-http-api.ts","../src/components/app-sync/root-graphql-api.ts","../src/components/ssm/discoverable-string-parameter.ts","../src/components/cognito/cognito-user-pool.ts","../src/components/cognito/cognito-user-pool-client.ts","../src/components/cognito/cognito-user-pool-domain.ts","../src/components/cognito/cognito-user-pool-kms-key.ts","../src/components/cognito/post-authentication-lambda.ts","../src/components/cognito/post-confirmation-lambda.ts","../src/components/cognito/pre-token-generation-lambda.ts","../src/components/dynamodb/data-store-historical-archive.ts","../src/components/dynamodb/dynamo-db-data-store.ts","../src/components/dynamodb/workflow-dedup-table.ts","../src/components/event-bridge/data-event-bus.ts","../src/components/event-bridge/ops-event-bus.ts","../src/components/event-bridge/control-event-bus.ts","../src/components/postgres/data-store-postgres-replica.ts","../src/components/route-53/child-hosted-zone.ts","../src/components/route-53/root-hosted-zone.ts","../src/components/static-hosting/per-branch-hostname.ts","../src/components/static-hosting/static-hosting.ts","../src/components/static-hosting/static-content.ts","../src/services/open-hi-auth-service.ts","../src/services/open-hi-data-service.ts","../src/services/open-hi-global-service.ts","../src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge.ts","../src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge-lambda.ts","../src/workflows/control-plane/seed-demo-data/seed-demo-data-lambda.ts","../src/workflows/control-plane/seed-demo-data/seed-demo-data-workflow.ts","../src/workflows/control-plane/seed-system-data/seed-system-data-lambda.ts","../src/workflows/control-plane/seed-system-data/seed-system-data-workflow.ts","../src/services/open-hi-website-service.ts","../src/services/open-hi-rest-api-service.ts","../src/data/lambda/cors-options-lambda.ts","../src/data/lambda/rest-api-lambda.ts","../src/workflows/control-plane/user-onboarding/provision-default-workspace-lambda.ts","../src/workflows/control-plane/user-onboarding/user-onboarding-workflow.ts","../src/services/open-hi-graphql-service.ts","../src/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-lambdas.ts","../src/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-workflow.ts","../src/workflows/control-plane/rename-cascade/rename-cascade-lambdas.ts","../src/workflows/control-plane/rename-cascade/rename-cascade-workflow.ts"],"sourcesContent":["/*******************************************************************************\n *\n * OpenHi Config\n *\n * These types are kept in their own package to prevent dependency conflicts and\n * conditions between @openhi/constructs and @openhi/platform..\n *\n ******************************************************************************/\n\n/**\n * Stage Types\n *\n * What stage of deployment is this? Dev, staging, or prod?\n */\nexport const OPEN_HI_STAGE = {\n /**\n * Development environment, typically used for testing and development.\n */\n DEV: \"dev\",\n /**\n * Staging environment, used for pre-production testing.\n */\n STAGE: \"stage\",\n /**\n * Production environment, used for live deployments.\n */\n PROD: \"prod\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type OpenHiStageType =\n (typeof OPEN_HI_STAGE)[keyof typeof OPEN_HI_STAGE];\n\n/**\n * Deployment Target Role\n *\n * Is this (account, region) the primary or a secondary deployment target for the stage?\n * Works for both multi-region (different regions) and cellular (same region, different accounts).\n */\nexport const OPEN_HI_DEPLOYMENT_TARGET_ROLE = {\n /**\n * The primary deployment target for this stage (main account/region).\n * For example, the base DynamoDB region for global tables.\n */\n PRIMARY: \"primary\",\n\n /**\n * A secondary deployment target for this stage (additional account/region).\n * For example, a replica region for a global DynamoDB table, or another cell in the same region.\n */\n SECONDARY: \"secondary\",\n} as const;\n\n/**\n * Above const as a type.\n */\nexport type OpenHiDeploymentTargetRoleType =\n (typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE)[keyof typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE];\n\nexport interface OpenHiEnvironmentConfig {\n account: string;\n region: string;\n /**\n * Route53 zone containing DNS for this service.\n */\n hostedZoneId?: string;\n zoneName?: string;\n}\n\n/**\n * Represents the configuration for OpenHi services across different stages and\n * deployment targets.\n */\nexport interface OpenHiConfig {\n versions?: {\n cdk?: {\n cdkLibVersion?: string;\n cdkCliVersion?: string;\n };\n };\n deploymentTargets?: {\n [OPEN_HI_STAGE.DEV]?: {\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY]?: OpenHiEnvironmentConfig;\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY]?: Array<OpenHiEnvironmentConfig>;\n /**\n * Additional client origins trusted by this stage beyond the\n * stage-owned admin/website hosts that auto-injection derives from\n * branch context. Each entry is a full `<scheme>://<host>` string\n * with no path and no trailing slash (e.g.\n * `https://main.onsitedev.codedrifters.com`). Consumed by both the\n * REST API CORS allow-list and the Auth OAuth callback list at the\n * service layer.\n */\n additionalTrustedClientOrigins?: ReadonlyArray<string>;\n };\n [OPEN_HI_STAGE.STAGE]?: {\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY]?: OpenHiEnvironmentConfig;\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY]?: Array<OpenHiEnvironmentConfig>;\n /**\n * Additional client origins trusted by this stage beyond the\n * stage-owned admin/website hosts that auto-injection derives from\n * branch context. Each entry is a full `<scheme>://<host>` string\n * with no path and no trailing slash (e.g.\n * `https://main.onsitestage.codedrifters.com`). Consumed by both\n * the REST API CORS allow-list and the Auth OAuth callback list\n * at the service layer.\n */\n additionalTrustedClientOrigins?: ReadonlyArray<string>;\n };\n [OPEN_HI_STAGE.PROD]?: {\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY]?: OpenHiEnvironmentConfig;\n [OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY]?: Array<OpenHiEnvironmentConfig>;\n /**\n * Additional client origins trusted by this stage beyond the\n * stage-owned admin/website hosts that auto-injection derives from\n * branch context. Each entry is a full `<scheme>://<host>` string\n * with no path and no trailing slash. Consumed by both the REST\n * API CORS allow-list and the Auth OAuth callback list at the\n * service layer.\n */\n additionalTrustedClientOrigins?: ReadonlyArray<string>;\n };\n };\n}\n","export * from \"./open-hi-config\";\n","import { hashString } from \"@codedrifters/utils\";\n\n/**\n * Inputs required to compute the deterministic branch hash that\n * scopes per-branch resources within an OpenHI deployment target.\n *\n * @public\n */\nexport interface ComputeBranchHashOptions {\n /** Application name (e.g. \"openhi\"). */\n readonly appName: string;\n /** Deployment target role identifier (e.g. \"primary\", \"secondary\"). */\n readonly deploymentTargetRole: string;\n /** AWS account id the deployment targets. */\n readonly account: string;\n /** AWS region the deployment targets. */\n readonly region: string;\n /** Git branch name driving this deployment. */\n readonly branchName: string;\n}\n\n/**\n * Compute the deterministic branch hash used by `OpenHiService` to scope\n * per-branch resources. Every `DiscoverableStringParameter` published by\n * an OpenHI service uses this hash as the branch segment of its SSM\n * path: `/{version}/{branchHash}/{serviceType}/{account}/{region}/{paramName}`.\n *\n * Exporting the helper lets external tooling compute the same SSM\n * prefix the CDK stacks publish to without re-implementing — and silently\n * drifting from — the inlined math.\n *\n * @public\n */\nexport const computeBranchHash = (\n options: ComputeBranchHashOptions,\n): string => {\n const { appName, deploymentTargetRole, account, region, branchName } =\n options;\n return hashString(\n [appName, deploymentTargetRole, account, region, branchName].join(\"-\"),\n 6,\n );\n};\n","import {\n OPEN_HI_DEPLOYMENT_TARGET_ROLE,\n OPEN_HI_STAGE,\n OpenHiConfig,\n OpenHiEnvironmentConfig,\n} from \"@openhi/config\";\nimport { App, AppProps } from \"aws-cdk-lib\";\nimport { IConstruct } from \"constructs\";\nimport { OpenHiEnvironment } from \"./open-hi-environment\";\nimport { OpenHiStage } from \"./open-hi-stage\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-app.md\n */\n\n/**\n * Symbol used for runtime type checking to identify OpenHiApp instances.\n *\n * @internal\n */\nconst OPEN_HI_APP_SYMBOL = Symbol.for(\"@openhi/constructs/core.OpenHiApp\");\n\n/**\n * Properties for creating an OpenHiApp instance.\n */\nexport interface OpenHiAppProps extends AppProps {\n /**\n * Optional name for the application.\n */\n readonly appName?: string;\n\n /**\n * The OpenHi configuration object that defines stages, environments, and\n * their associated AWS account and region settings.\n */\n readonly config: OpenHiConfig;\n}\n\n/**\n * Root application construct for OpenHi CDK applications.\n */\nexport class OpenHiApp extends App {\n /**\n * Finds the OpenHiApp instance that contains the given construct in its\n * construct tree.\n */\n public static of(construct: IConstruct): OpenHiApp | undefined {\n return construct.node.scopes.reverse().find(OpenHiApp.isOpenHiApp);\n }\n\n /**\n * Type guard that checks if a value is an OpenHiApp instance.\n */\n public static isOpenHiApp(this: void, x: any): x is OpenHiApp {\n return x !== null && typeof x === \"object\" && OPEN_HI_APP_SYMBOL in x;\n }\n\n /**\n * Name for the application.\n */\n readonly appName: string;\n\n /**\n * The OpenHi configuration object for this application.\n */\n readonly config: OpenHiConfig;\n\n /**\n * Creates a new OpenHiApp instance.\n */\n constructor(props: OpenHiAppProps) {\n super(props);\n\n // Set runtime symbol for type checking\n Object.defineProperty(this, OPEN_HI_APP_SYMBOL, { value: true });\n\n // Store app name, defaulting to \"openhi\" if not provided\n this.appName = props.appName ?? \"openhi\";\n\n // Store configuration for use by child constructs\n this.config = props.config;\n\n // Create stages and environments based on configuration\n // Iterate through all possible stage types (dev, stage, prod)\n Object.values(OPEN_HI_STAGE).forEach((stageType) => {\n // Only create a stage if it's configured in the config\n if (this.config.deploymentTargets?.[stageType]) {\n const stage = new OpenHiStage(this, { stageType });\n\n // Create primary deployment target if configured\n // Each stage can have at most one primary deployment target\n if (\n this.config.deploymentTargets?.[stageType]?.[\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY\n ]\n ) {\n const envConfig =\n this.config.deploymentTargets[stageType][\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY\n ]!;\n new OpenHiEnvironment(stage, {\n deploymentTargetRole: OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY,\n config: envConfig,\n env: { account: envConfig.account, region: envConfig.region },\n });\n }\n\n // Create secondary deployment targets if configured\n // Each stage can have zero or more secondary deployment targets\n if (\n this.config.deploymentTargets?.[stageType]?.[\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY\n ]\n ) {\n this.config.deploymentTargets[stageType][\n OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY\n ]!.forEach((envConfig: OpenHiEnvironmentConfig) => {\n new OpenHiEnvironment(stage, {\n deploymentTargetRole: OPEN_HI_DEPLOYMENT_TARGET_ROLE.SECONDARY,\n config: envConfig,\n env: { account: envConfig.account, region: envConfig.region },\n });\n });\n }\n }\n });\n }\n\n /*****************************************************************************\n *\n * Stages\n *\n ****************************************************************************/\n\n /**\n * Gets all OpenHiStage instances that are direct children of this app.\n\n */\n public get stages(): Array<OpenHiStage> {\n return this.node.children.filter(OpenHiStage.isOpenHiStage);\n }\n\n /**\n * Gets the development stage, if it exists.\n */\n public get devStage(): OpenHiStage | undefined {\n return this.stages.find((stage) => stage.stageType === OPEN_HI_STAGE.DEV);\n }\n\n /**\n * Gets the staging stage, if it exists.\n */\n public get stageStage(): OpenHiStage | undefined {\n return this.stages.find((stage) => stage.stageType === OPEN_HI_STAGE.STAGE);\n }\n\n /**\n * Gets the production stage, if it exists.\n */\n public get prodStage(): OpenHiStage | undefined {\n return this.stages.find((stage) => stage.stageType === OPEN_HI_STAGE.PROD);\n }\n\n /*****************************************************************************\n *\n * Environments\n *\n ****************************************************************************/\n\n /**\n * Gets all OpenHiEnvironment instances across all stages in this app.\n */\n public get environments(): Array<OpenHiEnvironment> {\n return this.stages.flatMap((stage) => stage.environments);\n }\n\n /**\n * Gets all primary environments across all stages in this app.\n */\n public get primaryEnvironments(): Array<OpenHiEnvironment> {\n return this.environments.filter(\n (env) => env.deploymentTargetRole === \"primary\",\n );\n }\n\n /**\n * Gets all secondary environments across all stages in this app.\n */\n public get secondaryEnvironments(): Array<OpenHiEnvironment> {\n return this.environments.filter(\n (env) => env.deploymentTargetRole === \"secondary\",\n );\n }\n}\n","import {\n OPEN_HI_DEPLOYMENT_TARGET_ROLE,\n OpenHiEnvironmentConfig,\n} from \"@openhi/config\";\nimport { Stage, StageProps } from \"aws-cdk-lib\";\nimport { IConstruct } from \"constructs\";\nimport { OpenHiStage } from \"./open-hi-stage\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-environment.md\n */\n\n/**\n * Symbol used to identify OpenHiEnvironment instances at runtime.\n */\nconst OPEN_HI_ENVIRONMENT_SYMBOL = Symbol.for(\n \"@openhi/constructs/core.OpenHiEnvironment\",\n);\n\n/**\n * Properties for creating an OpenHiEnvironment.\n */\nexport interface OpenHiEnvironmentProps extends StageProps {\n /**\n * The deployment target role for this (account, region).\n */\n readonly deploymentTargetRole: (typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE)[keyof typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE];\n\n /**\n * Configuration for this specific environment.\n */\n readonly config: OpenHiEnvironmentConfig;\n}\n\n/**\n * Represents an OpenHi environment within an AWS CDK stage.\n */\nexport class OpenHiEnvironment extends Stage {\n /**\n * Finds the OpenHiEnvironment that contains the given construct.\n */\n public static of(construct: IConstruct): OpenHiEnvironment | undefined {\n return construct.node.scopes\n .reverse()\n .find(OpenHiEnvironment.isOpenHiEnvironment);\n }\n\n /**\n * Type guard to check if a value is an OpenHiEnvironment instance.\n */\n public static isOpenHiEnvironment(\n this: void,\n x: any,\n ): x is OpenHiEnvironment {\n return (\n x !== null && typeof x === \"object\" && OPEN_HI_ENVIRONMENT_SYMBOL in x\n );\n }\n\n /**\n * Configuration for this specific environment.\n */\n readonly config: OpenHiEnvironmentConfig;\n\n /**\n * The deployment target role for this (account, region).\n */\n public readonly deploymentTargetRole: (typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE)[keyof typeof OPEN_HI_DEPLOYMENT_TARGET_ROLE];\n\n /**\n * Creates a new OpenHiEnvironment.\n */\n constructor(\n /**\n * The OpenHiStage that contains this environment.\n */\n public ohStage: OpenHiStage,\n /**\n * Properties for creating the environment.\n */\n public props: OpenHiEnvironmentProps,\n ) {\n // Copy account and region from config into env, if provided.\n // This allows all resources in this environment to default to the correct\n // account and region without having to specify it on each stack or resource.\n if (props.config.account && props.config.region) {\n props = {\n ...props,\n env: {\n account: props.config.account,\n region: props.config.region,\n },\n };\n }\n\n // Determine the stage name:\n // - Primary environments use the environment type as the name\n // - Secondary deployment targets use \"{deploymentTargetRole}-{index}\" format\n const stageName =\n props.deploymentTargetRole === OPEN_HI_DEPLOYMENT_TARGET_ROLE.PRIMARY\n ? props.deploymentTargetRole\n : [props.deploymentTargetRole, ohStage.environments.length].join(\"-\");\n\n super(ohStage, stageName, {\n env: props.env ?? ohStage.props.env,\n ...props,\n });\n\n // Mark this instance as an OpenHiEnvironment for runtime type checking\n Object.defineProperty(this, OPEN_HI_ENVIRONMENT_SYMBOL, { value: true });\n\n this.deploymentTargetRole = props.deploymentTargetRole;\n this.config = props.config;\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport { Stage, StageProps } from \"aws-cdk-lib\";\nimport { IConstruct } from \"constructs\";\nimport { OpenHiApp } from \"./open-hi-app\";\nimport { OpenHiEnvironment } from \"./open-hi-environment\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-stage.md\n */\n\n/**\n * Symbol used to identify OpenHiStage instances at runtime.\n *\n * @internal\n */\nconst OPEN_HI_STAGE_SYMBOL = Symbol.for(\"@openhi/constructs/core.OpenHiStage\");\n\n/**\n * Properties for creating an OpenHiStage instance.\n */\nexport interface OpenHiStageProps extends StageProps {\n /**\n * The type of the OpenHi stage.\n */\n readonly stageType: (typeof OPEN_HI_STAGE)[keyof typeof OPEN_HI_STAGE];\n}\n\n/**\n * Represents a deployment stage in the OpenHi infrastructure hierarchy.\n */\nexport class OpenHiStage extends Stage {\n /**\n * Finds the OpenHiStage that contains the given construct.\n */\n public static of(construct: IConstruct): OpenHiStage | undefined {\n return construct.node.scopes.reverse().find(OpenHiStage.isOpenHiStage);\n }\n\n /**\n * Type guard to check if a value is an OpenHiStage instance.\n */\n public static isOpenHiStage(this: void, x: any): x is OpenHiStage {\n return x !== null && typeof x === \"object\" && OPEN_HI_STAGE_SYMBOL in x;\n }\n\n /**\n * The type of this OpenHi stage.\n */\n public readonly stageType: (typeof OPEN_HI_STAGE)[keyof typeof OPEN_HI_STAGE];\n\n /**\n * Creates a new OpenHiStage instance.\n */\n constructor(\n /**\n * The OpenHiApp that this stage belongs to.\n *\n * @public\n */\n public ohApp: OpenHiApp,\n\n /**\n * Properties for configuring the stage.\n *\n * @public\n */\n public props: OpenHiStageProps,\n ) {\n super(ohApp, props.stageType, props);\n\n Object.defineProperty(this, OPEN_HI_STAGE_SYMBOL, { value: true });\n\n this.stageType = props.stageType;\n }\n\n /**\n * Gets all OpenHiEnvironment instances contained within this stage.\n */\n public get environments(): Array<OpenHiEnvironment> {\n return this.node.children.filter(OpenHiEnvironment.isOpenHiEnvironment);\n }\n\n /**\n * Gets the primary OpenHiEnvironment for this stage, if one exists.\n */\n public get primaryEnvironment(): OpenHiEnvironment | undefined {\n return this.environments.find(\n (env) => env.deploymentTargetRole === \"primary\",\n );\n }\n\n /**\n * Gets all secondary OpenHiEnvironment instances for this stage.\n */\n public get secondaryEnvironments(): Array<OpenHiEnvironment> {\n return this.environments.filter(\n (env) => env.deploymentTargetRole === \"secondary\",\n );\n }\n}\n","import {\n findGitBranch,\n findGitRepoName,\n hashString,\n} from \"@codedrifters/utils\";\nimport { OPEN_HI_STAGE, OpenHiEnvironmentConfig } from \"@openhi/config\";\nimport { RemovalPolicy, Stack, StackProps, Tags } from \"aws-cdk-lib\";\nimport { paramCase } from \"change-case\";\nimport { computeBranchHash } from \"./compute-branch-hash\";\nimport { OpenHiEnvironment } from \"./open-hi-environment\";\n\n/**\n * Default release branch used when no override is supplied.\n */\nconst DEFAULT_RELEASE_BRANCH = \"main\";\n\n/**\n * Max length of the `childZonePrefix` segment. See\n * {@link OpenHiService.childZonePrefix} for the byte-budget reasoning.\n */\nconst CHILD_ZONE_PREFIX_MAX_LENGTH = 56;\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/app/open-hi-service.md\n */\n\n/**\n * Known OpenHI service type strings. Each service class defines its own\n * static SERVICE_TYPE (e.g. OpenHiAuthService.SERVICE_TYPE === \"auth\").\n *\n * @public\n */\nexport type OpenHiServiceType =\n | \"auth\"\n | \"rest-api\"\n | \"data\"\n | \"global\"\n | \"graphql-api\"\n | \"website\";\n\n/**\n * Inputs to {@link OpenHiService.composeServiceDomain}. All fields are\n * supplied by the caller — the helper itself reads no environment state.\n *\n * @public\n */\nexport interface ComposeServiceDomainOptions {\n /** Sub-domain prefix (e.g. `\"api\"`, `\"admin\"`). */\n readonly domainPrefix: string;\n /** Branch name being deployed. */\n readonly branchName: string;\n /** Release branch name (e.g. `\"main\"`). */\n readonly defaultReleaseBranch: string;\n /** Per-branch child-zone prefix (typically `paramCase(branchName)` truncated). */\n readonly childZonePrefix: string;\n /** DNS zone name (e.g. `\"dev.openhi.org\"`). */\n readonly zoneName: string;\n}\n\n/**\n * Tag-key suffixes applied by every OpenHiService stack via Tags.of().\n * Full keys are composed `${appName}:${suffix}` — see {@link openHiTagKey}.\n * Consumers that filter or project these tags (e.g. the platform-deploy\n * bridge) import these suffixes rather than redeclaring the strings.\n *\n * @public\n */\nexport const OPENHI_TAG_SUFFIX_REPO_NAME = \"repo-name\";\n/** @public */\nexport const OPENHI_TAG_SUFFIX_BRANCH_NAME = \"branch-name\";\n/** @public */\nexport const OPENHI_TAG_SUFFIX_SERVICE_TYPE = \"service-type\";\n/** @public */\nexport const OPENHI_TAG_SUFFIX_STAGE_TYPE = \"stage-type\";\n\n/**\n * Compose a full stack-tag key from an `appName` and a suffix from\n * {@link OPENHI_TAG_SUFFIX_REPO_NAME} et al.\n *\n * @public\n */\nexport const openHiTagKey = (appName: string, suffix: string): string =>\n `${appName}:${suffix}`;\n\n/**\n * Properties for creating an {@link OpenHiService} stack.\n *\n * @public\n */\nexport interface OpenHiServiceProps extends StackProps {\n /**\n * Optional branch name override.\n */\n readonly branchName?: string;\n\n /**\n * Optional repository name override.\n */\n readonly repoName?: string;\n\n /**\n * Optional application name override.\n */\n readonly appName?: string;\n\n /**\n * Default release branch name.\n */\n readonly defaultReleaseBranch?: string;\n\n /**\n * The removal policy for persistent stack resources.\n */\n readonly removalPolicy?: RemovalPolicy;\n\n /**\n * Environment configuration for this service.\n */\n readonly config?: OpenHiEnvironmentConfig;\n\n /**\n * A constant that identifies the service type.\n */\n readonly serviceType?: OpenHiServiceType;\n}\n\n/**\n * Represents an OpenHI service stack within the OpenHI platform.\n * Subclasses must override {@link serviceType} to return their static SERVICE_TYPE.\n */\nexport abstract class OpenHiService extends Stack {\n /**\n * Compose the full per-deploy domain for an OpenHI service.\n *\n * On the release branch (`branchName === defaultReleaseBranch`), the\n * full domain is `<domainPrefix>.<zoneName>`. On every other branch\n * the per-PR preview hostname is\n * `<domainPrefix>-<childZonePrefix>.<zoneName>`.\n *\n * Pure helper — reads no environment state. Subclasses expose thin\n * statics (`composeFullDomain`) that fill in `domainPrefix` from their\n * own service constant and delegate here.\n */\n public static composeServiceDomain(\n opts: ComposeServiceDomainOptions,\n ): string {\n const isRelease = opts.branchName === opts.defaultReleaseBranch;\n const subDomain = isRelease\n ? opts.domainPrefix\n : `${opts.domainPrefix}-${opts.childZonePrefix}`;\n return `${subDomain}.${opts.zoneName}`;\n }\n\n /**\n * Compute the `childZonePrefix` segment for a given branch — kebab-cased\n * and truncated to {@link CHILD_ZONE_PREFIX_MAX_LENGTH}. Matches the\n * per-instance {@link OpenHiService.childZonePrefix} getter so consumers\n * can compose hostnames identical to the service's own without\n * instantiating it.\n */\n public static computeChildZonePrefix(branchName: string): string {\n return paramCase(branchName).slice(0, CHILD_ZONE_PREFIX_MAX_LENGTH);\n }\n\n /**\n * Resolve the branch context the service would compute internally given\n * an environment and optional overrides. Mirrors the same defaulting\n * (props override → JEST sentinel → `GIT_BRANCH_NAME` env → git\n * detection on DEV → release branch on stage/prod) the\n * {@link OpenHiService} constructor uses.\n *\n * Consumers (e.g. sibling stack entries) call this to predict the\n * branch values a service will see at synth time so they can compose\n * hostnames against the same inputs.\n */\n public static resolveBranchContext(\n ohEnv: OpenHiEnvironment,\n overrides: { branchName?: string; defaultReleaseBranch?: string } = {},\n ): {\n branchName: string;\n defaultReleaseBranch: string;\n childZonePrefix: string;\n } {\n const defaultReleaseBranch =\n overrides.defaultReleaseBranch ?? DEFAULT_RELEASE_BRANCH;\n const branchName =\n overrides.branchName ??\n (process.env.JEST_WORKER_ID\n ? \"test-branch\"\n : process.env.GIT_BRANCH_NAME?.trim() ||\n (ohEnv.ohStage.stageType === OPEN_HI_STAGE.DEV\n ? findGitBranch()\n : defaultReleaseBranch));\n const childZonePrefix = OpenHiService.computeChildZonePrefix(branchName);\n return { branchName, defaultReleaseBranch, childZonePrefix };\n }\n\n /**\n * The service/stack ID that was passed to the constructor.\n */\n public readonly serviceId: string;\n\n /**\n * The deployment target role identifier.\n */\n public readonly deploymentTargetRole: string;\n\n /**\n * Repository name used in resource tagging.\n */\n public readonly repoName: string;\n\n /**\n * Application name identifier.\n */\n public readonly appName: string;\n\n /**\n * Default release branch name.\n */\n public readonly defaultReleaseBranch: string;\n\n /**\n * Branch name used when calculating resource names and hashes.\n */\n public readonly branchName: string;\n\n /**\n * Short hash unique to the deployment target (app name, deployment target role, account, region).\n */\n public readonly environmentHash: string;\n\n /**\n * Short hash unique to the environment and branch combination.\n */\n public readonly branchHash: string;\n\n /**\n * Branch hash computed against {@link defaultReleaseBranch} rather than\n * {@link branchName}. On the release branch this equals {@link branchHash};\n * on every other branch it identifies the namespace the release-branch\n * deploy of this service writes to. Use when looking up SSM parameters\n * that only the release-branch stack publishes (e.g. shared static-hosting\n * bucket ARN).\n */\n public readonly releaseBranchHash: string;\n\n /**\n * Short hash unique to the specific stack/service.\n */\n public readonly stackHash: string;\n\n /**\n * The removal policy for persistent stack resources.\n */\n public readonly removalPolicy: RemovalPolicy;\n\n /**\n * Environment configuration for this service.\n * This is either the value passed in or the default config\n */\n public readonly config: OpenHiEnvironmentConfig;\n\n /**\n * Service type identifier. Override in subclasses to return the class's static SERVICE_TYPE.\n * Used for parameter names, tags, and service discovery.\n */\n abstract get serviceType(): OpenHiServiceType | string;\n\n /**\n * Creates a new OpenHI service stack.\n *\n * @param ohEnv - The OpenHI environment (stage) this service belongs to\n * @param id - Unique identifier for this service stack (e.g., \"user-service\")\n * @param props - Optional properties for configuring the service\n *\n * @throws \\{Error\\} If account and region are not defined in props or environment\n *\n */\n constructor(\n public ohEnv: OpenHiEnvironment,\n id: string,\n public props: OpenHiServiceProps = {},\n ) {\n // Determine the account and region based on environment or user passed props.\n // This must be done before calling super() as it's needed for stack naming.\n const { account, region } = props.env || ohEnv;\n if (!account || !region) {\n throw new Error(\n \"Account and region must be defined in OpenHiServiceProps or OpenHiEnvironment\",\n );\n }\n\n // Get app name from the app in the hierarchy (via environment -> stage -> app)\n const appName = props.appName ?? ohEnv.ohStage.ohApp.appName ?? \"openhi\";\n\n // Initialize deployment context properties\n // Repo name is used in tagging. This tag value is important for tracking\n // when tearing preview stacks back down. If not provided, detect from git.\n const repoName = props.repoName ?? findGitRepoName();\n\n // Resolve branch name + defaultReleaseBranch via the public helper so\n // sibling stacks can reproduce the same values without instantiating\n // a service. Detection logic (in resolveBranchContext):\n // - If explicitly provided in props, use that value\n // - If Jest is running, use \"test-branch\" to avoid snapshot test issues\n // - If GIT_BRANCH_NAME env is set (e.g. by CI), use it\n // - If in dev stage, detect from git using findGitBranch()\n // - Otherwise (stage/prod), default to defaultReleaseBranch\n const { branchName, defaultReleaseBranch } =\n OpenHiService.resolveBranchContext(ohEnv, {\n ...(props.branchName !== undefined && { branchName: props.branchName }),\n ...(props.defaultReleaseBranch !== undefined && {\n defaultReleaseBranch: props.defaultReleaseBranch,\n }),\n });\n\n // Compute environment hash: unique to deployment target (app name, role, account, region)\n // Mainly used for DNS names and deployment-target-scoped resources\n const environmentHash = hashString(\n [appName, ohEnv.deploymentTargetRole, account, region].join(\"-\"),\n 6,\n );\n\n // Compute branch hash: unique to deployment target and branch combination\n // Useful for resources shared across stacks within the same branch\n const branchHash = computeBranchHash({\n appName,\n deploymentTargetRole: ohEnv.deploymentTargetRole,\n account,\n region,\n branchName,\n });\n\n // Compute release-branch hash: same formula as branchHash but with\n // defaultReleaseBranch in place of branchName. Lets feature-branch stacks\n // address resources published into the release-branch namespace (e.g. the\n // shared static-hosting bucket ARN written by the release-branch website\n // deploy).\n const releaseBranchHash = hashString(\n [\n appName,\n ohEnv.deploymentTargetRole,\n account,\n region,\n defaultReleaseBranch,\n ].join(\"-\"),\n 6,\n );\n\n // Compute stack hash: unique to the specific stack/service\n // Useful for stack-specific resources like S3 buckets, KMS key aliases\n // This ensures two PR builds or different services don't collide\n const stackHash = hashString(\n [\n appName,\n ohEnv.deploymentTargetRole,\n account,\n region,\n branchName,\n id,\n ].join(\"-\"),\n 6,\n );\n\n // Set the removal policy for this stack based on the deployment target role.\n // Production stages retain resources, others destroy them on stack deletion.\n const removalPolicy =\n props.removalPolicy ??\n (ohEnv.ohStage.stageType === OPEN_HI_STAGE.PROD\n ? RemovalPolicy.RETAIN\n : RemovalPolicy.DESTROY);\n Object.assign(props, { removalPolicy });\n\n // Description to use for the stack and all resources within it.\n // Includes service ID, branch name, and hash for easy identification.\n const description = `OpenHi Service: ${id} [${branchName}] - ${branchHash}`;\n\n // Call the super constructor of Stack.\n // This initializes the AWS CDK Stack with:\n // - Scope: the OpenHI environment\n // - ID: unique stack name including branch hash\n // - Props: stack properties including description and removal policy\n super(ohEnv, [branchHash, id, account, region].join(\"-\"), {\n ...props,\n description,\n });\n\n // Store the service ID for use in deployment context and other operations.\n this.serviceId = id;\n\n // Set the removal policy for this stack based on the deployment target role.\n this.removalPolicy = removalPolicy;\n\n /**\n * Explicit config or use the environment config as a backup,\n */\n this.config = props.config ?? ohEnv.props.config;\n\n // Initialize deployment context properties directly on the service\n this.deploymentTargetRole = ohEnv.deploymentTargetRole;\n this.repoName = repoName;\n this.appName = appName;\n this.defaultReleaseBranch = defaultReleaseBranch;\n this.branchName = branchName;\n this.environmentHash = environmentHash;\n this.branchHash = branchHash;\n this.releaseBranchHash = releaseBranchHash;\n this.stackHash = stackHash;\n\n // Pre-populate the AZ context cache for this stack so any construct that\n // calls `stack.availabilityZones` (notably RDS DatabaseCluster, ELBs, and\n // anything else that fans out across AZs) gets concrete values without\n // triggering a CDK context lookup. Without this, CI synth records the\n // lookup as \"missing\" and deploy fails because the GitHubOpenHiDeployer\n // role can't assume `cdk-…-lookup-role-…` (only granted to dev machines\n // by default). AZ names follow the stable `<region>a/b/c…` pattern across\n // all current commercial AWS regions; if a deployment ever targets a\n // region where this assumption breaks, override here per region.\n this.node.setContext(\n `availability-zones:account=${account}:region=${region}`,\n [`${region}a`, `${region}b`, `${region}c`],\n );\n\n // Standard tagging across all resources in the stack.\n // Use id (the service type string passed to super) since abstract serviceType cannot be accessed in constructor.\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_REPO_NAME),\n repoName.slice(0, 255),\n );\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_BRANCH_NAME),\n branchName.slice(0, 255),\n );\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_SERVICE_TYPE),\n id.slice(0, 255),\n );\n Tags.of(this).add(\n openHiTagKey(appName, OPENHI_TAG_SUFFIX_STAGE_TYPE),\n ohEnv.ohStage.stageType.slice(0, 255),\n );\n }\n\n /**\n * DNS prefix for this branche's child zone. Capped at 56 chars so\n * that a `<service>-<prefix>` hostname segment stays under the 63-byte\n * DNS label limit even for the longest current prefix (`admin-`, 6\n * bytes; the matching API uses `api-`, 4 bytes). 56 leaves 1 byte of\n * headroom on the longer side.\n */\n public get childZonePrefix(): string {\n return OpenHiService.computeChildZonePrefix(this.branchName);\n }\n}\n","import {\n Certificate,\n CertificateProps,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/acm/root-wildcard-certificate.md\n */\n\nexport class RootWildcardCertificate extends Certificate {\n /**\n * Used when storing the Certificate ARN in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"ROOT_WILDCARD_CERT_ARN\";\n\n /**\n * Using a special name here since this will be shared and used among many\n * stacks and services. Use with OpenHiGlobalService.rootWildcardCertificateFromConstruct.\n */\n public static ssmParameterName(): string {\n return (\n \"/\" +\n [\"GLOBAL\", RootWildcardCertificate.SSM_PARAM_NAME].join(\"/\").toUpperCase()\n );\n }\n\n constructor(scope: Construct, props: CertificateProps) {\n super(scope, \"root-wildcard-certificate\", { ...props });\n\n /**\n * Generate the SSM Parameter used to store this Certificate's ARN.\n */\n new StringParameter(this, \"wildcard-cert-param\", {\n parameterName: RootWildcardCertificate.ssmParameterName(),\n stringValue: this.certificateArn,\n });\n }\n}\n","import { HttpApi, HttpApiProps } from \"aws-cdk-lib/aws-apigatewayv2\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/api-gateway/root-http-api.md\n */\n\nexport interface RootHttpApiProps extends HttpApiProps {}\n\nexport class RootHttpApi extends HttpApi {\n /**\n * Used when storing the API ID in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"ROOT_HTTP_API\";\n\n constructor(scope: Construct, props: RootHttpApiProps = {}) {\n const stack = OpenHiService.of(scope) as OpenHiService;\n\n const origins = props.corsPreflight?.allowOrigins;\n if (origins?.length) {\n const withTrailingSlash = origins.filter((o) => o.endsWith(\"/\"));\n if (withTrailingSlash.length > 0) {\n throw new Error(\n `CORS allowOrigins must not include a trailing slash. The browser Origin header is scheme + host + port only (no path), so API Gateway will not match origins like \"https://example.com/\" and CORS can fail. Invalid: ${withTrailingSlash.join(\", \")}. Use e.g. \"https://example.com\" instead.`,\n );\n }\n }\n\n super(scope, \"http-api\", {\n /**\n * User provided props\n */\n ...props,\n\n /**\n * Required\n */\n apiName: [\"root\", \"http\", \"api\", stack.branchHash].join(\"-\"),\n });\n }\n}\n","import {\n Definition,\n GraphqlApi,\n GraphqlApiProps,\n IGraphqlApi,\n} from \"aws-cdk-lib/aws-appsync\";\nimport { CodeFirstSchema, GraphqlType, ObjectType } from \"awscdk-appsync-utils\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/app-sync/root-graphql-api.md\n */\n\nexport interface RootGraphqlApiProps extends GraphqlApiProps {}\n\nexport class RootGraphqlApi extends GraphqlApi {\n /**\n * Used when storing the GraphQl API ID in SSM\n */\n public static readonly SSM_PARAM_NAME = \"ROOT_GRAPHQL_API\";\n\n public static fromConstruct(scope: Construct): IGraphqlApi {\n const graphqlApiId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: RootGraphqlApi.SSM_PARAM_NAME,\n serviceType: \"graphql-api\",\n });\n\n return GraphqlApi.fromGraphqlApiAttributes(scope, \"root-graphql-api\", {\n graphqlApiId,\n });\n }\n\n constructor(scope: Construct, props?: Omit<RootGraphqlApiProps, \"name\">) {\n const stack = OpenHiService.of(scope) as OpenHiService;\n\n const schema = new CodeFirstSchema();\n schema.addType(\n new ObjectType(\"Query\", {\n definition: { HelloWorld: GraphqlType.string() },\n }),\n );\n\n super(scope, \"root-graphql-api\", {\n /**\n * Defaults\n */\n queryDepthLimit: 2,\n resolverCountLimit: 50,\n definition: Definition.fromSchema(schema),\n\n /**\n * Overrideable props\n */\n ...props,\n\n /**\n * Required\n */\n name: [\"root\", \"graphql\", \"api\", stack.branchHash].join(\"-\"),\n });\n\n /**\n * Generate the SSM Parameter used to store this GraphQL API's ID.\n */\n new DiscoverableStringParameter(this, \"graphql-api-param\", {\n ssmParamName: RootGraphqlApi.SSM_PARAM_NAME,\n serviceType: \"graphql-api\",\n stringValue: this.apiId,\n });\n }\n}\n","import { Tags } from \"aws-cdk-lib\";\nimport {\n StringParameter,\n type StringParameterProps,\n} from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/ssm/discoverable-string-parameter.md\n */\n\n/*******************************************************************************\n *\n * DiscoverableStringParameterProps: props for creating or looking up SSM\n * parameters. Includes StringParameterProps (minus parameterName) plus\n * name-building fields used by buildParameterName.\n *\n ******************************************************************************/\n\nexport interface DiscoverableStringParameterProps extends Omit<\n StringParameterProps,\n \"parameterName\"\n> {\n /**\n * SSM param name used to build the SSM parameter name via buildParameterName\n * and stored as a tag on the parameter for discoverability.\n */\n readonly ssmParamName: string;\n\n /**\n * The environment hash the parameter belongs to.\n * @default - the current stack's environment hash\n */\n readonly branchHash?: string;\n\n /**\n * The service type the parameter belongs to.\n * @default - the current stack's service type\n */\n readonly serviceType?: string;\n\n /**\n * The AWS account the parameter belongs to.\n * @default - the current stack's account\n */\n readonly account?: string;\n\n /**\n * The AWS region the parameter belongs to.\n * @default - the current stack's region\n */\n readonly region?: string;\n}\n\n/**\n * Props for buildParameterName and valueForLookupName.\n * Includes ssmParamName (required) and optional overrides (branchHash, serviceType, account, region).\n */\nexport type BuildParameterNameProps = Pick<\n DiscoverableStringParameterProps,\n \"ssmParamName\" | \"branchHash\" | \"serviceType\" | \"account\" | \"region\"\n>;\n\n/**\n * Discoverable SSM string parameter construct. Extends CDK StringParameter:\n * builds parameterName from the given name via buildParameterName and tags\n * the parameter with the name constant.\n */\nexport class DiscoverableStringParameter extends StringParameter {\n /**\n * Version of the parameter name format / discoverability schema.\n * Bump when buildParameterName or tagging semantics change.\n * Also used to drive replacement of parameters during CloudFormation deploys.\n */\n public static readonly version = \"v1\";\n\n /**\n * Build a param name based on predictable attributes found in services and\n * constructs. Used for storage and retrieval of SSM values across services.\n */\n public static buildParameterName(\n scope: Construct,\n props: BuildParameterNameProps,\n ): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return (\n \"/\" +\n [\n DiscoverableStringParameter.version,\n props.branchHash ?? stack.branchHash,\n props.serviceType ?? stack.serviceType,\n props.account ?? stack.account,\n props.region ?? stack.region,\n props.ssmParamName,\n ]\n .join(\"/\")\n .toUpperCase()\n );\n }\n\n /**\n * Read the string value of an SSM parameter created with DiscoverableStringParameter,\n * using props that include ssmParamName and optional overrides (e.g. serviceType).\n */\n public static valueForLookupName(\n scope: Construct,\n props: BuildParameterNameProps,\n ): string {\n const paramName = DiscoverableStringParameter.buildParameterName(\n scope,\n props,\n );\n return StringParameter.valueForStringParameter(scope, paramName);\n }\n\n constructor(\n scope: Construct,\n id: string,\n props: DiscoverableStringParameterProps,\n ) {\n const { ssmParamName, branchHash, serviceType, account, region, ...rest } =\n props;\n\n const parameterName = DiscoverableStringParameter.buildParameterName(\n scope,\n props,\n );\n\n super(scope, id + \"-\" + DiscoverableStringParameter.version, {\n ...rest,\n parameterName,\n });\n\n const { appName } = OpenHiService.of(scope) as OpenHiService;\n Tags.of(this).add(`${appName}:param-name`, ssmParamName);\n }\n}\n","import {\n FeaturePlan,\n UserPool,\n UserPoolProps,\n VerificationEmailStyle,\n} from \"aws-cdk-lib/aws-cognito\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool.md\n */\n\nexport class CognitoUserPool extends UserPool {\n /**\n * Used when storing the User Pool ID in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL\";\n\n constructor(scope: Construct, props: UserPoolProps = {}) {\n const service = OpenHiService.of(scope) as OpenHiService;\n\n super(scope, \"user-pool\", {\n /**\n * Defaults\n */\n selfSignUpEnabled: true,\n signInAliases: {\n email: true,\n },\n userVerification: {\n emailSubject: \"Verify your email!\",\n emailBody: \"Your verification code is {####}.\",\n emailStyle: VerificationEmailStyle.CODE,\n },\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n // Plus is required for access-token V2 claim customization in the\n // pre-token-generation Lambda. Essentials silently drops\n // claimsAndScopeOverrideDetails.accessTokenGeneration.claimsToAddOrOverride.\n featurePlan: FeaturePlan.PLUS,\n\n /**\n * Over-rideable props\n */\n ...props,\n\n /**\n * Required\n */\n userPoolName: [\"cognito\", \"user\", \"pool\", service.branchHash].join(\"-\"),\n });\n }\n}\n","import { UserPoolClient, UserPoolClientProps } from \"aws-cdk-lib/aws-cognito\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool-client.md\n */\n\nexport class CognitoUserPoolClient extends UserPoolClient {\n /**\n * Used when storing the User Pool Client ID in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL_CLIENT\";\n\n constructor(scope: Construct, props: UserPoolClientProps) {\n super(scope, \"user-pool-client\", {\n // Default: SPA client (no secret). OAuth flow + callback/logout URL\n // composition is the owning service's responsibility — pass via\n // `props.oAuth` (see `OpenHiAuthService.resolveOAuthRedirectUrls`).\n generateSecret: false,\n ...props,\n });\n }\n}\n","import { UserPoolDomain, UserPoolDomainProps } from \"aws-cdk-lib/aws-cognito\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool-domain.md\n */\n\nexport class CognitoUserPoolDomain extends UserPoolDomain {\n /**\n * Used when storing the User Pool Domain in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL_DOMAIN\";\n\n constructor(scope: Construct, props: UserPoolDomainProps) {\n /**\n * This supports both custom and native Cognito domains, but we need to\n * name them uniquely so that swap outs work and don't cause conflicts\n * when cloudformation does it's deploy.\n */\n const id = props.cognitoDomain?.domainPrefix\n ? \"cognito-domain\"\n : \"custom-domain\";\n\n super(scope, id, {\n ...props,\n });\n }\n}\n","import { Key, KeyProps } from \"aws-cdk-lib/aws-kms\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/cognito-user-pool-kms-key.md\n */\n\nexport class CognitoUserPoolKmsKey extends Key {\n /**\n * Used when storing the KMS Key in SSM.\n */\n public static readonly SSM_PARAM_NAME = \"COGNITO_USER_POOL_KMS_KEY\";\n\n constructor(scope: Construct, props: KeyProps = {}) {\n const service = OpenHiService.of(scope) as OpenHiService;\n\n super(scope, \"kms-key\", {\n ...props,\n // alias: [\"alias\", \"cognito\", service.branchHash].join(\"/\"),\n description: `KMS Key for Cognito User Pool - ${service.branchHash}`,\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/post-authentication-lambda.md\n */\n\nconst HANDLER_NAME = \"post-authentication.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\n/**\n * Lambda used as Cognito Post Authentication trigger.\n */\nexport class PostAuthenticationLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct) {\n super(scope, \"post-authentication-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/post-confirmation-lambda.md\n */\n\nconst HANDLER_NAME = \"post-confirmation.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nconst resolveHandlerEntry = (dirname: string): string => {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n};\n\nexport interface PostConfirmationLambdaProps {\n /**\n * Control-plane EventBridge bus name. Passed to the Lambda as\n * CONTROL_EVENT_BUS_NAME so it can publish onboarding workflow events.\n */\n readonly controlEventBusName: string;\n}\n\n/**\n * Lambda used as Cognito Post Confirmation trigger. It publishes a control\n * event and returns quickly; workflow Lambdas own provisioning.\n */\nexport class PostConfirmationLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, props: PostConfirmationLambdaProps) {\n super(scope, \"post-confirmation-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n CONTROL_EVENT_BUS_NAME: props.controlEventBusName,\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/cognito/pre-token-generation-lambda.md\n */\n\nconst HANDLER_NAME = \"pre-token-generation.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\nexport interface PreTokenGenerationLambdaProps {\n /**\n * DynamoDB data store table name. Passed to the Lambda as DYNAMO_TABLE_NAME\n * so the control-plane ElectroDB service reads the User by Cognito `sub`\n * (GSI2) and the user's first active Membership (fallback path).\n */\n readonly dynamoTableName: string;\n}\n\n/**\n * Lambda used as Cognito Pre Token Generation trigger. Resolves the OpenHI\n * User from the request's Cognito `sub` and injects `ohi_tid`, `ohi_wid`,\n * `ohi_uid`, `ohi_uname` into both the ID token and the access token\n * (ADR 2026-03-17-01).\n */\nexport class PreTokenGenerationLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, props: PreTokenGenerationLambdaProps) {\n super(scope, \"pre-token-generation-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n DYNAMO_TABLE_NAME: props.dynamoTableName,\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, RemovalPolicy, Size, Tags } from \"aws-cdk-lib\";\nimport * as events from \"aws-cdk-lib/aws-events\";\nimport * as kinesis from \"aws-cdk-lib/aws-kinesis\";\nimport * as kinesisfirehose from \"aws-cdk-lib/aws-kinesisfirehose\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport * as s3 from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService, openHiTagKey } from \"../../app\";\n\nconst HANDLER_NAME = \"firehose-archive-transform.handler.js\";\n\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n return path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface DataStoreHistoricalArchiveProps {\n /**\n * Kinesis stream that receives DynamoDB item-level changes (table Kinesis destination).\n */\n readonly kinesisStream: kinesis.IStream;\n /**\n * Removal policy for the archive bucket and related resources.\n */\n readonly removalPolicy: RemovalPolicy;\n /**\n * Short hash for unique stream/bucket naming within the deployment.\n */\n readonly stackHash: string;\n /**\n * When set, the Firehose transform Lambda publishes qualifying changes to\n * this bus via PutEvents (ADR 2026-03-02-01).\n */\n readonly dataEventBus?: events.IEventBus;\n}\n\n/**\n * DynamoDB change stream → Kinesis → Firehose → S3 with a transform Lambda for\n * scope filtering and dynamic partitioning (ADR 2026-03-11-02). The same Lambda\n * publishes qualifying current-resource changes to the data event bus (ADR 2026-03-02-01)\n * when {@link DataStoreHistoricalArchiveProps.dataEventBus} is set.\n */\nexport class DataStoreHistoricalArchive extends Construct {\n public readonly archiveBucket: s3.Bucket;\n /**\n * Receives PutEvents payloads that still fail after in-Lambda retries when\n * {@link DataStoreHistoricalArchiveProps.dataEventBus} is configured.\n */\n public readonly putEventsFailureDlqBucket?: s3.Bucket;\n public readonly deliveryStream: kinesisfirehose.IDeliveryStream;\n public readonly transformFunction: NodejsFunction;\n\n constructor(\n scope: Construct,\n id: string,\n props: DataStoreHistoricalArchiveProps,\n ) {\n super(scope, id);\n\n this.archiveBucket = new s3.Bucket(this, \"ArchiveBucket\", {\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n removalPolicy: props.removalPolicy,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n versioned: true,\n });\n\n const putEventsFailureDlqBucket = props.dataEventBus\n ? new s3.Bucket(this, \"PutEventsFailureDlq\", {\n blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,\n encryption: s3.BucketEncryption.S3_MANAGED,\n enforceSSL: true,\n removalPolicy: props.removalPolicy,\n autoDeleteObjects: props.removalPolicy === RemovalPolicy.DESTROY,\n versioned: false,\n })\n : undefined;\n if (putEventsFailureDlqBucket) {\n const appName = (OpenHiService.of(this) as OpenHiService).appName;\n Tags.of(putEventsFailureDlqBucket).add(\n openHiTagKey(appName, \"resource-role\"),\n \"dead-letter-queue\",\n );\n Tags.of(putEventsFailureDlqBucket).add(\n openHiTagKey(appName, \"pipeline\"),\n \"data-replication\",\n );\n }\n this.putEventsFailureDlqBucket = putEventsFailureDlqBucket;\n\n this.transformFunction = new NodejsFunction(this, \"FirehoseTransform\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n description:\n \"Firehose transform: filter CURRENT resource rows, S3 keys, EventBridge PutEvents\",\n environment:\n props.dataEventBus && putEventsFailureDlqBucket\n ? {\n DATA_EVENT_BUS_NAME: props.dataEventBus.eventBusName,\n DATA_STORE_PUT_EVENTS_DLQ_BUCKET:\n putEventsFailureDlqBucket.bucketName,\n }\n : undefined,\n bundling: {\n minify: true,\n sourceMap: false,\n },\n });\n\n props.dataEventBus?.grantPutEventsTo(this.transformFunction);\n putEventsFailureDlqBucket?.grantPut(this.transformFunction);\n\n const processor = new kinesisfirehose.LambdaFunctionProcessor(\n this.transformFunction,\n {\n bufferInterval: Duration.seconds(60),\n bufferSize: Size.mebibytes(3),\n retries: 3,\n },\n );\n\n const destination = new kinesisfirehose.S3Bucket(this.archiveBucket, {\n compression: kinesisfirehose.Compression.GZIP,\n bufferingInterval: Duration.seconds(300),\n // Firehose requires SizeInMBs ≥ 64 when dynamic partitioning is enabled.\n bufferingSize: Size.mebibytes(64),\n processors: [processor],\n errorOutputPrefix:\n \"errors/!{firehose:error-output-type}/!{timestamp:yyyy/MM/dd/HH}/\",\n loggingConfig: new kinesisfirehose.EnableLogging(),\n });\n\n this.deliveryStream = new kinesisfirehose.DeliveryStream(\n this,\n \"ArchiveDeliveryStream\",\n {\n deliveryStreamName: `openhi-dstore-arch-${props.stackHash}`,\n source: new kinesisfirehose.KinesisStreamSource(props.kinesisStream),\n destination,\n },\n );\n\n const cfn = this.deliveryStream.node\n .defaultChild as kinesisfirehose.CfnDeliveryStream;\n cfn.addPropertyOverride(\n \"ExtendedS3DestinationConfiguration.DynamicPartitioningConfiguration\",\n {\n Enabled: true,\n RetryOptions: { DurationInSeconds: 300 },\n },\n );\n cfn.addPropertyOverride(\n \"ExtendedS3DestinationConfiguration.Prefix\",\n \"!{partitionKeyFromLambda:tenantId}/!{partitionKeyFromLambda:workspaceId}/!{partitionKeyFromLambda:resourceType}/!{partitionKeyFromLambda:resourceId}/!{partitionKeyFromLambda:version}/\",\n );\n }\n}\n","import { RemovalPolicy } from \"aws-cdk-lib\";\nimport {\n AttributeType,\n BillingMode,\n ProjectionType,\n Table,\n TableProps,\n} from \"aws-cdk-lib/aws-dynamodb\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/dynamodb/dynamo-db-data-store.md\n */\n\n/**\n * DynamoDB table name for the data store. Used for cross-stack reference and\n * deterministic naming per branch. The table backs the app data store.\n */\nexport function getDynamoDbDataStoreTableName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `data-store-${stack.branchHash}`;\n}\n\nexport interface DynamoDbDataStoreProps extends Omit<\n TableProps,\n \"tableName\" | \"removalPolicy\"\n> {\n /**\n * Optional removal policy override. If not set, uses the service's default\n * removal policy (RETAIN for prod, DESTROY otherwise).\n */\n readonly removalPolicy?: RemovalPolicy;\n}\n\n/**\n * DynamoDB table implementing the single-table design for app data (FHIR\n * resources data plane and platform control plane), per planning ADR-011 and\n * DR-004.\n *\n * @see {@link https://github.com/codedrifters/openhi/blob/main/sites/www-docs/content/architecture/dynamodb-single-table-design.md | DynamoDB Single-Table Design}\n *\n * Primary key: PK (String), SK (String).\n *\n * GSIs:\n * - **GSI1 — Unified Sharded List** (`GSI1PK`/`GSI1SK`, INCLUDE projection per\n * DR-004). Primary list/lookup index for both data-plane FHIR resources and\n * control-plane entities (User, Tenant, Workspace, Membership, Role,\n * RoleAssignment, Configuration). PK shape:\n * `TID#<tid>#WID#<wid>#RT#<Type>#SHARD#<n>` with 4 shards\n * (`n = hash(id) mod 4`). SK shape per `extractSortKey`: labeled types use\n * `<normalizedLabel>#<id>`; unlabeled use `<ISO-8601 lastUpdated>#<id>`.\n * - **GSI2 — Sub-Lookup** (`GSI2PK`/`GSI2SK`, INCLUDE projection). Resolves\n * `UserEntity` from a Cognito `sub` for the Pre Token Generation Lambda.\n * PK shape: `USER#SUB#<cognitoSub>`. SK shape: `CURRENT`.\n *\n * For historical archive to S3, pass `kinesisStream` and `stream` (e.g.\n * `StreamViewType.NEW_AND_OLD_IMAGES`) on the table props per ADR 2026-03-11-02.\n */\nexport class DynamoDbDataStore extends Table {\n constructor(\n scope: Construct,\n id: string,\n props: DynamoDbDataStoreProps = {},\n ) {\n const service = OpenHiService.of(scope) as OpenHiService;\n\n super(scope, id, {\n ...props,\n tableName: getDynamoDbDataStoreTableName(scope),\n partitionKey: {\n name: \"PK\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"SK\",\n type: AttributeType.STRING,\n },\n billingMode: BillingMode.PAY_PER_REQUEST,\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n });\n\n // GSI1 — Unified Sharded List (data plane + control plane) per ADR-011 and DR-004.\n this.addGlobalSecondaryIndex({\n indexName: \"GSI1\",\n partitionKey: {\n name: \"GSI1PK\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"GSI1SK\",\n type: AttributeType.STRING,\n },\n projectionType: ProjectionType.INCLUDE,\n nonKeyAttributes: [\n \"id\",\n \"summary\",\n \"vid\",\n \"lastUpdated\",\n \"createdDate\",\n \"modifiedDate\",\n \"createdById\",\n \"modifiedById\",\n // ElectroDB filters every query result through `ownsItem`, which\n // verifies `__edb_e__` (entity name) and `__edb_v__` (version) match\n // the entity. Without these projected, every GSI1 query returns 0\n // results — list endpoints silently return empty bundles.\n \"__edb_e__\",\n \"__edb_v__\",\n ],\n });\n\n // GSI2 — Sub-Lookup: Cognito sub → UserEntity (Pre Token Generation Lambda).\n this.addGlobalSecondaryIndex({\n indexName: \"GSI2\",\n partitionKey: {\n name: \"GSI2PK\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"GSI2SK\",\n type: AttributeType.STRING,\n },\n projectionType: ProjectionType.INCLUDE,\n nonKeyAttributes: [\n \"id\",\n \"currentTenant\",\n \"currentWorkspace\",\n \"displayName\",\n // See GSI1 above: ElectroDB's `ownsItem` filter rejects items\n // without these, so any query against GSI2 returns 0 results\n // unless they're projected.\n \"__edb_e__\",\n \"__edb_v__\",\n ],\n });\n }\n}\n","import {\n WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH,\n WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR,\n} from \"@openhi/workflows\";\nimport { Annotations, RemovalPolicy } from \"aws-cdk-lib\";\nimport { AttributeType, BillingMode, Table } from \"aws-cdk-lib/aws-dynamodb\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Function } from \"aws-cdk-lib/aws-lambda\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService, type OpenHiServiceType } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/dynamodb/workflow-dedup-table.md\n */\n\n/**\n * Deterministic table name for the shared workflow dedup table.\n * Mirrors `getDynamoDbDataStoreTableName` naming: `workflow-dedup-${branchHash}`.\n */\nexport function getWorkflowDedupTableName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `workflow-dedup-${stack.branchHash}`;\n}\n\n/** Props for `WorkflowDedupTable`. */\nexport interface WorkflowDedupTableProps {\n /**\n * Optional removal policy override. Defaults to the service's default\n * (RETAIN for prod, DESTROY otherwise).\n */\n readonly removalPolicy?: RemovalPolicy;\n}\n\n/** Options for `WorkflowDedupTable.grantConsumer`. */\nexport interface GrantConsumerOptions {\n /**\n * Override the default TTL applied by the runtime client. The 14-day\n * default lives in `@openhi/workflows`; per-consumer overrides clamp\n * shorter per TR-015. Stored in the consumer's environment so the\n * `WorkflowDedupClient` factory can pick it up.\n */\n readonly defaultTtlSeconds?: number;\n}\n\n/**\n * Shared platform-level dedup table every retryable workflow consumer\n * dedupes against. Provisioned exactly once at the platform stack.\n *\n * Schema (per TR-015):\n * - Partition key `consumerName` (S)\n * - Sort key `sk` (S) — encodes `<eventId>#<attempt>`\n * - TTL attribute `expiresAt` (N, Unix epoch seconds)\n * - On-demand billing\n *\n * @see https://github.com/codedrifters/openhi-planning/blob/main/docs/src/content/docs/requirements/technical-requirements/TR-015-workflow-dedup-table.md\n */\nexport class WorkflowDedupTable extends Construct {\n /** SSM param name (short) used by `DiscoverableStringParameter` for the table name lookup. */\n public static readonly TABLE_NAME_SSM_PARAM_NAME =\n \"workflow-dedup-table-name\";\n /** SSM param name (short) used by `DiscoverableStringParameter` for the table ARN lookup. */\n public static readonly TABLE_ARN_SSM_PARAM_NAME = \"workflow-dedup-table-arn\";\n\n /** Cross-stack lookup for the table name. */\n public static tableNameFromLookup(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: WorkflowDedupTable.TABLE_NAME_SSM_PARAM_NAME,\n serviceType: WorkflowDedupTable.PUBLISHER_SERVICE_TYPE,\n });\n }\n\n /** Cross-stack lookup for the table ARN. */\n public static tableArnFromLookup(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: WorkflowDedupTable.TABLE_ARN_SSM_PARAM_NAME,\n serviceType: WorkflowDedupTable.PUBLISHER_SERVICE_TYPE,\n });\n }\n\n /**\n * Cross-stack equivalent of {@link grantConsumer}. Use when the dedup\n * table is on a different stack than the consumer Lambda — the\n * grant resolves the table name + ARN via SSM at synth time, so the\n * consumer stack does not pick up a CloudFormation export dependency\n * on the global stack.\n *\n * Inverts the singleton-guard semantics of `grantConsumer`: there is\n * no synth-time check that the same `consumerName` was registered\n * twice across stacks. Consumer names are agreed by convention\n * (see TR-015); double-registration is operator error caught at\n * design time, not synth time.\n */\n public static grantConsumerFromLookup(\n scope: Construct,\n fn: Function,\n consumerName: string,\n options: GrantConsumerOptions = {},\n ): void {\n WorkflowDedupTable.assertConsumerNameStatic(consumerName);\n const tableName = WorkflowDedupTable.tableNameFromLookup(scope);\n const tableArn = WorkflowDedupTable.tableArnFromLookup(scope);\n\n fn.addEnvironment(WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR, tableName);\n if (options.defaultTtlSeconds !== undefined) {\n fn.addEnvironment(\n \"OPENHI_WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS\",\n String(options.defaultTtlSeconds),\n );\n }\n\n fn.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n ],\n resources: [tableArn],\n conditions: {\n \"ForAllValues:StringEquals\": {\n \"dynamodb:LeadingKeys\": [consumerName],\n },\n },\n }),\n );\n }\n\n /**\n * Service-type the publishing stack runs under. The cross-stack lookups\n * pin to this value so consumer stacks on a different service-type\n * (e.g. `data`, `auth`) resolve the parameter at the publisher's SSM\n * path instead of their own. Typed against `OpenHiServiceType` so a\n * future rename of the literal triggers a compile error; not pulled\n * from `OpenHiGlobalService.SERVICE_TYPE` because\n * `OpenHiGlobalService` already imports `WorkflowDedupTable` — a\n * back-import would create a circular dependency.\n */\n private static readonly PUBLISHER_SERVICE_TYPE: OpenHiServiceType = \"global\";\n\n /**\n * Standalone consumer-name validator shared by the instance method\n * and `grantConsumerFromLookup` so the two grants enforce identical\n * invariants.\n */\n private static assertConsumerNameStatic(consumerName: string): void {\n if (consumerName.length === 0) {\n throw new WorkflowDedupConsumerNameInvalidError(\n \"consumerName must be non-empty.\",\n );\n }\n if (consumerName.length > WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {\n throw new WorkflowDedupConsumerNameInvalidError(\n `consumerName must be at most ${WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`,\n );\n }\n if (/\\s/.test(consumerName)) {\n throw new WorkflowDedupConsumerNameInvalidError(\n \"consumerName must not contain whitespace.\",\n );\n }\n }\n\n /** The underlying DynamoDB table. */\n public readonly table: Table;\n\n private readonly registeredConsumers = new Set<string>();\n\n constructor(\n scope: Construct,\n id: string,\n props: WorkflowDedupTableProps = {},\n ) {\n super(scope, id);\n\n const service = OpenHiService.of(scope) as OpenHiService;\n\n // Synth-time singleton guard: refuse a second WorkflowDedupTable in\n // the same owning service (one stack per deploy target). The check is\n // intentionally scoped to the host service rather than to the CDK\n // app root: openhi/global/src/app.ts maps over `app.environments` and\n // instantiates one `OpenHiGlobalService` per environment, and each of\n // those is a separate deployment target that owns its own dedup\n // table per TR-015.\n const others = service.node\n .findAll()\n .filter(\n (c): c is WorkflowDedupTable =>\n c instanceof WorkflowDedupTable && c !== this,\n );\n if (others.length > 0) {\n throw new WorkflowDedupTableDuplicateError(\n `WorkflowDedupTable already exists at ${others[0].node.path}; ` +\n \"only one shared dedup table is allowed per service stack (TR-015).\",\n );\n }\n\n this.table = new Table(this, \"Table\", {\n tableName: getWorkflowDedupTableName(scope),\n partitionKey: {\n name: \"consumerName\",\n type: AttributeType.STRING,\n },\n sortKey: {\n name: \"sk\",\n type: AttributeType.STRING,\n },\n billingMode: BillingMode.PAY_PER_REQUEST,\n timeToLiveAttribute: \"expiresAt\",\n removalPolicy: props.removalPolicy ?? service.removalPolicy,\n });\n\n // Publish the table name and ARN so consumer stacks can discover\n // them without a cross-stack prop dependency.\n new DiscoverableStringParameter(this, \"table-name-param\", {\n ssmParamName: WorkflowDedupTable.TABLE_NAME_SSM_PARAM_NAME,\n stringValue: this.table.tableName,\n });\n new DiscoverableStringParameter(this, \"table-arn-param\", {\n ssmParamName: WorkflowDedupTable.TABLE_ARN_SSM_PARAM_NAME,\n stringValue: this.table.tableArn,\n });\n }\n\n /**\n * Wire a Lambda consumer to this table. Injects the table-name env var\n * so the runtime `WorkflowDedupClient` can resolve it, then attaches a\n * per-consumer IAM grant scoped by `dynamodb:LeadingKeys` so the\n * consumer can only read/write its own partition.\n */\n public grantConsumer(\n fn: Function,\n consumerName: string,\n options: GrantConsumerOptions = {},\n ): void {\n this.assertConsumerName(consumerName);\n\n if (this.registeredConsumers.has(consumerName)) {\n Annotations.of(this).addWarning(\n `WorkflowDedupTable: consumerName \"${consumerName}\" registered more than once; ` +\n \"subsequent grantConsumer calls add policy statements but do not re-inject the env var.\",\n );\n }\n this.registeredConsumers.add(consumerName);\n\n fn.addEnvironment(WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR, this.table.tableName);\n if (options.defaultTtlSeconds !== undefined) {\n fn.addEnvironment(\n \"OPENHI_WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS\",\n String(options.defaultTtlSeconds),\n );\n }\n\n fn.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n ],\n resources: [this.table.tableArn],\n conditions: {\n \"ForAllValues:StringEquals\": {\n \"dynamodb:LeadingKeys\": [consumerName],\n },\n },\n }),\n );\n }\n\n private assertConsumerName(consumerName: string): void {\n WorkflowDedupTable.assertConsumerNameStatic(consumerName);\n }\n}\n\n/** Thrown when a second `WorkflowDedupTable` is instantiated in the same app. */\nexport class WorkflowDedupTableDuplicateError extends Error {\n /** @param message - human-readable description of the duplicate. */\n constructor(message: string) {\n super(message);\n this.name = \"WorkflowDedupTableDuplicateError\";\n }\n}\n\n/** Thrown when a consumerName violates the TR-015 invariants. */\nexport class WorkflowDedupConsumerNameInvalidError extends Error {\n /** @param message - human-readable description of the invariant violation. */\n constructor(message: string) {\n super(message);\n this.name = \"WorkflowDedupConsumerNameInvalidError\";\n }\n}\n","import { Duration, Stack } from \"aws-cdk-lib\";\nimport { Archive, EventBus, EventBusProps } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/event-bridge/data-event-bus.md\n */\n\n/** Default retention for the archive — 7 days. */\nconst DEFAULT_ARCHIVE_RETENTION = Duration.days(7);\n\nexport interface DataEventBusOptions {\n /**\n * Retention for the bus's event archive. Defaults to 7 days. Pass\n * `Duration.days(0)` (or omit and override the archive in a subclass)\n * to disable archiving entirely.\n */\n readonly archiveRetention?: Duration;\n}\n\nexport class DataEventBus extends EventBus {\n /*****************************************************************************\n *\n * Return a name for this EventBus based on the stack environment hash. This\n * name is common across all stacks since it's using the environment hash in\n * it's name.\n *\n ****************************************************************************/\n\n public static getEventBusName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `datav1${stack.branchHash}`;\n }\n\n /**\n * Replay archive of every event written to this bus, retained for the\n * configured TTL (default 7 days). Enables EventBridge `StartReplay`\n * for incident response and ad-hoc backfill.\n *\n * Named `replayArchive` rather than `archive` to avoid shadowing the\n * inherited `EventBus.archive(id, options)` instance method.\n */\n public readonly replayArchive: Archive;\n\n constructor(\n scope: Construct,\n props: (EventBusProps & DataEventBusOptions) | undefined = undefined,\n ) {\n const { archiveRetention, ...busProps } = props ?? {};\n super(scope, \"data-event-bus-v1\", {\n ...busProps,\n eventBusName: DataEventBus.getEventBusName(scope),\n });\n\n // Archive everything on this bus. EventBridge archive requires an\n // eventPattern; the `account` filter scoped to this stack's account is\n // the canonical \"match every event on this bus\" form (the bus only\n // accepts events from within the account, so this is a no-op filter that\n // satisfies the API contract).\n this.replayArchive = new Archive(this, \"Archive\", {\n sourceEventBus: this,\n archiveName: `${DataEventBus.getEventBusName(scope)}-archive`,\n description:\n \"Replay archive for the OpenHI data event bus (data-store change notifications).\",\n eventPattern: { account: [Stack.of(this).account] },\n retention: archiveRetention ?? DEFAULT_ARCHIVE_RETENTION,\n });\n }\n}\n","import { EventBus, EventBusProps } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/event-bridge/ops-event-bus.md\n */\n\nexport class OpsEventBus extends EventBus {\n /*****************************************************************************\n *\n * Return a name for this EventBus based on the stack environment hash. This\n * name is common across all stacks since it's using the environment hash in\n * it's name.\n *\n ****************************************************************************/\n\n public static getEventBusName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `opsv1${stack.branchHash}`;\n }\n\n constructor(scope: Construct, props?: EventBusProps) {\n super(scope, \"ops-event-bus-v1\", {\n ...props,\n eventBusName: OpsEventBus.getEventBusName(scope),\n });\n }\n}\n","import { EventBus, EventBusProps } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/event-bridge/control-event-bus.md\n */\n\nexport class ControlEventBus extends EventBus {\n /*****************************************************************************\n *\n * Return a name for this EventBus based on the stack environment hash. This\n * name is common across all stacks since it's using the environment hash in\n * its name.\n *\n ****************************************************************************/\n\n public static getEventBusName(scope: Construct): string {\n const stack = OpenHiService.of(scope) as OpenHiService;\n return `controlv1${stack.branchHash}`;\n }\n\n constructor(scope: Construct, props?: EventBusProps) {\n super(scope, \"control-event-bus-v1\", {\n ...props,\n eventBusName: ControlEventBus.getEventBusName(scope),\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, RemovalPolicy, Stack } from \"aws-cdk-lib\";\nimport * as ec2 from \"aws-cdk-lib/aws-ec2\";\nimport * as kinesis from \"aws-cdk-lib/aws-kinesis\";\nimport { Runtime, StartingPosition } from \"aws-cdk-lib/aws-lambda\";\nimport { KinesisEventSource } from \"aws-cdk-lib/aws-lambda-event-sources\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport * as rds from \"aws-cdk-lib/aws-rds\";\nimport { Construct } from \"constructs\";\nimport { DiscoverableStringParameter } from \"../ssm/discoverable-string-parameter\";\n\nconst HANDLER_NAME = \"data-store-postgres-replication.handler.js\";\nconst DEFAULT_DATABASE_NAME = \"openhi\";\nconst SCHEMA_NAME_PATTERN = /^[a-z_][a-z0-9_]{0,62}$/;\n\n/**\n * SSM parameter names that publish the Postgres replica's coordinates so other\n * stacks (notably the REST API stack) can discover them without a direct CDK\n * cross-stack reference. The schema name is intentionally NOT published — it\n * is a deterministic function of `branchHash` and consumers compute it locally\n * via {@link getPostgresReplicaSchemaName}.\n */\nexport const POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME =\n \"POSTGRES_REPLICA_CLUSTER_ARN\";\nexport const POSTGRES_REPLICA_SECRET_ARN_SSM_NAME =\n \"POSTGRES_REPLICA_SECRET_ARN\";\nexport const POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME =\n \"POSTGRES_REPLICA_DATABASE_NAME\";\n\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n return path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\n/**\n * Derive the per-branch Postgres schema name from a branch hash. The `b_`\n * prefix guarantees a leading letter (Postgres identifier rule). Branch hashes\n * are 6 hex chars from {@link OpenHiService.branchHash} so the resulting\n * `b_xxxxxx` is well within the 63-byte identifier limit.\n */\nexport function getPostgresReplicaSchemaName(branchHash: string): string {\n const candidate = `b_${branchHash.toLowerCase()}`;\n if (!SCHEMA_NAME_PATTERN.test(candidate)) {\n throw new Error(\n `Branch hash ${JSON.stringify(branchHash)} produces an invalid Postgres ` +\n `schema name ${JSON.stringify(candidate)}; expected /[a-z_][a-z0-9_]{0,62}/.`,\n );\n }\n return candidate;\n}\n\nexport interface DataStorePostgresReplicaProps {\n /**\n * Kinesis stream that receives DynamoDB item-level changes (the same stream\n * that backs {@link DataStoreHistoricalArchive}). The replication Lambda is\n * registered as a parallel consumer.\n */\n readonly kinesisStream: kinesis.IStream;\n /**\n * Removal policy for the cluster, secret, and dependent resources.\n */\n readonly removalPolicy: RemovalPolicy;\n /**\n * Short hash unique to the stack — used in the cluster identifier.\n */\n readonly stackHash: string;\n /**\n * Short hash unique to the branch — used to derive the per-branch schema\n * name (`b_<branchHash>`) inside the Postgres database.\n */\n readonly branchHash: string;\n /**\n * Optional VPC override. If absent, the construct creates a minimal isolated\n * VPC (2 AZs, no NAT gateways) just for the cluster and replication Lambda.\n */\n readonly vpc?: ec2.IVpc;\n /**\n * Optional database name override.\n * @default \"openhi\"\n */\n readonly databaseName?: string;\n /**\n * Aurora Serverless v2 minimum capacity in ACUs. Defaults to 1 so the\n * writer stays warm — avoids the ~10–20s scale-up wait that a cold\n * (0 ACU) cluster imposes on the next request. Set explicitly to 0 to\n * opt back into scale-to-zero if idle cost becomes the dominant concern.\n */\n readonly minCapacity?: number;\n /**\n * Aurora Serverless v2 maximum capacity in ACUs. Defaults to 2 — adequate\n * for the PoC's replication-only workload.\n */\n readonly maxCapacity?: number;\n}\n\n/**\n * DynamoDB change stream → Postgres replication tier (ADR 2026-04-17-01,\n * phase 1). Provisions an Aurora Serverless v2 PostgreSQL cluster and a\n * Lambda consumer on the existing change-stream that projects each current\n * FHIR resource into a JSONB `resources` table under a per-branch schema.\n *\n * Phase 1 is replication-only; query routing and SearchParameter-specific\n * indexes are intentionally deferred. Per-branch *clusters* (rather than the\n * shared cluster suggested by the ADR) are an explicit PoC simplification —\n * see the ADR's \"Operational notes\" section for the long-term direction.\n *\n * @see sites/www-docs/content/architecture/adr/2026-04-17-01-ad-hoc-query-support-fhir-api.md\n */\nexport class DataStorePostgresReplica extends Construct {\n /**\n * Resolve the cluster ARN published by an upstream {@link DataStorePostgresReplica}.\n * Use from any stack that needs to grant `rds-data:ExecuteStatement` against\n * the cluster.\n */\n public static clusterArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME,\n serviceType: \"data\",\n });\n }\n\n /**\n * Resolve the credentials secret ARN published by an upstream\n * {@link DataStorePostgresReplica}. Use from any stack that needs to grant\n * `secretsmanager:GetSecretValue` against the secret.\n */\n public static secretArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: POSTGRES_REPLICA_SECRET_ARN_SSM_NAME,\n serviceType: \"data\",\n });\n }\n\n /**\n * Resolve the database name published by an upstream\n * {@link DataStorePostgresReplica}.\n */\n public static databaseNameFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME,\n serviceType: \"data\",\n });\n }\n\n public readonly vpc: ec2.IVpc;\n public readonly cluster: rds.DatabaseCluster;\n public readonly replicationFunction: NodejsFunction;\n public readonly databaseName: string;\n public readonly schemaName: string;\n\n constructor(\n scope: Construct,\n id: string,\n props: DataStorePostgresReplicaProps,\n ) {\n super(scope, id);\n\n this.databaseName = props.databaseName ?? DEFAULT_DATABASE_NAME;\n this.schemaName = getPostgresReplicaSchemaName(props.branchHash);\n\n // Pass explicit AZ names (derived from the stack region) instead of using\n // `maxAzs`, which triggers a CDK availability-zones context lookup. CI's\n // synth step doesn't have full deploy-account creds, so an unresolved AZ\n // lookup gets recorded as \"missing\" in the cdk.out manifest and the deploy\n // step then refuses to proceed. AWS region AZ names follow a stable\n // `<region>a/b/c…` pattern across all current commercial regions.\n const region = Stack.of(this).region;\n const ownsVpc = props.vpc === undefined;\n this.vpc =\n props.vpc ??\n new ec2.Vpc(this, \"Vpc\", {\n availabilityZones: [`${region}a`, `${region}b`],\n natGateways: 0,\n subnetConfiguration: [\n {\n name: \"isolated\",\n subnetType: ec2.SubnetType.PRIVATE_ISOLATED,\n cidrMask: 24,\n },\n ],\n });\n\n // Secrets Manager interface endpoint. The replication Lambda runs in\n // PRIVATE_ISOLATED subnets with no NAT (per the cost-of-cluster trade-off\n // in this construct), so every AWS API call from the function code needs\n // a corresponding VPC endpoint. The handler's only AWS SDK call on the\n // cold-start path is `GetSecretValue` to fetch the cluster credentials —\n // without this endpoint, that call hangs at TCP connect and the Lambda\n // returns `ETIMEDOUT` for every Kinesis batch.\n //\n // `privateDnsEnabled: true` is the bit that lets the function resolve\n // the standard `secretsmanager.<region>.amazonaws.com` DNS name to the\n // endpoint's private IP — without it the SDK would need a custom endpoint\n // override, which we'd rather not bake into the handler.\n //\n // Only provisioned when this construct owns its VPC. If a VPC is passed\n // in via `props.vpc`, the caller is expected to add the endpoint there\n // (or supply a NAT path) so we don't accidentally double-provision the\n // endpoint on a shared VPC.\n if (ownsVpc) {\n new ec2.InterfaceVpcEndpoint(this, \"SecretsManagerEndpoint\", {\n vpc: this.vpc,\n service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,\n subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },\n privateDnsEnabled: true,\n });\n }\n\n this.cluster = new rds.DatabaseCluster(this, \"Cluster\", {\n clusterIdentifier: `openhi-dstore-pg-${props.stackHash}`,\n engine: rds.DatabaseClusterEngine.auroraPostgres({\n version: rds.AuroraPostgresEngineVersion.VER_16_4,\n }),\n vpc: this.vpc,\n vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },\n writer: rds.ClusterInstance.serverlessV2(\"writer\"),\n serverlessV2MinCapacity: props.minCapacity ?? 1,\n serverlessV2MaxCapacity: props.maxCapacity ?? 2,\n defaultDatabaseName: this.databaseName,\n credentials: rds.Credentials.fromGeneratedSecret(\"openhi_admin\"),\n storageEncrypted: true,\n removalPolicy: props.removalPolicy,\n // Phase 2 of ADR 2026-04-17-01: the REST API Lambda queries Postgres\n // via the RDS Data API (HTTPS) so it can stay out of the cluster's VPC.\n // Direct `pg` from the replication Lambda continues to work in parallel.\n enableDataApi: true,\n });\n\n this.publishCoordinatesToSsm();\n\n this.replicationFunction = new NodejsFunction(this, \"ReplicationFunction\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n vpc: this.vpc,\n vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },\n description:\n \"Replicates DynamoDB current-resource changes into the Postgres `resources` JSONB table (ADR 2026-04-17-01).\",\n environment: {\n OPENHI_PG_HOST: this.cluster.clusterEndpoint.hostname,\n OPENHI_PG_PORT: this.cluster.clusterEndpoint.port.toString(),\n OPENHI_PG_DATABASE: this.databaseName,\n OPENHI_PG_SCHEMA: this.schemaName,\n OPENHI_PG_SECRET_ARN: this.cluster.secret!.secretArn,\n OPENHI_PG_SSL: \"true\",\n },\n bundling: {\n minify: true,\n sourceMap: false,\n // pg's conditional optional deps (pg-native, pg-cloudflare) are\n // marked external so esbuild does not try to resolve them — pg's\n // runtime code wraps the requires in try/catch and falls back to\n // the pure-JS client when they are not present.\n externalModules: [\"pg-native\", \"pg-cloudflare\"],\n },\n });\n\n this.cluster.secret!.grantRead(this.replicationFunction);\n this.cluster.connections.allowDefaultPortFrom(this.replicationFunction);\n\n this.replicationFunction.addEventSource(\n new KinesisEventSource(props.kinesisStream, {\n startingPosition: StartingPosition.LATEST,\n batchSize: 100,\n maxBatchingWindow: Duration.seconds(5),\n retryAttempts: 10,\n bisectBatchOnError: true,\n parallelizationFactor: 2,\n reportBatchItemFailures: true,\n }),\n );\n }\n\n /**\n * Publishes the cluster ARN, secret ARN, and database name as discoverable\n * SSM parameters so the REST API stack (and any future read-side consumer)\n * can wire RDS Data API access without a direct CDK cross-stack reference.\n */\n private publishCoordinatesToSsm(): void {\n new DiscoverableStringParameter(this, \"cluster-arn-param\", {\n ssmParamName: POSTGRES_REPLICA_CLUSTER_ARN_SSM_NAME,\n stringValue: this.cluster.clusterArn,\n description:\n \"ARN of the Aurora Serverless v2 cluster backing the Postgres replication tier (ADR 2026-04-17-01).\",\n });\n new DiscoverableStringParameter(this, \"secret-arn-param\", {\n ssmParamName: POSTGRES_REPLICA_SECRET_ARN_SSM_NAME,\n stringValue: this.cluster.secret!.secretArn,\n description:\n \"ARN of the Secrets Manager secret with credentials for the Postgres replication tier.\",\n });\n new DiscoverableStringParameter(this, \"database-name-param\", {\n ssmParamName: POSTGRES_REPLICA_DATABASE_NAME_SSM_NAME,\n stringValue: this.databaseName,\n description: \"Database name within the Postgres replication cluster.\",\n });\n }\n}\n","import { Duration } from \"aws-cdk-lib\";\nimport {\n HostedZone,\n HostedZoneProps,\n IHostedZone,\n NsRecord,\n} from \"aws-cdk-lib/aws-route53\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/route-53/child-hosted-zone.md\n */\n\nexport interface ChildHostedZoneProps extends HostedZoneProps {\n /**\n * The root zone we will attach this sub-zone to.\n */\n readonly parentHostedZone: IHostedZone;\n}\n\nexport class ChildHostedZone extends HostedZone {\n /**\n * Used when storing the child zone ID in SSM. Use {@link OpenHiGlobalService.childHostedZoneFromConstruct} to look up.\n */\n public static readonly SSM_PARAM_NAME = \"CHILDHOSTEDZONE\";\n\n constructor(scope: Construct, id: string, props: ChildHostedZoneProps) {\n super(scope, id, { ...props });\n\n /**\n * Chain the child zone to the parent zone using NS record.\n */\n new NsRecord(this, \"child-ns-record\", {\n zone: props.parentHostedZone,\n recordName: this.zoneName,\n values: this.hostedZoneNameServers || [],\n ttl: Duration.minutes(5),\n });\n }\n}\n","import { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/route-53/root-hosted-zone.md\n */\n\n/**\n * Placeholder for root hosted zone. Use {@link OpenHiGlobalService.rootHostedZoneFromConstruct}\n * to obtain an IHostedZone from attributes (e.g. from config). The root zone is always\n * created manually and imported via config.\n */\nexport class RootHostedZone extends Construct {}\n","import { Distribution } from \"aws-cdk-lib/aws-cloudfront\";\nimport {\n ARecord,\n type IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { Construct } from \"constructs\";\nimport { StaticHosting, STATIC_HOSTING_SERVICE_TYPE } from \"./static-hosting\";\nimport { OpenHiService } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/static-hosting/per-branch-hostname.md\n */\n\n/**\n * Props for the PerBranchHostname construct.\n */\nexport interface PerBranchHostnameProps {\n /**\n * Fully-qualified hostname to alias to the release-branch distribution\n * (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`). Used as the ARecord's\n * `recordName`.\n */\n readonly hostname: string;\n\n /**\n * Hosted zone the ARecord is created in. The zone's `zoneName` must be\n * a suffix of `hostname`.\n */\n readonly hostedZone: IHostedZone;\n\n /**\n * Service type used to address the release-branch StaticHosting SSM\n * parameters. Defaults to {@link STATIC_HOSTING_SERVICE_TYPE} so the\n * construct resolves the same `\"website\"` namespace `StaticHosting`\n * writes to.\n *\n * @default STATIC_HOSTING_SERVICE_TYPE (\"website\")\n */\n readonly serviceType?: string;\n}\n\n/**\n * Creates a single Route53 `ARecord` that aliases a per-PR hostname (e.g.\n * `admin-feat-1093-patient-migration.dev.openhi.org`) to the release-branch CloudFront\n * distribution. The distribution domain and ID are resolved from SSM\n * parameters published by {@link StaticHosting}, addressed against the\n * containing service's {@link OpenHiService.releaseBranchHash} so a\n * feature-branch stack reads the values the release-branch stack wrote.\n *\n * No per-PR CloudFront distribution is created — the release-branch\n * distribution's wildcard SAN (`*.dev.openhi.org`) covers every per-PR\n * hostname.\n */\nexport class PerBranchHostname extends Construct {\n public readonly record: ARecord;\n\n constructor(scope: Construct, id: string, props: PerBranchHostnameProps) {\n super(scope, id);\n\n const stack = OpenHiService.of(scope) as OpenHiService;\n const serviceType = props.serviceType ?? STATIC_HOSTING_SERVICE_TYPE;\n\n // Resolve the release-branch distribution from SSM. The parameters are\n // published by StaticHosting on the release-branch deploy; on every\n // other branch the values are read cross-namespace via\n // releaseBranchHash. Same pattern as\n // OpenHiWebsiteService.resolveStaticHostingBucket.\n const distributionDomain = DiscoverableStringParameter.valueForLookupName(\n this,\n {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_DOMAIN,\n serviceType,\n branchHash: stack.releaseBranchHash,\n },\n );\n const distributionId = DiscoverableStringParameter.valueForLookupName(\n this,\n {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ID,\n serviceType,\n branchHash: stack.releaseBranchHash,\n },\n );\n\n const distribution = Distribution.fromDistributionAttributes(\n this,\n \"imported-distribution\",\n {\n domainName: distributionDomain,\n distributionId,\n },\n );\n\n // Embed the hostname in the construct id so a rename produces a\n // CloudFormation Add + Remove pair rather than an in-place\n // Replacement on AWS::Route53::RecordSet.Name (which is\n // UpdateRequiresReplacement). Avoids the TtyNotAttached error CDK\n // raises on `--no-rollback` deploys in CI when the hostname\n // changes — mirrors the StaticHosting ARecord fix from #1131.\n this.record = new ARecord(this, `alias-record-${props.hostname}`, {\n zone: props.hostedZone,\n recordName: props.hostname,\n target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)),\n });\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ICertificate } from \"aws-cdk-lib/aws-certificatemanager\";\nimport {\n AccessLevel,\n AllowedMethods,\n type BehaviorOptions,\n CacheCookieBehavior,\n CacheHeaderBehavior,\n CachePolicy,\n type CachePolicyProps,\n CacheQueryStringBehavior,\n Distribution,\n type DistributionProps,\n Function as CloudFrontFunction,\n FunctionCode,\n FunctionEventType,\n type IOrigin,\n LambdaEdgeEventType,\n OriginProtocolPolicy,\n OriginRequestPolicy,\n S3OriginAccessControl,\n Signing,\n ViewerProtocolPolicy,\n} from \"aws-cdk-lib/aws-cloudfront\";\nimport { HttpOrigin, S3BucketOrigin } from \"aws-cdk-lib/aws-cloudfront-origins\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { LogGroup, RetentionDays } from \"aws-cdk-lib/aws-logs\";\nimport {\n ARecord,\n type IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { CloudFrontTarget } from \"aws-cdk-lib/aws-route53-targets\";\nimport { Bucket, type BucketProps, type IBucket } from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\nimport type { HostingMode } from \"./static-hosting.viewer-request-handler\";\nimport { OpenHiService } from \"../../app\";\nimport { DiscoverableStringParameter } from \"../ssm\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/static-hosting/static-hosting.md\n */\n\n/**\n * Service type for the website service. Used in SSM parameter paths and by\n * OpenHiWebsiteService for fromConstruct() lookups.\n */\nexport const STATIC_HOSTING_SERVICE_TYPE = \"website\";\n\n/**\n * Shared prefix for every per-PR preview hostname / S3 key. Per-PR\n * uploads land under `admin-<branch-prefix>.<zone>/...` and the\n * lifecycle rule below expires every object whose key starts with\n * `admin-`. Release-branch content lands under `admin.<zone>/...`\n * (note byte 5 is `.`, not `-`); S3 prefix matching is byte-exact from\n * byte 0, so `admin-` and `admin.` are disjoint — the rule never\n * matches release content. The constant is locked to the admin-console\n * domain prefix because no other website service currently consumes\n * per-PR previews; parameterize on `domainPrefix` when a second\n * consumer appears.\n */\nexport const PER_BRANCH_PREVIEW_PREFIX = \"admin-\";\n\n/**\n * Default TTL applied to objects matching the `prefixPattern` lifecycle\n * rule when `enablePreviewLifecycle` is `true` and `previewExpiration`\n * is omitted. Per-PR preview content is small and short-lived; 14 days\n * is long enough that a forgotten preview can still be opened during a\n * code review and short enough that abandoned content does not\n * accumulate.\n */\nexport const DEFAULT_PREVIEW_EXPIRATION_DAYS = 14;\n\n/**\n * Props for the StaticHosting construct.\n */\nexport interface StaticHostingProps {\n /**\n * Optional S3 bucket props. Bucket name must not be set statically.\n */\n readonly bucketProps?: Omit<BucketProps, \"bucketName\">;\n\n /**\n * Optional CloudFront distribution props. Defaults wire a custom cache\n * policy (60s/300s with gzip+brotli), `REDIRECT_TO_HTTPS`, and\n * `ALLOW_GET_HEAD_OPTIONS` on the default behavior; overrides apply on top.\n */\n readonly distributionProps?: Omit<\n DistributionProps,\n \"defaultBehavior\" | \"defaultRootObject\"\n >;\n\n /**\n * Optional cache policy overrides. Defaults: `defaultTtl=60s`, `maxTtl=300s`,\n * `minTtl=0s`, gzip+brotli enabled, no headers/cookies/query strings cached.\n */\n readonly cachePolicyProps?: Omit<CachePolicyProps, \"cachePolicyName\">;\n\n /**\n * Wildcard certificate to attach to the CloudFront distribution. When\n * supplied together with `hostedZone` and `domainNames`, CloudFront serves\n * the listed domains and Route53 ARecords are created in the zone.\n *\n * @default - no custom certificate; CloudFront default domain is served\n */\n readonly certificate?: ICertificate;\n\n /**\n * Hosted zone to create Route53 ARecords in. Required together with\n * `certificate` and `domainNames` to attach a custom domain.\n */\n readonly hostedZone?: IHostedZone;\n\n /**\n * Domain names to attach to the CloudFront distribution. Each name also\n * gets an ARecord in `hostedZone`, except for wildcard entries\n * (e.g. `*.dev.openhi.org`) which are added as CloudFront alternate\n * domain names (so the cert covers them) but skipped by the ARecord\n * loop — Route53 cannot create an `A` record at a wildcard apex, and\n * per-host ARecords are created later by downstream stacks.\n */\n readonly domainNames?: ReadonlyArray<string>;\n\n /**\n * Selects how path-like URIs are rewritten by the viewer-request\n * Lambda@Edge handler.\n *\n * - `spa` (default): path-like URIs rewrite to `/index.html`.\n * - `static`: path-like URIs append `/index.html`.\n *\n * @default \"spa\"\n */\n readonly hostingMode?: HostingMode;\n\n /**\n * Service type for SSM parameter paths.\n *\n * @default STATIC_HOSTING_SERVICE_TYPE (\"website\")\n */\n readonly serviceType?: string;\n\n /**\n * Optional human-readable description used in distribution comment and\n * SSM parameter descriptions.\n */\n readonly description?: string;\n\n /**\n * When supplied, the distribution proxies API traffic to the supplied\n * REST API custom domain (e.g. `api.example.com`). Three CloudFront\n * behaviors are added — `/config.json`, `/api/control/runtime-config`,\n * and `/api/*` — and each is wired with two edge stages so per-PR\n * websites reach their own per-PR API gateway:\n *\n * 1. **Viewer-request CloudFront Function** copies the viewer `Host`\n * header into `x-viewer-host` before CloudFront strips `Host` per\n * `ALL_VIEWER_EXCEPT_HOST_HEADER`. The `/config.json` function\n * additionally rewrites the URI to the configured\n * `runtimeConfigPath` (defaults to `/control/runtime-config`).\n * 2. **Origin-request Lambda@Edge** reads `x-viewer-host`, computes\n * the matching API host by swapping `hostMapping.viewerPrefix` for\n * `hostMapping.apiPrefix` at the start of the host, and overrides\n * both `request.origin.custom.domainName` and the upstream `Host`\n * header so the upstream's custom-domain mapping resolves to the\n * correct per-PR stack.\n *\n * `runtimeConfigPath` and `hostMapping` default to the OpenHI\n * admin-console / REST API values; downstream consumers (e.g. a\n * marketing site with its own bootstrap path and origin pair) can\n * override them per-site without touching the construct.\n *\n * Behavior cache keys:\n * - `/config.json` and `/api/control/runtime-config` share a cache\n * policy whose cache key includes the `v` query string AND the\n * `Host` header. The `Host` partition prevents PR-A's runtime\n * config from being served from PR-B's cache slot.\n * - `/api/*` uses `CachePolicy.CACHING_DISABLED`, so no cache-key\n * partitioning is needed.\n *\n * None of these behaviors are wired through the default-behavior\n * viewer-request edge Lambda — SPA path rewriting only applies to\n * the default S3 origin.\n *\n * @default - no REST API proxy; the distribution serves S3 only\n */\n readonly restApi?: {\n /**\n * REST API custom-domain hostname (no scheme — e.g. `api.example.com`).\n */\n readonly domainName: string;\n\n /**\n * Default / max TTL for the cached `/api/control/runtime-config` response.\n *\n * @default 5 minutes default, 1 hour max\n */\n readonly runtimeConfigCacheTtl?: {\n readonly defaultTtl?: Duration;\n readonly maxTtl?: Duration;\n };\n\n /**\n * Path the viewer-request CloudFront Function rewrites `/config.json`\n * to on the REST API origin. Override when a downstream consumer\n * bootstraps its SPA from a different endpoint.\n *\n * @default \"/control/runtime-config\" — the OpenHI admin-console\n * runtime-config endpoint.\n */\n readonly runtimeConfigPath?: string;\n\n /**\n * Host-prefix substitution applied by the origin-request Lambda@Edge.\n * `viewerPrefix` is matched at the start of the viewer's `Host`\n * header; when it matches, the upstream `Host` (and the custom\n * origin's `domainName`) becomes `apiPrefix + viewerHost.slice(viewerPrefix.length)`.\n *\n * The prefixes are baked into the Lambda@Edge bundle via esbuild\n * `define` at synth time (Lambda@Edge forbids runtime env vars), so\n * a change requires re-deploying the website stack.\n *\n * @default `{ viewerPrefix: \"admin\", apiPrefix: \"api\" }` — maps\n * `admin[-<branch>].<zone>` -> `api[-<branch>].<zone>` for the\n * OpenHI admin-console / REST API pair.\n */\n readonly hostMapping?: {\n readonly viewerPrefix: string;\n readonly apiPrefix: string;\n };\n };\n\n /**\n * S3 key prefix that the per-PR preview lifecycle rule matches. Must\n * be the same value the per-PR deployment construct writes under\n * (see `PER_BRANCH_PREVIEW_PREFIX`). Required even when\n * `enablePreviewLifecycle` is `false` so the contract between the\n * deployment side and the lifecycle side is single-sourced.\n */\n readonly prefixPattern: string;\n\n /**\n * When `true`, add an S3 lifecycle rule to the bucket that expires\n * objects matching `prefixPattern` after `previewExpiration`. **No\n * default** — every caller must make the stage decision explicitly\n * because adding the rule in production could silently delete real\n * content if the prefix ever overlapped with release content.\n *\n * Callers gate this against the stage (e.g.\n * `ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD`) before passing\n * the prop.\n */\n readonly enablePreviewLifecycle: boolean;\n\n /**\n * TTL applied to objects under `prefixPattern` when the lifecycle\n * rule is created.\n *\n * @default Duration.days(14)\n */\n readonly previewExpiration?: Duration;\n}\n\n/**\n * Static hosting: S3 bucket (private) + CloudFront distribution with Origin\n * Access Control (OAC) + Lambda@Edge viewer-request handler. Publishes\n * bucket ARN, distribution ARN, distribution domain, and distribution ID\n * via DiscoverableStringParameter for cross-stack lookup.\n */\nexport class StaticHosting extends Construct {\n /**\n * SSM parameter name for the S3 bucket ARN.\n */\n public static readonly SSM_PARAM_NAME_BUCKET_ARN =\n \"STATIC_HOSTING_BUCKET_ARN\";\n\n /**\n * SSM parameter name for the CloudFront distribution ARN.\n */\n public static readonly SSM_PARAM_NAME_DISTRIBUTION_ARN =\n \"STATIC_HOSTING_DISTRIBUTION_ARN\";\n\n /**\n * SSM parameter name for the CloudFront distribution domain\n * (e.g. dXXXXX.cloudfront.net).\n */\n public static readonly SSM_PARAM_NAME_DISTRIBUTION_DOMAIN =\n \"STATIC_HOSTING_DISTRIBUTION_DOMAIN\";\n\n /**\n * SSM parameter name for the CloudFront distribution ID.\n */\n public static readonly SSM_PARAM_NAME_DISTRIBUTION_ID =\n \"STATIC_HOSTING_DISTRIBUTION_ID\";\n\n /**\n * Returns true when `domainName` begins with a wildcard label (`*.`),\n * indicating it is an alt-name on the CloudFront cert but cannot be\n * created as a Route53 ARecord.\n */\n private static isWildcardDomain(domainName: string): boolean {\n return domainName.startsWith(\"*.\");\n }\n\n public readonly bucket: IBucket;\n public readonly distribution: Distribution;\n public readonly viewerRequestHandler: NodejsFunction;\n /**\n * Viewer-request CloudFront Function attached to the `/config.json`\n * behavior. Rewrites the URI to `/control/runtime-config` and copies\n * the viewer `Host` header into `x-viewer-host` so the origin-request\n * Lambda@Edge can pick the matching per-PR API origin. Only present\n * when the `restApi` prop is supplied.\n */\n public readonly configJsonRewriteFunction?: CloudFrontFunction;\n /**\n * Viewer-request CloudFront Function attached to the `/api/*`\n * behavior. Copies the viewer `Host` header into `x-viewer-host` so\n * the origin-request Lambda@Edge can route to the matching per-PR\n * API origin (the `ALL_VIEWER_EXCEPT_HOST_HEADER` origin-request\n * policy strips the literal `Host` header). Only present when the\n * `restApi` prop is supplied.\n */\n public readonly hostCopyFunction?: CloudFrontFunction;\n /**\n * Origin-request Lambda@Edge attached to the `/config.json`,\n * `/api/control/runtime-config`, and `/api/*` behaviors. Reads\n * `x-viewer-host` and overrides the upstream origin's `domainName`\n * (and the `Host` header) to the matching per-PR API host so PR-A's\n * website calls PR-A's API. Only present when the `restApi` prop is\n * supplied.\n */\n public readonly originRequestHandler?: NodejsFunction;\n\n constructor(scope: Construct, id: string, props: StaticHostingProps) {\n super(scope, id);\n\n const stack = OpenHiService.of(scope) as OpenHiService;\n const serviceType = props.serviceType ?? STATIC_HOSTING_SERVICE_TYPE;\n const hostingMode: HostingMode = props.hostingMode ?? \"spa\";\n\n /***************************************************************************\n *\n * PRIVATE BUCKET\n *\n * Per-PR preview content under `prefixPattern` self-expires on a\n * bucket-wide lifecycle rule when `enablePreviewLifecycle` is true.\n * The rule never runs in production — the caller gates it on\n * stage. See PER_BRANCH_PREVIEW_PREFIX for the shared constant.\n *\n **************************************************************************/\n\n const previewLifecycleRules = props.enablePreviewLifecycle\n ? [\n {\n id: \"expire-pr-previews\",\n enabled: true,\n prefix: props.prefixPattern,\n expiration:\n props.previewExpiration ??\n Duration.days(DEFAULT_PREVIEW_EXPIRATION_DAYS),\n },\n ]\n : undefined;\n\n this.bucket = new Bucket(this, \"bucket\", {\n blockPublicAccess: {\n blockPublicAcls: true,\n blockPublicPolicy: true,\n ignorePublicAcls: true,\n restrictPublicBuckets: true,\n },\n ...(previewLifecycleRules !== undefined && {\n lifecycleRules: previewLifecycleRules,\n }),\n ...props.bucketProps,\n });\n\n /***************************************************************************\n *\n * LAMBDA@EDGE VIEWER-REQUEST HANDLER\n *\n * Rewrites path-like URIs and prepends the Host header as a folder so\n * each domain maps to its own bucket prefix.\n *\n **************************************************************************/\n\n // Explicit entry required: when omitted, NodejsFunction infers the path from the\n // call site (the built lib/index.js), so it looks for index.viewer-request-handler.js\n // in the package. That file is only emitted if we add it as a separate tsup entry.\n // Use .js when present (built package); fall back to .ts for tests running from source.\n const handlerJs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.js\",\n );\n const handlerTs = path.join(\n __dirname,\n \"static-hosting.viewer-request-handler.ts\",\n );\n const handlerEntry = fs.existsSync(handlerJs) ? handlerJs : handlerTs;\n\n this.viewerRequestHandler = new NodejsFunction(\n this,\n \"viewer-request-handler\",\n {\n entry: handlerEntry,\n handler: hostingMode === \"static\" ? \"staticHandler\" : \"spaHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_LATEST,\n logGroup: new LogGroup(this, \"viewer-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n },\n );\n\n /***************************************************************************\n *\n * CLOUDFRONT DISTRIBUTION\n *\n **************************************************************************/\n\n const cachePolicy = new CachePolicy(this, \"cache-policy\", {\n cachePolicyName: `static-hosting-${stack.branchHash}`,\n comment: \"Static hosting default: 60s default / 300s max, gzip+brotli.\",\n defaultTtl: Duration.seconds(60),\n minTtl: Duration.seconds(0),\n maxTtl: Duration.seconds(300),\n headerBehavior: CacheHeaderBehavior.none(),\n queryStringBehavior: CacheQueryStringBehavior.none(),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n ...props.cachePolicyProps,\n });\n\n const oac = new S3OriginAccessControl(this, \"origin-access-control\", {\n signing: Signing.SIGV4_NO_OVERRIDE,\n });\n const origin = S3BucketOrigin.withOriginAccessControl(this.bucket, {\n originAccessControl: oac,\n originAccessLevels: [AccessLevel.READ],\n });\n\n const hasCustomDomain =\n props.certificate !== undefined &&\n props.hostedZone !== undefined &&\n props.domainNames !== undefined &&\n props.domainNames.length > 0;\n\n const restApiWiring = this.buildRestApiBehaviors(\n stack.branchHash,\n props.restApi,\n );\n const additionalBehaviors = restApiWiring?.behaviors;\n this.configJsonRewriteFunction = restApiWiring?.configJsonRewriteFunction;\n this.hostCopyFunction = restApiWiring?.hostCopyFunction;\n this.originRequestHandler = restApiWiring?.originRequestHandler;\n\n this.distribution = new Distribution(this, \"distribution\", {\n comment: `Static hosting distribution for ${props.description ?? id}`,\n ...(hasCustomDomain\n ? {\n certificate: props.certificate,\n domainNames: [...props.domainNames!],\n }\n : {}),\n defaultRootObject: \"index.html\",\n defaultBehavior: {\n origin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n cachePolicy,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n edgeLambdas: [\n {\n functionVersion: this.viewerRequestHandler.currentVersion,\n eventType: LambdaEdgeEventType.VIEWER_REQUEST,\n includeBody: false,\n },\n ],\n },\n ...(additionalBehaviors !== undefined && { additionalBehaviors }),\n ...props.distributionProps,\n });\n\n /***************************************************************************\n *\n * DNS RECORDS\n *\n * Only created when a hosted zone, certificate, and domain names are\n * all supplied. One ARecord per supplied domain name.\n *\n **************************************************************************/\n\n if (hasCustomDomain) {\n props.domainNames!.forEach((domainName, index) => {\n // Wildcard entries (e.g. `*.dev.openhi.org`) are included in the\n // distribution's `domainNames` so the cert covers them, but\n // Route53 cannot create an `A` record at a wildcard apex.\n // Per-host ARecords for each concrete subdomain are created\n // later by downstream stacks.\n if (StaticHosting.isWildcardDomain(domainName)) {\n return;\n }\n new ARecord(this, `dns-record-${index}-${domainName}`, {\n zone: props.hostedZone!,\n recordName: domainName,\n target: RecordTarget.fromAlias(\n new CloudFrontTarget(this.distribution),\n ),\n });\n });\n }\n\n /***************************************************************************\n *\n * SSM PARAMETERS (4)\n *\n * Bucket ARN, distribution ARN, distribution domain, distribution ID.\n * Consumers (e.g. StaticContent uploader, DNS-aware tools) look these\n * up via DiscoverableStringParameter rather than reconstructing them.\n *\n **************************************************************************/\n\n new DiscoverableStringParameter(this, \"bucket-arn-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,\n serviceType,\n stringValue: this.bucket.bucketArn,\n description: `Static hosting bucket ARN (${props.description ?? id})`,\n });\n\n new DiscoverableStringParameter(this, \"distribution-arn-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ARN,\n serviceType,\n stringValue: this.distribution.distributionArn,\n description: `Static hosting distribution ARN (${props.description ?? id})`,\n });\n\n new DiscoverableStringParameter(this, \"distribution-domain-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_DOMAIN,\n serviceType,\n stringValue: this.distribution.domainName,\n description: `Static hosting distribution domain (${props.description ?? id})`,\n });\n\n new DiscoverableStringParameter(this, \"distribution-id-param\", {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ID,\n serviceType,\n stringValue: this.distribution.distributionId,\n description: `Static hosting distribution ID (${props.description ?? id})`,\n });\n }\n\n /**\n * Builds the `/config.json`, `/api/*`, and `/api/control/runtime-config`\n * behaviors backed by the REST API custom-domain origin, plus the\n * viewer-request CloudFront Functions and the origin-request\n * Lambda@Edge that route each request to the matching per-PR API\n * origin. Returns `undefined` when no `restApi` prop is supplied so\n * the Distribution stays S3-only.\n */\n protected buildRestApiBehaviors(\n branchHash: string,\n restApi: StaticHostingProps[\"restApi\"],\n ):\n | {\n behaviors: Record<string, BehaviorOptions>;\n configJsonRewriteFunction: CloudFrontFunction;\n hostCopyFunction: CloudFrontFunction;\n originRequestHandler: NodejsFunction;\n }\n | undefined {\n if (restApi === undefined) {\n return undefined;\n }\n // Caller-overridable defaults for the OpenHI admin-console use case.\n // The construct stays generic — a second website consumer passes\n // different values to point the same wiring at a different SPA.\n const runtimeConfigPath =\n restApi.runtimeConfigPath ?? \"/control/runtime-config\";\n const viewerHostPrefix = restApi.hostMapping?.viewerPrefix ?? \"admin\";\n const apiHostPrefix = restApi.hostMapping?.apiPrefix ?? \"api\";\n const apiOrigin: IOrigin = new HttpOrigin(restApi.domainName, {\n protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,\n });\n const runtimeConfigCachePolicy = new CachePolicy(\n this,\n \"runtime-config-cache-policy\",\n {\n cachePolicyName: `static-hosting-runtime-config-${branchHash}`,\n comment:\n \"/config.json: cache key keyed on Host + `v` so per-PR responses cannot leak across hosts.\",\n defaultTtl:\n restApi.runtimeConfigCacheTtl?.defaultTtl ?? Duration.minutes(5),\n minTtl: Duration.seconds(0),\n maxTtl: restApi.runtimeConfigCacheTtl?.maxTtl ?? Duration.hours(1),\n // `Host` keys the cache per-PR — the origin-request edge Lambda\n // forwards each PR's request to its own API origin, and two\n // PRs' /config.json payloads must not share a cache slot.\n headerBehavior: CacheHeaderBehavior.allowList(\"Host\"),\n queryStringBehavior: CacheQueryStringBehavior.allowList(\"v\"),\n cookieBehavior: CacheCookieBehavior.none(),\n enableAcceptEncodingGzip: true,\n enableAcceptEncodingBrotli: true,\n },\n );\n\n // CloudFront Function (JS 1.0) attached to /config.json at the\n // viewer-request stage. Copies the viewer Host header into\n // x-viewer-host before CloudFront strips Host (per\n // ALL_VIEWER_EXCEPT_HOST_HEADER), then rewrites the URI so the\n // upstream sees /control/runtime-config. ES5.1-compatible body —\n // CloudFront Functions JS 1.0 forbids `const`/`let`, arrow\n // functions, and template literals.\n // JSON.stringify gives a properly-escaped JS string literal even\n // for paths containing quotes — safe to splice into the inline\n // function source.\n const runtimeConfigPathLiteral = JSON.stringify(runtimeConfigPath);\n const configJsonRewriteFunction = new CloudFrontFunction(\n this,\n \"config-json-rewrite-function\",\n {\n functionName: `static-hosting-config-json-rewrite-${branchHash}`,\n // CloudFront caps `Comment` at 128 chars; the full rationale is in\n // the preceding code comment.\n comment:\n \"Rewrites /config.json to the runtime-config path; copies Host into x-viewer-host.\",\n code: FunctionCode.fromInline(\n [\n \"function handler(event) {\",\n \" var request = event.request;\",\n \" if (request.headers.host && request.headers.host.value) {\",\n \" request.headers['x-viewer-host'] = { value: request.headers.host.value };\",\n \" }\",\n ` request.uri = ${runtimeConfigPathLiteral};`,\n \" return request;\",\n \"}\",\n ].join(\"\\n\"),\n ),\n },\n );\n\n // CloudFront Function (JS 1.0) attached to /api/* at the\n // viewer-request stage. Same Host-copy step as the rewrite function\n // above, without the URI rewrite (the API path is forwarded\n // verbatim).\n const hostCopyFunction = new CloudFrontFunction(\n this,\n \"host-copy-function\",\n {\n functionName: `static-hosting-host-copy-${branchHash}`,\n // CloudFront caps `Comment` at 128 chars; the full rationale is in\n // the preceding code comment.\n comment:\n \"Copies viewer Host into x-viewer-host for the origin-request Lambda@Edge.\",\n code: FunctionCode.fromInline(\n [\n \"function handler(event) {\",\n \" var request = event.request;\",\n \" if (request.headers.host && request.headers.host.value) {\",\n \" request.headers['x-viewer-host'] = { value: request.headers.host.value };\",\n \" }\",\n \" return request;\",\n \"}\",\n ].join(\"\\n\"),\n ),\n },\n );\n\n // Lambda@Edge attached at the origin-request stage. Reads\n // x-viewer-host and overrides the upstream origin's domainName +\n // Host header so PR-A's website calls PR-A's API gateway.\n const originHandlerJs = path.join(\n __dirname,\n \"static-hosting.origin-request-handler.js\",\n );\n const originHandlerTs = path.join(\n __dirname,\n \"static-hosting.origin-request-handler.ts\",\n );\n const originHandlerEntry = fs.existsSync(originHandlerJs)\n ? originHandlerJs\n : originHandlerTs;\n const originRequestHandler = new NodejsFunction(\n this,\n \"origin-request-handler\",\n {\n entry: originHandlerEntry,\n handler: \"originRequestHandler\",\n memorySize: 128,\n runtime: Runtime.NODEJS_LATEST,\n logGroup: new LogGroup(this, \"origin-request-handler-log-group\", {\n retention: RetentionDays.ONE_MONTH,\n }),\n // Lambda@Edge forbids runtime env vars, so the host-prefix\n // mapping is inlined into the bundle at synth time via esbuild\n // `define`. The handler reads `process.env.VIEWER_HOST_PREFIX`\n // and `process.env.API_HOST_PREFIX` at module load; esbuild\n // replaces those identifiers with literal strings during\n // bundling so the shipped code carries no env-var lookups.\n bundling: {\n define: {\n \"process.env.VIEWER_HOST_PREFIX\": JSON.stringify(viewerHostPrefix),\n \"process.env.API_HOST_PREFIX\": JSON.stringify(apiHostPrefix),\n },\n },\n },\n );\n\n const originRequestEdgeLambda = {\n functionVersion: originRequestHandler.currentVersion,\n eventType: LambdaEdgeEventType.ORIGIN_REQUEST,\n includeBody: false,\n };\n\n return {\n configJsonRewriteFunction,\n hostCopyFunction,\n originRequestHandler,\n behaviors: {\n \"/config.json\": {\n origin: apiOrigin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n cachePolicy: runtimeConfigCachePolicy,\n originRequestPolicy:\n OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,\n functionAssociations: [\n {\n function: configJsonRewriteFunction,\n eventType: FunctionEventType.VIEWER_REQUEST,\n },\n ],\n edgeLambdas: [originRequestEdgeLambda],\n },\n \"/api/control/runtime-config\": {\n origin: apiOrigin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,\n cachePolicy: runtimeConfigCachePolicy,\n originRequestPolicy:\n OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,\n functionAssociations: [\n {\n function: hostCopyFunction,\n eventType: FunctionEventType.VIEWER_REQUEST,\n },\n ],\n edgeLambdas: [originRequestEdgeLambda],\n },\n \"/api/*\": {\n origin: apiOrigin,\n viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,\n allowedMethods: AllowedMethods.ALLOW_ALL,\n cachePolicy: CachePolicy.CACHING_DISABLED,\n originRequestPolicy:\n OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,\n functionAssociations: [\n {\n function: hostCopyFunction,\n eventType: FunctionEventType.VIEWER_REQUEST,\n },\n ],\n edgeLambdas: [originRequestEdgeLambda],\n },\n },\n };\n }\n}\n","import { IBucket } from \"aws-cdk-lib/aws-s3\";\nimport { BucketDeployment, Source } from \"aws-cdk-lib/aws-s3-deployment\";\nimport { paramCase } from \"change-case\";\nimport { Construct } from \"constructs\";\nimport { OpenHiService } from \"../../app\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/components/static-hosting/static-content.md\n */\n\n/*******************************************************************************\n *\n * STATIC CONTENT UPLOADER\n *\n * This construct uploads a directory of content from a local location into S3.\n *\n * To support PR and branch specific builds, each S3 bucket can store content\n * for multiple domains and builds, using the following format:\n *\n * S3-bucket/<sub-domain>.<full-domain>/*\n *\n * A bucket used to store content for stage.openhi.org might have the\n * following directory structure (all in the same bucket):\n *\n * /www.stage.openhi.org/* -\\> serves content to www.stage.openhi.org\n * /feature-7.stage.openhi.org/* -\\> serves content to feature-7.stage.openhi.org\n * /pr-123.stage.openhi.org/* -\\> serves content to pr-123.stage.openhi.org\n *\n ******************************************************************************/\n\n/**\n * Props for the StaticContent construct.\n */\nexport interface StaticContentProps {\n /**\n * Destination bucket the content is uploaded to. Callers resolve this\n * reference themselves so the construct doesn't need to know whether the\n * bucket was created in the same stack or imported across branches.\n */\n readonly bucket: IBucket;\n\n /**\n * Absolute path to directory containing content for the website.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Directory to place content into. Should start with a slash.\n * Example: '/widget'\n *\n * @default \"/\"\n */\n readonly contentDestinationDirectory?: string;\n\n /**\n * The sub domain prefix (e.g. \"feature-7\"). Used as the per-branch folder\n * name in the bucket so each branch deploys to its own prefix.\n *\n * @default the current stack's branch name (kebab-cased)\n */\n readonly subDomain?: string;\n\n /**\n * The full domain (e.g. \"stage.openhi.org\"). Used together with\n * `subDomain` to form the destination prefix\n * `<sub-domain>.<full-domain>`.\n */\n readonly fullDomain: string;\n}\n\n/**\n * Static content uploader: deploys a local directory to a static-hosting\n * S3 bucket under `<sub-domain>.<full-domain>/<dest>` so each branch\n * deploys to its own prefix without clobbering siblings. The destination\n * bucket is supplied by the caller, which lets the same construct run in\n * both same-stack (release-branch) and cross-stack/cross-branch\n * (feature-branch) contexts.\n */\nexport class StaticContent extends Construct {\n constructor(scope: Construct, id: string, props: StaticContentProps) {\n super(scope, id);\n\n const stack = OpenHiService.of(scope) as OpenHiService;\n\n const {\n bucket,\n contentSourceDirectory,\n contentDestinationDirectory = \"/\",\n subDomain = stack.branchName,\n fullDomain,\n } = props;\n\n const keyPrefix = [paramCase(subDomain), fullDomain].join(\".\");\n\n /***************************************************************************\n *\n * Gather the sources we'll be deploying. Use an empty source list when\n * running under Jest so the asset bundle (which is built every time and\n * embeds machine-specific paths) doesn't bork test snapshots.\n *\n **************************************************************************/\n\n const isTestEnv = process.env.JEST_WORKER_ID !== undefined;\n const sources = isTestEnv ? [] : [Source.asset(contentSourceDirectory)];\n\n new BucketDeployment(this, \"deploy\", {\n sources,\n destinationBucket: bucket,\n retainOnDelete: false,\n destinationKeyPrefix: `${keyPrefix}${contentDestinationDirectory}`,\n });\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport {\n IUserPool,\n IUserPoolClient,\n IUserPoolDomain,\n LambdaVersion,\n UserPool,\n UserPoolClient,\n UserPoolDomain,\n UserPoolOperation,\n UserPoolProps,\n} from \"aws-cdk-lib/aws-cognito\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { IKey, Key } from \"aws-cdk-lib/aws-kms\";\nimport { IFunction } from \"aws-cdk-lib/aws-lambda\";\nimport { Stack } from \"aws-cdk-lib/core\";\nimport { Construct } from \"constructs\";\nimport { OpenHiDataService } from \"./open-hi-data-service\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport {\n ADMIN_DOMAIN_PREFIX,\n OpenHiWebsiteService,\n} from \"./open-hi-website-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { CognitoUserPool } from \"../components/cognito/cognito-user-pool\";\nimport { CognitoUserPoolClient } from \"../components/cognito/cognito-user-pool-client\";\nimport { CognitoUserPoolDomain } from \"../components/cognito/cognito-user-pool-domain\";\nimport { CognitoUserPoolKmsKey } from \"../components/cognito/cognito-user-pool-kms-key\";\nimport { PostAuthenticationLambda } from \"../components/cognito/post-authentication-lambda\";\nimport { PostConfirmationLambda } from \"../components/cognito/post-confirmation-lambda\";\nimport { PreTokenGenerationLambda } from \"../components/cognito/pre-token-generation-lambda\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport { UserOnboardingWorkflow } from \"../workflows/control-plane/user-onboarding\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-auth-service.md\n */\n\n/**\n * Localhost OAuth callback URLs auto-injected into the Cognito User Pool\n * Client on every non-prod (`stageType !== \"prod\"`) deploy. Both schemes\n * (`http`, `https`) on `localhost:3000` are covered so admin-console\n * previews running on `localhost` can complete the redirect flow.\n *\n * Excluded from prod on purpose — a prod Cognito client that accepts\n * `localhost` redirects is a phishing vector.\n */\nexport const LOCALHOST_OAUTH_CALLBACK_URLS: ReadonlyArray<string> = [\n \"http://localhost:3000/oauth/callback\",\n \"https://localhost:3000/oauth/callback\",\n];\n\n/**\n * Localhost OAuth logout URLs auto-injected alongside\n * {@link LOCALHOST_OAUTH_CALLBACK_URLS} on non-prod deploys so the hosted\n * UI sign-out flow can redirect back to a local dev server.\n */\nexport const LOCALHOST_OAUTH_LOGOUT_URLS: ReadonlyArray<string> = [\n \"http://localhost:3000/oauth/logout\",\n \"https://localhost:3000/oauth/logout\",\n];\n\nexport interface OpenHiAuthServiceProps extends OpenHiServiceProps {\n /**\n * Optional props for the Cognito User Pool.\n */\n readonly userPoolProps?: UserPoolProps;\n}\n\n/**\n * OpenHI Auth Service stack.\n *\n * @remarks\n * The Auth service manages authentication infrastructure including:\n * - Cognito User Pool for user management and authentication\n * - User Pool Client for application integration\n * - User Pool Domain for hosting the Cognito hosted UI\n * - KMS Key for Cognito User Pool encryption\n *\n * Resources are created in protected methods; subclasses may override to customize.\n * Other stacks obtain auth by calling **OpenHiAuthService.userPoolFromConstruct(scope)**,\n * **OpenHiAuthService.userPoolClientFromConstruct(scope)**,\n * **OpenHiAuthService.userPoolDomainFromConstruct(scope)**,\n * and **OpenHiAuthService.userPoolKmsKeyFromConstruct(scope)** for each resource needed.\n *\n * Only one instance of the auth service should exist per environment.\n *\n * @public\n */\nexport class OpenHiAuthService extends OpenHiService {\n static readonly SERVICE_TYPE = \"auth\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns an IUserPool by looking up the Auth stack's User Pool ID from SSM.\n */\n static userPoolFromConstruct(scope: Construct): IUserPool {\n const userPoolId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPool.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n return UserPool.fromUserPoolId(scope, \"user-pool\", userPoolId);\n }\n\n /**\n * Returns an IUserPoolClient by looking up the Auth stack's User Pool Client ID from SSM.\n */\n static userPoolClientFromConstruct(scope: Construct): IUserPoolClient {\n const userPoolClientId = DiscoverableStringParameter.valueForLookupName(\n scope,\n {\n ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n },\n );\n return UserPoolClient.fromUserPoolClientId(\n scope,\n \"user-pool-client\",\n userPoolClientId,\n );\n }\n\n /**\n * Returns an IUserPoolDomain by looking up the Auth stack's User Pool Domain from SSM.\n */\n static userPoolDomainFromConstruct(scope: Construct): IUserPoolDomain {\n const domainName = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n return UserPoolDomain.fromDomainName(scope, \"user-pool-domain\", domainName);\n }\n\n /**\n * Returns the full Cognito Hosted UI base URL (e.g.\n * `https://auth-abc.auth.us-east-2.amazoncognito.com`) by looking up\n * the Auth stack's User Pool Domain from SSM and composing it with the\n * calling stack's region.\n *\n * Equivalent to `UserPoolDomain.baseUrl()` on the concrete construct,\n * but works across stacks where the looked-up `IUserPoolDomain` is an\n * `Import` and does not carry the `baseUrl()` method. Assumes the\n * domain was created as a Cognito-managed prefix domain (the only\n * variant `OpenHiAuthService.createUserPoolDomain` produces).\n */\n static userPoolDomainBaseUrlFromConstruct(scope: Construct): string {\n const domainName = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n const region = Stack.of(scope).region;\n return `https://${domainName}.auth.${region}.amazoncognito.com`;\n }\n\n /**\n * Returns an IKey (KMS) by looking up the Auth stack's User Pool KMS Key ARN from SSM.\n */\n static userPoolKmsKeyFromConstruct(scope: Construct): IKey {\n const keyArn = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: CognitoUserPoolKmsKey.SSM_PARAM_NAME,\n serviceType: OpenHiAuthService.SERVICE_TYPE,\n });\n return Key.fromKeyArn(scope, \"kms-key\", keyArn);\n }\n\n get serviceType(): string {\n return OpenHiAuthService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options (e.g. userPoolProps). */\n public override props: OpenHiAuthServiceProps;\n\n public readonly userPoolKmsKey: IKey;\n public readonly preTokenGenerationLambda: IFunction;\n public readonly postAuthenticationLambda: IFunction;\n public readonly postConfirmationLambda: IFunction;\n public readonly userOnboardingWorkflow: UserOnboardingWorkflow;\n public readonly userPool: IUserPool;\n public readonly userPoolClient: IUserPoolClient;\n public readonly userPoolDomain: IUserPoolDomain;\n\n /**\n * Cross-stack reference to the data store table. Cached so repeated\n * lookups share a single CDK construct id (\"dynamo-db-data-store\") in\n * this stack — a second `Table.fromTableName` call under the same scope\n * would collide.\n */\n private _dataStoreTable: ReturnType<\n typeof OpenHiDataService.dynamoDbDataStoreFromConstruct\n > | null = null;\n private _controlEventBus: IEventBus | null = null;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiAuthServiceProps = {}) {\n super(ohEnv, OpenHiAuthService.SERVICE_TYPE, props);\n this.props = props;\n\n this.userPoolKmsKey = this.createUserPoolKmsKey();\n this.preTokenGenerationLambda = this.createPreTokenGenerationLambda();\n this.postAuthenticationLambda = this.createPostAuthenticationLambda();\n this.postConfirmationLambda = this.createPostConfirmationLambda();\n this.userOnboardingWorkflow = this.createUserOnboardingWorkflow();\n this.userPool = this.createUserPool();\n this.grantPreTokenGenerationPermissions();\n this.grantPostAuthenticationPermissions();\n this.grantPostConfirmationPermissions();\n this.userPoolClient = this.createUserPoolClient();\n this.userPoolDomain = this.createUserPoolDomain();\n }\n\n /**\n * Creates the KMS key for the Cognito User Pool and exports its ARN to SSM.\n * Look up via {@link OpenHiAuthService.userPoolKmsKeyFromConstruct}.\n * Override to customize.\n */\n protected createUserPoolKmsKey(): IKey {\n const key = new CognitoUserPoolKmsKey(this);\n new DiscoverableStringParameter(this, \"kms-key-param\", {\n ssmParamName: CognitoUserPoolKmsKey.SSM_PARAM_NAME,\n stringValue: key.keyArn,\n description:\n \"KMS key ARN for Cognito User Pool (e.g. custom sender); cross-stack reference\",\n });\n return key;\n }\n\n /**\n * Creates the Pre Token Generation Lambda (Cognito trigger). On every\n * sign-in and token refresh the Lambda resolves the User by Cognito `sub`\n * (GSI2) and injects `ohi_tid`, `ohi_wid`, `ohi_uid`, `ohi_uname` into\n * both the ID token and the access token (ADR 2026-03-17-01).\n */\n protected createPreTokenGenerationLambda(): IFunction {\n const construct = new PreTokenGenerationLambda(this, {\n dynamoTableName: this.dataStoreTable().tableName,\n });\n return construct.lambda;\n }\n\n /**\n * Creates the Post Authentication Lambda (Cognito trigger). Calls\n * AdminUserGlobalSignOut on every sign-in to enforce single-device-per-user\n * sessions per ADR 2026-03-17-01.\n */\n protected createPostAuthenticationLambda(): IFunction {\n const construct = new PostAuthenticationLambda(this);\n return construct.lambda;\n }\n\n /**\n * Creates the Post Confirmation Lambda (Cognito trigger). On sign-up\n * confirmation, publishes a control-plane workflow event; provisioning lives\n * behind EventBridge.\n */\n protected createPostConfirmationLambda(): IFunction {\n const construct = new PostConfirmationLambda(this, {\n controlEventBusName: this.controlEventBus().eventBusName,\n });\n return construct.lambda;\n }\n\n protected createUserOnboardingWorkflow(): UserOnboardingWorkflow {\n return new UserOnboardingWorkflow(this, {\n controlEventBus: this.controlEventBus(),\n dataStoreTable: this.dataStoreTable(),\n });\n }\n\n private dataStoreTable() {\n if (this._dataStoreTable === null) {\n this._dataStoreTable =\n OpenHiDataService.dynamoDbDataStoreFromConstruct(this);\n }\n return this._dataStoreTable;\n }\n\n private controlEventBus() {\n if (this._controlEventBus === null) {\n this._controlEventBus =\n OpenHiGlobalService.controlEventBusFromConstruct(this);\n }\n return this._controlEventBus;\n }\n\n /**\n * Creates the Cognito User Pool and exports its ID to SSM.\n * Look up via {@link OpenHiAuthService.userPoolFromConstruct}.\n * Override to customize.\n */\n protected createUserPool(): IUserPool {\n const userPool = new CognitoUserPool(this, {\n ...this.props.userPoolProps,\n customSenderKmsKey: this.userPoolKmsKey,\n });\n // Access-token-only claims require Pre Token Generation V2_0.\n userPool.addTrigger(\n UserPoolOperation.PRE_TOKEN_GENERATION_CONFIG,\n this.preTokenGenerationLambda,\n LambdaVersion.V2_0,\n );\n userPool.addTrigger(\n UserPoolOperation.POST_AUTHENTICATION,\n this.postAuthenticationLambda,\n );\n userPool.addTrigger(\n UserPoolOperation.POST_CONFIRMATION,\n this.postConfirmationLambda,\n );\n new DiscoverableStringParameter(this, \"user-pool-param\", {\n ssmParamName: CognitoUserPool.SSM_PARAM_NAME,\n stringValue: userPool.userPoolId,\n description:\n \"Cognito User Pool ID for this Auth stack; cross-stack reference\",\n });\n return userPool;\n }\n\n /**\n * Grants the Pre Token Generation Lambda read-only access on the data\n * store table and its GSIs. The Lambda needs:\n * - `Query` on GSI2 to resolve a User by Cognito `sub`\n * - `GetItem` on the base table for direct User reads (canonical row hydration\n * after the GSI2 hit, per #1175)\n * - `BatchGetItem` on the base table for ElectroDB `batchGetWithRetry`\n * hydration used by `listMembershipsOperation` and\n * `listRoleAssignmentsOperation` when resolving the\n * `ohi_organization_roles` / `ohi_platform_roles` claims\n *\n * No write or scan access: a User missing `currentTenant`/`currentWorkspace`\n * falls into the absent-claims path; repair belongs in a separate backfill.\n */\n protected grantPreTokenGenerationPermissions(): void {\n const dataStoreTable = this.dataStoreTable();\n const dynamoActions = [\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n \"dynamodb:BatchGetItem\",\n ] as const;\n dataStoreTable.grant(this.preTokenGenerationLambda, ...dynamoActions);\n this.preTokenGenerationLambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [...dynamoActions],\n resources: [`${dataStoreTable.tableArn}/index/*`],\n }),\n );\n }\n\n /**\n * Grants the Post Authentication Lambda permission to call\n * `cognito-idp:AdminUserGlobalSignOut`.\n *\n * Scoped via `Stack.of(this).formatArn` rather than `userPool.userPoolArn`\n * because the User Pool registers this Lambda as a Post Authentication\n * trigger, creating the cycle:\n * userPool → lambda (trigger ARN) → role policy → userPool ARN.\n * Using `formatArn` avoids referencing the User Pool resource directly\n * while still scoping to user pools in this account+region. The Lambda\n * is invoked only by Cognito with a Cognito-provided `event.userPoolId`,\n * so the runtime target is constrained by the trigger contract.\n */\n protected grantPostAuthenticationPermissions(): void {\n this.postAuthenticationLambda.addToRolePolicy(\n new PolicyStatement({\n actions: [\"cognito-idp:AdminUserGlobalSignOut\"],\n resources: [\n Stack.of(this).formatArn({\n service: \"cognito-idp\",\n resource: \"userpool\",\n resourceName: \"*\",\n }),\n ],\n }),\n );\n }\n\n /**\n * Grants the Post Confirmation Lambda publish-only access to the\n * control-plane event bus. Workflow Lambdas own DynamoDB writes.\n */\n protected grantPostConfirmationPermissions(): void {\n this.controlEventBus().grantPutEventsTo(this.postConfirmationLambda);\n }\n\n /**\n * Creates the User Pool Client and exports its ID to SSM (AUTH service type).\n * OAuth flows are enabled with auto-injected callback/logout URLs derived\n * from this stack's branch context (see {@link resolveOAuthRedirectUrls}).\n * Look up via {@link OpenHiAuthService.userPoolClientFromConstruct}.\n * Override to customize.\n */\n protected createUserPoolClient(): IUserPoolClient {\n const { callbackUrls, logoutUrls } = this.resolveOAuthRedirectUrls();\n const client = new CognitoUserPoolClient(this, {\n userPool: this.userPool,\n oAuth: {\n flows: {\n authorizationCodeGrant: true,\n implicitCodeGrant: true,\n },\n callbackUrls,\n logoutUrls,\n },\n });\n new DiscoverableStringParameter(this, \"user-pool-client-param\", {\n ssmParamName: CognitoUserPoolClient.SSM_PARAM_NAME,\n stringValue: client.userPoolClientId,\n description:\n \"Cognito User Pool Client ID for this Auth stack; cross-stack reference\",\n });\n return client;\n }\n\n /**\n * Returns the OAuth `callbackUrls` and `logoutUrls` the Cognito User Pool\n * Client should accept. Composed from the same branch context the website\n * service will see at synth time:\n *\n * - `https://admin{,-<childZonePrefix>}.<zone>/oauth/{callback,logout}`\n * - `https://www{,-<childZonePrefix>}.<zone>/oauth/{callback,logout}`\n *\n * Both deployed-host pairs are auto-injected on every stage. The stage's\n * `additionalTrustedClientOrigins` entries (e.g. on-site customer SPA\n * hosts) are filtered to `https://`-prefix values and contribute\n * `/oauth/callback` + `/oauth/logout` URLs to the merge — Cognito rejects\n * non-localhost http callbacks, so `http://` entries are silently dropped.\n * On non-prod stages the localhost dev URLs from\n * {@link LOCALHOST_OAUTH_CALLBACK_URLS} /\n * {@link LOCALHOST_OAUTH_LOGOUT_URLS} join the merge; on prod they are\n * deliberately excluded.\n *\n * If `zoneName` is absent (no-DNS test configurations), the deployed-host\n * pairs are skipped — only the localhost set and any configured\n * additional `https://` origins survive (the latter on every stage).\n * Override to customize.\n */\n protected resolveOAuthRedirectUrls(): {\n callbackUrls: Array<string>;\n logoutUrls: Array<string>;\n } {\n const isNonProd = this.ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD;\n const zoneName = this.props.config?.zoneName;\n const deployedOrigins: Array<string> = [];\n if (zoneName !== undefined) {\n const adminHost = OpenHiWebsiteService.composeFullDomain({\n domainPrefix: ADMIN_DOMAIN_PREFIX,\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n const websiteHost = OpenHiWebsiteService.composeFullDomain({\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n deployedOrigins.push(`https://${adminHost}`, `https://${websiteHost}`);\n }\n const stageType = this.ohEnv.ohStage.stageType;\n const additionalHttpsOrigins =\n this.ohEnv.ohStage.ohApp.config.deploymentTargets?.[\n stageType\n ]?.additionalTrustedClientOrigins?.filter((o) =>\n o.startsWith(\"https://\"),\n ) ?? [];\n const localhostCallbacks = isNonProd ? LOCALHOST_OAUTH_CALLBACK_URLS : [];\n const localhostLogouts = isNonProd ? LOCALHOST_OAUTH_LOGOUT_URLS : [];\n return {\n callbackUrls: [\n ...deployedOrigins.map((o) => `${o}/oauth/callback`),\n ...additionalHttpsOrigins.map((o) => `${o}/oauth/callback`),\n ...localhostCallbacks,\n ],\n logoutUrls: [\n ...deployedOrigins.map((o) => `${o}/oauth/logout`),\n ...additionalHttpsOrigins.map((o) => `${o}/oauth/logout`),\n ...localhostLogouts,\n ],\n };\n }\n\n /**\n * Creates the User Pool Domain (Cognito hosted UI) and exports domain name to SSM.\n * Look up via {@link OpenHiAuthService.userPoolDomainFromConstruct}.\n * Override to customize.\n */\n protected createUserPoolDomain(): IUserPoolDomain {\n const domain = new CognitoUserPoolDomain(this, {\n userPool: this.userPool,\n cognitoDomain: {\n domainPrefix: `auth-${this.branchHash}`,\n },\n });\n new DiscoverableStringParameter(this, \"user-pool-domain-param\", {\n ssmParamName: CognitoUserPoolDomain.SSM_PARAM_NAME,\n stringValue: domain.domainName,\n description:\n \"Cognito User Pool Domain (hosted UI) for this Auth stack; cross-stack reference\",\n });\n return domain;\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport { StreamViewType, ITable, Table } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport * as kinesis from \"aws-cdk-lib/aws-kinesis\";\nimport { Construct } from \"constructs\";\nimport { OpenHiAuthService } from \"./open-hi-auth-service\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { DataStoreHistoricalArchive } from \"../components/dynamodb/data-store-historical-archive\";\nimport {\n DynamoDbDataStore,\n getDynamoDbDataStoreTableName,\n} from \"../components/dynamodb/dynamo-db-data-store\";\nimport { DataStorePostgresReplica } from \"../components/postgres/data-store-postgres-replica\";\nimport { SeedDemoDataWorkflow } from \"../workflows/control-plane/seed-demo-data\";\nimport { SeedSystemDataWorkflow } from \"../workflows/control-plane/seed-system-data\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-data-service.md\n */\n\nexport type OpenHiDataServiceProps = OpenHiServiceProps;\n\n/**\n * Data storage service stack: centralizes DynamoDB, S3, and other persistence\n * resources for OpenHI. Creates the single-table data store in a protected\n * method; subclasses may override to customize. EventBridge event buses\n * (data, ops, control) are owned by {@link OpenHiGlobalService} so they deploy\n * ahead of regional services.\n */\nexport class OpenHiDataService extends OpenHiService {\n static readonly SERVICE_TYPE = \"data\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns the data store table by name. Use from other stacks (e.g. REST API Lambda) to obtain an ITable reference.\n */\n static dynamoDbDataStoreFromConstruct(\n scope: Construct,\n id = \"dynamo-db-data-store\",\n ): ITable {\n return Table.fromTableName(scope, id, getDynamoDbDataStoreTableName(scope));\n }\n\n get serviceType(): string {\n return OpenHiDataService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options. */\n public override props: OpenHiDataServiceProps;\n\n /**\n * The single-table DynamoDB data store. Use {@link OpenHiDataService.dynamoDbDataStoreFromConstruct}\n * from other stacks to obtain an ITable reference by name.\n */\n public readonly dataStore: ITable;\n\n /**\n * Kinesis stream receiving DynamoDB item-level changes for the data store table.\n */\n public readonly dataStoreChangeStream: kinesis.IStream;\n\n /**\n * Historical archive pipeline (Kinesis → Firehose → S3) and data-event-bus\n * notifications for current FHIR resources (ADRs 2026-03-11-02, 2026-03-02-01).\n */\n public readonly dataStoreHistoricalArchive: DataStoreHistoricalArchive;\n\n /**\n * Postgres replication tier (ADR 2026-04-17-01, phase 1). A second consumer\n * on the change stream that projects current FHIR resources into a JSONB\n * `resources` table on Aurora Serverless v2. Phase 1 is replication-only;\n * the read path is not wired up yet.\n */\n public readonly dataStorePostgresReplica: DataStorePostgresReplica;\n\n /**\n * Deploy-triggered workflow that idempotently re-asserts the\n * platform-singleton control-plane records (today: the three canonical\n * Roles via `PLATFORM_ROLE_CONCEPTS`; future: additional system\n * data). Subscribes to `platform.deployment-completed.v1` on the\n * control event bus and dedups via the shared `WorkflowDedupTable`.\n */\n public readonly seedSystemDataWorkflow: SeedSystemDataWorkflow;\n\n /**\n * Deploy-triggered workflow that idempotently re-asserts the demo\n * data graph (placeholder + 3 demo Tenants + 5 Workspaces; per\n * dev-user Cognito users with their DynamoDB User records,\n * Memberships, and RoleAssignments). **Non-prod only** —\n * `undefined` on prod stages. The synth-time stage gate in\n * {@link createSeedDemoDataWorkflow} is the only guarantee\n * separating prod stacks from the workflow's IAM grants and rule\n * target; the construct itself never checks the stage.\n */\n public readonly seedDemoDataWorkflow?: SeedDemoDataWorkflow;\n\n /**\n * Cached control-event-bus lookup. `OpenHiGlobalService.controlEventBusFromConstruct`\n * registers a child `EventBus.fromEventBusName` construct with a\n * fixed id under the scope it is passed, so calling it twice on the\n * same `OpenHiDataService` instance collides. The cache mirrors the\n * `private controlEventBus()` pattern already used in\n * `OpenHiAuthService`. Use {@link controlEventBus} from this class\n * — never call the static lookup from inside `OpenHiDataService`.\n */\n private _controlEventBus: IEventBus | null = null;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiDataServiceProps = {}) {\n super(ohEnv, OpenHiDataService.SERVICE_TYPE, props);\n this.props = props;\n\n this.dataStoreChangeStream = new kinesis.Stream(\n this,\n \"data-store-change-stream\",\n {\n streamName: `openhi-dstore-cdc-${this.branchHash}`,\n streamMode: kinesis.StreamMode.ON_DEMAND,\n // CDK default for kinesis.Stream is RETAIN, which strands the stream\n // when a non-prod stack is destroyed. Use the service's policy so\n // non-prod tears down cleanly while prod retains.\n removalPolicy: this.removalPolicy,\n },\n );\n\n this.dataStore = this.createDataStore();\n\n this.dataStoreHistoricalArchive = new DataStoreHistoricalArchive(\n this,\n \"data-store-historical-archive\",\n {\n kinesisStream: this.dataStoreChangeStream,\n removalPolicy: this.removalPolicy,\n stackHash: this.stackHash,\n dataEventBus: OpenHiGlobalService.dataEventBusFromConstruct(this),\n },\n );\n\n // Stay warm on the release branch (production-shaped traffic); opt into\n // Aurora Serverless v2 scale-to-zero on every other branch so a preview\n // env that nobody is hitting doesn't burn idle ACU between deploys. The\n // ~10–20s scale-up wait on the next request after a pause is acceptable\n // in a preview environment; it is not on the release branch where the\n // first request after a quiet period is somebody's live UI interaction.\n const isReleaseBranch = this.branchName === this.defaultReleaseBranch;\n this.dataStorePostgresReplica = new DataStorePostgresReplica(\n this,\n \"data-store-postgres-replica\",\n {\n kinesisStream: this.dataStoreChangeStream,\n removalPolicy: this.removalPolicy,\n stackHash: this.stackHash,\n branchHash: this.branchHash,\n minCapacity: isReleaseBranch ? 1 : 0,\n },\n );\n\n this.seedSystemDataWorkflow = this.createSeedSystemDataWorkflow();\n this.seedDemoDataWorkflow = this.createSeedDemoDataWorkflow();\n }\n\n /**\n * Lazily looks up the control event bus exactly once per\n * `OpenHiDataService` instance and caches the reference. Every\n * workflow that consumes the bus must read it through this method\n * — see {@link _controlEventBus} for the underlying collision risk.\n */\n private controlEventBus(): IEventBus {\n if (this._controlEventBus === null) {\n this._controlEventBus =\n OpenHiGlobalService.controlEventBusFromConstruct(this);\n }\n return this._controlEventBus;\n }\n\n /**\n * Creates the seed-system-data workflow. Override to customize.\n */\n protected createSeedSystemDataWorkflow(): SeedSystemDataWorkflow {\n return new SeedSystemDataWorkflow(this, {\n controlEventBus: this.controlEventBus(),\n dataStoreTable: this.dataStore,\n });\n }\n\n /**\n * Creates the seed-demo-data workflow — but only on non-prod\n * stages. Returns `undefined` on prod so the workflow literally\n * does not exist in prod stacks. Override to customize.\n */\n protected createSeedDemoDataWorkflow(): SeedDemoDataWorkflow | undefined {\n if (this.ohEnv.ohStage.stageType === OPEN_HI_STAGE.PROD) {\n return undefined;\n }\n return new SeedDemoDataWorkflow(this, {\n controlEventBus: this.controlEventBus(),\n dataStoreTable: this.dataStore,\n userPool: OpenHiAuthService.userPoolFromConstruct(this),\n });\n }\n\n /**\n * Creates the single-table DynamoDB data store.\n * Override to customize.\n */\n protected createDataStore(): ITable {\n return new DynamoDbDataStore(this, \"dynamo-db-data-store\", {\n kinesisStream: this.dataStoreChangeStream,\n stream: StreamViewType.NEW_AND_OLD_IMAGES,\n });\n }\n}\n","import {\n Certificate,\n CertificateValidation,\n ICertificate,\n} from \"aws-cdk-lib/aws-certificatemanager\";\nimport { EventBus, IEventBus } from \"aws-cdk-lib/aws-events\";\nimport {\n HostedZone,\n HostedZoneAttributes,\n IHostedZone,\n} from \"aws-cdk-lib/aws-route53\";\nimport { StringParameter } from \"aws-cdk-lib/aws-ssm\";\nimport { Construct } from \"constructs\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { RootWildcardCertificate } from \"../components/acm/root-wildcard-certificate\";\nimport { WorkflowDedupTable } from \"../components/dynamodb/workflow-dedup-table\";\nimport { ControlEventBus } from \"../components/event-bridge/control-event-bus\";\nimport { DataEventBus } from \"../components/event-bridge/data-event-bus\";\nimport { OpsEventBus } from \"../components/event-bridge/ops-event-bus\";\nimport { ChildHostedZone } from \"../components/route-53/child-hosted-zone\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport { PlatformDeployBridge } from \"../workflows/control-plane/platform-deploy-bridge\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-global-service.md\n */\n\nexport interface OpenHiGlobalServiceProps extends OpenHiServiceProps {}\n\n/**\n * Global Infrastructure stack: owns global DNS, certificates, and the\n * cross-region EventBridge buses (data, ops, control). Resources (root zone,\n * optional child zone, wildcard cert, data/ops/control buses) are created in\n * protected methods; subclasses may override to customize.\n */\nexport class OpenHiGlobalService extends OpenHiService {\n static readonly SERVICE_TYPE = \"global\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns an IHostedZone from the given attributes (no SSM). Use when the zone is imported from config.\n */\n static rootHostedZoneFromConstruct(\n scope: Construct,\n props: HostedZoneAttributes,\n ): IHostedZone {\n return HostedZone.fromHostedZoneAttributes(scope, \"root-zone\", props);\n }\n\n /**\n * Returns an ICertificate by looking up the Global stack's wildcard cert ARN from SSM.\n */\n static rootWildcardCertificateFromConstruct(scope: Construct): ICertificate {\n const certificateArn = StringParameter.valueForStringParameter(\n scope,\n RootWildcardCertificate.ssmParameterName(),\n );\n return Certificate.fromCertificateArn(\n scope,\n \"wildcard-certificate\",\n certificateArn,\n );\n }\n\n /**\n * Returns an IHostedZone by looking up the child hosted zone ID from SSM. Defaults to GLOBAL service type.\n */\n static childHostedZoneFromConstruct(\n scope: Construct,\n props: { zoneName: string; serviceType?: OpenHiServiceType },\n ): IHostedZone {\n const hostedZoneId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: ChildHostedZone.SSM_PARAM_NAME,\n serviceType: props.serviceType ?? OpenHiGlobalService.SERVICE_TYPE,\n });\n return HostedZone.fromHostedZoneAttributes(scope, \"child-zone\", {\n hostedZoneId,\n zoneName: props.zoneName,\n });\n }\n\n /**\n * Returns the data event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.\n */\n static dataEventBusFromConstruct(scope: Construct): IEventBus {\n return EventBus.fromEventBusName(\n scope,\n \"data-event-bus\",\n DataEventBus.getEventBusName(scope),\n );\n }\n\n /**\n * Returns the ops event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.\n */\n static opsEventBusFromConstruct(scope: Construct): IEventBus {\n return EventBus.fromEventBusName(\n scope,\n \"ops-event-bus\",\n OpsEventBus.getEventBusName(scope),\n );\n }\n\n /**\n * Returns the control-plane event bus by name (deterministic per branch). Use from other stacks to obtain an IEventBus reference.\n */\n static controlEventBusFromConstruct(scope: Construct): IEventBus {\n return EventBus.fromEventBusName(\n scope,\n \"control-event-bus\",\n ControlEventBus.getEventBusName(scope),\n );\n }\n\n /**\n * Returns the workflow dedup table by name (deterministic per branch).\n * Use from other stacks to obtain an ITable reference. Consumer Lambdas\n * are typically wired via `WorkflowDedupTable.grantConsumer(fn, name)`\n * on the owning service's `workflowDedupTable` reference; the\n * `tableNameFromLookup` / `tableArnFromLookup` SSM helpers on the\n * construct cover cross-stack consumers that need only the name/ARN.\n */\n static workflowDedupTableNameFromLookup(scope: Construct): string {\n return WorkflowDedupTable.tableNameFromLookup(scope);\n }\n\n get serviceType(): string {\n return OpenHiGlobalService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options. */\n public override props: OpenHiGlobalServiceProps;\n\n public readonly rootHostedZone: IHostedZone;\n public readonly childHostedZone?: IHostedZone;\n public readonly rootWildcardCertificate: ICertificate;\n\n /**\n * Event bus for data-related events (ingestion, transformation, storage).\n * Other stacks obtain it via {@link OpenHiGlobalService.dataEventBusFromConstruct}.\n */\n public readonly dataEventBus: IEventBus;\n\n /**\n * Event bus for operational events (monitoring, alerting, system health).\n * Other stacks obtain it via {@link OpenHiGlobalService.opsEventBusFromConstruct}.\n */\n public readonly opsEventBus: IEventBus;\n\n /**\n * Event bus for control-plane lifecycle and command events.\n * Other stacks obtain it via {@link OpenHiGlobalService.controlEventBusFromConstruct}.\n */\n public readonly controlEventBus: IEventBus;\n\n /**\n * Bridge that watches CloudFormation Stack Status Change events on the\n * default AWS bus and republishes terminal-success events for OpenHi-tagged\n * stacks onto {@link controlEventBus} as `platform.deployment-completed.v1`.\n */\n public readonly platformDeployBridge: PlatformDeployBridge;\n\n /**\n * Shared dedup table every retryable workflow consumer dedupes against\n * (TR-015). Singleton per deployment — provisioned here on the global\n * stack so consumer stacks reach it via SSM lookups, not props.\n */\n public readonly workflowDedupTable: WorkflowDedupTable;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiGlobalServiceProps = {}) {\n super(ohEnv, OpenHiGlobalService.SERVICE_TYPE, props);\n this.props = props;\n\n this.validateConfig(props);\n\n this.rootHostedZone = this.createRootHostedZone();\n this.childHostedZone = this.createChildHostedZone();\n this.rootWildcardCertificate = this.createRootWildcardCertificate();\n this.dataEventBus = this.createDataEventBus();\n this.opsEventBus = this.createOpsEventBus();\n this.controlEventBus = this.createControlEventBus();\n this.workflowDedupTable = this.createWorkflowDedupTable();\n this.platformDeployBridge = this.createPlatformDeployBridge();\n }\n\n /**\n * Validates that config required for the Global stack is present.\n */\n protected validateConfig(props: OpenHiGlobalServiceProps): void {\n const { config } = props;\n if (!config) {\n throw new Error(\"Config is required\");\n }\n if (!config.zoneName) {\n throw new Error(\"Zone name is required to import the root zone\");\n }\n if (!config.hostedZoneId) {\n throw new Error(\"Hosted zone ID is required to import the root zone\");\n }\n }\n\n /**\n * Creates the root hosted zone (imported via attributes from config).\n * Override to customize or create the zone.\n */\n protected createRootHostedZone(): IHostedZone {\n return OpenHiGlobalService.rootHostedZoneFromConstruct(this, {\n zoneName: this.config.zoneName!,\n hostedZoneId: this.config.hostedZoneId!,\n });\n }\n\n /**\n * Creates the optional child hosted zone (e.g. branch subdomain).\n * Override to create a child zone when config provides childHostedZoneAttributes.\n * If you create a ChildHostedZone, also create a DiscoverableStringParameter\n * with ChildHostedZone.SSM_PARAM_NAME and the zone's hostedZoneId.\n */\n protected createChildHostedZone(): IHostedZone | undefined {\n return undefined;\n }\n\n /**\n * Creates the root wildcard certificate. On main branch, creates a new cert\n * with DNS validation; otherwise imports from SSM.\n * Override to customize certificate creation.\n */\n protected createRootWildcardCertificate(): ICertificate {\n if (this.branchName === \"main\") {\n return new RootWildcardCertificate(this, {\n domainName: `*.${this.rootHostedZone.zoneName}`,\n subjectAlternativeNames: [this.rootHostedZone.zoneName],\n validation: CertificateValidation.fromDns(this.rootHostedZone),\n });\n }\n return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);\n }\n\n /**\n * Creates the data event bus.\n * Override to customize.\n */\n protected createDataEventBus(): IEventBus {\n return new DataEventBus(this);\n }\n\n /**\n * Creates the ops event bus.\n * Override to customize.\n */\n protected createOpsEventBus(): IEventBus {\n return new OpsEventBus(this);\n }\n\n /**\n * Creates the control-plane event bus.\n * Override to customize.\n */\n protected createControlEventBus(): IEventBus {\n return new ControlEventBus(this);\n }\n\n /**\n * Creates the platform deploy bridge that republishes CloudFormation\n * Stack Status Change events onto the control event bus.\n * Override to customize.\n */\n protected createPlatformDeployBridge(): PlatformDeployBridge {\n return new PlatformDeployBridge(this, {\n controlEventBus: this.controlEventBus,\n });\n }\n\n /**\n * Creates the shared workflow dedup table (TR-015 singleton).\n * Override to customize.\n */\n protected createWorkflowDedupTable(): WorkflowDedupTable {\n return new WorkflowDedupTable(this, \"workflow-dedup-table\");\n }\n}\n","import { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { PlatformDeployBridgeLambda } from \"./platform-deploy-bridge-lambda\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/platform-deploy-bridge/index.md\n */\n\nexport interface PlatformDeployBridgeProps {\n /** Destination control event bus the bridge republishes onto. */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Source-side reactor that watches CloudFormation Stack Status Change\n * events on the default AWS bus and republishes terminal-success events\n * (`CREATE_COMPLETE` / `UPDATE_COMPLETE`) for OpenHi-tagged stacks onto\n * the control event bus as `platform.deployment-completed.v1`.\n *\n * Implements row 4 of the workflow placement matrix\n * (codedrifters/openhi#953): ops-plane reactor → republishes to\n * control event bus.\n */\nexport class PlatformDeployBridge extends Construct {\n public readonly bridgeLambda: PlatformDeployBridgeLambda;\n\n constructor(scope: Construct, props: PlatformDeployBridgeProps) {\n super(scope, \"platform-deploy-bridge\");\n\n this.bridgeLambda = new PlatformDeployBridgeLambda(this, {\n controlEventBus: props.controlEventBus,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, Stack } from \"aws-cdk-lib\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n BRIDGED_STATUSES,\n CLOUDFORMATION_EVENT_SOURCE,\n CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,\n CONTROL_EVENT_BUS_NAME_ENV_VAR,\n OPENHI_REPO_TAG_KEY_ENV_VAR,\n OPENHI_TAG_KEY_PREFIX_ENV_VAR,\n} from \"./events\";\nimport {\n OPENHI_TAG_SUFFIX_REPO_NAME,\n OpenHiService,\n openHiTagKey,\n} from \"../../../app/open-hi-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/platform-deploy-bridge/index.md\n */\n\nconst HANDLER_NAME = \"platform-deploy-bridge.handler.js\";\n\n/**\n * Resolve handler entry so it works from src/ (tests) or lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface PlatformDeployBridgeLambdaProps {\n /** Destination control event bus the bridge republishes onto. */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Lambda that bridges CloudFormation Stack Status Change events from the\n * default AWS bus into typed `platform.deployment-completed.v1` envelopes on\n * the OpenHI control event bus.\n *\n * Owns its EventBridge Rule (on the default AWS bus) and the IAM\n * permissions it needs — colocating routing + permissions with the\n * function they target.\n *\n * The EventBridge rule pre-filters by stack-id prefix so the rule (and\n * therefore the Lambda) only fires on the host stack's own branch deploys.\n * This prevents cross-branch leak when multiple branches are deployed into\n * the same account.\n */\nexport class PlatformDeployBridgeLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: PlatformDeployBridgeLambdaProps) {\n super(scope, \"platform-deploy-bridge-lambda\");\n\n // Resolve the host OpenHiService so we can pin the rule to this\n // branch's stacks and project the appName-aware tag key.\n const service = OpenHiService.of(this) as OpenHiService;\n const repoTagKey = openHiTagKey(\n service.appName,\n OPENHI_TAG_SUFFIX_REPO_NAME,\n );\n const tagKeyPrefix = `${service.appName}:`;\n\n // Derive the shared sibling-stack prefix from this stack's own\n // synthesized name. `service.branchHash` alone (e.g. `4e4512-`)\n // is wrong because CDK prepends the Stage hierarchy\n // (`<stage>-<environment>-…`) to every stack name, so the\n // CloudFormation events carry stack-ids like\n // `dev-primary-4e4512-data-…`. Stripping the known\n // `-<serviceId>-<account>-<region>` suffix off the bridge's own\n // stack name yields the prefix every sibling stack shares.\n const ownStackName = Stack.of(this).stackName;\n const ownSuffix = `-${service.serviceId}-${Stack.of(this).account}-${\n Stack.of(this).region\n }`;\n const sharedPrefix = ownStackName.endsWith(ownSuffix)\n ? ownStackName.slice(0, -ownSuffix.length)\n : service.branchHash;\n const stackIdPrefix = `arn:aws:cloudformation:${Stack.of(this).region}:${\n Stack.of(this).account\n }:stack/${sharedPrefix}-`;\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 256,\n timeout: Duration.seconds(30),\n environment: {\n [CONTROL_EVENT_BUS_NAME_ENV_VAR]: props.controlEventBus.eventBusName,\n [OPENHI_REPO_TAG_KEY_ENV_VAR]: repoTagKey,\n [OPENHI_TAG_KEY_PREFIX_ENV_VAR]: tagKeyPrefix,\n },\n });\n\n // Fetch stack tags so the handler can project them into the envelope.\n // Scope to stacks in the deploying account/region.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"cloudformation:DescribeStacks\"],\n resources: [\n `arn:aws:cloudformation:${Stack.of(this).region}:${\n Stack.of(this).account\n }:stack/*`,\n ],\n }),\n );\n\n // Republish the projected envelope onto the control event bus.\n props.controlEventBus.grantPutEventsTo(this.lambda);\n\n // Rule lives on the default AWS bus — that is where AWS publishes\n // its own CloudFormation Stack Status Change events. Omitting\n // `eventBus` defaults to the account's default bus.\n //\n // The `stack-id` prefix scopes the rule to this branch's own stacks\n // only (OpenHi stack names start with the deterministic per-branch\n // hash), so other branches' deploys never trigger this Lambda even\n // though every branch deploys its own bridge into the same account.\n this.rule = new Rule(this, \"rule\", {\n eventPattern: {\n source: [CLOUDFORMATION_EVENT_SOURCE],\n detailType: [CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE],\n detail: {\n \"stack-id\": [{ prefix: stackIdPrefix }],\n \"status-details\": {\n status: [...BRIDGED_STATUSES],\n },\n },\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration, Stack } from \"aws-cdk-lib\";\nimport { IUserPool } from \"aws-cdk-lib/aws-cognito\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport { PlatformSystemDataSeededV1 } from \"./events\";\nimport { SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR } from \"./seed-demo-data.handler\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/seed-demo-data-lambda.md\n */\n\nconst HANDLER_NAME = \"seed-demo-data.handler.js\";\n\n/**\n * Resolve the bundled handler entry. Same dual-path lookup the\n * seed-system-data Lambda uses: src/ for tests (the file lives next\n * to this one) or lib/ for the compiled bundle.\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface SeedDemoDataLambdaProps {\n /**\n * Data-store table the workflow upserts demo-data records into.\n * Wired via `DYNAMO_TABLE_NAME` env var; granted `dynamodb:GetItem`\n * (pre-flight Role lookup) and `dynamodb:PutItem`/`dynamodb:UpdateItem`\n * (write phase). The grants are scoped to the table ARN only; the\n * handler itself is the scope guarantee for which records the\n * workflow touches (see the construct body for the previous\n * `LeadingKeys`-based grants and the reason they were dropped).\n */\n readonly dataStoreTable: ITable;\n\n /**\n * Control event bus that re-publishes\n * `platform.deployment-completed.v1` from the platform-deploy bridge.\n * The Rule mounts here.\n */\n readonly controlEventBus: IEventBus;\n\n /**\n * Cognito User Pool the workflow provisions dev users into. The\n * Lambda's IAM grant is scoped to this exact user-pool ARN — the\n * grant uses the user-pool ARN, **not** the wildcard formatArn\n * pattern used by `post-authentication-lambda` (that Lambda's\n * trigger-driven dependency cycle does not apply here, so the\n * tighter scope is safe).\n */\n readonly userPool: IUserPool;\n}\n\n/**\n * Lambda + EventBridge Rule pair for the seed-demo-data workflow.\n * Owns the routing (`source` / `detail-type` pattern), the scoped\n * DynamoDB grants, and the scoped Cognito Admin grant — co-locating\n * routing + permissions with the function they target. Wiring to the\n * workflow dedup table is the parent construct's job (it has the\n * singleton reference) and happens via `WorkflowDedupTable.grantConsumer`.\n *\n * Stage-gating is the parent's job too — this construct itself never\n * checks the stage. The CDK stage-router (`OpenHiDataService`)\n * decides whether to instantiate it at all on each stage.\n */\nexport class SeedDemoDataLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: SeedDemoDataLambdaProps) {\n super(scope, \"seed-demo-data-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(2),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n [SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR]: props.userPool.userPoolId,\n },\n });\n\n // Pre-flight Role check: read the three platform-singleton Role\n // PKs. The grant is scoped to the table ARN with no `LeadingKeys`\n // condition; the handler enumerates the exact Role ids it reads\n // and is the actual scope guarantee. The previous\n // `ForAllValues:StringEquals` / `dynamodb:LeadingKeys` condition\n // (one entry per Role id) was dropped because the rendered\n // managed-policy JSON — combined with the write grant below —\n // exceeded the 6,144-byte IAM managed-policy quota and broke every\n // `Deploy openhi-data dev/primary` run. The Lambda only runs on\n // stage-gated non-prod stages (no production data is at risk), so\n // the belt-and-suspenders IAM condition is not worth the deploy\n // breakage.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:GetItem\"],\n resources: [props.dataStoreTable.tableArn],\n }),\n );\n\n // Write grant: scoped to the table ARN with no `LeadingKeys`\n // condition. The handler writes a deterministic, hand-coded set\n // of demo Tenant / Workspace / Membership / RoleAssignment / User\n // records plus the OPS-009 v1 data-plane FHIR fixtures (Patient /\n // Practitioner / Observation / Encounter / Account) — that\n // enumeration is the scope guarantee, not the IAM policy. See the\n // pre-flight grant above for why the previous\n // `ForAllValues:StringEquals` condition was dropped.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:PutItem\", \"dynamodb:UpdateItem\"],\n resources: [props.dataStoreTable.tableArn],\n }),\n );\n\n // Cognito grant. The User Pool is imported by ARN from the auth\n // stack via SSM lookup, so reconstructing the ARN with\n // `Stack.of(this).formatArn` scopes the grant to this exact\n // account+region+pool without depending on the auth stack's\n // exported ARN.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"cognito-idp:AdminCreateUser\",\n \"cognito-idp:AdminGetUser\",\n \"cognito-idp:AdminSetUserPassword\",\n ],\n resources: [\n Stack.of(this).formatArn({\n service: \"cognito-idp\",\n resource: \"userpool\",\n resourceName: props.userPool.userPoolId,\n }),\n ],\n }),\n );\n\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.controlEventBus,\n eventPattern: {\n source: [PlatformSystemDataSeededV1.source],\n detailType: [PlatformSystemDataSeededV1.detailType],\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import { IUserPool } from \"aws-cdk-lib/aws-cognito\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { SEED_DEMO_DATA_CONSUMER_NAME } from \"./events\";\nimport { SeedDemoDataLambda } from \"./seed-demo-data-lambda\";\nimport { WorkflowDedupTable } from \"../../../components/dynamodb/workflow-dedup-table\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/seed-demo-data-workflow.md\n */\n\nexport interface SeedDemoDataWorkflowProps {\n /** Control event bus carrying `platform.system-data-seeded.v1`. */\n readonly controlEventBus: IEventBus;\n /** Data-store table the workflow upserts demo-data records into. */\n readonly dataStoreTable: ITable;\n /** Cognito User Pool the workflow provisions dev users into. */\n readonly userPool: IUserPool;\n}\n\n/**\n * Control-plane workflow that fires on every platform deploy and\n * idempotently re-asserts the demo-data graph: placeholder tenant +\n * workspace, 3 demo tenants + 4 workspaces, and per-dev-user Cognito\n * users with their DynamoDB User records, Memberships, and\n * RoleAssignments.\n *\n * Mounted on the data-service stack so the IAM grants against the\n * data-store table stay local. The control event bus and the workflow\n * dedup table reach in cross-stack via the SSM lookups\n * `OpenHiGlobalService.controlEventBusFromConstruct` and\n * `WorkflowDedupTable.grantConsumerFromLookup` respectively. The\n * Cognito User Pool similarly reaches in via\n * `OpenHiAuthService.userPoolFromConstruct`.\n *\n * Non-prod-only: the CDK stage-router (`OpenHiDataService`)\n * conditionally constructs this workflow only on non-prod stages.\n * The construct itself never checks the stage — its absence in prod\n * stacks is the gate.\n */\nexport class SeedDemoDataWorkflow extends Construct {\n public readonly seedDemoData: SeedDemoDataLambda;\n\n constructor(scope: Construct, props: SeedDemoDataWorkflowProps) {\n super(scope, \"seed-demo-data-workflow\");\n\n this.seedDemoData = new SeedDemoDataLambda(this, {\n controlEventBus: props.controlEventBus,\n dataStoreTable: props.dataStoreTable,\n userPool: props.userPool,\n });\n\n // Cross-stack grant resolves the dedup table's name + ARN via SSM\n // at synth time, so the data-service stack does not pick up a\n // CloudFormation export dependency on the global stack that owns\n // the dedup table.\n WorkflowDedupTable.grantConsumerFromLookup(\n this,\n this.seedDemoData.lambda,\n SEED_DEMO_DATA_CONSUMER_NAME,\n );\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { PLATFORM_ROLE_IDS } from \"@openhi/types\";\nimport { Duration, Stack } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n PlatformDeploymentCompletedV1,\n SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-system-data/seed-system-data-lambda.md\n */\n\nconst HANDLER_NAME = \"seed-system-data.handler.js\";\n\n/**\n * Resolve the bundled handler entry. Same dual-path lookup the user-onboarding\n * Lambda uses: src/ for tests (the file lives next to this one) or lib/ for\n * the compiled bundle.\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface SeedSystemDataLambdaProps {\n /**\n * Data-store table the workflow upserts platform-singleton control-plane\n * records into. Wired via `DYNAMO_TABLE_NAME` env var; granted scoped\n * write permission to the role records' partition keys only.\n */\n readonly dataStoreTable: ITable;\n\n /**\n * Control event bus that re-publishes\n * `platform.deployment-completed.v1` from the platform-deploy bridge.\n * The Rule mounts here.\n */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Lambda + EventBridge Rule pair for the seed-system-data workflow. Owns\n * the routing (`source` / `detail-type` pattern) and the scoped data-store\n * grants — co-locating routing + permissions with the function they\n * target. Wiring to the workflow dedup table is the parent construct's\n * job (it has the singleton reference) and happens via\n * `WorkflowDedupTable.grantConsumer`.\n */\nexport class SeedSystemDataLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: SeedSystemDataLambdaProps) {\n super(scope, \"seed-system-data-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n [SEED_SYSTEM_DATA_CONTROL_BUS_ENV_VAR]:\n props.controlEventBus.eventBusName,\n },\n });\n\n // Least-privilege grant: only the three known role PKs are\n // writable. A regression that tried to write a non-Role record\n // (or a Role with an unrecognized id) would be rejected by IAM,\n // not by application code. Updating the role-id set means\n // amending the generator's `ID_PREFIX_BY_VALUE_SET_URL` table\n // and regenerating — the role-id values flow through.\n //\n // The leading-keys values must match what ElectroDB *actually\n // writes*, not the entity definition's pretty template. The\n // base-table PK template `ROLE#ID#${id}` has no\n // `casing: \"none\"`, so ElectroDB applies its default casing\n // (lowercase) at runtime and the on-the-wire PK is\n // `role#id#<id>`. Authoring the policy in the uppercase\n // template form silently produces an IAM denial on every\n // PutItem the seeder attempts.\n const roleArns = Object.values(PLATFORM_ROLE_IDS).map(\n (id) => `role#id#${id}`,\n );\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:PutItem\", \"dynamodb:UpdateItem\"],\n resources: [props.dataStoreTable.tableArn],\n conditions: {\n \"ForAllValues:StringEquals\": {\n \"dynamodb:LeadingKeys\": roleArns,\n },\n },\n }),\n );\n\n // Allow the handler to publish `platform.system-data-seeded.v1`\n // onto the control event bus after a successful seed. Downstream\n // consumers (`seed-demo-data`) subscribe to that event instead\n // of the raw deploy-completion event so the dependency is\n // enforced by a happens-before edge rather than by EventBridge\n // retry timing.\n props.controlEventBus.grantPutEventsTo(this.lambda);\n\n // Gate the rule on the host (data) stack's own completion event.\n // The data-store table is created by this stack, so seeding only\n // becomes safe once this stack reaches CREATE/UPDATE_COMPLETE.\n // Without this filter, sibling stacks (global, auth, rest-api,\n // graphql) completing before the data stack would trigger the\n // seeder against a not-yet-created table.\n //\n // The filter targets `detail.payload.stackName`: the outer\n // `detail` is EventBridge's envelope (which holds the whole\n // workflow envelope), and `payload` is the per-workflow payload\n // field on `WorkflowEvent` — i.e. the projection the bridge\n // produced from the CloudFormation Stack Status Change event.\n const hostStackName = Stack.of(this).stackName;\n\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.controlEventBus,\n eventPattern: {\n source: [PlatformDeploymentCompletedV1.source],\n detailType: [PlatformDeploymentCompletedV1.detailType],\n detail: {\n payload: {\n stackName: [hostStackName],\n },\n },\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { SEED_SYSTEM_DATA_CONSUMER_NAME } from \"./events\";\nimport { SeedSystemDataLambda } from \"./seed-system-data-lambda\";\nimport { WorkflowDedupTable } from \"../../../components/dynamodb/workflow-dedup-table\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-system-data/seed-system-data-workflow.md\n */\n\nexport interface SeedSystemDataWorkflowProps {\n /** Control event bus carrying `platform.deployment-completed.v1`. */\n readonly controlEventBus: IEventBus;\n /** Data-store table the workflow upserts platform-singleton records into. */\n readonly dataStoreTable: ITable;\n}\n\n/**\n * Control-plane workflow that fires on every platform deploy and\n * idempotently re-asserts the platform-singleton control-plane records\n * (today: the three canonical Roles; future: additional system data\n * slotted in as sibling steps under the same dedup record).\n *\n * Mounted on the data-service stack so the IAM grants against the\n * data-store table stay local. The control event bus and the\n * workflow dedup table reach in cross-stack via the SSM lookups\n * `OpenHiGlobalService.controlEventBusFromConstruct` and\n * `WorkflowDedupTable.grantConsumerFromLookup` respectively.\n */\nexport class SeedSystemDataWorkflow extends Construct {\n public readonly seedSystemData: SeedSystemDataLambda;\n\n constructor(scope: Construct, props: SeedSystemDataWorkflowProps) {\n super(scope, \"seed-system-data-workflow\");\n\n this.seedSystemData = new SeedSystemDataLambda(this, {\n controlEventBus: props.controlEventBus,\n dataStoreTable: props.dataStoreTable,\n });\n\n // Cross-stack grant resolves the dedup table's name + ARN via SSM\n // at synth time, so the data-service stack does not pick up a\n // CloudFormation export dependency on the global stack that owns\n // the dedup table.\n WorkflowDedupTable.grantConsumerFromLookup(\n this,\n this.seedSystemData.lambda,\n SEED_SYSTEM_DATA_CONSUMER_NAME,\n );\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport { ICertificate } from \"aws-cdk-lib/aws-certificatemanager\";\nimport { IHostedZone } from \"aws-cdk-lib/aws-route53\";\nimport { Bucket, IBucket } from \"aws-cdk-lib/aws-s3\";\nimport { Construct } from \"constructs\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport { OpenHiRestApiService } from \"./open-hi-rest-api-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport {\n PER_BRANCH_PREVIEW_PREFIX,\n PerBranchHostname,\n StaticContent,\n StaticHosting,\n} from \"../components/static-hosting\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-website-service.md\n */\n\nexport interface OpenHiWebsiteServiceProps extends OpenHiServiceProps {\n /**\n * Sub-domain prefix attached to the child zone (e.g. \"www\" -\\> \"www.\\<zone\\>\").\n *\n * @default \"www\"\n */\n readonly domainPrefix?: string;\n\n /**\n * Absolute path to the local directory whose contents should be uploaded\n * to the static-hosting bucket. Required.\n */\n readonly contentSourceDirectory: string;\n\n /**\n * Path under the per-branch destination prefix to upload into. Should start\n * with a slash.\n *\n * @default \"/\"\n */\n readonly contentDestinationDirectory?: string;\n\n /**\n * Force the `StaticHosting` infrastructure (bucket + distribution +\n * Lambda@Edge + DNS + 4 SSM params) to be created on this branch even when\n * it is not the release branch. Useful for one-off bootstraps and tests.\n *\n * When omitted, hosting infrastructure is created only on\n * `defaultReleaseBranch`. The `StaticContent` uploader is always\n * created so feature branches can publish their content under their own\n * sub-domain folder against the release-branch bucket.\n *\n * @default - true on release branch, false otherwise\n */\n readonly createHostingInfrastructure?: boolean;\n\n /**\n * Whether to create the `StaticContent` uploader. Set to `false` to skip\n * it entirely on every branch — used as a one-shot bootstrap toggle while\n * the release-branch deploy of this service first creates the static-hosting\n * bucket and writes `STATIC_HOSTING_BUCKET_ARN` to SSM. Once that\n * parameter exists, flip back to `true` so feature-branch deploys can read\n * it and upload content under their per-branch sub-domain folder.\n *\n * @default true\n */\n readonly createStaticContent?: boolean;\n\n /**\n * When `true`, the website's CloudFront distribution proxies `/api/*` to\n * the REST API service deployed for this branch. The API custom-domain\n * hostname is resolved at synth time via SSM\n * ({@link OpenHiRestApiService.restApiDomainNameFromConstruct}), so the\n * REST API stack must have written its `REST_API_DOMAIN_NAME` SSM\n * parameter at least once before the website stack updates — the\n * workflow already orders these deploys (rest-api → website).\n *\n * Used together with the admin-console's runtime-config fetch (which\n * calls `/api/control/runtime-config` same-origin) so the React bundle\n * stays branch-agnostic.\n *\n * Only takes effect when `createHostingInfrastructure` is also true,\n * since the additional CloudFront behaviors live on the release-branch\n * distribution; feature-branch deploys share that distribution.\n *\n * @default false\n */\n readonly restApi?: boolean;\n}\n\n/**\n * SSM parameter name suffix for the website's full domain\n * (e.g. www.example.com).\n */\nexport const SSM_PARAM_NAME_FULL_DOMAIN = \"WEBSITE_FULL_DOMAIN\";\n\n/**\n * Sub-domain prefix the openhi admin console is deployed under. The\n * website-service deploys at `admin.<zone>` (release branch) or\n * `admin-<childZonePrefix>.<zone>` (per-PR), so any stack that needs to\n * reference the admin console's hostname — most notably the REST API\n * stack composing its CORS `allowOrigins` — should import this constant\n * rather than redeclaring the literal.\n *\n * @public\n */\nexport const ADMIN_DOMAIN_PREFIX = \"admin\";\n\n/**\n * Website service stack. Release-branch deploys compose `StaticHosting`\n * (bucket + CloudFront distribution with a wildcard SAN for per-PR\n * previews + Lambda@Edge + DNS) and `StaticContent` (content upload).\n * Non-release-branch deploys compose `PerBranchHostname` (a per-PR\n * Route53 alias aliased to the release distribution) and `StaticContent`\n * (per-PR upload to the shared bucket) so feature branches publish\n * previews without provisioning duplicate infrastructure.\n *\n * Resources are created in protected methods; subclasses may override to\n * customize.\n */\nexport class OpenHiWebsiteService extends OpenHiService {\n static readonly SERVICE_TYPE = \"website\" as const satisfies OpenHiServiceType;\n\n /**\n * Default `domainPrefix` for this service when none is supplied.\n * Release-branch hostname is `www.<zone>`; per-PR preview hostname is\n * `www-<childZonePrefix>.<zone>`.\n */\n static readonly DEFAULT_DOMAIN_PREFIX = \"www\";\n\n /**\n * Compose the website's full per-deploy domain. Thin wrapper over\n * {@link OpenHiService.composeServiceDomain} that fills in\n * {@link DEFAULT_DOMAIN_PREFIX} when `domainPrefix` is omitted.\n *\n * Use from sibling stacks that need to predict the website's hostname\n * before the website stack is synthesised — e.g. the REST API stack\n * computing its CORS `allowOrigins` for the admin-console.\n */\n static composeFullDomain(opts: {\n domainPrefix?: string;\n branchName: string;\n defaultReleaseBranch: string;\n childZonePrefix: string;\n zoneName: string;\n }): string {\n return OpenHiService.composeServiceDomain({\n ...opts,\n domainPrefix:\n opts.domainPrefix ?? OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX,\n });\n }\n\n /**\n * Looks up the static-hosting bucket ARN published by the release-branch\n * deploy of this service.\n */\n static bucketArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the CloudFront distribution ARN published by the release-branch\n * deploy of this service.\n */\n static distributionArnFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ARN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the CloudFront distribution domain\n * (e.g. dXXXXX.cloudfront.net) published by the release-branch deploy.\n */\n static distributionDomainFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_DOMAIN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the CloudFront distribution ID published by the release-branch\n * deploy of this service.\n */\n static distributionIdFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_DISTRIBUTION_ID,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Looks up the website's full domain (e.g. www.example.com) published by\n * the release-branch deploy of this service.\n */\n static fullDomainFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: SSM_PARAM_NAME_FULL_DOMAIN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n get serviceType(): string {\n return OpenHiWebsiteService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options. */\n public override props: OpenHiWebsiteServiceProps;\n\n /**\n * Full domain served by this website. On the release branch this is\n * `<domainPrefix>.<zone>` (e.g. `admin.dev.openhi.org`); on every\n * other branch it is the per-PR preview hostname\n * `<domainPrefix>-<childZonePrefix>.<zone>`\n * (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`), where\n * `childZonePrefix` is `paramCase(branchName)` truncated to 56 chars.\n */\n public readonly fullDomain: string;\n\n /**\n * The hosting construct, only created on release-branch deploys (or when\n * `createHostingInfrastructure` is true).\n */\n public readonly staticHosting?: StaticHosting;\n\n /**\n * The content uploader. Created on every deploy unless\n * {@link OpenHiWebsiteServiceProps.createStaticContent} is `false`, in\n * which case the property is `undefined` and the stack ships no\n * `BucketDeployment`. Used during release-branch bootstrap, before the\n * shared static-hosting bucket has been written to SSM for the first time.\n */\n public readonly staticContent?: StaticContent;\n\n /**\n * Per-PR alias record. Created on non-release-branch deploys (when\n * `createHostingInfrastructure` is left at its default) so the PR\n * hostname `<domainPrefix>-<childZonePrefix>.<zone>` resolves to the\n * release-branch CloudFront distribution. `undefined` on release-branch\n * deploys and on bootstrap deploys that force hosting infra on.\n */\n public readonly perBranchHostname?: PerBranchHostname;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiWebsiteServiceProps) {\n super(ohEnv, OpenHiWebsiteService.SERVICE_TYPE, props);\n this.props = props;\n\n this.validateConfig(props);\n\n const isReleaseBranch = this.branchName === this.defaultReleaseBranch;\n\n const hostedZone = this.createHostedZone();\n this.fullDomain = this.computeFullDomain(hostedZone);\n\n const shouldCreateHostingInfra =\n props.createHostingInfrastructure ?? isReleaseBranch;\n\n if (shouldCreateHostingInfra) {\n const certificate = this.createCertificate();\n this.staticHosting = this.createStaticHosting({\n certificate,\n hostedZone,\n });\n this.createFullDomainParameter();\n } else if (!isReleaseBranch) {\n this.perBranchHostname = this.createPerBranchHostname(hostedZone);\n }\n\n if (props.createStaticContent !== false) {\n const bucket = this.resolveStaticHostingBucket();\n this.staticContent = this.createStaticContent(bucket);\n }\n }\n\n /**\n * Validates that config required for the website stack is present.\n */\n protected validateConfig(props: OpenHiWebsiteServiceProps): void {\n const { config } = props;\n if (!config) {\n throw new Error(\"Config is required\");\n }\n if (!config.zoneName) {\n throw new Error(\"Zone name is required\");\n }\n if (!config.hostedZoneId) {\n throw new Error(\"Hosted zone ID is required to import the website zone\");\n }\n }\n\n /**\n * Imports the website's hosted zone from config attributes (no SSM lookup).\n * The website attaches DNS records here on the release-branch deploy and\n * the same zone is imported on feature-branch deploys for any sub-domain\n * routing.\n * Override to customize.\n */\n protected createHostedZone(): IHostedZone {\n return OpenHiGlobalService.rootHostedZoneFromConstruct(this, {\n zoneName: this.config.zoneName!,\n hostedZoneId: this.config.hostedZoneId!,\n });\n }\n\n /**\n * Returns the wildcard certificate looked up from the Global service.\n * Override to customize.\n */\n protected createCertificate(): ICertificate {\n return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);\n }\n\n /**\n * Computes the full website domain from `domainPrefix`,\n * `childZonePrefix`, and the child zone name. Release-branch deploys\n * serve at `\\<domainPrefix\\>.\\<zone\\>` (e.g. `admin.dev.openhi.org`);\n * every other deploy serves a per-PR preview at\n * `\\<domainPrefix\\>-\\<childZonePrefix\\>.\\<zone\\>`\n * (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`).\n *\n * Delegates to {@link OpenHiWebsiteService.composeFullDomain} so the\n * release-vs-feature composition stays in one place.\n */\n protected computeFullDomain(hostedZone: IHostedZone): string {\n return OpenHiWebsiteService.composeFullDomain({\n domainPrefix: this.props.domainPrefix,\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName: hostedZone.zoneName,\n });\n }\n\n /**\n * Returns the sub-domain label (left of the zone) for the current\n * deploy. Used for the per-branch S3 key prefix passed to\n * {@link StaticContent} so the upload prefix always matches the\n * served hostname.\n *\n * Non-release deploys compose the per-PR slug as\n * `\\<domainPrefix\\>-\\<childZonePrefix\\>`, mirroring the REST API's\n * `api-\\<childZonePrefix\\>` convention. When `domainPrefix` is `admin`\n * (the only consumer today), the resulting sub-domain starts with\n * {@link PER_BRANCH_PREVIEW_PREFIX}, so the per-PR S3 key prefix\n * matches what `StaticHosting`'s lifecycle rule expires.\n */\n protected computeSubDomain(): string {\n const isReleaseBranch = this.branchName === this.defaultReleaseBranch;\n const domainPrefix =\n this.props.domainPrefix ?? OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX;\n if (isReleaseBranch) {\n return domainPrefix;\n }\n return `${domainPrefix}-${this.childZonePrefix}`;\n }\n\n /**\n * Creates the StaticHosting infrastructure (bucket + distribution +\n * Lambda@Edge + 4 SSM params + DNS). The release-branch distribution\n * adds `*.\\<zone\\>` as a wildcard alt-name on top of the canonical\n * hostname so per-PR previews resolve via the same distribution.\n *\n * The bucket carries an S3 lifecycle rule that expires per-PR\n * preview content (keys under {@link PER_BRANCH_PREVIEW_PREFIX})\n * on non-production stages. PROD never gets the rule — see\n * `enablePreviewLifecycle`.\n */\n protected createStaticHosting(deps: {\n certificate: ICertificate;\n hostedZone: IHostedZone;\n }): StaticHosting {\n const restApi =\n this.props.restApi === true ? this.resolveRestApi() : undefined;\n const wildcardSan = `*.${deps.hostedZone.zoneName}`;\n return new StaticHosting(this, \"static-hosting\", {\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n certificate: deps.certificate,\n hostedZone: deps.hostedZone,\n domainNames: [this.fullDomain, wildcardSan],\n description: `OpenHI website (${this.fullDomain})`,\n prefixPattern: PER_BRANCH_PREVIEW_PREFIX,\n enablePreviewLifecycle:\n this.ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD,\n ...(restApi !== undefined && { restApi }),\n });\n }\n\n /**\n * Resolves the REST API custom-domain hostname from the rest-api stack's\n * `REST_API_DOMAIN_NAME` SSM parameter. Wrapped in a private method so\n * it can be overridden / stubbed in subclasses and tests.\n */\n protected resolveRestApi(): { domainName: string } {\n return {\n domainName: OpenHiRestApiService.restApiDomainNameFromConstruct(this),\n };\n }\n\n /**\n * Creates the SSM parameter that publishes the website's full domain.\n * Look up via {@link OpenHiWebsiteService.fullDomainFromConstruct}.\n */\n protected createFullDomainParameter(): void {\n new DiscoverableStringParameter(this, \"full-domain-param\", {\n ssmParamName: SSM_PARAM_NAME_FULL_DOMAIN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n stringValue: this.fullDomain,\n description: \"Full website domain (e.g. www.example.com)\",\n });\n }\n\n /**\n * Creates the StaticContent uploader. Receives the resolved static-hosting\n * bucket from the constructor — on the release-branch deploy this is the\n * just-created {@link staticHosting} bucket (no SSM round-trip within a\n * single stack); on every other deploy it is imported from the bucket ARN\n * the release-branch deploy publishes to SSM, addressed against\n * {@link OpenHiService.releaseBranchHash}. See\n * {@link resolveStaticHostingBucket}.\n *\n * The S3 key prefix is `\\<sub-domain\\>.\\<zone\\>/\\<contentDest\\>` so the\n * upload location matches the Host-header-derived folder the Lambda@Edge\n * viewer-request handler prepends. Passing the zone name (rather than\n * `this.fullDomain`) for the `fullDomain` prop keeps the prefix flat —\n * `admin-feat-foo.dev.openhi.org/`, not\n * `admin-feat-foo.admin.dev.openhi.org/`.\n */\n protected createStaticContent(bucket: IBucket): StaticContent {\n const { contentSourceDirectory, contentDestinationDirectory } = this.props;\n return new StaticContent(this, \"static-content\", {\n bucket,\n contentSourceDirectory,\n contentDestinationDirectory,\n subDomain: this.computeSubDomain(),\n fullDomain: this.config.zoneName!,\n });\n }\n\n /**\n * Creates the per-PR `PerBranchHostname` alias record on non-release\n * branch deploys. The record points\n * `\\<domainPrefix\\>-\\<childZonePrefix\\>.\\<zone\\>` at the release-branch\n * CloudFront distribution (resolved from SSM against\n * {@link OpenHiService.releaseBranchHash}).\n */\n protected createPerBranchHostname(\n hostedZone: IHostedZone,\n ): PerBranchHostname {\n return new PerBranchHostname(this, \"per-branch-hostname\", {\n hostname: this.fullDomain,\n hostedZone,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n });\n }\n\n /**\n * Returns an {@link IBucket} pointing at the static-hosting bucket the\n * uploaders write to. On the release-branch deploy this is the bucket\n * just provisioned by {@link staticHosting}; on every other deploy it's\n * imported from the bucket ARN the release-branch deploy publishes to\n * SSM, addressed against {@link OpenHiService.releaseBranchHash}.\n */\n protected resolveStaticHostingBucket(): IBucket {\n if (this.staticHosting) {\n return this.staticHosting.bucket;\n }\n const bucketArn = DiscoverableStringParameter.valueForLookupName(this, {\n ssmParamName: StaticHosting.SSM_PARAM_NAME_BUCKET_ARN,\n serviceType: OpenHiWebsiteService.SERVICE_TYPE,\n branchHash: this.releaseBranchHash,\n });\n return Bucket.fromBucketArn(this, \"shared-bucket\", bucketArn);\n }\n}\n","import { OPEN_HI_STAGE } from \"@openhi/config\";\nimport {\n CorsHttpMethod,\n CorsPreflightOptions,\n DomainName,\n HttpApi,\n HttpMethod,\n HttpNoneAuthorizer,\n HttpRoute,\n HttpRouteKey,\n IHttpApi,\n} from \"aws-cdk-lib/aws-apigatewayv2\";\nimport { HttpUserPoolAuthorizer } from \"aws-cdk-lib/aws-apigatewayv2-authorizers\";\nimport { HttpLambdaIntegration } from \"aws-cdk-lib/aws-apigatewayv2-integrations\";\nimport { ICertificate } from \"aws-cdk-lib/aws-certificatemanager\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport {\n ARecord,\n HostedZone,\n IHostedZone,\n RecordTarget,\n} from \"aws-cdk-lib/aws-route53\";\nimport { ApiGatewayv2DomainProperties } from \"aws-cdk-lib/aws-route53-targets\";\nimport { Duration } from \"aws-cdk-lib/core\";\nimport { Construct } from \"constructs\";\nimport { OpenHiAuthService } from \"./open-hi-auth-service\";\nimport { OpenHiDataService } from \"./open-hi-data-service\";\nimport { OpenHiGlobalService } from \"./open-hi-global-service\";\nimport {\n ADMIN_DOMAIN_PREFIX,\n OpenHiWebsiteService,\n} from \"./open-hi-website-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport {\n RootHttpApi,\n RootHttpApiProps,\n} from \"../components/api-gateway/root-http-api\";\nimport {\n DataStorePostgresReplica,\n getPostgresReplicaSchemaName,\n} from \"../components/postgres/data-store-postgres-replica\";\nimport { DiscoverableStringParameter } from \"../components/ssm\";\nimport { CorsOptionsLambda } from \"../data/lambda/cors-options-lambda\";\nimport { RestApiLambda } from \"../data/lambda/rest-api-lambda\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/services/open-hi-rest-api-service.md\n */\n\nexport interface OpenHiRestApiServiceProps extends OpenHiServiceProps {\n /**\n * Optional props passed through to the RootHttpApi (API Gateway HTTP API) construct.\n * Use corsPreflight (CDK CorsPreflightOptions) for CORS; other HttpApiProps (e.g. description, disableExecuteApiEndpoint) apply as well.\n */\n readonly rootHttpApiProps?: RootHttpApiProps;\n}\n\n/**\n * SSM parameter name suffix for the REST API base URL.\n * Full parameter name is built via buildParameterName with serviceType REST_API.\n */\nexport const REST_API_BASE_URL_SSM_NAME = \"REST_API_BASE_URL\";\n\n/**\n * SSM parameter name suffix for the REST API's custom domain (bare hostname,\n * no scheme — e.g. `api.example.com`). Consumed by the website service as\n * the CloudFront `/api/*` origin host.\n */\nexport const REST_API_DOMAIN_NAME_SSM_NAME = \"REST_API_DOMAIN_NAME\";\n\n/**\n * Localhost / 127.0.0.1 dev origins auto-injected into CORS `allowOrigins`\n * on every non-prod (`stageType !== \"prod\"`) REST API deploy. Both schemes\n * (`http`, `https`) and both ports the local SPAs use (`3000`, `5173`) are\n * covered so admin-console / on-site previews running on `localhost` or\n * `127.0.0.1` can call the API direct cross-origin without per-consumer\n * boilerplate.\n */\nexport const DEV_CORS_ALLOW_ORIGINS: ReadonlyArray<string> = [\n \"http://localhost:3000\",\n \"https://localhost:3000\",\n \"http://localhost:5173\",\n \"https://localhost:5173\",\n \"http://127.0.0.1:3000\",\n \"https://127.0.0.1:3000\",\n \"http://127.0.0.1:5173\",\n \"https://127.0.0.1:5173\",\n];\n\n/**\n * REST API service stack: HTTP API, custom domain, and Lambda; exports base URL via SSM.\n * Resources are created in protected methods; subclasses may override to customize.\n */\nexport class OpenHiRestApiService extends OpenHiService {\n static readonly SERVICE_TYPE =\n \"rest-api\" as const satisfies OpenHiServiceType;\n\n /**\n * Sub-domain prefix used by the REST API. Release-branch hostname is\n * `api.<zone>`; per-PR preview hostname is `api-<childZonePrefix>.<zone>`.\n */\n static readonly API_DOMAIN_PREFIX = \"api\";\n\n /**\n * Compose the REST API's full per-deploy domain. Thin wrapper over\n * {@link OpenHiService.composeServiceDomain} that pins `domainPrefix`\n * to {@link API_DOMAIN_PREFIX}.\n *\n * Use from sibling stacks that need to predict the API's hostname\n * before the REST API stack is synthesised.\n */\n static composeFullDomain(opts: {\n branchName: string;\n defaultReleaseBranch: string;\n childZonePrefix: string;\n zoneName: string;\n }): string {\n return OpenHiService.composeServiceDomain({\n ...opts,\n domainPrefix: OpenHiRestApiService.API_DOMAIN_PREFIX,\n });\n }\n\n /**\n * Returns an IHttpApi by looking up the REST API stack's HTTP API ID from SSM.\n */\n static rootHttpApiFromConstruct(scope: Construct): IHttpApi {\n const httpApiId = DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: RootHttpApi.SSM_PARAM_NAME,\n serviceType: OpenHiRestApiService.SERVICE_TYPE,\n });\n return HttpApi.fromHttpApiAttributes(scope, \"http-api\", { httpApiId });\n }\n\n /**\n * Returns the REST API base URL (e.g. https://api.example.com) by looking it up from SSM.\n * Use in other stacks for E2E, scripts, or config.\n */\n static restApiBaseUrlFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: REST_API_BASE_URL_SSM_NAME,\n serviceType: OpenHiRestApiService.SERVICE_TYPE,\n });\n }\n\n /**\n * Returns the REST API's custom domain name (bare hostname, no scheme — e.g.\n * `api.example.com`) by looking it up from SSM. Use as the host for a\n * CloudFront `HttpOrigin` so the website's distribution can proxy `/api/*`\n * to this stack's API Gateway without per-branch DNS knowledge.\n */\n static restApiDomainNameFromConstruct(scope: Construct): string {\n return DiscoverableStringParameter.valueForLookupName(scope, {\n ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,\n serviceType: OpenHiRestApiService.SERVICE_TYPE,\n });\n }\n\n get serviceType(): string {\n return OpenHiRestApiService.SERVICE_TYPE;\n }\n\n /** Override so this.props is typed with this service's options (e.g. rootHttpApiProps). */\n public override props: OpenHiRestApiServiceProps;\n\n public readonly rootHttpApi: RootHttpApi;\n\n /**\n * REST API custom domain (bare hostname, no scheme — e.g.\n * `api.dev.openhi.org`). Captured so {@link resolveRuntimeConfigEnvVars}\n * can derive the public `apiBaseUrl` without callers having to hardcode it.\n */\n private readonly apiDomainName: string;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiRestApiServiceProps = {}) {\n super(ohEnv, OpenHiRestApiService.SERVICE_TYPE, props);\n this.props = props;\n\n this.validateConfig(props);\n\n const hostedZone = this.createHostedZone();\n const certificate = this.createCertificate();\n this.apiDomainName = this.createApiDomainNameString(hostedZone);\n this.createRestApiBaseUrlParameter(this.apiDomainName);\n this.createRestApiDomainNameParameter(this.apiDomainName);\n const domainName = this.createDomainName(hostedZone, certificate);\n this.rootHttpApi = this.createRootHttpApi(domainName);\n this.createRestApiLambdaAndRoutes(hostedZone, domainName);\n }\n\n /**\n * Validates that config required for the REST API stack is present.\n */\n protected validateConfig(props: OpenHiRestApiServiceProps): void {\n const { config } = props;\n if (!config) {\n throw new Error(\"Config is required\");\n }\n if (!config.hostedZoneId) {\n throw new Error(\"Hosted zone ID is required\");\n }\n if (!config.zoneName) {\n throw new Error(\"Zone name is required\");\n }\n }\n\n /**\n * Creates the hosted zone reference (imported from config).\n * Override to customize.\n */\n protected createHostedZone(): IHostedZone {\n const { config } = this.props;\n return HostedZone.fromHostedZoneAttributes(this, \"root-zone\", {\n hostedZoneId: config!.hostedZoneId!,\n zoneName: config!.zoneName!,\n });\n }\n\n /**\n * Creates the wildcard certificate (imported from Global stack via SSM).\n * Override to customize.\n */\n protected createCertificate() {\n return OpenHiGlobalService.rootWildcardCertificateFromConstruct(this);\n }\n\n /**\n * Returns the API domain name string (e.g. api.example.com or api-\\{prefix\\}.example.com).\n * Delegates to {@link OpenHiRestApiService.composeFullDomain} so the\n * release-vs-feature composition stays in one place; picks up\n * `this.defaultReleaseBranch` (not a hard-coded `\"main\"`).\n * Override to customize.\n */\n protected createApiDomainNameString(hostedZone: IHostedZone): string {\n return OpenHiRestApiService.composeFullDomain({\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName: hostedZone.zoneName,\n });\n }\n\n /**\n * Creates the SSM parameter for the REST API base URL.\n * Look up via {@link OpenHiRestApiService.restApiBaseUrlFromConstruct}.\n * Override to customize.\n */\n protected createRestApiBaseUrlParameter(apiDomainName: string): void {\n const restApiBaseUrl = `https://${apiDomainName}`;\n new DiscoverableStringParameter(this, \"rest-api-base-url-param\", {\n ssmParamName: REST_API_BASE_URL_SSM_NAME,\n stringValue: restApiBaseUrl,\n description: \"REST API base URL for this deployment (E2E, scripts)\",\n });\n }\n\n /**\n * Creates the SSM parameter exposing the REST API's custom domain (bare\n * hostname, no scheme). Consumed by the website service as the CloudFront\n * `/api/*` origin host.\n * Look up via {@link OpenHiRestApiService.restApiDomainNameFromConstruct}.\n * Override to customize.\n */\n protected createRestApiDomainNameParameter(apiDomainName: string): void {\n new DiscoverableStringParameter(this, \"rest-api-domain-name-param\", {\n ssmParamName: REST_API_DOMAIN_NAME_SSM_NAME,\n stringValue: apiDomainName,\n description:\n \"REST API custom domain name (bare hostname) for cross-stack CloudFront origin lookup\",\n });\n }\n\n /**\n * Creates the API Gateway custom domain name resource.\n * Override to customize.\n */\n protected createDomainName(\n _hostedZone: IHostedZone,\n certificate: ICertificate,\n ): DomainName {\n const apiDomainName = this.createApiDomainNameString(_hostedZone);\n return new DomainName(this, \"domain\", {\n domainName: apiDomainName,\n certificate,\n });\n }\n\n /**\n * Creates the Lambda integration, HTTP routes, and API DNS record.\n * Override to customize. Uses {@link rootHttpApi} set by the constructor.\n */\n protected createRestApiLambdaAndRoutes(\n hostedZone: IHostedZone,\n domainName: DomainName,\n ): void {\n const dataStoreTable =\n OpenHiDataService.dynamoDbDataStoreFromConstruct(this);\n\n // Phase 2 of ADR 2026-04-17-01: REST API Lambda queries Postgres via the\n // RDS Data API. Cluster ARN, secret ARN, and database name come from SSM\n // (cross-stack discovery); the per-branch schema name is deterministic\n // from `branchHash` and computed locally to avoid an extra SSM lookup.\n const postgresClusterArn =\n DataStorePostgresReplica.clusterArnFromConstruct(this);\n const postgresSecretArn =\n DataStorePostgresReplica.secretArnFromConstruct(this);\n const postgresDatabase =\n DataStorePostgresReplica.databaseNameFromConstruct(this);\n const postgresSchema = getPostgresReplicaSchemaName(this.branchHash);\n\n const extraEnvironment = this.resolveRuntimeConfigEnvVars();\n\n const { lambda } = new RestApiLambda(this, {\n dynamoTableName: dataStoreTable.tableName,\n branchTagValue: this.branchName,\n httpApiTagValue: RootHttpApi.SSM_PARAM_NAME,\n postgresClusterArn,\n postgresSecretArn,\n postgresDatabase,\n postgresSchema,\n extraEnvironment,\n });\n\n // Allow the Lambda to issue Data API statements against this cluster and\n // read the cluster credentials secret. These are scoped to the specific\n // ARNs published by the data stack — no wildcards.\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"rds-data:ExecuteStatement\",\n \"rds-data:BatchExecuteStatement\",\n ],\n resources: [postgresClusterArn],\n }),\n );\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"secretsmanager:GetSecretValue\"],\n resources: [postgresSecretArn],\n }),\n );\n const dynamoActions = [\n \"dynamodb:GetItem\",\n \"dynamodb:Query\",\n \"dynamodb:BatchGetItem\",\n \"dynamodb:ConditionCheckItem\",\n \"dynamodb:DescribeTable\",\n \"dynamodb:BatchWriteItem\",\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:DeleteItem\",\n ] as const;\n dataStoreTable.grant(lambda, ...dynamoActions);\n // Query (and other operations) on GSIs require the index resource ARN\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [...dynamoActions],\n resources: [`${dataStoreTable.tableArn}/index/*`],\n }),\n );\n // Temporary: broad SSM read for dynamic config (test only)\n lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\n \"ssm:GetParameter\",\n \"ssm:GetParameters\",\n \"ssm:DescribeParameters\",\n ],\n resources: [\"*\"],\n }),\n );\n const integration = new HttpLambdaIntegration(\"lambda-integration\", lambda);\n const { lambda: optionsLambda } = new CorsOptionsLambda(this);\n const optionsIntegration = new HttpLambdaIntegration(\n \"options-integration\",\n optionsLambda,\n );\n const noAuth = new HttpNoneAuthorizer();\n // OPTIONS routes use dedicated low-memory Lambda so main REST API Lambda is not invoked for preflight (#694).\n new HttpRoute(this, \"options-route-root\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/\", HttpMethod.OPTIONS),\n integration: optionsIntegration,\n authorizer: noAuth,\n });\n new HttpRoute(this, \"options-route-proxy\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/{proxy+}\", HttpMethod.OPTIONS),\n integration: optionsIntegration,\n authorizer: noAuth,\n });\n // `GET /control/runtime-config` is the admin-console's bootstrap endpoint —\n // the SPA fetches it *before* it has a Cognito JWT, so it must bypass the\n // default JWT authorizer applied to the catch-all `/{proxy+}` route below.\n // HTTP APIs match the most-specific route first, so these literal-path\n // routes preempt the proxy automatically. HEAD mirrors GET for parity\n // with the proxy route's HttpMethod.ANY (#1111).\n new HttpRoute(this, \"runtime-config-get-route\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/control/runtime-config\", HttpMethod.GET),\n integration,\n authorizer: noAuth,\n });\n new HttpRoute(this, \"runtime-config-head-route\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/control/runtime-config\", HttpMethod.HEAD),\n integration,\n authorizer: noAuth,\n });\n new HttpRoute(this, \"proxy-route-root\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/\", HttpMethod.ANY),\n integration,\n });\n new HttpRoute(this, \"proxy-route\", {\n httpApi: this.rootHttpApi,\n routeKey: HttpRouteKey.with(\"/{proxy+}\", HttpMethod.ANY),\n integration,\n });\n // Derive the sub-domain label from the already-composed full domain\n // (`this.apiDomainName`) so this block can't drift from\n // {@link createApiDomainNameString}.\n const apiPrefix = this.apiDomainName.slice(\n 0,\n -(hostedZone.zoneName.length + 1),\n );\n // Embed apiPrefix in the construct id so a recordName change\n // (e.g. when `childZonePrefix`'s truncation cap shifts and a\n // branch's paramCased name re-slices to a different value)\n // produces a CloudFormation Add + Remove pair rather than an\n // in-place Replacement on AWS::Route53::RecordSet.Name (which is\n // UpdateRequiresReplacement). Avoids the TtyNotAttached error CDK\n // raises on `--no-rollback` deploys — mirrors the StaticHosting\n // ARecord fix from #1131 and the PerBranchHostname fix on #1132.\n new ARecord(this, `api-a-record-${apiPrefix}`, {\n zone: hostedZone,\n recordName: apiPrefix,\n target: RecordTarget.fromAlias(\n new ApiGatewayv2DomainProperties(\n domainName.regionalDomainName,\n domainName.regionalHostedZoneId,\n ),\n ),\n });\n }\n\n /**\n * Creates the Root HTTP API with default domain mapping, Cognito JWT authorizer, and exports API ID to SSM.\n * Look up via {@link OpenHiRestApiService.rootHttpApiFromConstruct}.\n * Override to customize.\n */\n protected createRootHttpApi(domainName: DomainName): RootHttpApi {\n const userPool = OpenHiAuthService.userPoolFromConstruct(this);\n const userPoolClient = OpenHiAuthService.userPoolClientFromConstruct(this);\n const cognitoAuthorizer = new HttpUserPoolAuthorizer(\n \"cognito-authorizer\",\n userPool,\n { userPoolClients: [userPoolClient] },\n );\n const { corsPreflight: cors, ...restRootHttpApiProps } =\n this.props.rootHttpApiProps ?? {};\n const isNonProd = this.ohEnv.ohStage.stageType !== OPEN_HI_STAGE.PROD;\n const callerOrigins = cors?.allowOrigins ?? [];\n // Auto-inject this stack's admin + website hostnames on every stage so\n // callers don't have to predict them; `DEV_CORS_ALLOW_ORIGINS` still\n // only joins on non-prod.\n const autoOrigins = this.resolveAutoInjectedCorsOrigins();\n const mergedOrigins = Array.from(\n new Set([\n ...callerOrigins,\n ...autoOrigins,\n ...(isNonProd ? DEV_CORS_ALLOW_ORIGINS : []),\n ]),\n );\n // CORS is always configured: `autoOrigins` is non-empty on every stage,\n // so the admin SPA has a working preflight without the caller passing\n // any `corsPreflight` block.\n const corsPreflight = this.buildCorsPreflightOptions(mergedOrigins, cors);\n const rootHttpApi = new RootHttpApi(this, {\n ...restRootHttpApiProps,\n corsPreflight,\n defaultDomainMapping: {\n domainName,\n mappingKey: undefined,\n },\n defaultAuthorizer: cognitoAuthorizer,\n });\n new DiscoverableStringParameter(this, \"http-api-url-param\", {\n ssmParamName: RootHttpApi.SSM_PARAM_NAME,\n stringValue: rootHttpApi.httpApiId,\n description:\n \"API Gateway HTTP API ID for this REST API stack (cross-stack reference)\",\n });\n return rootHttpApi;\n }\n\n /**\n * Returns the admin-console and marketing-website origins this REST API\n * stack should accept by default, composed from the same branch context\n * the website service will see at synth time. Both hostnames are\n * `https://`-only — they always resolve to real DNS records.\n *\n * The stage's `additionalTrustedClientOrigins` config entries (e.g. on-site\n * customer SPA hosts) are appended verbatim — both `http://` and `https://`\n * entries flow into CORS. Scheme filtering is OAuth-specific and happens\n * in `OpenHiAuthService.resolveOAuthRedirectUrls`.\n *\n * Auto-injected on every stage (no `isNonProd` gate) so the admin SPA can\n * call the API cross-origin without the caller having to predict the\n * per-deploy hostname. Override to customize the auto-injected set.\n */\n protected resolveAutoInjectedCorsOrigins(): ReadonlyArray<string> {\n const zoneName = this.props.config!.zoneName!;\n const adminHost = OpenHiWebsiteService.composeFullDomain({\n domainPrefix: ADMIN_DOMAIN_PREFIX,\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n const websiteHost = OpenHiWebsiteService.composeFullDomain({\n branchName: this.branchName,\n defaultReleaseBranch: this.defaultReleaseBranch,\n childZonePrefix: this.childZonePrefix,\n zoneName,\n });\n const stageType = this.ohEnv.ohStage.stageType;\n const additional =\n this.ohEnv.ohStage.ohApp.config.deploymentTargets?.[stageType]\n ?.additionalTrustedClientOrigins ?? [];\n return [`https://${adminHost}`, `https://${websiteHost}`, ...additional];\n }\n\n /**\n * Builds the full `CorsPreflightOptions` from a merged origins array,\n * filling defaults for `allowMethods`/`allowHeaders`/`allowCredentials`/\n * `maxAge` from the caller-supplied block when present.\n */\n protected buildCorsPreflightOptions(\n allowOrigins: ReadonlyArray<string>,\n cors: CorsPreflightOptions | undefined,\n ): CorsPreflightOptions {\n return {\n allowOrigins: [...allowOrigins],\n allowMethods: cors?.allowMethods ?? [\n CorsHttpMethod.GET,\n CorsHttpMethod.HEAD,\n CorsHttpMethod.POST,\n CorsHttpMethod.PUT,\n CorsHttpMethod.PATCH,\n CorsHttpMethod.DELETE,\n CorsHttpMethod.OPTIONS,\n ],\n allowHeaders: cors?.allowHeaders ?? [\"Content-Type\", \"Authorization\"],\n allowCredentials: cors?.allowCredentials ?? true,\n maxAge: cors?.maxAge ?? Duration.days(1),\n ...(cors?.exposeHeaders !== undefined && {\n exposeHeaders: cors.exposeHeaders,\n }),\n };\n }\n\n /**\n * Builds the `OPENHI_RUNTIME_CONFIG_*` env-var map the REST API Lambda\n * exposes through `GET /control/runtime-config`. The four values are\n * always populated — the three Cognito IDs are resolved via SSM lookups\n * against the auth stack from a dedicated sub-scope (`runtime-config`)\n * so they don't collide with the user-pool / user-pool-client constructs\n * already created in {@link createRootHttpApi}, and `apiBaseUrl` is\n * derived from this stack's own custom domain. The OAuth callback URL\n * is no longer plumbed through the API — the admin-console derives it\n * client-side from `window.location.origin`.\n */\n protected resolveRuntimeConfigEnvVars(): Record<string, string> {\n const cognitoScope = new Construct(this, \"runtime-config\");\n const userPool = OpenHiAuthService.userPoolFromConstruct(cognitoScope);\n const userPoolClient =\n OpenHiAuthService.userPoolClientFromConstruct(cognitoScope);\n const cognitoDomainUrl =\n OpenHiAuthService.userPoolDomainBaseUrlFromConstruct(cognitoScope);\n return {\n OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_ID: userPool.userPoolId,\n OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_CLIENT_ID:\n userPoolClient.userPoolClientId,\n OPENHI_RUNTIME_CONFIG_COGNITO_DOMAIN_URL: cognitoDomainUrl,\n OPENHI_RUNTIME_CONFIG_API_BASE_URL: `https://${this.apiDomainName}`,\n };\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * Dedicated Lambda for CORS preflight (OPTIONS) requests. Returns 204 so API Gateway\n * can add CORS headers from the API's corsPreflight config. Low memory footprint.\n * @see #694\n */\n\nconst HANDLER_NAME = \"cors-options-lambda.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\nexport class CorsOptionsLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, id: string = \"cors-options-lambda\") {\n super(scope, id);\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 128,\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/lambda/rest-api-lambda.md\n */\n\nconst HANDLER_NAME = \"rest-api-lambda.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n const fromLib = path.join(dirname, \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n return fromLib;\n}\n\nexport interface RestApiLambdaProps {\n /**\n * DynamoDB table name for the data store. The Lambda receives it as the\n * environment variable DYNAMO_TABLE_NAME at runtime.\n */\n readonly dynamoTableName: string;\n\n /**\n * Branch name from the service. Passed as BRANCH_TAG_VALUE at runtime.\n */\n readonly branchTagValue: string;\n\n /**\n * SSM parameter name for the HTTP API (e.g. RootHttpApi.SSM_PARAM_NAME).\n * Passed as HTTP_API_TAG_VALUE at runtime.\n */\n readonly httpApiTagValue: string;\n\n /**\n * Aurora cluster ARN published by `DataStorePostgresReplica`. Passed as\n * `OPENHI_PG_CLUSTER_ARN` at runtime so the Lambda can target the cluster\n * via the RDS Data API (ADR 2026-04-17-01, phase 2).\n */\n readonly postgresClusterArn: string;\n\n /**\n * Secrets Manager ARN with the cluster credentials. Passed as\n * `OPENHI_PG_SECRET_ARN` at runtime; consumed by the RDS Data API client.\n */\n readonly postgresSecretArn: string;\n\n /**\n * Database name on the cluster. Passed as `OPENHI_PG_DATABASE` at runtime.\n */\n readonly postgresDatabase: string;\n\n /**\n * Per-branch schema name on the cluster (e.g. `b_<branchHash>`). Passed as\n * `OPENHI_PG_SCHEMA` at runtime.\n */\n readonly postgresSchema: string;\n\n /**\n * Additional environment variables to set on the Lambda. Each entry is\n * passed through verbatim — the key becomes the env-var name and the value\n * its string contents. Used by the runtime-config plumbing to expose\n * Cognito IDs etc. to the public `/control/runtime-config` route.\n */\n readonly extraEnvironment?: Record<string, string>;\n}\n\nexport class RestApiLambda extends Construct {\n public readonly lambda: NodejsFunction;\n\n constructor(scope: Construct, props: RestApiLambdaProps) {\n super(scope, \"rest-api-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n DYNAMO_TABLE_NAME: props.dynamoTableName,\n BRANCH_TAG_VALUE: props.branchTagValue,\n HTTP_API_TAG_VALUE: props.httpApiTagValue,\n OPENHI_PG_CLUSTER_ARN: props.postgresClusterArn,\n OPENHI_PG_SECRET_ARN: props.postgresSecretArn,\n OPENHI_PG_DATABASE: props.postgresDatabase,\n OPENHI_PG_SCHEMA: props.postgresSchema,\n ...props.extraEnvironment,\n },\n bundling: {\n minify: true,\n sourceMap: false,\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { LambdaFunction } from \"aws-cdk-lib/aws-events-targets\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport {\n PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE,\n USER_ONBOARDING_EVENT_SOURCE,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/user-onboarding/provision-default-workspace-lambda.md\n */\n\nconst HANDLER_NAME = \"provision-default-workspace.handler.js\";\n\n/**\n * Resolve Lambda entry so it works when running from src/ (tests) or from lib/ (built).\n */\nfunction resolveHandlerEntry(dirname: string): string {\n const sameDir = path.join(dirname, HANDLER_NAME);\n if (fs.existsSync(sameDir)) {\n return sameDir;\n }\n\n return path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", HANDLER_NAME);\n}\n\nexport interface ProvisionDefaultWorkspaceLambdaProps {\n /**\n * DynamoDB data store table. Used for the Lambda's `DYNAMO_TABLE_NAME`\n * env var and for granting the Lambda the writes + GSI queries it needs\n * to provision default control-plane resources.\n */\n readonly dataStoreTable: ITable;\n\n /**\n * Control-plane event bus that the EventBridge Rule listens on.\n */\n readonly controlEventBus: IEventBus;\n}\n\n/**\n * Lambda used by the user-onboarding workflow to create a user's default\n * Tenant, Workspace, Memberships, and RoleAssignment.\n *\n * Owns the EventBridge Rule that routes the default-workspace onboarding\n * event to itself, and the IAM permissions it needs on the data store\n * table — colocating routing + permissions with the function they target.\n */\nexport class ProvisionDefaultWorkspaceLambda extends Construct {\n public readonly lambda: NodejsFunction;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: ProvisionDefaultWorkspaceLambdaProps) {\n super(scope, \"provision-default-workspace-lambda\");\n\n this.lambda = new NodejsFunction(this, \"handler\", {\n entry: resolveHandlerEntry(__dirname),\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 1024,\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n\n // Grant table writes for default resources and User repair.\n props.dataStoreTable.grant(\n this.lambda,\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n );\n\n // Table.grant(\"dynamodb:Query\") only covers the table itself, not its\n // GSIs, so an explicit policy is required to query GSI2 by cognitoSub.\n this.lambda.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"dynamodb:Query\"],\n resources: [`${props.dataStoreTable.tableArn}/index/*`],\n }),\n );\n\n // Route only the default-workspace onboarding event to this worker.\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.controlEventBus,\n eventPattern: {\n source: [USER_ONBOARDING_EVENT_SOURCE],\n detailType: [PROVISION_DEFAULT_WORKSPACE_DETAIL_TYPE],\n },\n targets: [\n new LambdaFunction(this.lambda, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Construct } from \"constructs\";\nimport { ProvisionDefaultWorkspaceLambda } from \"./provision-default-workspace-lambda\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/user-onboarding/user-onboarding-workflow.md\n */\n\nexport interface UserOnboardingWorkflowProps {\n readonly controlEventBus: IEventBus;\n readonly dataStoreTable: ITable;\n}\n\n/**\n * Control-plane workflow for onboarding users after Cognito confirmation.\n */\nexport class UserOnboardingWorkflow extends Construct {\n public readonly provisionDefaultWorkspace: ProvisionDefaultWorkspaceLambda;\n\n constructor(scope: Construct, props: UserOnboardingWorkflowProps) {\n super(scope, \"user-onboarding-workflow\");\n\n this.provisionDefaultWorkspace = new ProvisionDefaultWorkspaceLambda(this, {\n dataStoreTable: props.dataStoreTable,\n controlEventBus: props.controlEventBus,\n });\n }\n}\n","import {\n AuthorizationType,\n IGraphqlApi,\n UserPoolDefaultAction,\n} from \"aws-cdk-lib/aws-appsync\";\nimport { Construct } from \"constructs\";\nimport { OpenHiAuthService } from \"./open-hi-auth-service\";\nimport { OpenHiEnvironment } from \"../app/open-hi-environment\";\nimport {\n OpenHiService,\n OpenHiServiceProps,\n OpenHiServiceType,\n} from \"../app/open-hi-service\";\nimport { RootGraphqlApi } from \"../components/app-sync/root-graphql-api\";\n\nexport interface OpenHiGraphqlServiceProps extends OpenHiServiceProps {}\n\n/**\n * GraphQL API service stack: creates the AppSync API via {@link RootGraphqlApi}\n * and exports its ID via SSM. Look up from other stacks via\n * {@link OpenHiGraphqlService.graphqlApiFromConstruct}.\n */\nexport class OpenHiGraphqlService extends OpenHiService {\n static readonly SERVICE_TYPE =\n \"graphql-api\" as const satisfies OpenHiServiceType;\n\n /**\n * Returns the GraphQL API by looking up the GraphQL stack's API ID from SSM.\n * Use from other stacks to obtain an IGraphqlApi reference.\n */\n static graphqlApiFromConstruct(scope: Construct): IGraphqlApi {\n return RootGraphqlApi.fromConstruct(scope);\n }\n\n get serviceType(): string {\n return OpenHiGraphqlService.SERVICE_TYPE;\n }\n\n /* Override so this.props is typed with this service's options */\n public override props: OpenHiGraphqlServiceProps;\n\n public readonly rootGraphqlApi: RootGraphqlApi;\n\n constructor(ohEnv: OpenHiEnvironment, props: OpenHiGraphqlServiceProps = {}) {\n super(ohEnv, OpenHiGraphqlService.SERVICE_TYPE, props);\n this.props = props;\n this.rootGraphqlApi = this.createRootGraphqlApi();\n }\n\n /** Creates the root GraphQL API with Cognito user pool. */\n protected createRootGraphqlApi(): RootGraphqlApi {\n const userPool = OpenHiAuthService.userPoolFromConstruct(this);\n return new RootGraphqlApi(this, {\n authorizationConfig: {\n defaultAuthorization: {\n authorizationType: AuthorizationType.USER_POOL,\n userPoolConfig: {\n userPool,\n defaultAction: UserPoolDefaultAction.ALLOW,\n },\n },\n },\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport { OWNING_DELETE_OPS_EVENT_BUS_ENV_VAR } from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-lambdas.md\n */\n\ninterface ResolvedHandler {\n readonly entry: string;\n readonly handler: string;\n}\n\n/**\n * Resolve a Lambda entry under `src/` (tests) or `lib/` (compiled\n * bundle). Mirrors the dual-path lookup the seed-* lambdas use so the\n * same code path runs in unit tests, CDK snapshot tests, and prod.\n */\nfunction resolveHandlerEntry(\n dirname: string,\n handlerName: string,\n): ResolvedHandler {\n const sameDir = path.join(dirname, handlerName);\n if (fs.existsSync(sameDir)) {\n return { entry: sameDir, handler: \"handler\" };\n }\n const libDir = path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", handlerName);\n return { entry: libDir, handler: \"handler\" };\n}\n\nexport interface OwningDeleteCascadeLambdasProps {\n /** Data-store table the cascade reads (Query) and writes (DeleteItem / TransactWriteItems) against. */\n readonly dataStoreTable: ITable;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n}\n\n/**\n * The three Lambdas that power the TR-022 owning-entity hard-delete\n * cascade state machine. Bundled together because the state machine\n * wires them in a fixed topology and they share the same data-store\n * grant pattern.\n *\n * - `listChunks` — pages through the owner's adjacency-list partition\n * via ElectroDB Query, splits the page into \\<=100-item chunks for\n * the inline Map state.\n * - `deleteChunk` — Map-iteration handler; submits one chunk as a\n * single `TransactWriteItems` via `executeMultiWrite`. The state\n * machine's `MaxConcurrency = 8` runs up to eight of these in\n * parallel.\n * - `finalize` — deletes the owning canonical record at\n * `SK = \"CURRENT\"` conditional on `lifecycleState = \"deleting\"`,\n * then emits the `control-plane.owning-delete-complete.v1` terminal\n * event on the ops event bus.\n *\n * IAM grants are scoped per-Lambda: the read/write Lambdas get\n * table-level Query / TransactWriteItems on the data store; the\n * finalize Lambda gets a focused `DeleteItem` + `PutEvents` policy\n * (no Query, no broad writes).\n */\nexport class OwningDeleteCascadeLambdas extends Construct {\n public readonly listChunks: NodejsFunction;\n public readonly deleteChunk: NodejsFunction;\n public readonly finalize: NodejsFunction;\n\n constructor(scope: Construct, props: OwningDeleteCascadeLambdasProps) {\n super(scope, \"owning-delete-cascade-lambdas\");\n\n const listResolved = resolveHandlerEntry(\n __dirname,\n \"list-chunks.handler.js\",\n );\n this.listChunks = new NodejsFunction(this, \"list-chunks-handler\", {\n entry: listResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n props.dataStoreTable.grant(this.listChunks, \"dynamodb:Query\");\n\n const deleteResolved = resolveHandlerEntry(\n __dirname,\n \"delete-chunk.handler.js\",\n );\n this.deleteChunk = new NodejsFunction(this, \"delete-chunk-handler\", {\n entry: deleteResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n // TransactWriteItems requires both the resource-level action and\n // (implicitly) DeleteItem on the items it touches. AWS treats\n // `dynamodb:DeleteItem` as the per-item permission inside a\n // TransactWriteItems delete, so grant both.\n props.dataStoreTable.grant(\n this.deleteChunk,\n \"dynamodb:DeleteItem\",\n \"dynamodb:UpdateItem\",\n \"dynamodb:PutItem\",\n );\n\n const finalizeResolved = resolveHandlerEntry(\n __dirname,\n \"finalize.handler.js\",\n );\n this.finalize = new NodejsFunction(this, \"finalize-handler\", {\n entry: finalizeResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n [OWNING_DELETE_OPS_EVENT_BUS_ENV_VAR]: props.opsEventBus.eventBusName,\n },\n });\n props.dataStoreTable.grant(this.finalize, \"dynamodb:DeleteItem\");\n this.finalize.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"events:PutEvents\"],\n resources: [props.opsEventBus.eventBusArn],\n }),\n );\n }\n}\n","import { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { SfnStateMachine } from \"aws-cdk-lib/aws-events-targets\";\nimport {\n Choice,\n Condition,\n CustomState,\n DefinitionBody,\n Pass,\n StateMachine,\n Succeed,\n TaskInput,\n Wait,\n WaitTime,\n} from \"aws-cdk-lib/aws-stepfunctions\";\nimport { LambdaInvoke } from \"aws-cdk-lib/aws-stepfunctions-tasks\";\nimport { Construct } from \"constructs\";\nimport {\n ControlPlaneOwningDeleteV1,\n OPENHI_DATA_SOURCE,\n OWNING_DELETE_CASCADE_DEFAULT_CONCURRENCY,\n} from \"./events\";\nimport { OwningDeleteCascadeLambdas } from \"./owning-delete-cascade-lambdas\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-workflow.md\n */\n\nexport interface OwningDeleteCascadeWorkflowProps {\n /**\n * Data event bus carrying `control-plane.owning-delete.v1`. The\n * workflow's EventBridge rule lives on this bus and starts a state\n * machine execution per matching event.\n */\n readonly dataEventBus: IEventBus;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n /** Data-store table the cascade reads from / writes deletes into. */\n readonly dataStoreTable: ITable;\n /**\n * Inline-Map max concurrency. Defaults to\n * {@link OWNING_DELETE_CASCADE_DEFAULT_CONCURRENCY} (8) per the\n * ADR-018 implementation guide section 4 — tunable per environment.\n * NOTE: this is an **inline** Map (NOT Distributed Map — TR-022\n * Choice 2A pins inline; TR-023 uses Distributed for renames).\n */\n readonly cascadeMapConcurrency?: number;\n}\n\n/**\n * Control-plane workflow that fans out the TR-022 owning-entity\n * hard-delete cascade.\n *\n * Pipeline (per the ADR-018 implementation guide section 4):\n *\n * 1. Synchronous API entry point flips the canonical owning record's\n * `lifecycleState: active -> deleting`. (Owned by the REST adapter,\n * not this construct.)\n * 2. DynamoDB stream / Firehose transform publishes\n * `control-plane.owning-delete.v1` on the data event bus.\n * 3. EventBridge rule (owned here) starts this state machine.\n * 4. State machine outer loop:\n * - `ListChunks` Lambda pages through the owner's adjacency-list\n * partition and emits chunks of up to 100 projection rows.\n * - `RewriteChunks` inline Map (MaxConcurrency = 8) deletes each\n * chunk in parallel via `executeMultiWrite`.\n * - `IsExhausted` Choice loops back to `ListChunks` until the page\n * query returns zero items and every per-entity cursor is `null`.\n * 5. `Finalize` Lambda deletes the canonical owning record\n * (conditional on `lifecycleState = \"deleting\"`) and emits\n * `control-plane.owning-delete-complete.v1` on the ops event bus.\n *\n * Idempotency: every Map iteration uses a per-chunk `ClientRequestToken`,\n * and the finalize step's canonical delete is conditional on\n * `lifecycleState = \"deleting\"`. A replayed execution finds no rows\n * to delete, no canonical to remove, and emits no terminal event.\n */\nexport class OwningDeleteCascadeWorkflow extends Construct {\n public readonly lambdas: OwningDeleteCascadeLambdas;\n public readonly stateMachine: StateMachine;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: OwningDeleteCascadeWorkflowProps) {\n super(scope, \"owning-delete-cascade-workflow\");\n\n this.lambdas = new OwningDeleteCascadeLambdas(this, {\n dataStoreTable: props.dataStoreTable,\n opsEventBus: props.opsEventBus,\n });\n\n const concurrency =\n props.cascadeMapConcurrency ?? OWNING_DELETE_CASCADE_DEFAULT_CONCURRENCY;\n\n // Step 1: Initialize cascade state from the EventBridge envelope.\n // Pulls `ownerType`, `ownerId`, `tenantId` from the workflow event\n // payload and seeds cursors / accumulators.\n const initState = new Pass(this, \"init-state\", {\n parameters: {\n \"ownerType.$\": \"$.detail.payload.ownerType\",\n \"ownerId.$\": \"$.detail.payload.ownerId\",\n \"tenantId.$\": \"$.detail.payload.tenantId\",\n cursors: {},\n projectionsRemoved: 0,\n chunkCount: 0,\n // Used by the finalize step to compute `durationMs`.\n \"startedAt.$\": \"$$.Execution.StartTime\",\n // Propagate envelope identity for ADR-016 causation chaining.\n \"eventId.$\": \"$.detail.eventId\",\n \"correlationId.$\": \"$.detail.correlationId\",\n \"causationId.$\": \"$.detail.eventId\",\n },\n });\n\n // Step 2: Query a page of child projection rows + split into chunks.\n // `payload` defaults to the entire task input (`$`), so the\n // listChunks handler receives the cascade state verbatim.\n const listChunks = new LambdaInvoke(this, \"list-chunks\", {\n lambdaFunction: this.lambdas.listChunks,\n resultPath: \"$.listResult\",\n retryOnServiceExceptions: true,\n });\n\n // Reconstitute the cascade state from the page response. Carry\n // every field forward so the loop's next iteration has the\n // accumulator + envelope identity intact.\n const updateAfterList = new Pass(this, \"update-after-list\", {\n parameters: {\n \"ownerType.$\": \"$.ownerType\",\n \"ownerId.$\": \"$.ownerId\",\n \"tenantId.$\": \"$.tenantId\",\n \"cursors.$\": \"$.listResult.Payload.cursors\",\n \"projectionsRemoved.$\": \"$.listResult.Payload.projectionsRemoved\",\n \"chunkCount.$\": \"$.listResult.Payload.chunkCount\",\n \"exhausted.$\": \"$.listResult.Payload.exhausted\",\n \"chunks.$\": \"$.listResult.Payload.chunks\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n },\n });\n\n // Step 3: Inline Map state — fan out chunk deletes.\n //\n // Step Functions L2 `Map` exists, but the L2 + `LambdaInvoke`\n // task-state combination does not surface inline-Map's `Retry` /\n // `Catch` blocks the implementation guide specifies. Drop to an\n // ASL `CustomState` so the retry / catch shapes match the guide\n // exactly. The state is INLINE (not Distributed) per TR-022\n // Choice 2A — `Mode` is omitted (inline is the default); the\n // sibling rename cascade (TR-023) is the one that uses\n // `Mode: DISTRIBUTED`.\n const rewriteChunks = new CustomState(this, \"rewrite-chunks\", {\n stateJson: {\n Type: \"Map\",\n ItemsPath: \"$.chunks\",\n MaxConcurrency: concurrency,\n ResultPath: \"$.rewriteResults\",\n ItemSelector: {\n // The handler receives `CascadeChunkInput` verbatim from the\n // `listChunks` output, no additional wrapping.\n \"ownerType.$\": \"$$.Map.Item.Value.ownerType\",\n \"ownerId.$\": \"$$.Map.Item.Value.ownerId\",\n \"tenantId.$\": \"$$.Map.Item.Value.tenantId\",\n \"rows.$\": \"$$.Map.Item.Value.rows\",\n \"chunkToken.$\": \"$$.Map.Item.Value.chunkToken\",\n },\n ItemProcessor: {\n ProcessorConfig: {\n // Inline mode (NOT Distributed). Per TR-022 Choice 2A.\n Mode: \"INLINE\",\n },\n StartAt: \"DeleteChunk\",\n States: {\n DeleteChunk: {\n Type: \"Task\",\n Resource: \"arn:aws:states:::lambda:invoke\",\n Parameters: {\n FunctionName: this.lambdas.deleteChunk.functionArn,\n \"Payload.$\": \"$\",\n },\n Retry: [\n {\n ErrorEquals: [\n \"DynamoDB.ProvisionedThroughputExceededException\",\n \"DynamoDB.ThrottlingException\",\n \"DynamoDB.TransactionConflictException\",\n \"Lambda.ServiceException\",\n \"Lambda.AWSLambdaException\",\n \"Lambda.SdkClientException\",\n ],\n IntervalSeconds: 1,\n MaxAttempts: 3,\n BackoffRate: 2.0,\n MaxDelaySeconds: 30,\n },\n ],\n Catch: [\n {\n // Replay path: the rows in this chunk are already\n // gone. Treat as a no-op success so the outer loop\n // keeps draining the partition.\n ErrorEquals: [\"DynamoDB.TransactionCanceledException\"],\n Next: \"ChunkAlreadyDeleted\",\n },\n ],\n End: true,\n },\n ChunkAlreadyDeleted: {\n Type: \"Succeed\",\n },\n },\n },\n },\n });\n\n // Outer loop control: a small wait between pages so retries do\n // not hammer DynamoDB on a partial throttle.\n const interPageWait = new Wait(this, \"inter-page-wait\", {\n time: WaitTime.duration(Duration.seconds(0)),\n });\n\n // Step 4: Choice — keep draining until the per-entity cursors all\n // return null AND the page is empty.\n const isExhausted = new Choice(this, \"is-exhausted\");\n\n // Step 5: Finalize — delete the canonical owning row and emit the\n // terminal event.\n const finalize = new LambdaInvoke(this, \"finalize\", {\n lambdaFunction: this.lambdas.finalize,\n payload: TaskInput.fromObject({\n \"ownerType.$\": \"$.ownerType\",\n \"ownerId.$\": \"$.ownerId\",\n \"tenantId.$\": \"$.tenantId\",\n \"projectionsRemoved.$\": \"$.projectionsRemoved\",\n \"chunkCount.$\": \"$.chunkCount\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n }),\n resultPath: \"$.finalizeResult\",\n retryOnServiceExceptions: true,\n });\n\n const success = new Succeed(this, \"success\");\n\n // Wire the state machine topology:\n // InitState\n // -> ListChunks\n // -> UpdateAfterList\n // -> RewriteChunks (inline Map, MaxConcurrency = 8)\n // -> InterPageWait\n // -> IsExhausted\n // ├── (exhausted) -> Finalize -> Success\n // └── (more) -> ListChunks (loop)\n const definition = initState\n .next(listChunks)\n .next(updateAfterList)\n .next(rewriteChunks)\n .next(interPageWait)\n .next(\n isExhausted\n .when(\n Condition.booleanEquals(\"$.exhausted\", true),\n finalize.next(success),\n )\n .otherwise(listChunks),\n );\n\n this.stateMachine = new StateMachine(this, \"state-machine\", {\n definitionBody: DefinitionBody.fromChainable(definition),\n // Long timeout because real-world cascades can run minutes when\n // a workspace has thousands of members. The stuck-cascade alarm\n // fires at 15 minutes; the state machine itself does not abort.\n timeout: Duration.hours(2),\n });\n\n // EventBridge rule — match the input detail-type on the data bus\n // and start the state machine for each event.\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.dataEventBus,\n eventPattern: {\n source: [OPENHI_DATA_SOURCE],\n detailType: [ControlPlaneOwningDeleteV1.detailType],\n },\n targets: [\n new SfnStateMachine(this.stateMachine, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus } from \"aws-cdk-lib/aws-events\";\nimport { Effect, PolicyStatement } from \"aws-cdk-lib/aws-iam\";\nimport { Runtime } from \"aws-cdk-lib/aws-lambda\";\nimport { NodejsFunction } from \"aws-cdk-lib/aws-lambda-nodejs\";\nimport { Construct } from \"constructs\";\nimport { RENAME_CASCADE_OPS_EVENT_BUS_ENV_VAR } from \"./events\";\n\ninterface ResolvedHandler {\n readonly entry: string;\n readonly handler: string;\n}\n\n/**\n * Resolve a Lambda entry under `src/` (tests) or `lib/` (compiled\n * bundle). Mirrors the dual-path lookup the sibling cascade lambdas use\n * so the same code path runs in unit tests, CDK snapshot tests, and prod.\n */\nfunction resolveHandlerEntry(\n dirname: string,\n handlerName: string,\n): ResolvedHandler {\n const sameDir = path.join(dirname, handlerName);\n if (fs.existsSync(sameDir)) {\n return { entry: sameDir, handler: \"handler\" };\n }\n const libDir = path.join(dirname, \"..\", \"..\", \"..\", \"..\", \"lib\", handlerName);\n return { entry: libDir, handler: \"handler\" };\n}\n\nexport interface RenameCascadeLambdasProps {\n /** Data-store table the cascade reads (Query) and writes (TransactWriteItems) against. */\n readonly dataStoreTable: ITable;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n}\n\n/**\n * The three Lambdas that power the TR-023 rename cascade state machine.\n *\n * - `listTargets` — pages through the affected projection partitions\n * for a rename and emits chunks of up to 50 rewrite targets.\n * - `rewriteChunk` — Distributed-Map iteration handler; submits one\n * chunk as a single `TransactWriteItems` via `executeMultiWrite`. The\n * state machine's `MaxConcurrency = 10` runs up to ten of these in\n * parallel.\n * - `finalize` — emits `control-plane.rename-complete.v1` on the ops\n * event bus.\n *\n * IAM grants are scoped per-Lambda: read/write Lambdas get table-level\n * Query / TransactWriteItems on the data store; the finalize Lambda\n * gets only `events:PutEvents` on the ops event bus.\n */\nexport class RenameCascadeLambdas extends Construct {\n public readonly listTargets: NodejsFunction;\n public readonly rewriteChunk: NodejsFunction;\n public readonly finalize: NodejsFunction;\n\n constructor(scope: Construct, props: RenameCascadeLambdasProps) {\n super(scope, \"rename-cascade-lambdas\");\n\n const listResolved = resolveHandlerEntry(\n __dirname,\n \"rename-list-targets.handler.js\",\n );\n this.listTargets = new NodejsFunction(this, \"list-targets-handler\", {\n entry: listResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n props.dataStoreTable.grant(this.listTargets, \"dynamodb:Query\");\n\n const rewriteResolved = resolveHandlerEntry(\n __dirname,\n \"rename-rewrite-chunk.handler.js\",\n );\n this.rewriteChunk = new NodejsFunction(this, \"rewrite-chunk-handler\", {\n entry: rewriteResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n DYNAMO_TABLE_NAME: props.dataStoreTable.tableName,\n },\n });\n // TransactWriteItems with delete + put pairs requires both per-item\n // permissions.\n props.dataStoreTable.grant(\n this.rewriteChunk,\n \"dynamodb:DeleteItem\",\n \"dynamodb:PutItem\",\n \"dynamodb:UpdateItem\",\n );\n\n const finalizeResolved = resolveHandlerEntry(\n __dirname,\n \"rename-finalize.handler.js\",\n );\n this.finalize = new NodejsFunction(this, \"finalize-handler\", {\n entry: finalizeResolved.entry,\n runtime: Runtime.NODEJS_LATEST,\n memorySize: 512,\n timeout: Duration.minutes(1),\n environment: {\n [RENAME_CASCADE_OPS_EVENT_BUS_ENV_VAR]: props.opsEventBus.eventBusName,\n },\n });\n this.finalize.addToRolePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n actions: [\"events:PutEvents\"],\n resources: [props.opsEventBus.eventBusArn],\n }),\n );\n }\n}\n","import { Duration } from \"aws-cdk-lib\";\nimport { ITable } from \"aws-cdk-lib/aws-dynamodb\";\nimport { IEventBus, Rule } from \"aws-cdk-lib/aws-events\";\nimport { SfnStateMachine } from \"aws-cdk-lib/aws-events-targets\";\nimport {\n Choice,\n Condition,\n CustomState,\n DefinitionBody,\n Pass,\n StateMachine,\n Succeed,\n TaskInput,\n} from \"aws-cdk-lib/aws-stepfunctions\";\nimport { LambdaInvoke } from \"aws-cdk-lib/aws-stepfunctions-tasks\";\nimport { Construct } from \"constructs\";\nimport {\n ControlPlaneRenameV1,\n OPENHI_DATA_SOURCE,\n RENAME_CASCADE_DEFAULT_CONCURRENCY,\n} from \"./events\";\nimport { RenameCascadeLambdas } from \"./rename-cascade-lambdas\";\n\nexport interface RenameCascadeWorkflowProps {\n /**\n * Data event bus carrying `control-plane.rename.v1`. The workflow's\n * EventBridge rule lives on this bus and starts a state machine\n * execution per matching event.\n */\n readonly dataEventBus: IEventBus;\n /** Ops event bus the cascade finalize step publishes terminal events onto. */\n readonly opsEventBus: IEventBus;\n /** Data-store table the cascade reads from / writes rewrites into. */\n readonly dataStoreTable: ITable;\n /**\n * Distributed-Map max concurrency. Defaults to\n * {@link RENAME_CASCADE_DEFAULT_CONCURRENCY} (10) per the ADR-018\n * implementation guide section 5 — tunable per environment.\n *\n * NOTE: this is a **Distributed** Map (NOT inline — TR-022's\n * owning-delete cascade uses inline; TR-023's rename cascade uses\n * Distributed).\n */\n readonly cascadeMapConcurrency?: number;\n}\n\n/**\n * Control-plane workflow that fans out the TR-023 rename cascade.\n *\n * Pipeline (per the ADR-018 implementation guide section 5):\n *\n * 1. The Firehose transform Lambda publishes\n * `control-plane.rename.v1` on the data event bus when it observes\n * a stream record showing a display-name change on a canonical\n * Tenant / User / Role row.\n * 2. EventBridge rule (owned here) starts this state machine.\n * 3. State machine outer loop:\n * - `ListTargets` Lambda pages through the affected projection\n * partitions for the renamed entity and emits chunks of up to 50\n * rewrite targets.\n * - `RewriteChunks` Distributed Map (MaxConcurrency = 10) rewrites\n * each chunk in parallel via `executeMultiWrite`. Each target\n * maps to either a `delete oldKey` + `put newItem` pair (SK\n * rewrite) or a single `put newItem` overwrite (attr-only update).\n * - `IsExhausted` Choice loops back to `ListTargets` until every\n * per-stream cursor returns `null`.\n * 4. `Finalize` Lambda emits `control-plane.rename-complete.v1` on the\n * ops event bus.\n *\n * Idempotency: every Map iteration uses a per-chunk `ClientRequestToken`,\n * and the state machine's `Catch` block absorbs\n * `DynamoDB.TransactionCanceledException` as a no-op success — a\n * replayed chunk where every row is already at the new SK fails its\n * delete-old triple and the helper rolls back; the cascade keeps\n * draining the page until the outer loop terminates on exhaustion.\n * Lost-race writes (per the TR-023 idempotency rule) are accepted —\n * the renaming write loses to a later concurrent write on the same row.\n */\nexport class RenameCascadeWorkflow extends Construct {\n public readonly lambdas: RenameCascadeLambdas;\n public readonly stateMachine: StateMachine;\n public readonly rule: Rule;\n\n constructor(scope: Construct, props: RenameCascadeWorkflowProps) {\n super(scope, \"rename-cascade-workflow\");\n\n this.lambdas = new RenameCascadeLambdas(this, {\n dataStoreTable: props.dataStoreTable,\n opsEventBus: props.opsEventBus,\n });\n\n const concurrency =\n props.cascadeMapConcurrency ?? RENAME_CASCADE_DEFAULT_CONCURRENCY;\n\n // Step 1: Initialize cascade state from the EventBridge envelope.\n // Pulls `entityType`, `entityId`, `tenantId`, name/normalizedName\n // from the workflow event payload and seeds cursors / accumulators.\n const initState = new Pass(this, \"init-state\", {\n parameters: {\n \"entityType.$\": \"$.detail.payload.entityType\",\n \"entityId.$\": \"$.detail.payload.entityId\",\n \"tenantId.$\": \"$.detail.payload.tenantId\",\n \"oldName.$\": \"$.detail.payload.oldName\",\n \"newName.$\": \"$.detail.payload.newName\",\n \"oldNormalizedName.$\": \"$.detail.payload.oldNormalizedName\",\n \"newNormalizedName.$\": \"$.detail.payload.newNormalizedName\",\n cursors: {},\n itemsRewritten: 0,\n chunkCount: 0,\n \"startedAt.$\": \"$$.Execution.StartTime\",\n \"eventId.$\": \"$.detail.eventId\",\n \"correlationId.$\": \"$.detail.correlationId\",\n \"causationId.$\": \"$.detail.eventId\",\n },\n });\n\n // Step 2: Query one page of targets + split into chunks.\n const listTargets = new LambdaInvoke(this, \"list-targets\", {\n lambdaFunction: this.lambdas.listTargets,\n resultPath: \"$.listResult\",\n retryOnServiceExceptions: true,\n });\n\n // Reconstitute the cascade state from the page response.\n const updateAfterList = new Pass(this, \"update-after-list\", {\n parameters: {\n \"entityType.$\": \"$.entityType\",\n \"entityId.$\": \"$.entityId\",\n \"tenantId.$\": \"$.tenantId\",\n \"oldName.$\": \"$.oldName\",\n \"newName.$\": \"$.newName\",\n \"oldNormalizedName.$\": \"$.oldNormalizedName\",\n \"newNormalizedName.$\": \"$.newNormalizedName\",\n \"cursors.$\": \"$.listResult.Payload.cursors\",\n \"itemsRewritten.$\": \"$.listResult.Payload.itemsRewritten\",\n \"chunkCount.$\": \"$.listResult.Payload.chunkCount\",\n \"exhausted.$\": \"$.listResult.Payload.exhausted\",\n \"chunks.$\": \"$.listResult.Payload.chunks\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n },\n });\n\n // Step 3: Distributed Map state — fan out chunk rewrites.\n //\n // Step Functions L2 `Map` does not surface Distributed-Map's\n // `ItemProcessor.ProcessorConfig.Mode: DISTRIBUTED` cleanly with\n // the L2 + `LambdaInvoke` combo. Drop to an ASL `CustomState` so\n // the shape matches the implementation guide exactly. The state\n // is DISTRIBUTED (per TR-023 — distinguishes the rename cascade\n // from TR-022's INLINE owning-delete cascade) with MaxConcurrency\n // = 10.\n const rewriteChunks = new CustomState(this, \"rewrite-chunks\", {\n stateJson: {\n Type: \"Map\",\n ItemsPath: \"$.chunks\",\n MaxConcurrency: concurrency,\n ResultPath: \"$.rewriteResults\",\n ItemSelector: {\n \"entityType.$\": \"$$.Map.Item.Value.entityType\",\n \"entityId.$\": \"$$.Map.Item.Value.entityId\",\n \"tenantId.$\": \"$$.Map.Item.Value.tenantId\",\n \"targets.$\": \"$$.Map.Item.Value.targets\",\n \"chunkToken.$\": \"$$.Map.Item.Value.chunkToken\",\n },\n ItemProcessor: {\n ProcessorConfig: {\n // DISTRIBUTED mode — per TR-023 (NOT INLINE; that is\n // TR-022's territory).\n Mode: \"DISTRIBUTED\",\n ExecutionType: \"STANDARD\",\n },\n StartAt: \"RewriteChunk\",\n States: {\n RewriteChunk: {\n Type: \"Task\",\n Resource: \"arn:aws:states:::lambda:invoke\",\n Parameters: {\n FunctionName: this.lambdas.rewriteChunk.functionArn,\n \"Payload.$\": \"$\",\n },\n Retry: [\n {\n ErrorEquals: [\n \"DynamoDB.ProvisionedThroughputExceededException\",\n \"DynamoDB.ThrottlingException\",\n \"DynamoDB.TransactionConflictException\",\n \"Lambda.ServiceException\",\n \"Lambda.AWSLambdaException\",\n \"Lambda.SdkClientException\",\n ],\n IntervalSeconds: 1,\n MaxAttempts: 5,\n BackoffRate: 2.0,\n MaxDelaySeconds: 30,\n },\n ],\n Catch: [\n {\n // Replay path: the rewrite race \"lost to a later\n // write\" per TR-023 idempotency. Treat as a no-op\n // success so the outer loop keeps draining the page.\n ErrorEquals: [\"DynamoDB.TransactionCanceledException\"],\n Next: \"ChunkAlreadyRewritten\",\n },\n ],\n End: true,\n },\n ChunkAlreadyRewritten: {\n Type: \"Succeed\",\n },\n },\n },\n },\n });\n\n // Step 4: Choice — keep draining until every per-stream cursor\n // returns null.\n const isExhausted = new Choice(this, \"is-exhausted\");\n\n // Step 5: Finalize — emit the terminal event.\n const finalize = new LambdaInvoke(this, \"finalize\", {\n lambdaFunction: this.lambdas.finalize,\n payload: TaskInput.fromObject({\n \"entityType.$\": \"$.entityType\",\n \"entityId.$\": \"$.entityId\",\n \"tenantId.$\": \"$.tenantId\",\n \"newName.$\": \"$.newName\",\n \"itemsRewritten.$\": \"$.itemsRewritten\",\n \"chunkCount.$\": \"$.chunkCount\",\n \"startedAt.$\": \"$.startedAt\",\n \"eventId.$\": \"$.eventId\",\n \"correlationId.$\": \"$.correlationId\",\n \"causationId.$\": \"$.causationId\",\n }),\n resultPath: \"$.finalizeResult\",\n retryOnServiceExceptions: true,\n });\n\n const success = new Succeed(this, \"success\");\n\n // Wire the state machine topology:\n // InitState\n // -> ListTargets\n // -> UpdateAfterList\n // -> RewriteChunks (Distributed Map, MaxConcurrency = 10)\n // -> IsExhausted\n // ├── (exhausted) -> Finalize -> Success\n // └── (more) -> ListTargets (loop)\n const definition = initState\n .next(listTargets)\n .next(updateAfterList)\n .next(rewriteChunks)\n .next(\n isExhausted\n .when(\n Condition.booleanEquals(\"$.exhausted\", true),\n finalize.next(success),\n )\n .otherwise(listTargets),\n );\n\n this.stateMachine = new StateMachine(this, \"state-machine\", {\n definitionBody: DefinitionBody.fromChainable(definition),\n // Long timeout — large renames may rewrite thousands of rows;\n // the `CascadeSlow` alarm fires at 300s p99 but the state\n // machine itself does not abort.\n timeout: Duration.hours(2),\n });\n\n // EventBridge rule — match the input detail-type on the data bus.\n this.rule = new Rule(this, \"rule\", {\n eventBus: props.dataEventBus,\n eventPattern: {\n source: [OPENHI_DATA_SOURCE],\n detailType: [ControlPlaneRenameV1.detailType],\n },\n targets: [\n new SfnStateMachine(this.stateMachine, {\n retryAttempts: 2,\n maxEventAge: Duration.hours(2),\n }),\n ],\n });\n }\n}\n"],"mappings":"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","names":["import_config","Stage","Stage","import_config","hashString","hashString","Tags","StringParameter","StringParameter","Tags","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","fs","path","RemovalPolicy","Tags","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","RemovalPolicy","Tags","NodejsFunction","Runtime","import_workflows","AttributeType","BillingMode","Table","Construct","Construct","Table","AttributeType","BillingMode","Duration","Stack","Duration","Stack","EventBus","EventBus","EventBus","EventBus","fs","path","Duration","Stack","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","Stack","NodejsFunction","Runtime","Duration","Duration","Construct","Distribution","ARecord","RecordTarget","CloudFrontTarget","Construct","fs","path","Duration","Runtime","NodejsFunction","Bucket","Construct","Construct","Duration","Bucket","NodejsFunction","Runtime","Construct","Distribution","ARecord","RecordTarget","CloudFrontTarget","paramCase","Construct","Construct","paramCase","import_config","UserPool","UserPoolClient","UserPoolDomain","Effect","PolicyStatement","Key","Stack","import_config","Table","Certificate","EventBus","HostedZone","StringParameter","Construct","fs","path","Duration","Stack","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","Stack","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Construct","HostedZone","StringParameter","Certificate","EventBus","fs","path","Duration","Stack","Rule","LambdaFunction","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Stack","Rule","LambdaFunction","Construct","Construct","fs","path","Duration","Stack","Rule","LambdaFunction","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Stack","Rule","LambdaFunction","Construct","Construct","Table","import_config","Bucket","import_config","HttpApi","Effect","PolicyStatement","ARecord","HostedZone","RecordTarget","Duration","Construct","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","fs","path","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","HttpApi","HostedZone","PolicyStatement","Effect","ARecord","RecordTarget","Duration","Construct","Bucket","fs","path","Duration","Rule","LambdaFunction","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","HANDLER_NAME","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","PolicyStatement","Effect","Rule","LambdaFunction","Duration","Construct","Construct","UserPool","UserPoolClient","UserPoolDomain","Stack","Key","PolicyStatement","Effect","fs","path","Duration","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Duration","Rule","Construct","Construct","Duration","Rule","fs","path","Duration","Effect","PolicyStatement","Runtime","NodejsFunction","Construct","resolveHandlerEntry","path","fs","Construct","NodejsFunction","Runtime","Duration","PolicyStatement","Effect","Duration","Rule","SfnStateMachine","Choice","Condition","CustomState","DefinitionBody","Pass","StateMachine","Succeed","TaskInput","LambdaInvoke","Construct","Construct","Pass","LambdaInvoke","CustomState","Choice","TaskInput","Succeed","Condition","StateMachine","DefinitionBody","Duration","Rule","SfnStateMachine"]}
|