@openhi/constructs 0.0.156 → 0.0.158
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/{chunk-ZVDVKCNC.mjs → chunk-BQMJSDOD.mjs} +12 -2
- package/lib/{chunk-ZVDVKCNC.mjs.map → chunk-BQMJSDOD.mjs.map} +1 -1
- package/lib/index.js +11 -1
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1 -1
- package/lib/seed-demo-data.handler.js +11 -1
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +1 -1
- package/package.json +3 -3
|
@@ -66,7 +66,17 @@ var DEV_USERS = [
|
|
|
66
66
|
{ id: "dev-drew", email: "drew@codedrifters.com" },
|
|
67
67
|
{ id: "dev-jessica", email: "jessica@codedrifters.com" },
|
|
68
68
|
{ id: "dev-jared", email: "jared@codedrifters.com" },
|
|
69
|
-
{ id: "dev-goddess", email: "goddess@codedrifters.com" }
|
|
69
|
+
{ id: "dev-goddess", email: "goddess@codedrifters.com" },
|
|
70
|
+
// Dedicated end-to-end test principal for admin-console Playwright
|
|
71
|
+
// specs (issue #1275). Reuses the standard DEV_USERS plumbing
|
|
72
|
+
// (Cognito user + DynamoDB User + per-tenant Memberships +
|
|
73
|
+
// tenant-admin RAs + platform-scoped system-admin RA) and the
|
|
74
|
+
// existing SSM-sourced password convention from #1249 — the
|
|
75
|
+
// operator provisions the SecureString at
|
|
76
|
+
// /openhi/seed/users/e2e-admin-console_at_codedrifters.com/password
|
|
77
|
+
// out of band, and the seeded Cognito user picks it up via
|
|
78
|
+
// AdminSetUserPassword on every seed run.
|
|
79
|
+
{ id: "dev-e2e-admin-console", email: "e2e-admin-console@codedrifters.com" }
|
|
70
80
|
];
|
|
71
81
|
var DEMO_TENANT_SPECS = [
|
|
72
82
|
{
|
|
@@ -1123,4 +1133,4 @@ export {
|
|
|
1123
1133
|
productionCognitoProvisioner,
|
|
1124
1134
|
handler
|
|
1125
1135
|
};
|
|
1126
|
-
//# sourceMappingURL=chunk-
|
|
1136
|
+
//# sourceMappingURL=chunk-BQMJSDOD.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/workflows/control-plane/seed-demo-data/seed-demo-data.handler.ts","../src/workflows/control-plane/seed-demo-data/events.ts","../src/workflows/control-plane/seed-demo-data/data-plane-fixtures.ts"],"sourcesContent":["import {\n AdminCreateUserCommand,\n AdminGetUserCommand,\n AdminSetUserPasswordCommand,\n CognitoIdentityProviderClient,\n UsernameExistsException,\n type AttributeType,\n} from \"@aws-sdk/client-cognito-identity-provider\";\nimport { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n GetParameterCommand,\n ParameterNotFound,\n SSMClient,\n} from \"@aws-sdk/client-ssm\";\nimport {\n PLATFORM_ROLE_CODE,\n PLATFORM_ROLE_CONCEPTS,\n PLATFORM_ROLE_IDS,\n extractSummary,\n type PlatformRoleCode,\n type FhirResourceLike,\n} from \"@openhi/types\";\nimport {\n parseWorkflowEvent,\n workflowDedupClient,\n type WorkflowDedupClient,\n} from \"@openhi/workflows\";\nimport type { EventBridgeEvent } from \"aws-lambda\";\nimport {\n DEMO_DATA_PLANE_FIXTURES,\n type DemoWorkspaceDataPlaneFixtures,\n} from \"./data-plane-fixtures\";\nimport {\n DEMO_PERIOD,\n DEMO_TENANT_SPECS,\n DEV_USERS,\n PLACEHOLDER_TENANT_ID,\n PLACEHOLDER_WORKSPACE_ID,\n PLATFORM_SCOPE_TENANT_ID,\n PlatformSystemDataSeededV1,\n SEED_DEMO_DATA_CONSUMER_NAME,\n demoMembershipId,\n demoRoleAssignmentId,\n demoRolesForUserInTenant,\n demoScenarioIdentifier,\n openhiResourceIdentifier,\n type DemoDevUser,\n} from \"./events\";\nimport { getDynamoControlService } from \"../../../data/dynamo/dynamo-control-service\";\nimport { NotFoundError } from \"../../../data/errors\";\nimport type { OpenHiContext } from \"../../../data/openhi-context\";\nimport { createMembershipOperation } from \"../../../data/operations/control/membership/membership-create-operation\";\nimport { getRoleByIdOperation } from \"../../../data/operations/control/role/role-get-by-id-operation\";\nimport { createRoleAssignmentOperation } from \"../../../data/operations/control/roleassignment/roleassignment-create-operation\";\nimport { createTenantOperation } from \"../../../data/operations/control/tenant/tenant-create-operation\";\nimport { createWorkspaceOperation } from \"../../../data/operations/control/workspace/workspace-create-operation\";\nimport { createAccountOperation } from \"../../../data/operations/data/account/account-create-operation\";\nimport { createEncounterOperation } from \"../../../data/operations/data/encounter/encounter-create-operation\";\nimport { createObservationOperation } from \"../../../data/operations/data/observation/observation-create-operation\";\nimport { createPatientOperation } from \"../../../data/operations/data/patient/patient-create-operation\";\nimport { createPractitionerOperation } from \"../../../data/operations/data/practitioner/practitioner-create-operation\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/seed-demo-data-handler.md\n *\n * EventBridge workflow handler invoked once per platform-deploy event\n * on the control event bus. Pre-flight verifies that\n * `seed-system-data` has already seeded the canonical Role records,\n * then idempotently re-asserts the demo-data graph: the placeholder\n * Tenant + Workspace, the three demo tenants + their workspaces, and\n * for each entry in {@link DEV_USERS} a Cognito user, a DynamoDB User,\n * 4 Memberships, 4 `tenant-admin` RoleAssignments, plus 1\n * platform-scoped `system-admin` RoleAssignment.\n */\n\ntype SeedDemoDataEvent = EventBridgeEvent<\n \"platform.system-data-seeded.v1\",\n unknown\n>;\n\n/** Env var the lambda construct injects with the Cognito User Pool ID. */\nexport const SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR =\n \"SEED_DEMO_DATA_USER_POOL_ID\";\n\n/** Inputs the handler needs to provision Cognito users for every dev. */\nexport interface CognitoProvisioner {\n /**\n * Ensure a Cognito user exists for `email`. Returns the user's\n * `sub`. Implementations MUST be idempotent: a second invocation\n * for the same email returns the existing user's sub without\n * resetting its password or touching any attribute.\n */\n readonly ensureUser: (email: string) => Promise<string>;\n}\n\n/**\n * Dependency seam for tests. The factory mirrors the production\n * arrangement: a real DynamoDB client + the real `workflowDedupClient`\n * factory bound to the `OPENHI_WORKFLOW_DEDUP_TABLE_NAME` env var the\n * construct injects via `grantConsumer`.\n */\nexport interface SeedDemoDataDependencies {\n readonly dedupClient: WorkflowDedupClient;\n /**\n * Reads every id in `PLATFORM_ROLE_IDS`. Throws when any Role\n * record is missing — that means `seed-system-data` has not yet\n * run on this environment, and emitting demo RoleAssignments that\n * reference non-existent Roles would produce orphaned records.\n */\n readonly verifyRoles: () => Promise<void>;\n /**\n * Upserts every Tenant + Workspace in {@link DEMO_TENANT_SPECS},\n * plus per-dev-user Cognito users, DynamoDB User records,\n * Memberships, and RoleAssignments.\n */\n readonly seedDemoGraph: (params: {\n readonly baseContext: OpenHiContext;\n readonly devUsers: ReadonlyArray<DemoDevUser>;\n readonly cognito: CognitoProvisioner;\n }) => Promise<void>;\n /** Cognito provisioner threaded into `seedDemoGraph`. */\n readonly cognito: CognitoProvisioner;\n}\n\nconst errorMessage = (err: unknown): string => {\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n};\n\n/**\n * Structured per-item failure record collected during {@link seedDemoGraph}.\n * Every individual upsert in Phases 1-3 is wrapped so a single item's\n * failure (transient AWS error, write-time constraint violation, etc.)\n * does not skip the rest of its phase. After all phases run, the\n * collected failures are aggregate-thrown so EventBridge still routes\n * the workflow into its failure-detection path (DLQ + CloudWatch\n * alarm) and the outer `runSeedDemoData` records `markFailed` on the\n * dedup row.\n *\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/index.md\n */\ntype SeedPhase = \"phase-1\" | \"phase-2\" | \"phase-3\";\n\ninterface SeedFailure {\n readonly phase: SeedPhase;\n /**\n * Short human label identifying where the failure happened\n * (e.g. tenant id, workspace id, user id). Used purely to enrich\n * the aggregate-throw message; no machine reads it.\n */\n readonly scope: string;\n readonly resourceType: string;\n readonly resourceId: string;\n readonly error: unknown;\n}\n\n/**\n * Wrap a single upsert call. On success, returns `true`. On failure,\n * pushes a structured {@link SeedFailure} onto `failures` and returns\n * `false` — the caller can use the boolean to decide whether to skip\n * downstream writes that strictly depend on this one. Most call sites\n * ignore the boolean (per-item isolation: the next item is attempted\n * regardless of this one's outcome).\n *\n * The wrapped function's return value is discarded; only the success/\n * failure signal is propagated. Callers that need the operation's\n * result (e.g. the `cognitoSub` from `cognito.ensureUser`) must hoist\n * the call out of `tryRun` and handle the failure inline.\n */\nconst tryRun = async (\n failures: Array<SeedFailure>,\n phase: SeedPhase,\n scope: string,\n resourceType: string,\n resourceId: string,\n fn: () => Promise<unknown>,\n): Promise<boolean> => {\n try {\n await fn();\n return true;\n } catch (err) {\n failures.push({ phase, scope, resourceType, resourceId, error: err });\n return false;\n }\n};\n\n/**\n * Format collected failures into a single Error suitable for the\n * outer `markFailed` reason and CloudWatch alarm payload. Lists every\n * failure as `<phase> <scope>/<resourceType>/<resourceId>: <message>`\n * joined by `; ` so a single CloudWatch line carries the full picture.\n */\nconst aggregateFailureError = (failures: ReadonlyArray<SeedFailure>): Error => {\n const summary = failures\n .map(\n (f) =>\n `${f.phase} ${f.scope}/${f.resourceType}/${f.resourceId}: ${errorMessage(\n f.error,\n )}`,\n )\n .join(\"; \");\n return new Error(\n `seed-demo-data: ${failures.length} item(s) failed across phases: ${summary}`,\n );\n};\n\n/**\n * Map a role code back to its canonical stable id from\n * `PLATFORM_ROLE_IDS`. Mirrors the lookup in\n * `seed-system-data.handler.ts` — the generator emits `_IDS` and\n * `_CONCEPTS` in lockstep, so a single SCREAMING_SNAKE key resolves\n * both projections.\n */\nconst idForRoleCode = (code: PlatformRoleCode): string => {\n for (const key of Object.keys(PLATFORM_ROLE_IDS) as Array<\n keyof typeof PLATFORM_ROLE_IDS\n >) {\n if (PLATFORM_ROLE_CONCEPTS[key].code === code) {\n return PLATFORM_ROLE_IDS[key];\n }\n }\n throw new Error(`No id mapping for role code \"${code}\".`);\n};\n\n/**\n * Pre-flight Role check. Iterates every id in\n * `PLATFORM_ROLE_IDS` and reads the record. If any read returns\n * not-found, this throws and `markFailed`'s the dedup row — the\n * replay tooling can retry once `seed-system-data` has populated the\n * Roles.\n */\nconst verifySystemRolesExist = async (): Promise<void> => {\n const probeContext: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: new Date(0).toISOString(),\n actorId: \"platform-deploy-bridge\",\n actorName: \"Platform Deploy Bridge\",\n actorType: \"internal-system\",\n source: \"step-function\",\n };\n for (const id of Object.values(PLATFORM_ROLE_IDS)) {\n try {\n await getRoleByIdOperation({ context: probeContext, id });\n } catch (err) {\n if (err instanceof NotFoundError) {\n throw new Error(\n `seed-demo-data pre-flight: control-plane Role \"${id}\" is missing. ` +\n \"Ensure seed-system-data has run on this environment before retrying.\",\n );\n }\n throw err;\n }\n }\n};\n\n/**\n * Build the resource body for one demo Tenant. Carries the two\n * identifiers per scope decision #6: the demo-scenario URN and the\n * canonical OpenHI resource URN (ADR 2026-03-12-01).\n */\nconst tenantResourceBody = (\n spec: (typeof DEMO_TENANT_SPECS)[number],\n): Record<string, unknown> => ({\n name: spec.tenantName,\n active: true,\n identifier: [\n demoScenarioIdentifier(spec.scenario, \"tenant\"),\n openhiResourceIdentifier({\n tenantId: spec.tenantId,\n workspaceId: \"\",\n resourceType: \"Tenant\",\n id: spec.tenantId,\n }),\n ],\n});\n\nconst workspaceResourceBody = (\n spec: (typeof DEMO_TENANT_SPECS)[number],\n workspace: (typeof DEMO_TENANT_SPECS)[number][\"workspaces\"][number],\n): Record<string, unknown> => ({\n name: workspace.name,\n active: true,\n identifier: [\n demoScenarioIdentifier(spec.scenario, workspace.roleSuffix),\n openhiResourceIdentifier({\n tenantId: spec.tenantId,\n workspaceId: \"\",\n resourceType: \"Workspace\",\n id: workspace.id,\n }),\n ],\n tenant: { reference: `Tenant/${spec.tenantId}`, type: \"Tenant\" },\n});\n\nconst membershipResourceBody = (\n spec: (typeof DEMO_TENANT_SPECS)[number],\n user: DemoDevUser,\n membershipId: string,\n): Record<string, unknown> => ({\n identifier: [\n demoScenarioIdentifier(spec.scenario, `membership-${user.id}`),\n openhiResourceIdentifier({\n tenantId: spec.tenantId,\n workspaceId: \"\",\n resourceType: \"Membership\",\n id: membershipId,\n }),\n ],\n user: { reference: `User/${user.id}`, type: \"User\" },\n tenant: { reference: `Tenant/${spec.tenantId}`, type: \"Tenant\" },\n period: DEMO_PERIOD,\n});\n\nconst roleAssignmentResourceBody = (\n scenario: string,\n tenantId: string,\n user: DemoDevUser,\n roleCode: PlatformRoleCode,\n roleAssignmentId: string,\n): Record<string, unknown> => ({\n identifier: [\n demoScenarioIdentifier(scenario, `roleassignment-${user.id}-${roleCode}`),\n openhiResourceIdentifier({\n tenantId,\n workspaceId: \"\",\n resourceType: \"RoleAssignment\",\n id: roleAssignmentId,\n }),\n ],\n user: { reference: `User/${user.id}`, type: \"User\" },\n role: { reference: `Role/${idForRoleCode(roleCode)}`, type: \"Role\" },\n tenant: { reference: `Tenant/${tenantId}`, type: \"Tenant\" },\n period: DEMO_PERIOD,\n});\n\n/** Build the FHIR User resource body for a seeded dev user. */\nconst userResourceBody = (\n user: DemoDevUser,\n cognitoSub: string,\n): Record<string, unknown> => ({\n resourceType: \"User\",\n id: user.id,\n name: [{ text: user.email }],\n status: \"active\",\n cognitoSub,\n currentTenant: { reference: `Tenant/${PLACEHOLDER_TENANT_ID}` },\n currentWorkspace: { reference: `Workspace/${PLACEHOLDER_WORKSPACE_ID}` },\n});\n\n/**\n * Idempotent User upsert. The User entity is non-tenant-isolated, so\n * the put is unconditional — the entity's PK is `USER#ID#<id>` and\n * supplying the same id re-asserts the same row.\n */\nconst upsertUser = async (\n context: OpenHiContext,\n user: DemoDevUser,\n cognitoSub: string,\n): Promise<void> => {\n const service = getDynamoControlService();\n const resource = userResourceBody(user, cognitoSub);\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n await service.entities.user\n .put({\n id: user.id,\n cognitoSub,\n resource: JSON.stringify(resource),\n summary,\n vid: \"1\",\n lastUpdated: context.date ?? new Date().toISOString(),\n })\n .go();\n};\n\n/**\n * Phase 3 helper: upsert one workspace's data-plane FHIR resources\n * (Patient/Practitioner/Observation/Encounter/Account). Parents are\n * written before children that reference them so direct `Resource/<id>`\n * references resolve on read paths that follow them — Patient first,\n * then Practitioner, then Observation/Encounter/Account which may\n * carry `subject: Patient/...` references.\n *\n * Every create operation's `body.id` is supplied from the fixture, so\n * re-fires after dedup-TTL expiry upsert the same record (ElectroDB's\n * `put` is unconditional — same composite key overwrites the prior\n * row).\n *\n * Each individual create is wrapped via {@link tryRun}: a failed\n * Patient does not skip the remaining Patients or the downstream\n * Practitioner/Observation/Encounter/Account writes in this workspace,\n * and a failure here does not propagate up to skip the next workspace\n * in the outer loop. Downstream resources that reference a failed\n * parent (`subject: Patient/<id>`) will likely fail too — that is\n * fine, they are attempted, the failures recorded, and the next\n * deploy upserts them once the parent lands.\n */\nconst seedWorkspaceDataPlane = async (\n baseContext: OpenHiContext,\n group: DemoWorkspaceDataPlaneFixtures,\n failures: Array<SeedFailure>,\n): Promise<void> => {\n const workspaceContext: OpenHiContext = {\n ...baseContext,\n tenantId: group.tenantId,\n workspaceId: group.workspaceId,\n };\n const scope = `${group.tenantId}/${group.workspaceId}`;\n for (const patient of group.patients) {\n await tryRun(failures, \"phase-3\", scope, \"Patient\", patient.id ?? \"\", () =>\n createPatientOperation({\n context: workspaceContext,\n body: patient,\n }),\n );\n }\n for (const practitioner of group.practitioners) {\n await tryRun(\n failures,\n \"phase-3\",\n scope,\n \"Practitioner\",\n practitioner.id ?? \"\",\n () =>\n createPractitionerOperation({\n context: workspaceContext,\n body: practitioner,\n }),\n );\n }\n for (const observation of group.observations) {\n await tryRun(\n failures,\n \"phase-3\",\n scope,\n \"Observation\",\n observation.id ?? \"\",\n () =>\n createObservationOperation({\n context: workspaceContext,\n body: observation,\n }),\n );\n }\n for (const encounter of group.encounters) {\n await tryRun(\n failures,\n \"phase-3\",\n scope,\n \"Encounter\",\n encounter.id ?? \"\",\n () =>\n createEncounterOperation({\n context: workspaceContext,\n body: encounter,\n }),\n );\n }\n for (const account of group.accounts) {\n await tryRun(failures, \"phase-3\", scope, \"Account\", account.id ?? \"\", () =>\n createAccountOperation({\n context: workspaceContext,\n body: account,\n }),\n );\n }\n};\n\n/**\n * Upsert the full demo-data graph. Walks each spec in\n * {@link DEMO_TENANT_SPECS}: Tenant → Workspaces. Then per dev user:\n * Cognito user → DynamoDB User → per-tenant Membership +\n * `tenant-admin` RoleAssignment → platform-scoped `system-admin`\n * RoleAssignment. Finally walks {@link DEMO_DATA_PLANE_FIXTURES} and\n * upserts the OPS-009 v1 FHIR resource set (Patient, Practitioner,\n * Observation, Encounter, Account) into each demo workspace. Every\n * put is keyed by a deterministic stable id so re-runs after\n * dedup-TTL expiry upsert the same records.\n *\n * Self-healing-on-every-deploy contract: every individual upsert\n * across all three phases is wrapped via {@link tryRun}. A single\n * item's failure (transient AWS error, write-time constraint\n * violation, etc.) is collected into a per-call accumulator and\n * does not skip the remaining items in its phase. After all phases\n * run, collected failures are aggregate-thrown as a single error so\n * EventBridge still routes the workflow into its failure-detection\n * path (DLQ + CloudWatch alarm) and the outer `runSeedDemoData`\n * records `markFailed` on the dedup row. Because every put is keyed\n * by a deterministic stable id, the next deploy re-attempts every\n * item — failed items eventually heal, successful items overwrite\n * themselves with the same body.\n *\n * Exported so the seeder test file can exercise it directly against\n * a mocked DynamoControlService; the production handler reaches it\n * through {@link SeedDemoDataDependencies.seedDemoGraph}.\n */\nexport const seedDemoGraph = async (params: {\n baseContext: OpenHiContext;\n devUsers: ReadonlyArray<DemoDevUser>;\n cognito: CognitoProvisioner;\n}): Promise<void> => {\n const { baseContext, devUsers, cognito } = params;\n const failures: Array<SeedFailure> = [];\n\n // Phase 1: Tenants + Workspaces (no Memberships / RoleAssignments\n // yet — those need the per-user Cognito sub).\n //\n // Each tenant and each workspace is wrapped individually. A failed\n // tenant create does NOT skip its workspaces — the tenant may\n // already exist from a prior deploy (the put is unconditional, so\n // a duplicate is a no-op; we treat all errors as recoverable here\n // and let the workspace writes proceed). A failed workspace does\n // not skip the next workspace under the same tenant or any\n // subsequent tenant.\n for (const spec of DEMO_TENANT_SPECS) {\n const tenantContext: OpenHiContext = {\n ...baseContext,\n tenantId: spec.tenantId,\n };\n\n await tryRun(\n failures,\n \"phase-1\",\n spec.tenantId,\n \"Tenant\",\n spec.tenantId,\n () =>\n createTenantOperation({\n context: tenantContext,\n body: { id: spec.tenantId, resource: tenantResourceBody(spec) },\n }),\n );\n\n for (const workspace of spec.workspaces) {\n await tryRun(\n failures,\n \"phase-1\",\n spec.tenantId,\n \"Workspace\",\n workspace.id,\n () =>\n createWorkspaceOperation({\n context: tenantContext,\n body: {\n id: workspace.id,\n resource: workspaceResourceBody(spec, workspace),\n },\n }),\n );\n }\n }\n\n // Phase 2: per-dev-user records. The Cognito provisioner is the\n // only step that can fail in a way EventBridge can usefully retry —\n // it runs first because every subsequent write needs the\n // `cognitoSub` it returns. If `ensureUser` throws, skip the whole\n // user (we have no sub to write into the User row).\n //\n // For each individual call inside the per-user block we use\n // {@link tryRun} so a Membership failure in tenant 3 does not skip\n // the Membership + RAs in tenant 4 for the same user, and the\n // platform-scoped RA is attempted last regardless of any earlier\n // per-tenant failure.\n //\n // `upsertUser` is wrapped via tryRun BUT we do NOT short-circuit\n // the per-tenant writes on its failure. The User row carries\n // `cognitoSub` for JWT-claim resolution; the Memberships and\n // RoleAssignments only reference the User by stable id. A previous\n // deploy may have already written the User row with the correct\n // sub. Attempting the per-tenant writes lets the next deploy heal\n // any partial state.\n for (const user of devUsers) {\n let cognitoSub: string;\n try {\n cognitoSub = await cognito.ensureUser(user.email);\n } catch (err) {\n failures.push({\n phase: \"phase-2\",\n scope: user.id,\n resourceType: \"CognitoUser\",\n resourceId: user.email,\n error: err,\n });\n continue;\n }\n\n await tryRun(failures, \"phase-2\", user.id, \"User\", user.id, () =>\n upsertUser(baseContext, user, cognitoSub),\n );\n\n for (const spec of DEMO_TENANT_SPECS) {\n const tenantContext: OpenHiContext = {\n ...baseContext,\n tenantId: spec.tenantId,\n };\n const userScope = `${user.id}@${spec.tenantId}`;\n\n const membershipId = demoMembershipId(user.id, spec.tenantId);\n await tryRun(\n failures,\n \"phase-2\",\n userScope,\n \"Membership\",\n membershipId,\n () =>\n createMembershipOperation({\n context: tenantContext,\n body: {\n id: membershipId,\n resource: membershipResourceBody(spec, user, membershipId),\n },\n }),\n );\n\n for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {\n const raId = demoRoleAssignmentId(user.id, spec.tenantId, roleCode);\n await tryRun(\n failures,\n \"phase-2\",\n userScope,\n \"RoleAssignment\",\n raId,\n () =>\n createRoleAssignmentOperation({\n context: tenantContext,\n body: {\n id: raId,\n resource: roleAssignmentResourceBody(\n spec.scenario,\n spec.tenantId,\n user,\n roleCode,\n raId,\n ),\n },\n }),\n );\n }\n }\n\n // Platform-scoped system-admin RoleAssignment. The sentinel\n // tenantId is PLATFORM_SCOPE_TENANT_ID — there is no real Tenant\n // to point at, so the operation runs with an OpenHiContext whose\n // tenantId is the sentinel. The write-time tenant-membership\n // constraint short-circuits for this sentinel so no Membership\n // prerequisite is required.\n const platformContext: OpenHiContext = {\n ...baseContext,\n tenantId: PLATFORM_SCOPE_TENANT_ID,\n };\n const platformRoleCode = PLATFORM_ROLE_CODE.SYSTEM_ADMIN;\n const platformRaId = demoRoleAssignmentId(\n user.id,\n PLATFORM_SCOPE_TENANT_ID,\n platformRoleCode,\n );\n await tryRun(\n failures,\n \"phase-2\",\n `${user.id}@${PLATFORM_SCOPE_TENANT_ID}`,\n \"RoleAssignment\",\n platformRaId,\n () =>\n createRoleAssignmentOperation({\n context: platformContext,\n body: {\n id: platformRaId,\n resource: roleAssignmentResourceBody(\n \"platform\",\n PLATFORM_SCOPE_TENANT_ID,\n user,\n platformRoleCode,\n platformRaId,\n ),\n },\n }),\n );\n }\n\n // Phase 3: data-plane FHIR resources (OPS-009 v1 set) per demo\n // workspace. Runs after the control-plane phases so the Tenants\n // and Workspaces those resources implicitly belong to already\n // exist by the time we write into them. The placeholder tenant\n // carries no data-plane fixtures.\n //\n // `seedWorkspaceDataPlane` already wraps each individual create\n // via tryRun; we wrap the helper call itself in an outer\n // try/catch as defence-in-depth so an unexpected throw from the\n // helper (programmer error, OOM, etc.) does not skip the next\n // workspace.\n for (const group of DEMO_DATA_PLANE_FIXTURES) {\n try {\n await seedWorkspaceDataPlane(baseContext, group, failures);\n } catch (err) {\n failures.push({\n phase: \"phase-3\",\n scope: `${group.tenantId}/${group.workspaceId}`,\n resourceType: \"Workspace\",\n resourceId: group.workspaceId,\n error: err,\n });\n }\n }\n\n if (failures.length > 0) {\n throw aggregateFailureError(failures);\n }\n};\n\n/**\n * Test-visible orchestrator. The production `handler` calls this with\n * real dependencies and the hardcoded {@link DEV_USERS} list; unit\n * tests inject fakes and pass the dev-user list directly.\n */\nexport const runSeedDemoData = async (\n event: SeedDemoDataEvent,\n deps: SeedDemoDataDependencies,\n devUsers: ReadonlyArray<DemoDevUser>,\n): Promise<void> => {\n const parsed = parseWorkflowEvent(event, PlatformSystemDataSeededV1);\n\n const recordResult = await deps.dedupClient.recordIfAbsent({\n consumerName: SEED_DEMO_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n });\n if (!recordResult.recorded) {\n return;\n }\n\n const baseContext: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: parsed.envelope.occurredAt,\n actorId: \"platform-deploy-bridge\",\n actorName: \"Platform Deploy Bridge\",\n actorType: \"internal-system\",\n source: \"step-function\",\n };\n\n try {\n await deps.verifyRoles();\n await deps.seedDemoGraph({\n baseContext,\n devUsers,\n cognito: deps.cognito,\n });\n } catch (err) {\n // Mark the dedup row failed so the replay tooling (TR-016 follow-up)\n // can re-publish the originating event with a fresh attempt. The\n // re-throw keeps EventBridge's failure-detection path intact — the\n // event ends up in the DLQ + the workflow Lambda's failure\n // CloudWatch alarm fires.\n await deps.dedupClient.markFailed({\n consumerName: SEED_DEMO_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n reason: errorMessage(err),\n });\n throw err;\n }\n};\n\n/**\n * SSM parameter-path prefix that hosts every seeded dev user's\n * password. The full path for a single user is\n * `${SEED_USER_PASSWORD_PARAMETER_PREFIX}<email_safe>/password`\n * where `<email_safe>` is `<user>_at_<domain>` (see\n * {@link emailToSsmPath}). The trailing `/password` segment leaves\n * room for additional per-user parameters under the same email\n * prefix in future phases without renaming the existing entries.\n */\nexport const SEED_USER_PASSWORD_PARAMETER_PREFIX = \"/openhi/seed/users/\";\n\n/**\n * SSM Parameter Store path validation regex. Names must be 1-1011\n * characters, may include alphanumerics plus `_`, `.`, `-`, and\n * `/`. We enforce a stricter subset here — alphanumerics, hyphen,\n * underscore, and period only — because the path is constructed by\n * substituting `@` → `_at_` and any other special character would\n * indicate an email that should have been rejected upstream.\n */\nconst SSM_PATH_SEGMENT = /^[A-Za-z0-9_.-]+$/;\n\n/**\n * Map an email address to its canonical SSM parameter path. The\n * `@` separator is replaced with the literal sentinel `_at_` so\n * the resulting path is a single SSM-legal name. Throws when the\n * email is empty, missing exactly one `@`, or contains characters\n * that would produce an invalid SSM path.\n *\n * Example: `alice@codedrifters.com` →\n * `/openhi/seed/users/alice_at_codedrifters.com/password`.\n */\nexport const emailToSsmPath = (email: string): string => {\n if (typeof email !== \"string\" || email.length === 0) {\n throw new Error(\n `emailToSsmPath: email must be a non-empty string (received \"${String(email)}\").`,\n );\n }\n const atIdx = email.indexOf(\"@\");\n if (atIdx === -1 || atIdx !== email.lastIndexOf(\"@\")) {\n throw new Error(\n `emailToSsmPath: email \"${email}\" must contain exactly one \"@\" character.`,\n );\n }\n const localPart = email.slice(0, atIdx);\n const domainPart = email.slice(atIdx + 1);\n if (localPart.length === 0 || domainPart.length === 0) {\n throw new Error(\n `emailToSsmPath: email \"${email}\" must have a non-empty local-part and domain.`,\n );\n }\n if (!SSM_PATH_SEGMENT.test(localPart) || !SSM_PATH_SEGMENT.test(domainPart)) {\n throw new Error(\n `emailToSsmPath: email \"${email}\" contains characters that would produce ` +\n \"an invalid SSM parameter path (only A-Z, a-z, 0-9, '.', '-', and '_' are allowed).\",\n );\n }\n return `${SEED_USER_PASSWORD_PARAMETER_PREFIX}${localPart}_at_${domainPart}/password`;\n};\n\n/**\n * Module-scoped SSM client. Lambda containers reuse the same\n * `SSMClient` instance across invocations so the TLS connection\n * + credential-resolver state warms once per container. Mirrors\n * the `CognitoIdentityProviderClient` / `DynamoDBClient` reuse\n * pattern at the bottom of this file.\n */\nlet cachedSsmClient: SSMClient | undefined;\n\nconst getSsmClient = (): SSMClient => {\n if (!cachedSsmClient) {\n cachedSsmClient = new SSMClient({});\n }\n return cachedSsmClient;\n};\n\n/**\n * Test seam: reset the module-scoped SSM client cache. Unit tests\n * that swap in different mocks across `it` blocks call this in\n * `beforeEach` so each test gets a fresh client instance.\n */\nexport const __resetSsmClientForTests = (): void => {\n cachedSsmClient = undefined;\n};\n\n/**\n * Read a seeded dev user's password from SSM Parameter Store. The\n * parameter is expected to be a `SecureString` provisioned out of\n * band by an operator (see the runbook in phase 3 of #1249). On\n * `ParameterNotFound`, the error message includes the exact\n * expected path so the operator can copy-paste-fix.\n */\nexport const fetchSeedUserPassword = async (email: string): Promise<string> => {\n const path = emailToSsmPath(email);\n const client = getSsmClient();\n try {\n const result = await client.send(\n new GetParameterCommand({ Name: path, WithDecryption: true }),\n );\n const value = result.Parameter?.Value;\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(\n `fetchSeedUserPassword: SSM parameter \"${path}\" returned an empty value.`,\n );\n }\n return value;\n } catch (err) {\n if (err instanceof ParameterNotFound) {\n throw new Error(\n `fetchSeedUserPassword: SSM parameter \"${path}\" not found. ` +\n `Provision a SecureString at \"${path}\" with the dev user's password before re-running seed-demo-data.`,\n );\n }\n throw err;\n }\n};\n\n/**\n * Production Cognito provisioner backed by the AWS SDK. Reads the\n * user-pool id from the env var the lambda construct injects.\n *\n * Idempotency contract:\n * - On first invocation, calls `AdminCreateUser` (with\n * `MessageAction: SUPPRESS` so no invitation email fires) then\n * `AdminSetUserPassword` (permanent, from the SSM-sourced\n * value). Returns the new user's sub.\n * - On subsequent invocations, `AdminCreateUser` throws\n * `UsernameExistsException`; the provisioner catches it, calls\n * `AdminGetUser` to read the existing user's sub, and **also\n * re-applies the current SSM-sourced password** via\n * `AdminSetUserPassword`. This is the rotation seam: operators\n * update the SSM SecureString value and re-publish the seed\n * event to push a fresh password down to Cognito.\n *\n * The SSM password is fetched once per email up-front so the new-\n * user and rotation branches share the same value and at most one\n * `GetParameter` call lands per `ensureUser` invocation.\n */\nexport const productionCognitoProvisioner = (): CognitoProvisioner => {\n const client = new CognitoIdentityProviderClient({});\n const userPoolId = process.env[SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR];\n if (!userPoolId || userPoolId.trim() === \"\") {\n throw new Error(\n `${SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR} is not set; the seed-demo-data Lambda ` +\n \"cannot provision Cognito users without a user-pool id.\",\n );\n }\n const subFromAttributes = (\n attrs: ReadonlyArray<AttributeType> | undefined,\n ): string | undefined => {\n for (const attr of attrs ?? []) {\n if (attr.Name === \"sub\" && typeof attr.Value === \"string\") {\n return attr.Value;\n }\n }\n return undefined;\n };\n const setPassword = async (\n email: string,\n password: string,\n ): Promise<void> => {\n await client.send(\n new AdminSetUserPasswordCommand({\n UserPoolId: userPoolId,\n Username: email,\n Password: password,\n Permanent: true,\n }),\n );\n };\n return {\n ensureUser: async (email: string): Promise<string> => {\n // Fetch the SSM-sourced password up-front so both the\n // new-user path and the rotation path consume the same\n // value with a single `GetParameter` call.\n const password = await fetchSeedUserPassword(email);\n try {\n const created = await client.send(\n new AdminCreateUserCommand({\n UserPoolId: userPoolId,\n Username: email,\n MessageAction: \"SUPPRESS\",\n UserAttributes: [\n { Name: \"email\", Value: email },\n { Name: \"email_verified\", Value: \"true\" },\n ],\n }),\n );\n await setPassword(email, password);\n const sub = subFromAttributes(created.User?.Attributes);\n if (!sub) {\n throw new Error(\n `AdminCreateUser response for \"${email}\" did not carry a sub attribute.`,\n );\n }\n return sub;\n } catch (err) {\n if (err instanceof UsernameExistsException) {\n // User already exists — re-apply the current SSM-sourced\n // password so operator rotations propagate, then read the\n // existing sub. AdminSetUserPassword on an existing user\n // overwrites the credential without affecting the sub.\n await setPassword(email, password);\n const got = await client.send(\n new AdminGetUserCommand({\n UserPoolId: userPoolId,\n Username: email,\n }),\n );\n const sub = subFromAttributes(got.UserAttributes);\n if (!sub) {\n throw new Error(\n `AdminGetUser response for \"${email}\" did not carry a sub attribute.`,\n );\n }\n return sub;\n }\n throw err;\n }\n },\n };\n};\n\nconst productionDependencies = (): SeedDemoDataDependencies => {\n const dynamodb = new DynamoDBClient({});\n const cognito = productionCognitoProvisioner();\n return {\n dedupClient: workflowDedupClient(dynamodb),\n verifyRoles: verifySystemRolesExist,\n seedDemoGraph,\n cognito,\n };\n};\n\nexport const handler = async (event: SeedDemoDataEvent): Promise<void> =>\n runSeedDemoData(event, productionDependencies(), DEV_USERS);\n","import { PLATFORM_ROLE_CODE, type PlatformRoleCode } from \"@openhi/types\";\nimport { PlatformSystemDataSeededV1 } from \"@openhi/workflows\";\nimport { PLATFORM_SCOPE_TENANT_ID } from \"../../../data/operations/control/membership-constraints/platform-scope-tenant-id\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/events.md\n */\n\n/**\n * Stable logical name this workflow registers with the shared\n * `WorkflowDedupTable` (TR-015). Used in both the construct grant\n * (`workflowDedupTable.grantConsumer(lambda, SEED_DEMO_DATA_CONSUMER_NAME)`)\n * and the handler's runtime `recordIfAbsent` call — keep them aligned by\n * importing this constant in both places.\n */\nexport const SEED_DEMO_DATA_CONSUMER_NAME = \"seed-demo-data\";\n\n/** FHIR `Identifier.system` for the demo-scenario URN scheme. */\nexport const DEMO_URN_SYSTEM = \"urn:openhi:demo\";\n\n/**\n * FHIR `Identifier.system` for the canonical OpenHI resource URN\n * (ADR 2026-03-12-01). The value form is\n * `urn:ohi:<tenantId>:<workspaceId>:<resourceType>:<id>`; empty\n * workspaceId means the resource has tenant-wide scope (or is itself\n * the workspace identity, in the Workspace case).\n */\nexport const OPENHI_RESOURCE_URN_SYSTEM = \"http://openhi.org/\";\n\n/**\n * Period stamped on every demo Membership and RoleAssignment. Fixed\n * start so re-runs produce byte-identical bodies (decision #4); no\n * end so demo memberships are open-ended.\n */\nexport const DEMO_PERIOD = { start: \"2026-01-01T00:00:00Z\" } as const;\n\n/**\n * Sentinel `tenantId` used on every dev user's `system-admin`\n * RoleAssignment. A `system-admin` RA is platform-scoped (it spans\n * every tenant), but the RoleAssignment entity requires a tenantId on\n * its key for sharding — there is no real tenant to point at.\n *\n * The canonical definition lives in\n * `data/operations/control/membership-constraints/platform-scope-tenant-id.ts`\n * so the write-time tenant-membership constraint can short-circuit\n * the sentinel without the data layer importing from the workflow.\n * Re-exported here so existing imports that resolve through\n * `seed-demo-data/events` keep working.\n */\nexport { PLATFORM_SCOPE_TENANT_ID };\n\n/** Placeholder Tenant id seeded by the workflow as the dev-user `currentTenant`. */\nexport const PLACEHOLDER_TENANT_ID = \"placeholder-tenant-id\";\n\n/** Placeholder Workspace id seeded by the workflow as the dev-user `currentWorkspace`. */\nexport const PLACEHOLDER_WORKSPACE_ID = \"placeholder-workspace-id\";\n\n/**\n * Dev-user descriptor. Every entry produces:\n * - one Cognito user (idempotent create-then-skip),\n * - one DynamoDB User record (id = `dev-<email-local-part>`),\n * - four Memberships (placeholder + the three demo tenants),\n * - four `tenant-admin` RoleAssignments (one per tenant the user\n * belongs to),\n * - one `system-admin` RoleAssignment scoped to {@link PLATFORM_SCOPE_TENANT_ID}.\n */\nexport interface DemoDevUser {\n /** Stable DynamoDB User id. */\n readonly id: string;\n /** Email used as the Cognito `Username` and as the seed for the password algorithm. */\n readonly email: string;\n}\n\n/**\n * Hardcoded dev-user roster. Adding a new developer is a one-line\n * change here plus a re-deploy. The list intentionally lives in-source\n * (not behind an env-var seam) so the IAM grant in\n * `seed-demo-data-lambda.ts` can enumerate per-user PKs at synth time.\n */\nexport const DEV_USERS: ReadonlyArray<DemoDevUser> = [\n { id: \"dev-russell\", email: \"russell@codedrifters.com\" },\n { id: \"dev-cameron\", email: \"cameron@codedrifters.com\" },\n { id: \"dev-neelima\", email: \"neelima@codedrifters.com\" },\n { id: \"dev-garon\", email: \"garon@codedrifters.com\" },\n { id: \"dev-dave\", email: \"dave@codedrifters.com\" },\n { id: \"dev-drew\", email: \"drew@codedrifters.com\" },\n { id: \"dev-jessica\", email: \"jessica@codedrifters.com\" },\n { id: \"dev-jared\", email: \"jared@codedrifters.com\" },\n { id: \"dev-goddess\", email: \"goddess@codedrifters.com\" },\n];\n\n/**\n * A single workspace inside a demo tenant. The mixed tenant has two\n * workspaces (wound-care and primary-care sub-workspaces); the other\n * two tenants have one each.\n */\nexport interface DemoWorkspaceSpec {\n /** Stable id (DynamoDB record id; also drives the canonical OHI URN). */\n readonly id: string;\n /** FHIR `Workspace.name`. */\n readonly name: string;\n /**\n * Role suffix used in the demo URN value (`<scenario>:<roleSuffix>`).\n * `workspace` for single-workspace tenants, `workspace-<sub>` for the\n * mixed tenant.\n */\n readonly roleSuffix: string;\n}\n\n/**\n * One demo tenant + the workspaces it owns. Re-exported via {@link\n * DEMO_TENANT_SPECS} as the canonical list; iterate that constant in\n * the handler and the IAM-grant builder so the value sets agree.\n */\nexport interface DemoTenantSpec {\n /**\n * Scenario slug — `placeholder`, `demo-wound-care`, `demo-primary-care`,\n * `demo-mixed`. The placeholder tenant's slug is `placeholder`; the\n * three demo tenants use `demo-*` slugs.\n */\n readonly scenario: string;\n /** Stable id (DynamoDB record id; also drives the canonical OHI URN). */\n readonly tenantId: string;\n /** FHIR `Tenant.name`. */\n readonly tenantName: string;\n /** Workspaces owned by this tenant. */\n readonly workspaces: ReadonlyArray<DemoWorkspaceSpec>;\n}\n\n/**\n * The full demo-tenant graph. Four entries: the placeholder tenant the\n * JWT-claim fallback resolves to, plus the three v1 demo scenarios\n * (OPS-009 §\"v1 scenarios\"):\n *\n * 0. Placeholder tenant — dereferences the JWT-claim fallback in\n * `pre-token-generation.handler.ts` and acts as every dev user's\n * `currentTenant`/`currentWorkspace` so Post-Confirmation skips\n * default provisioning when a seeded User signs in.\n * 1. Single-workspace wound-care tenant.\n * 2. Single-workspace primary-care tenant.\n * 3. Two-workspace mixed tenant — exercises the cross-workspace\n * isolation flow that single-workspace tenants cannot.\n */\nexport const DEMO_TENANT_SPECS: ReadonlyArray<DemoTenantSpec> = [\n {\n scenario: \"placeholder\",\n tenantId: PLACEHOLDER_TENANT_ID,\n tenantName: \"OpenHI Placeholder Tenant\",\n workspaces: [\n {\n id: PLACEHOLDER_WORKSPACE_ID,\n name: \"OpenHI Placeholder Workspace\",\n roleSuffix: \"workspace\",\n },\n ],\n },\n {\n scenario: \"demo-wound-care\",\n tenantId: \"demo-wound-care-tenant\",\n tenantName: \"Cedarbrook Wound Healing Institute\",\n workspaces: [\n {\n id: \"demo-wound-care-workspace\",\n name: \"Cedarbrook Outpatient Wound Clinic\",\n roleSuffix: \"workspace\",\n },\n ],\n },\n {\n scenario: \"demo-primary-care\",\n tenantId: \"demo-primary-care-tenant\",\n tenantName: \"Maple Ridge Family Medicine\",\n workspaces: [\n {\n id: \"demo-primary-care-workspace\",\n name: \"Maple Ridge Main Street Office\",\n roleSuffix: \"workspace\",\n },\n ],\n },\n {\n scenario: \"demo-mixed\",\n tenantId: \"demo-mixed-tenant\",\n tenantName: \"Northbridge Health Network\",\n workspaces: [\n {\n id: \"demo-mixed-workspace-wound-care\",\n name: \"Northbridge Wound Care Center\",\n roleSuffix: \"workspace-wound-care\",\n },\n {\n id: \"demo-mixed-workspace-primary-care\",\n name: \"Northbridge Family Practice\",\n roleSuffix: \"workspace-primary-care\",\n },\n ],\n },\n];\n\n/** Stable Membership id derived from `(devUserId, tenantId)`. */\nexport const demoMembershipId = (devUserId: string, tenantId: string): string =>\n `demo-membership-${devUserId}-${tenantId}`;\n\n/**\n * Stable RoleAssignment id derived from `(devUserId, tenantId, roleCode)`.\n * Each (user, tenant, role) tuple maps to exactly one record — re-runs\n * upsert the same id.\n */\nexport const demoRoleAssignmentId = (\n devUserId: string,\n tenantId: string,\n roleCode: PlatformRoleCode,\n): string => `demo-roleassignment-${devUserId}-${tenantId}-${roleCode}`;\n\n/**\n * Demo-scenario FHIR `Identifier` entry — `urn:openhi:demo:<scenario>:<role>`.\n */\nexport const demoScenarioIdentifier = (\n scenario: string,\n roleSuffix: string,\n): { system: string; value: string } => ({\n system: DEMO_URN_SYSTEM,\n value: `${scenario}:${roleSuffix}`,\n});\n\n/**\n * Canonical OpenHI resource FHIR `Identifier` entry per ADR\n * 2026-03-12-01. `workspaceId` is empty for both Tenant resources and\n * Workspace resources — for a Tenant, the resource is tenant-scoped\n * with no workspace context; for a Workspace, the resource IS the\n * workspace identity rather than living inside one.\n */\nexport const openhiResourceIdentifier = (params: {\n tenantId: string;\n workspaceId: string;\n resourceType: string;\n id: string;\n}): { use: string; system: string; value: string } => ({\n use: \"unversioned\",\n system: OPENHI_RESOURCE_URN_SYSTEM,\n value: `urn:ohi:${params.tenantId}:${params.workspaceId}:${params.resourceType}:${params.id}`,\n});\n\n/**\n * Roles every dev user holds in every tenant they belong to. Per scope\n * decision Q3, every dev user is `tenant-admin` in every tenant — there\n * is no per-(user, tenant) variance to drive from.\n */\nexport const demoRolesForUserInTenant = (\n _user: DemoDevUser,\n _tenantId: string,\n): ReadonlyArray<PlatformRoleCode> => {\n void _user;\n void _tenantId;\n return [PLATFORM_ROLE_CODE.TENANT_ADMIN];\n};\n\n/**\n * The trigger this workflow subscribes to on the control event bus.\n * The `seed-system-data` workflow publishes\n * `platform.system-data-seeded.v1` after it has finished re-asserting\n * the platform-singleton Role records; on every fire this workflow\n * re-asserts the demo-tenant graph.\n *\n * The indirection (subscribing to `system-data-seeded` rather than to\n * the raw `platform.deployment-completed.v1` event) enforces a strict\n * happens-before edge between the system-data seed and the demo-data\n * seed. Without it, both workflows would fire in parallel from the\n * same deploy event and seed-demo-data's pre-flight Role check would\n * race seed-system-data's puts — a race that EventBridge retries\n * resolve, but only after firing the failure alarm at least once on a\n * fresh environment's first deploy.\n *\n * Re-exported from `@openhi/workflows` for symmetry with the handler's\n * import — keep the construct + handler reading from the same registry\n * entry so a rename upstream surfaces at both call sites.\n */\nexport { PlatformSystemDataSeededV1 };\n","import type {\n Account,\n Encounter,\n Observation,\n Patient,\n Practitioner,\n} from \"@openhi/types\";\nimport {\n DEMO_TENANT_SPECS,\n PLACEHOLDER_TENANT_ID,\n demoScenarioIdentifier,\n openhiResourceIdentifier,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/data-plane-fixtures.md\n *\n * Hand-authored FHIR data-plane fixture bodies the `seed-demo-data`\n * workflow upserts into the data store on every non-prod deploy.\n * Mirrors the OPS-009 v1 resource set: Patient, Practitioner,\n * Observation, Encounter, Account.\n *\n * Ids are deterministic — re-fires of the workflow upsert the same\n * records, satisfying the workflow's idempotency contract (no\n * duplicates) and letting the IAM grant in `seed-demo-data-lambda.ts`\n * enumerate exact-match `dynamodb:LeadingKeys` rather than a wildcard.\n *\n * The placeholder tenant carries no data-plane fixtures — only the\n * three real demo tenants (wound-care, primary-care, mixed) get\n * Patient/Practitioner/Observation/Encounter/Account records. The\n * placeholder tenant exists solely as a routing target for the\n * Cognito pre-token-generation fallback and never holds clinical\n * data.\n */\n\n/**\n * Logical group of FHIR resources owned by a single (tenant, workspace)\n * pair. The workflow walks `DEMO_DATA_PLANE_FIXTURES` and writes every\n * entry against the matching workspace's `OpenHiContext`.\n */\nexport interface DemoWorkspaceDataPlaneFixtures {\n readonly tenantId: string;\n readonly workspaceId: string;\n /**\n * Scenario slug used in the demo-URN identifier — mirrors the\n * `DemoTenantSpec.scenario` value for the parent tenant. For the\n * mixed tenant both workspaces share the `demo-mixed` scenario.\n */\n readonly scenario: string;\n readonly patients: ReadonlyArray<Patient>;\n readonly practitioners: ReadonlyArray<Practitioner>;\n readonly observations: ReadonlyArray<Observation>;\n readonly encounters: ReadonlyArray<Encounter>;\n readonly accounts: ReadonlyArray<Account>;\n}\n\n/**\n * Map a tenant scenario to a `(roleSuffix, tenantId, workspaceId)`\n * triple plus the demo + OHI identifiers. Keeps the per-resource\n * builders compact below — they call this once per body to stamp the\n * two-identifier pair that matches the control-plane pattern.\n */\nconst fixtureIdentifiers = (\n scenario: string,\n tenantId: string,\n workspaceId: string,\n resourceType: string,\n id: string,\n roleSuffix: string,\n) => [\n demoScenarioIdentifier(scenario, roleSuffix),\n openhiResourceIdentifier({\n tenantId,\n workspaceId,\n resourceType,\n id,\n }),\n];\n\nconst buildWoundCareFixtures = (\n scenario: string,\n tenantId: string,\n workspaceId: string,\n idPrefix: string,\n): DemoWorkspaceDataPlaneFixtures => ({\n tenantId,\n workspaceId,\n scenario,\n patients: [\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-1`,\n `patient-1`,\n ),\n active: true,\n name: [{ family: \"Carter\", given: [\"Eleanor\"], use: \"official\" }],\n gender: \"female\",\n birthDate: \"1952-04-18\",\n },\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-2`,\n `patient-2`,\n ),\n active: true,\n name: [{ family: \"Nguyen\", given: [\"Hao\"], use: \"official\" }],\n gender: \"male\",\n birthDate: \"1968-11-02\",\n },\n ],\n practitioners: [\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-1`,\n `practitioner-1`,\n ),\n active: true,\n name: [{ family: \"Reyes\", given: [\"Maria\"], prefix: [\"Dr.\"] }],\n gender: \"female\",\n },\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-2`,\n `practitioner-2`,\n ),\n active: true,\n name: [{ family: \"Okafor\", given: [\"Chinedu\"], prefix: [\"Dr.\"] }],\n gender: \"male\",\n },\n ],\n observations: [\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-1`,\n `observation-1`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"39135-9\",\n display: \"Wound size\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n valueString: \"3.2cm x 2.1cm\",\n },\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-2`,\n `observation-2`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"72287-2\",\n display: \"Wound exudate amount\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n valueString: \"moderate\",\n },\n ],\n encounters: [\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-1`,\n `encounter-1`,\n ),\n status: \"finished\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n },\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-2`,\n `encounter-2`,\n ),\n status: \"finished\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n },\n ],\n accounts: [\n {\n resourceType: \"Account\",\n id: `${idPrefix}-account-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Account\",\n `${idPrefix}-account-1`,\n `account-1`,\n ),\n status: \"active\",\n name: \"Wound-care self-pay account\",\n subject: [{ reference: `Patient/${idPrefix}-patient-1` }],\n },\n ],\n});\n\nconst buildPrimaryCareFixtures = (\n scenario: string,\n tenantId: string,\n workspaceId: string,\n idPrefix: string,\n): DemoWorkspaceDataPlaneFixtures => ({\n tenantId,\n workspaceId,\n scenario,\n patients: [\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-1`,\n `patient-1`,\n ),\n active: true,\n name: [{ family: \"Bennett\", given: [\"Sophia\"], use: \"official\" }],\n gender: \"female\",\n birthDate: \"1985-06-09\",\n },\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-2`,\n `patient-2`,\n ),\n active: true,\n name: [{ family: \"Patel\", given: [\"Arjun\"], use: \"official\" }],\n gender: \"male\",\n birthDate: \"1979-02-21\",\n },\n ],\n practitioners: [\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-1`,\n `practitioner-1`,\n ),\n active: true,\n name: [{ family: \"Lin\", given: [\"Wei\"], prefix: [\"Dr.\"] }],\n gender: \"female\",\n },\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-2`,\n `practitioner-2`,\n ),\n active: true,\n name: [{ family: \"Kowalski\", given: [\"Piotr\"], prefix: [\"Dr.\"] }],\n gender: \"male\",\n },\n ],\n observations: [\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-1`,\n `observation-1`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"8480-6\",\n display: \"Systolic blood pressure\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n valueQuantity: { value: 122, unit: \"mm[Hg]\" },\n },\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-2`,\n `observation-2`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"8462-4\",\n display: \"Diastolic blood pressure\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n valueQuantity: { value: 78, unit: \"mm[Hg]\" },\n },\n ],\n encounters: [\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-1`,\n `encounter-1`,\n ),\n status: \"finished\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n },\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-2`,\n `encounter-2`,\n ),\n status: \"in-progress\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n },\n ],\n accounts: [\n {\n resourceType: \"Account\",\n id: `${idPrefix}-account-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Account\",\n `${idPrefix}-account-1`,\n `account-1`,\n ),\n status: \"active\",\n name: \"Primary-care insurance account\",\n subject: [{ reference: `Patient/${idPrefix}-patient-1` }],\n },\n ],\n});\n\n/**\n * Per-workspace fixtures the data-plane phase writes on every fire.\n * The placeholder tenant carries no fixtures. The mixed tenant carries\n * one fixture group per workspace; the two single-workspace tenants\n * carry one each. Total: 4 fixture groups × ≈ 9 resources = ~36\n * data-plane records.\n *\n * Ids embed the tenant + workspace slug so they remain unambiguous\n * across the four workspaces (the FHIR resource id is the only thing\n * that survives into the partition key, so a duplicate id across\n * workspaces would still collide on read paths that scan-by-id).\n */\nexport const DEMO_DATA_PLANE_FIXTURES: ReadonlyArray<DemoWorkspaceDataPlaneFixtures> =\n [\n buildWoundCareFixtures(\n \"demo-wound-care\",\n \"demo-wound-care-tenant\",\n \"demo-wound-care-workspace\",\n \"demo-wound-care\",\n ),\n buildPrimaryCareFixtures(\n \"demo-primary-care\",\n \"demo-primary-care-tenant\",\n \"demo-primary-care-workspace\",\n \"demo-primary-care\",\n ),\n buildWoundCareFixtures(\n \"demo-mixed\",\n \"demo-mixed-tenant\",\n \"demo-mixed-workspace-wound-care\",\n \"demo-mixed-wound-care\",\n ),\n buildPrimaryCareFixtures(\n \"demo-mixed\",\n \"demo-mixed-tenant\",\n \"demo-mixed-workspace-primary-care\",\n \"demo-mixed-primary-care\",\n ),\n ];\n\n/**\n * Regression guard: the fixture groups must each be tied to a Tenant +\n * Workspace combination that exists in {@link DEMO_TENANT_SPECS}.\n * Imported `DEMO_TENANT_SPECS` and `PLACEHOLDER_TENANT_ID` keep the\n * guard self-contained — no test plumbing required.\n */\nconst _validateFixturesAgainstTenantSpecs = (): void => {\n for (const group of DEMO_DATA_PLANE_FIXTURES) {\n if (group.tenantId === PLACEHOLDER_TENANT_ID) {\n throw new Error(\n \"The placeholder tenant must not carry data-plane fixtures.\",\n );\n }\n const tenant = DEMO_TENANT_SPECS.find((s) => s.tenantId === group.tenantId);\n if (!tenant) {\n throw new Error(\n `Fixture references unknown tenantId \"${group.tenantId}\". ` +\n \"Add a matching entry to DEMO_TENANT_SPECS first.\",\n );\n }\n const workspace = tenant.workspaces.find(\n (ws) => ws.id === group.workspaceId,\n );\n if (!workspace) {\n throw new Error(\n `Fixture references unknown workspaceId \"${group.workspaceId}\" ` +\n `for tenant \"${group.tenantId}\".`,\n );\n }\n }\n};\n_validateFixturesAgainstTenantSpecs();\n"],"mappings":"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","names":["import_workflows","PLATFORM_ROLE_CODE","PLATFORM_ROLE_CODE"]}
|
|
1
|
+
{"version":3,"sources":["../src/workflows/control-plane/seed-demo-data/seed-demo-data.handler.ts","../src/workflows/control-plane/seed-demo-data/events.ts","../src/workflows/control-plane/seed-demo-data/data-plane-fixtures.ts"],"sourcesContent":["import {\n AdminCreateUserCommand,\n AdminGetUserCommand,\n AdminSetUserPasswordCommand,\n CognitoIdentityProviderClient,\n UsernameExistsException,\n type AttributeType,\n} from \"@aws-sdk/client-cognito-identity-provider\";\nimport { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport {\n GetParameterCommand,\n ParameterNotFound,\n SSMClient,\n} from \"@aws-sdk/client-ssm\";\nimport {\n PLATFORM_ROLE_CODE,\n PLATFORM_ROLE_CONCEPTS,\n PLATFORM_ROLE_IDS,\n extractSummary,\n type PlatformRoleCode,\n type FhirResourceLike,\n} from \"@openhi/types\";\nimport {\n parseWorkflowEvent,\n workflowDedupClient,\n type WorkflowDedupClient,\n} from \"@openhi/workflows\";\nimport type { EventBridgeEvent } from \"aws-lambda\";\nimport {\n DEMO_DATA_PLANE_FIXTURES,\n type DemoWorkspaceDataPlaneFixtures,\n} from \"./data-plane-fixtures\";\nimport {\n DEMO_PERIOD,\n DEMO_TENANT_SPECS,\n DEV_USERS,\n PLACEHOLDER_TENANT_ID,\n PLACEHOLDER_WORKSPACE_ID,\n PLATFORM_SCOPE_TENANT_ID,\n PlatformSystemDataSeededV1,\n SEED_DEMO_DATA_CONSUMER_NAME,\n demoMembershipId,\n demoRoleAssignmentId,\n demoRolesForUserInTenant,\n demoScenarioIdentifier,\n openhiResourceIdentifier,\n type DemoDevUser,\n} from \"./events\";\nimport { getDynamoControlService } from \"../../../data/dynamo/dynamo-control-service\";\nimport { NotFoundError } from \"../../../data/errors\";\nimport type { OpenHiContext } from \"../../../data/openhi-context\";\nimport { createMembershipOperation } from \"../../../data/operations/control/membership/membership-create-operation\";\nimport { getRoleByIdOperation } from \"../../../data/operations/control/role/role-get-by-id-operation\";\nimport { createRoleAssignmentOperation } from \"../../../data/operations/control/roleassignment/roleassignment-create-operation\";\nimport { createTenantOperation } from \"../../../data/operations/control/tenant/tenant-create-operation\";\nimport { createWorkspaceOperation } from \"../../../data/operations/control/workspace/workspace-create-operation\";\nimport { createAccountOperation } from \"../../../data/operations/data/account/account-create-operation\";\nimport { createEncounterOperation } from \"../../../data/operations/data/encounter/encounter-create-operation\";\nimport { createObservationOperation } from \"../../../data/operations/data/observation/observation-create-operation\";\nimport { createPatientOperation } from \"../../../data/operations/data/patient/patient-create-operation\";\nimport { createPractitionerOperation } from \"../../../data/operations/data/practitioner/practitioner-create-operation\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/seed-demo-data-handler.md\n *\n * EventBridge workflow handler invoked once per platform-deploy event\n * on the control event bus. Pre-flight verifies that\n * `seed-system-data` has already seeded the canonical Role records,\n * then idempotently re-asserts the demo-data graph: the placeholder\n * Tenant + Workspace, the three demo tenants + their workspaces, and\n * for each entry in {@link DEV_USERS} a Cognito user, a DynamoDB User,\n * 4 Memberships, 4 `tenant-admin` RoleAssignments, plus 1\n * platform-scoped `system-admin` RoleAssignment.\n */\n\ntype SeedDemoDataEvent = EventBridgeEvent<\n \"platform.system-data-seeded.v1\",\n unknown\n>;\n\n/** Env var the lambda construct injects with the Cognito User Pool ID. */\nexport const SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR =\n \"SEED_DEMO_DATA_USER_POOL_ID\";\n\n/** Inputs the handler needs to provision Cognito users for every dev. */\nexport interface CognitoProvisioner {\n /**\n * Ensure a Cognito user exists for `email`. Returns the user's\n * `sub`. Implementations MUST be idempotent: a second invocation\n * for the same email returns the existing user's sub without\n * resetting its password or touching any attribute.\n */\n readonly ensureUser: (email: string) => Promise<string>;\n}\n\n/**\n * Dependency seam for tests. The factory mirrors the production\n * arrangement: a real DynamoDB client + the real `workflowDedupClient`\n * factory bound to the `OPENHI_WORKFLOW_DEDUP_TABLE_NAME` env var the\n * construct injects via `grantConsumer`.\n */\nexport interface SeedDemoDataDependencies {\n readonly dedupClient: WorkflowDedupClient;\n /**\n * Reads every id in `PLATFORM_ROLE_IDS`. Throws when any Role\n * record is missing — that means `seed-system-data` has not yet\n * run on this environment, and emitting demo RoleAssignments that\n * reference non-existent Roles would produce orphaned records.\n */\n readonly verifyRoles: () => Promise<void>;\n /**\n * Upserts every Tenant + Workspace in {@link DEMO_TENANT_SPECS},\n * plus per-dev-user Cognito users, DynamoDB User records,\n * Memberships, and RoleAssignments.\n */\n readonly seedDemoGraph: (params: {\n readonly baseContext: OpenHiContext;\n readonly devUsers: ReadonlyArray<DemoDevUser>;\n readonly cognito: CognitoProvisioner;\n }) => Promise<void>;\n /** Cognito provisioner threaded into `seedDemoGraph`. */\n readonly cognito: CognitoProvisioner;\n}\n\nconst errorMessage = (err: unknown): string => {\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n};\n\n/**\n * Structured per-item failure record collected during {@link seedDemoGraph}.\n * Every individual upsert in Phases 1-3 is wrapped so a single item's\n * failure (transient AWS error, write-time constraint violation, etc.)\n * does not skip the rest of its phase. After all phases run, the\n * collected failures are aggregate-thrown so EventBridge still routes\n * the workflow into its failure-detection path (DLQ + CloudWatch\n * alarm) and the outer `runSeedDemoData` records `markFailed` on the\n * dedup row.\n *\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/index.md\n */\ntype SeedPhase = \"phase-1\" | \"phase-2\" | \"phase-3\";\n\ninterface SeedFailure {\n readonly phase: SeedPhase;\n /**\n * Short human label identifying where the failure happened\n * (e.g. tenant id, workspace id, user id). Used purely to enrich\n * the aggregate-throw message; no machine reads it.\n */\n readonly scope: string;\n readonly resourceType: string;\n readonly resourceId: string;\n readonly error: unknown;\n}\n\n/**\n * Wrap a single upsert call. On success, returns `true`. On failure,\n * pushes a structured {@link SeedFailure} onto `failures` and returns\n * `false` — the caller can use the boolean to decide whether to skip\n * downstream writes that strictly depend on this one. Most call sites\n * ignore the boolean (per-item isolation: the next item is attempted\n * regardless of this one's outcome).\n *\n * The wrapped function's return value is discarded; only the success/\n * failure signal is propagated. Callers that need the operation's\n * result (e.g. the `cognitoSub` from `cognito.ensureUser`) must hoist\n * the call out of `tryRun` and handle the failure inline.\n */\nconst tryRun = async (\n failures: Array<SeedFailure>,\n phase: SeedPhase,\n scope: string,\n resourceType: string,\n resourceId: string,\n fn: () => Promise<unknown>,\n): Promise<boolean> => {\n try {\n await fn();\n return true;\n } catch (err) {\n failures.push({ phase, scope, resourceType, resourceId, error: err });\n return false;\n }\n};\n\n/**\n * Format collected failures into a single Error suitable for the\n * outer `markFailed` reason and CloudWatch alarm payload. Lists every\n * failure as `<phase> <scope>/<resourceType>/<resourceId>: <message>`\n * joined by `; ` so a single CloudWatch line carries the full picture.\n */\nconst aggregateFailureError = (failures: ReadonlyArray<SeedFailure>): Error => {\n const summary = failures\n .map(\n (f) =>\n `${f.phase} ${f.scope}/${f.resourceType}/${f.resourceId}: ${errorMessage(\n f.error,\n )}`,\n )\n .join(\"; \");\n return new Error(\n `seed-demo-data: ${failures.length} item(s) failed across phases: ${summary}`,\n );\n};\n\n/**\n * Map a role code back to its canonical stable id from\n * `PLATFORM_ROLE_IDS`. Mirrors the lookup in\n * `seed-system-data.handler.ts` — the generator emits `_IDS` and\n * `_CONCEPTS` in lockstep, so a single SCREAMING_SNAKE key resolves\n * both projections.\n */\nconst idForRoleCode = (code: PlatformRoleCode): string => {\n for (const key of Object.keys(PLATFORM_ROLE_IDS) as Array<\n keyof typeof PLATFORM_ROLE_IDS\n >) {\n if (PLATFORM_ROLE_CONCEPTS[key].code === code) {\n return PLATFORM_ROLE_IDS[key];\n }\n }\n throw new Error(`No id mapping for role code \"${code}\".`);\n};\n\n/**\n * Pre-flight Role check. Iterates every id in\n * `PLATFORM_ROLE_IDS` and reads the record. If any read returns\n * not-found, this throws and `markFailed`'s the dedup row — the\n * replay tooling can retry once `seed-system-data` has populated the\n * Roles.\n */\nconst verifySystemRolesExist = async (): Promise<void> => {\n const probeContext: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: new Date(0).toISOString(),\n actorId: \"platform-deploy-bridge\",\n actorName: \"Platform Deploy Bridge\",\n actorType: \"internal-system\",\n source: \"step-function\",\n };\n for (const id of Object.values(PLATFORM_ROLE_IDS)) {\n try {\n await getRoleByIdOperation({ context: probeContext, id });\n } catch (err) {\n if (err instanceof NotFoundError) {\n throw new Error(\n `seed-demo-data pre-flight: control-plane Role \"${id}\" is missing. ` +\n \"Ensure seed-system-data has run on this environment before retrying.\",\n );\n }\n throw err;\n }\n }\n};\n\n/**\n * Build the resource body for one demo Tenant. Carries the two\n * identifiers per scope decision #6: the demo-scenario URN and the\n * canonical OpenHI resource URN (ADR 2026-03-12-01).\n */\nconst tenantResourceBody = (\n spec: (typeof DEMO_TENANT_SPECS)[number],\n): Record<string, unknown> => ({\n name: spec.tenantName,\n active: true,\n identifier: [\n demoScenarioIdentifier(spec.scenario, \"tenant\"),\n openhiResourceIdentifier({\n tenantId: spec.tenantId,\n workspaceId: \"\",\n resourceType: \"Tenant\",\n id: spec.tenantId,\n }),\n ],\n});\n\nconst workspaceResourceBody = (\n spec: (typeof DEMO_TENANT_SPECS)[number],\n workspace: (typeof DEMO_TENANT_SPECS)[number][\"workspaces\"][number],\n): Record<string, unknown> => ({\n name: workspace.name,\n active: true,\n identifier: [\n demoScenarioIdentifier(spec.scenario, workspace.roleSuffix),\n openhiResourceIdentifier({\n tenantId: spec.tenantId,\n workspaceId: \"\",\n resourceType: \"Workspace\",\n id: workspace.id,\n }),\n ],\n tenant: { reference: `Tenant/${spec.tenantId}`, type: \"Tenant\" },\n});\n\nconst membershipResourceBody = (\n spec: (typeof DEMO_TENANT_SPECS)[number],\n user: DemoDevUser,\n membershipId: string,\n): Record<string, unknown> => ({\n identifier: [\n demoScenarioIdentifier(spec.scenario, `membership-${user.id}`),\n openhiResourceIdentifier({\n tenantId: spec.tenantId,\n workspaceId: \"\",\n resourceType: \"Membership\",\n id: membershipId,\n }),\n ],\n user: { reference: `User/${user.id}`, type: \"User\" },\n tenant: { reference: `Tenant/${spec.tenantId}`, type: \"Tenant\" },\n period: DEMO_PERIOD,\n});\n\nconst roleAssignmentResourceBody = (\n scenario: string,\n tenantId: string,\n user: DemoDevUser,\n roleCode: PlatformRoleCode,\n roleAssignmentId: string,\n): Record<string, unknown> => ({\n identifier: [\n demoScenarioIdentifier(scenario, `roleassignment-${user.id}-${roleCode}`),\n openhiResourceIdentifier({\n tenantId,\n workspaceId: \"\",\n resourceType: \"RoleAssignment\",\n id: roleAssignmentId,\n }),\n ],\n user: { reference: `User/${user.id}`, type: \"User\" },\n role: { reference: `Role/${idForRoleCode(roleCode)}`, type: \"Role\" },\n tenant: { reference: `Tenant/${tenantId}`, type: \"Tenant\" },\n period: DEMO_PERIOD,\n});\n\n/** Build the FHIR User resource body for a seeded dev user. */\nconst userResourceBody = (\n user: DemoDevUser,\n cognitoSub: string,\n): Record<string, unknown> => ({\n resourceType: \"User\",\n id: user.id,\n name: [{ text: user.email }],\n status: \"active\",\n cognitoSub,\n currentTenant: { reference: `Tenant/${PLACEHOLDER_TENANT_ID}` },\n currentWorkspace: { reference: `Workspace/${PLACEHOLDER_WORKSPACE_ID}` },\n});\n\n/**\n * Idempotent User upsert. The User entity is non-tenant-isolated, so\n * the put is unconditional — the entity's PK is `USER#ID#<id>` and\n * supplying the same id re-asserts the same row.\n */\nconst upsertUser = async (\n context: OpenHiContext,\n user: DemoDevUser,\n cognitoSub: string,\n): Promise<void> => {\n const service = getDynamoControlService();\n const resource = userResourceBody(user, cognitoSub);\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n await service.entities.user\n .put({\n id: user.id,\n cognitoSub,\n resource: JSON.stringify(resource),\n summary,\n vid: \"1\",\n lastUpdated: context.date ?? new Date().toISOString(),\n })\n .go();\n};\n\n/**\n * Phase 3 helper: upsert one workspace's data-plane FHIR resources\n * (Patient/Practitioner/Observation/Encounter/Account). Parents are\n * written before children that reference them so direct `Resource/<id>`\n * references resolve on read paths that follow them — Patient first,\n * then Practitioner, then Observation/Encounter/Account which may\n * carry `subject: Patient/...` references.\n *\n * Every create operation's `body.id` is supplied from the fixture, so\n * re-fires after dedup-TTL expiry upsert the same record (ElectroDB's\n * `put` is unconditional — same composite key overwrites the prior\n * row).\n *\n * Each individual create is wrapped via {@link tryRun}: a failed\n * Patient does not skip the remaining Patients or the downstream\n * Practitioner/Observation/Encounter/Account writes in this workspace,\n * and a failure here does not propagate up to skip the next workspace\n * in the outer loop. Downstream resources that reference a failed\n * parent (`subject: Patient/<id>`) will likely fail too — that is\n * fine, they are attempted, the failures recorded, and the next\n * deploy upserts them once the parent lands.\n */\nconst seedWorkspaceDataPlane = async (\n baseContext: OpenHiContext,\n group: DemoWorkspaceDataPlaneFixtures,\n failures: Array<SeedFailure>,\n): Promise<void> => {\n const workspaceContext: OpenHiContext = {\n ...baseContext,\n tenantId: group.tenantId,\n workspaceId: group.workspaceId,\n };\n const scope = `${group.tenantId}/${group.workspaceId}`;\n for (const patient of group.patients) {\n await tryRun(failures, \"phase-3\", scope, \"Patient\", patient.id ?? \"\", () =>\n createPatientOperation({\n context: workspaceContext,\n body: patient,\n }),\n );\n }\n for (const practitioner of group.practitioners) {\n await tryRun(\n failures,\n \"phase-3\",\n scope,\n \"Practitioner\",\n practitioner.id ?? \"\",\n () =>\n createPractitionerOperation({\n context: workspaceContext,\n body: practitioner,\n }),\n );\n }\n for (const observation of group.observations) {\n await tryRun(\n failures,\n \"phase-3\",\n scope,\n \"Observation\",\n observation.id ?? \"\",\n () =>\n createObservationOperation({\n context: workspaceContext,\n body: observation,\n }),\n );\n }\n for (const encounter of group.encounters) {\n await tryRun(\n failures,\n \"phase-3\",\n scope,\n \"Encounter\",\n encounter.id ?? \"\",\n () =>\n createEncounterOperation({\n context: workspaceContext,\n body: encounter,\n }),\n );\n }\n for (const account of group.accounts) {\n await tryRun(failures, \"phase-3\", scope, \"Account\", account.id ?? \"\", () =>\n createAccountOperation({\n context: workspaceContext,\n body: account,\n }),\n );\n }\n};\n\n/**\n * Upsert the full demo-data graph. Walks each spec in\n * {@link DEMO_TENANT_SPECS}: Tenant → Workspaces. Then per dev user:\n * Cognito user → DynamoDB User → per-tenant Membership +\n * `tenant-admin` RoleAssignment → platform-scoped `system-admin`\n * RoleAssignment. Finally walks {@link DEMO_DATA_PLANE_FIXTURES} and\n * upserts the OPS-009 v1 FHIR resource set (Patient, Practitioner,\n * Observation, Encounter, Account) into each demo workspace. Every\n * put is keyed by a deterministic stable id so re-runs after\n * dedup-TTL expiry upsert the same records.\n *\n * Self-healing-on-every-deploy contract: every individual upsert\n * across all three phases is wrapped via {@link tryRun}. A single\n * item's failure (transient AWS error, write-time constraint\n * violation, etc.) is collected into a per-call accumulator and\n * does not skip the remaining items in its phase. After all phases\n * run, collected failures are aggregate-thrown as a single error so\n * EventBridge still routes the workflow into its failure-detection\n * path (DLQ + CloudWatch alarm) and the outer `runSeedDemoData`\n * records `markFailed` on the dedup row. Because every put is keyed\n * by a deterministic stable id, the next deploy re-attempts every\n * item — failed items eventually heal, successful items overwrite\n * themselves with the same body.\n *\n * Exported so the seeder test file can exercise it directly against\n * a mocked DynamoControlService; the production handler reaches it\n * through {@link SeedDemoDataDependencies.seedDemoGraph}.\n */\nexport const seedDemoGraph = async (params: {\n baseContext: OpenHiContext;\n devUsers: ReadonlyArray<DemoDevUser>;\n cognito: CognitoProvisioner;\n}): Promise<void> => {\n const { baseContext, devUsers, cognito } = params;\n const failures: Array<SeedFailure> = [];\n\n // Phase 1: Tenants + Workspaces (no Memberships / RoleAssignments\n // yet — those need the per-user Cognito sub).\n //\n // Each tenant and each workspace is wrapped individually. A failed\n // tenant create does NOT skip its workspaces — the tenant may\n // already exist from a prior deploy (the put is unconditional, so\n // a duplicate is a no-op; we treat all errors as recoverable here\n // and let the workspace writes proceed). A failed workspace does\n // not skip the next workspace under the same tenant or any\n // subsequent tenant.\n for (const spec of DEMO_TENANT_SPECS) {\n const tenantContext: OpenHiContext = {\n ...baseContext,\n tenantId: spec.tenantId,\n };\n\n await tryRun(\n failures,\n \"phase-1\",\n spec.tenantId,\n \"Tenant\",\n spec.tenantId,\n () =>\n createTenantOperation({\n context: tenantContext,\n body: { id: spec.tenantId, resource: tenantResourceBody(spec) },\n }),\n );\n\n for (const workspace of spec.workspaces) {\n await tryRun(\n failures,\n \"phase-1\",\n spec.tenantId,\n \"Workspace\",\n workspace.id,\n () =>\n createWorkspaceOperation({\n context: tenantContext,\n body: {\n id: workspace.id,\n resource: workspaceResourceBody(spec, workspace),\n },\n }),\n );\n }\n }\n\n // Phase 2: per-dev-user records. The Cognito provisioner is the\n // only step that can fail in a way EventBridge can usefully retry —\n // it runs first because every subsequent write needs the\n // `cognitoSub` it returns. If `ensureUser` throws, skip the whole\n // user (we have no sub to write into the User row).\n //\n // For each individual call inside the per-user block we use\n // {@link tryRun} so a Membership failure in tenant 3 does not skip\n // the Membership + RAs in tenant 4 for the same user, and the\n // platform-scoped RA is attempted last regardless of any earlier\n // per-tenant failure.\n //\n // `upsertUser` is wrapped via tryRun BUT we do NOT short-circuit\n // the per-tenant writes on its failure. The User row carries\n // `cognitoSub` for JWT-claim resolution; the Memberships and\n // RoleAssignments only reference the User by stable id. A previous\n // deploy may have already written the User row with the correct\n // sub. Attempting the per-tenant writes lets the next deploy heal\n // any partial state.\n for (const user of devUsers) {\n let cognitoSub: string;\n try {\n cognitoSub = await cognito.ensureUser(user.email);\n } catch (err) {\n failures.push({\n phase: \"phase-2\",\n scope: user.id,\n resourceType: \"CognitoUser\",\n resourceId: user.email,\n error: err,\n });\n continue;\n }\n\n await tryRun(failures, \"phase-2\", user.id, \"User\", user.id, () =>\n upsertUser(baseContext, user, cognitoSub),\n );\n\n for (const spec of DEMO_TENANT_SPECS) {\n const tenantContext: OpenHiContext = {\n ...baseContext,\n tenantId: spec.tenantId,\n };\n const userScope = `${user.id}@${spec.tenantId}`;\n\n const membershipId = demoMembershipId(user.id, spec.tenantId);\n await tryRun(\n failures,\n \"phase-2\",\n userScope,\n \"Membership\",\n membershipId,\n () =>\n createMembershipOperation({\n context: tenantContext,\n body: {\n id: membershipId,\n resource: membershipResourceBody(spec, user, membershipId),\n },\n }),\n );\n\n for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {\n const raId = demoRoleAssignmentId(user.id, spec.tenantId, roleCode);\n await tryRun(\n failures,\n \"phase-2\",\n userScope,\n \"RoleAssignment\",\n raId,\n () =>\n createRoleAssignmentOperation({\n context: tenantContext,\n body: {\n id: raId,\n resource: roleAssignmentResourceBody(\n spec.scenario,\n spec.tenantId,\n user,\n roleCode,\n raId,\n ),\n },\n }),\n );\n }\n }\n\n // Platform-scoped system-admin RoleAssignment. The sentinel\n // tenantId is PLATFORM_SCOPE_TENANT_ID — there is no real Tenant\n // to point at, so the operation runs with an OpenHiContext whose\n // tenantId is the sentinel. The write-time tenant-membership\n // constraint short-circuits for this sentinel so no Membership\n // prerequisite is required.\n const platformContext: OpenHiContext = {\n ...baseContext,\n tenantId: PLATFORM_SCOPE_TENANT_ID,\n };\n const platformRoleCode = PLATFORM_ROLE_CODE.SYSTEM_ADMIN;\n const platformRaId = demoRoleAssignmentId(\n user.id,\n PLATFORM_SCOPE_TENANT_ID,\n platformRoleCode,\n );\n await tryRun(\n failures,\n \"phase-2\",\n `${user.id}@${PLATFORM_SCOPE_TENANT_ID}`,\n \"RoleAssignment\",\n platformRaId,\n () =>\n createRoleAssignmentOperation({\n context: platformContext,\n body: {\n id: platformRaId,\n resource: roleAssignmentResourceBody(\n \"platform\",\n PLATFORM_SCOPE_TENANT_ID,\n user,\n platformRoleCode,\n platformRaId,\n ),\n },\n }),\n );\n }\n\n // Phase 3: data-plane FHIR resources (OPS-009 v1 set) per demo\n // workspace. Runs after the control-plane phases so the Tenants\n // and Workspaces those resources implicitly belong to already\n // exist by the time we write into them. The placeholder tenant\n // carries no data-plane fixtures.\n //\n // `seedWorkspaceDataPlane` already wraps each individual create\n // via tryRun; we wrap the helper call itself in an outer\n // try/catch as defence-in-depth so an unexpected throw from the\n // helper (programmer error, OOM, etc.) does not skip the next\n // workspace.\n for (const group of DEMO_DATA_PLANE_FIXTURES) {\n try {\n await seedWorkspaceDataPlane(baseContext, group, failures);\n } catch (err) {\n failures.push({\n phase: \"phase-3\",\n scope: `${group.tenantId}/${group.workspaceId}`,\n resourceType: \"Workspace\",\n resourceId: group.workspaceId,\n error: err,\n });\n }\n }\n\n if (failures.length > 0) {\n throw aggregateFailureError(failures);\n }\n};\n\n/**\n * Test-visible orchestrator. The production `handler` calls this with\n * real dependencies and the hardcoded {@link DEV_USERS} list; unit\n * tests inject fakes and pass the dev-user list directly.\n */\nexport const runSeedDemoData = async (\n event: SeedDemoDataEvent,\n deps: SeedDemoDataDependencies,\n devUsers: ReadonlyArray<DemoDevUser>,\n): Promise<void> => {\n const parsed = parseWorkflowEvent(event, PlatformSystemDataSeededV1);\n\n const recordResult = await deps.dedupClient.recordIfAbsent({\n consumerName: SEED_DEMO_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n });\n if (!recordResult.recorded) {\n return;\n }\n\n const baseContext: OpenHiContext = {\n tenantId: \"\",\n workspaceId: \"\",\n date: parsed.envelope.occurredAt,\n actorId: \"platform-deploy-bridge\",\n actorName: \"Platform Deploy Bridge\",\n actorType: \"internal-system\",\n source: \"step-function\",\n };\n\n try {\n await deps.verifyRoles();\n await deps.seedDemoGraph({\n baseContext,\n devUsers,\n cognito: deps.cognito,\n });\n } catch (err) {\n // Mark the dedup row failed so the replay tooling (TR-016 follow-up)\n // can re-publish the originating event with a fresh attempt. The\n // re-throw keeps EventBridge's failure-detection path intact — the\n // event ends up in the DLQ + the workflow Lambda's failure\n // CloudWatch alarm fires.\n await deps.dedupClient.markFailed({\n consumerName: SEED_DEMO_DATA_CONSUMER_NAME,\n eventId: parsed.dedupKey.eventId,\n attempt: parsed.dedupKey.attempt,\n reason: errorMessage(err),\n });\n throw err;\n }\n};\n\n/**\n * SSM parameter-path prefix that hosts every seeded dev user's\n * password. The full path for a single user is\n * `${SEED_USER_PASSWORD_PARAMETER_PREFIX}<email_safe>/password`\n * where `<email_safe>` is `<user>_at_<domain>` (see\n * {@link emailToSsmPath}). The trailing `/password` segment leaves\n * room for additional per-user parameters under the same email\n * prefix in future phases without renaming the existing entries.\n */\nexport const SEED_USER_PASSWORD_PARAMETER_PREFIX = \"/openhi/seed/users/\";\n\n/**\n * SSM Parameter Store path validation regex. Names must be 1-1011\n * characters, may include alphanumerics plus `_`, `.`, `-`, and\n * `/`. We enforce a stricter subset here — alphanumerics, hyphen,\n * underscore, and period only — because the path is constructed by\n * substituting `@` → `_at_` and any other special character would\n * indicate an email that should have been rejected upstream.\n */\nconst SSM_PATH_SEGMENT = /^[A-Za-z0-9_.-]+$/;\n\n/**\n * Map an email address to its canonical SSM parameter path. The\n * `@` separator is replaced with the literal sentinel `_at_` so\n * the resulting path is a single SSM-legal name. Throws when the\n * email is empty, missing exactly one `@`, or contains characters\n * that would produce an invalid SSM path.\n *\n * Example: `alice@codedrifters.com` →\n * `/openhi/seed/users/alice_at_codedrifters.com/password`.\n */\nexport const emailToSsmPath = (email: string): string => {\n if (typeof email !== \"string\" || email.length === 0) {\n throw new Error(\n `emailToSsmPath: email must be a non-empty string (received \"${String(email)}\").`,\n );\n }\n const atIdx = email.indexOf(\"@\");\n if (atIdx === -1 || atIdx !== email.lastIndexOf(\"@\")) {\n throw new Error(\n `emailToSsmPath: email \"${email}\" must contain exactly one \"@\" character.`,\n );\n }\n const localPart = email.slice(0, atIdx);\n const domainPart = email.slice(atIdx + 1);\n if (localPart.length === 0 || domainPart.length === 0) {\n throw new Error(\n `emailToSsmPath: email \"${email}\" must have a non-empty local-part and domain.`,\n );\n }\n if (!SSM_PATH_SEGMENT.test(localPart) || !SSM_PATH_SEGMENT.test(domainPart)) {\n throw new Error(\n `emailToSsmPath: email \"${email}\" contains characters that would produce ` +\n \"an invalid SSM parameter path (only A-Z, a-z, 0-9, '.', '-', and '_' are allowed).\",\n );\n }\n return `${SEED_USER_PASSWORD_PARAMETER_PREFIX}${localPart}_at_${domainPart}/password`;\n};\n\n/**\n * Module-scoped SSM client. Lambda containers reuse the same\n * `SSMClient` instance across invocations so the TLS connection\n * + credential-resolver state warms once per container. Mirrors\n * the `CognitoIdentityProviderClient` / `DynamoDBClient` reuse\n * pattern at the bottom of this file.\n */\nlet cachedSsmClient: SSMClient | undefined;\n\nconst getSsmClient = (): SSMClient => {\n if (!cachedSsmClient) {\n cachedSsmClient = new SSMClient({});\n }\n return cachedSsmClient;\n};\n\n/**\n * Test seam: reset the module-scoped SSM client cache. Unit tests\n * that swap in different mocks across `it` blocks call this in\n * `beforeEach` so each test gets a fresh client instance.\n */\nexport const __resetSsmClientForTests = (): void => {\n cachedSsmClient = undefined;\n};\n\n/**\n * Read a seeded dev user's password from SSM Parameter Store. The\n * parameter is expected to be a `SecureString` provisioned out of\n * band by an operator (see the runbook in phase 3 of #1249). On\n * `ParameterNotFound`, the error message includes the exact\n * expected path so the operator can copy-paste-fix.\n */\nexport const fetchSeedUserPassword = async (email: string): Promise<string> => {\n const path = emailToSsmPath(email);\n const client = getSsmClient();\n try {\n const result = await client.send(\n new GetParameterCommand({ Name: path, WithDecryption: true }),\n );\n const value = result.Parameter?.Value;\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(\n `fetchSeedUserPassword: SSM parameter \"${path}\" returned an empty value.`,\n );\n }\n return value;\n } catch (err) {\n if (err instanceof ParameterNotFound) {\n throw new Error(\n `fetchSeedUserPassword: SSM parameter \"${path}\" not found. ` +\n `Provision a SecureString at \"${path}\" with the dev user's password before re-running seed-demo-data.`,\n );\n }\n throw err;\n }\n};\n\n/**\n * Production Cognito provisioner backed by the AWS SDK. Reads the\n * user-pool id from the env var the lambda construct injects.\n *\n * Idempotency contract:\n * - On first invocation, calls `AdminCreateUser` (with\n * `MessageAction: SUPPRESS` so no invitation email fires) then\n * `AdminSetUserPassword` (permanent, from the SSM-sourced\n * value). Returns the new user's sub.\n * - On subsequent invocations, `AdminCreateUser` throws\n * `UsernameExistsException`; the provisioner catches it, calls\n * `AdminGetUser` to read the existing user's sub, and **also\n * re-applies the current SSM-sourced password** via\n * `AdminSetUserPassword`. This is the rotation seam: operators\n * update the SSM SecureString value and re-publish the seed\n * event to push a fresh password down to Cognito.\n *\n * The SSM password is fetched once per email up-front so the new-\n * user and rotation branches share the same value and at most one\n * `GetParameter` call lands per `ensureUser` invocation.\n */\nexport const productionCognitoProvisioner = (): CognitoProvisioner => {\n const client = new CognitoIdentityProviderClient({});\n const userPoolId = process.env[SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR];\n if (!userPoolId || userPoolId.trim() === \"\") {\n throw new Error(\n `${SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR} is not set; the seed-demo-data Lambda ` +\n \"cannot provision Cognito users without a user-pool id.\",\n );\n }\n const subFromAttributes = (\n attrs: ReadonlyArray<AttributeType> | undefined,\n ): string | undefined => {\n for (const attr of attrs ?? []) {\n if (attr.Name === \"sub\" && typeof attr.Value === \"string\") {\n return attr.Value;\n }\n }\n return undefined;\n };\n const setPassword = async (\n email: string,\n password: string,\n ): Promise<void> => {\n await client.send(\n new AdminSetUserPasswordCommand({\n UserPoolId: userPoolId,\n Username: email,\n Password: password,\n Permanent: true,\n }),\n );\n };\n return {\n ensureUser: async (email: string): Promise<string> => {\n // Fetch the SSM-sourced password up-front so both the\n // new-user path and the rotation path consume the same\n // value with a single `GetParameter` call.\n const password = await fetchSeedUserPassword(email);\n try {\n const created = await client.send(\n new AdminCreateUserCommand({\n UserPoolId: userPoolId,\n Username: email,\n MessageAction: \"SUPPRESS\",\n UserAttributes: [\n { Name: \"email\", Value: email },\n { Name: \"email_verified\", Value: \"true\" },\n ],\n }),\n );\n await setPassword(email, password);\n const sub = subFromAttributes(created.User?.Attributes);\n if (!sub) {\n throw new Error(\n `AdminCreateUser response for \"${email}\" did not carry a sub attribute.`,\n );\n }\n return sub;\n } catch (err) {\n if (err instanceof UsernameExistsException) {\n // User already exists — re-apply the current SSM-sourced\n // password so operator rotations propagate, then read the\n // existing sub. AdminSetUserPassword on an existing user\n // overwrites the credential without affecting the sub.\n await setPassword(email, password);\n const got = await client.send(\n new AdminGetUserCommand({\n UserPoolId: userPoolId,\n Username: email,\n }),\n );\n const sub = subFromAttributes(got.UserAttributes);\n if (!sub) {\n throw new Error(\n `AdminGetUser response for \"${email}\" did not carry a sub attribute.`,\n );\n }\n return sub;\n }\n throw err;\n }\n },\n };\n};\n\nconst productionDependencies = (): SeedDemoDataDependencies => {\n const dynamodb = new DynamoDBClient({});\n const cognito = productionCognitoProvisioner();\n return {\n dedupClient: workflowDedupClient(dynamodb),\n verifyRoles: verifySystemRolesExist,\n seedDemoGraph,\n cognito,\n };\n};\n\nexport const handler = async (event: SeedDemoDataEvent): Promise<void> =>\n runSeedDemoData(event, productionDependencies(), DEV_USERS);\n","import { PLATFORM_ROLE_CODE, type PlatformRoleCode } from \"@openhi/types\";\nimport { PlatformSystemDataSeededV1 } from \"@openhi/workflows\";\nimport { PLATFORM_SCOPE_TENANT_ID } from \"../../../data/operations/control/membership-constraints/platform-scope-tenant-id\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/events.md\n */\n\n/**\n * Stable logical name this workflow registers with the shared\n * `WorkflowDedupTable` (TR-015). Used in both the construct grant\n * (`workflowDedupTable.grantConsumer(lambda, SEED_DEMO_DATA_CONSUMER_NAME)`)\n * and the handler's runtime `recordIfAbsent` call — keep them aligned by\n * importing this constant in both places.\n */\nexport const SEED_DEMO_DATA_CONSUMER_NAME = \"seed-demo-data\";\n\n/** FHIR `Identifier.system` for the demo-scenario URN scheme. */\nexport const DEMO_URN_SYSTEM = \"urn:openhi:demo\";\n\n/**\n * FHIR `Identifier.system` for the canonical OpenHI resource URN\n * (ADR 2026-03-12-01). The value form is\n * `urn:ohi:<tenantId>:<workspaceId>:<resourceType>:<id>`; empty\n * workspaceId means the resource has tenant-wide scope (or is itself\n * the workspace identity, in the Workspace case).\n */\nexport const OPENHI_RESOURCE_URN_SYSTEM = \"http://openhi.org/\";\n\n/**\n * Period stamped on every demo Membership and RoleAssignment. Fixed\n * start so re-runs produce byte-identical bodies (decision #4); no\n * end so demo memberships are open-ended.\n */\nexport const DEMO_PERIOD = { start: \"2026-01-01T00:00:00Z\" } as const;\n\n/**\n * Sentinel `tenantId` used on every dev user's `system-admin`\n * RoleAssignment. A `system-admin` RA is platform-scoped (it spans\n * every tenant), but the RoleAssignment entity requires a tenantId on\n * its key for sharding — there is no real tenant to point at.\n *\n * The canonical definition lives in\n * `data/operations/control/membership-constraints/platform-scope-tenant-id.ts`\n * so the write-time tenant-membership constraint can short-circuit\n * the sentinel without the data layer importing from the workflow.\n * Re-exported here so existing imports that resolve through\n * `seed-demo-data/events` keep working.\n */\nexport { PLATFORM_SCOPE_TENANT_ID };\n\n/** Placeholder Tenant id seeded by the workflow as the dev-user `currentTenant`. */\nexport const PLACEHOLDER_TENANT_ID = \"placeholder-tenant-id\";\n\n/** Placeholder Workspace id seeded by the workflow as the dev-user `currentWorkspace`. */\nexport const PLACEHOLDER_WORKSPACE_ID = \"placeholder-workspace-id\";\n\n/**\n * Dev-user descriptor. Every entry produces:\n * - one Cognito user (idempotent create-then-skip),\n * - one DynamoDB User record (id = `dev-<email-local-part>`),\n * - four Memberships (placeholder + the three demo tenants),\n * - four `tenant-admin` RoleAssignments (one per tenant the user\n * belongs to),\n * - one `system-admin` RoleAssignment scoped to {@link PLATFORM_SCOPE_TENANT_ID}.\n */\nexport interface DemoDevUser {\n /** Stable DynamoDB User id. */\n readonly id: string;\n /** Email used as the Cognito `Username` and as the seed for the password algorithm. */\n readonly email: string;\n}\n\n/**\n * Hardcoded dev-user roster. Adding a new developer is a one-line\n * change here plus a re-deploy. The list intentionally lives in-source\n * (not behind an env-var seam) so the IAM grant in\n * `seed-demo-data-lambda.ts` can enumerate per-user PKs at synth time.\n */\nexport const DEV_USERS: ReadonlyArray<DemoDevUser> = [\n { id: \"dev-russell\", email: \"russell@codedrifters.com\" },\n { id: \"dev-cameron\", email: \"cameron@codedrifters.com\" },\n { id: \"dev-neelima\", email: \"neelima@codedrifters.com\" },\n { id: \"dev-garon\", email: \"garon@codedrifters.com\" },\n { id: \"dev-dave\", email: \"dave@codedrifters.com\" },\n { id: \"dev-drew\", email: \"drew@codedrifters.com\" },\n { id: \"dev-jessica\", email: \"jessica@codedrifters.com\" },\n { id: \"dev-jared\", email: \"jared@codedrifters.com\" },\n { id: \"dev-goddess\", email: \"goddess@codedrifters.com\" },\n // Dedicated end-to-end test principal for admin-console Playwright\n // specs (issue #1275). Reuses the standard DEV_USERS plumbing\n // (Cognito user + DynamoDB User + per-tenant Memberships +\n // tenant-admin RAs + platform-scoped system-admin RA) and the\n // existing SSM-sourced password convention from #1249 — the\n // operator provisions the SecureString at\n // /openhi/seed/users/e2e-admin-console_at_codedrifters.com/password\n // out of band, and the seeded Cognito user picks it up via\n // AdminSetUserPassword on every seed run.\n { id: \"dev-e2e-admin-console\", email: \"e2e-admin-console@codedrifters.com\" },\n];\n\n/**\n * A single workspace inside a demo tenant. The mixed tenant has two\n * workspaces (wound-care and primary-care sub-workspaces); the other\n * two tenants have one each.\n */\nexport interface DemoWorkspaceSpec {\n /** Stable id (DynamoDB record id; also drives the canonical OHI URN). */\n readonly id: string;\n /** FHIR `Workspace.name`. */\n readonly name: string;\n /**\n * Role suffix used in the demo URN value (`<scenario>:<roleSuffix>`).\n * `workspace` for single-workspace tenants, `workspace-<sub>` for the\n * mixed tenant.\n */\n readonly roleSuffix: string;\n}\n\n/**\n * One demo tenant + the workspaces it owns. Re-exported via {@link\n * DEMO_TENANT_SPECS} as the canonical list; iterate that constant in\n * the handler and the IAM-grant builder so the value sets agree.\n */\nexport interface DemoTenantSpec {\n /**\n * Scenario slug — `placeholder`, `demo-wound-care`, `demo-primary-care`,\n * `demo-mixed`. The placeholder tenant's slug is `placeholder`; the\n * three demo tenants use `demo-*` slugs.\n */\n readonly scenario: string;\n /** Stable id (DynamoDB record id; also drives the canonical OHI URN). */\n readonly tenantId: string;\n /** FHIR `Tenant.name`. */\n readonly tenantName: string;\n /** Workspaces owned by this tenant. */\n readonly workspaces: ReadonlyArray<DemoWorkspaceSpec>;\n}\n\n/**\n * The full demo-tenant graph. Four entries: the placeholder tenant the\n * JWT-claim fallback resolves to, plus the three v1 demo scenarios\n * (OPS-009 §\"v1 scenarios\"):\n *\n * 0. Placeholder tenant — dereferences the JWT-claim fallback in\n * `pre-token-generation.handler.ts` and acts as every dev user's\n * `currentTenant`/`currentWorkspace` so Post-Confirmation skips\n * default provisioning when a seeded User signs in.\n * 1. Single-workspace wound-care tenant.\n * 2. Single-workspace primary-care tenant.\n * 3. Two-workspace mixed tenant — exercises the cross-workspace\n * isolation flow that single-workspace tenants cannot.\n */\nexport const DEMO_TENANT_SPECS: ReadonlyArray<DemoTenantSpec> = [\n {\n scenario: \"placeholder\",\n tenantId: PLACEHOLDER_TENANT_ID,\n tenantName: \"OpenHI Placeholder Tenant\",\n workspaces: [\n {\n id: PLACEHOLDER_WORKSPACE_ID,\n name: \"OpenHI Placeholder Workspace\",\n roleSuffix: \"workspace\",\n },\n ],\n },\n {\n scenario: \"demo-wound-care\",\n tenantId: \"demo-wound-care-tenant\",\n tenantName: \"Cedarbrook Wound Healing Institute\",\n workspaces: [\n {\n id: \"demo-wound-care-workspace\",\n name: \"Cedarbrook Outpatient Wound Clinic\",\n roleSuffix: \"workspace\",\n },\n ],\n },\n {\n scenario: \"demo-primary-care\",\n tenantId: \"demo-primary-care-tenant\",\n tenantName: \"Maple Ridge Family Medicine\",\n workspaces: [\n {\n id: \"demo-primary-care-workspace\",\n name: \"Maple Ridge Main Street Office\",\n roleSuffix: \"workspace\",\n },\n ],\n },\n {\n scenario: \"demo-mixed\",\n tenantId: \"demo-mixed-tenant\",\n tenantName: \"Northbridge Health Network\",\n workspaces: [\n {\n id: \"demo-mixed-workspace-wound-care\",\n name: \"Northbridge Wound Care Center\",\n roleSuffix: \"workspace-wound-care\",\n },\n {\n id: \"demo-mixed-workspace-primary-care\",\n name: \"Northbridge Family Practice\",\n roleSuffix: \"workspace-primary-care\",\n },\n ],\n },\n];\n\n/** Stable Membership id derived from `(devUserId, tenantId)`. */\nexport const demoMembershipId = (devUserId: string, tenantId: string): string =>\n `demo-membership-${devUserId}-${tenantId}`;\n\n/**\n * Stable RoleAssignment id derived from `(devUserId, tenantId, roleCode)`.\n * Each (user, tenant, role) tuple maps to exactly one record — re-runs\n * upsert the same id.\n */\nexport const demoRoleAssignmentId = (\n devUserId: string,\n tenantId: string,\n roleCode: PlatformRoleCode,\n): string => `demo-roleassignment-${devUserId}-${tenantId}-${roleCode}`;\n\n/**\n * Demo-scenario FHIR `Identifier` entry — `urn:openhi:demo:<scenario>:<role>`.\n */\nexport const demoScenarioIdentifier = (\n scenario: string,\n roleSuffix: string,\n): { system: string; value: string } => ({\n system: DEMO_URN_SYSTEM,\n value: `${scenario}:${roleSuffix}`,\n});\n\n/**\n * Canonical OpenHI resource FHIR `Identifier` entry per ADR\n * 2026-03-12-01. `workspaceId` is empty for both Tenant resources and\n * Workspace resources — for a Tenant, the resource is tenant-scoped\n * with no workspace context; for a Workspace, the resource IS the\n * workspace identity rather than living inside one.\n */\nexport const openhiResourceIdentifier = (params: {\n tenantId: string;\n workspaceId: string;\n resourceType: string;\n id: string;\n}): { use: string; system: string; value: string } => ({\n use: \"unversioned\",\n system: OPENHI_RESOURCE_URN_SYSTEM,\n value: `urn:ohi:${params.tenantId}:${params.workspaceId}:${params.resourceType}:${params.id}`,\n});\n\n/**\n * Roles every dev user holds in every tenant they belong to. Per scope\n * decision Q3, every dev user is `tenant-admin` in every tenant — there\n * is no per-(user, tenant) variance to drive from.\n */\nexport const demoRolesForUserInTenant = (\n _user: DemoDevUser,\n _tenantId: string,\n): ReadonlyArray<PlatformRoleCode> => {\n void _user;\n void _tenantId;\n return [PLATFORM_ROLE_CODE.TENANT_ADMIN];\n};\n\n/**\n * The trigger this workflow subscribes to on the control event bus.\n * The `seed-system-data` workflow publishes\n * `platform.system-data-seeded.v1` after it has finished re-asserting\n * the platform-singleton Role records; on every fire this workflow\n * re-asserts the demo-tenant graph.\n *\n * The indirection (subscribing to `system-data-seeded` rather than to\n * the raw `platform.deployment-completed.v1` event) enforces a strict\n * happens-before edge between the system-data seed and the demo-data\n * seed. Without it, both workflows would fire in parallel from the\n * same deploy event and seed-demo-data's pre-flight Role check would\n * race seed-system-data's puts — a race that EventBridge retries\n * resolve, but only after firing the failure alarm at least once on a\n * fresh environment's first deploy.\n *\n * Re-exported from `@openhi/workflows` for symmetry with the handler's\n * import — keep the construct + handler reading from the same registry\n * entry so a rename upstream surfaces at both call sites.\n */\nexport { PlatformSystemDataSeededV1 };\n","import type {\n Account,\n Encounter,\n Observation,\n Patient,\n Practitioner,\n} from \"@openhi/types\";\nimport {\n DEMO_TENANT_SPECS,\n PLACEHOLDER_TENANT_ID,\n demoScenarioIdentifier,\n openhiResourceIdentifier,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/seed-demo-data/data-plane-fixtures.md\n *\n * Hand-authored FHIR data-plane fixture bodies the `seed-demo-data`\n * workflow upserts into the data store on every non-prod deploy.\n * Mirrors the OPS-009 v1 resource set: Patient, Practitioner,\n * Observation, Encounter, Account.\n *\n * Ids are deterministic — re-fires of the workflow upsert the same\n * records, satisfying the workflow's idempotency contract (no\n * duplicates) and letting the IAM grant in `seed-demo-data-lambda.ts`\n * enumerate exact-match `dynamodb:LeadingKeys` rather than a wildcard.\n *\n * The placeholder tenant carries no data-plane fixtures — only the\n * three real demo tenants (wound-care, primary-care, mixed) get\n * Patient/Practitioner/Observation/Encounter/Account records. The\n * placeholder tenant exists solely as a routing target for the\n * Cognito pre-token-generation fallback and never holds clinical\n * data.\n */\n\n/**\n * Logical group of FHIR resources owned by a single (tenant, workspace)\n * pair. The workflow walks `DEMO_DATA_PLANE_FIXTURES` and writes every\n * entry against the matching workspace's `OpenHiContext`.\n */\nexport interface DemoWorkspaceDataPlaneFixtures {\n readonly tenantId: string;\n readonly workspaceId: string;\n /**\n * Scenario slug used in the demo-URN identifier — mirrors the\n * `DemoTenantSpec.scenario` value for the parent tenant. For the\n * mixed tenant both workspaces share the `demo-mixed` scenario.\n */\n readonly scenario: string;\n readonly patients: ReadonlyArray<Patient>;\n readonly practitioners: ReadonlyArray<Practitioner>;\n readonly observations: ReadonlyArray<Observation>;\n readonly encounters: ReadonlyArray<Encounter>;\n readonly accounts: ReadonlyArray<Account>;\n}\n\n/**\n * Map a tenant scenario to a `(roleSuffix, tenantId, workspaceId)`\n * triple plus the demo + OHI identifiers. Keeps the per-resource\n * builders compact below — they call this once per body to stamp the\n * two-identifier pair that matches the control-plane pattern.\n */\nconst fixtureIdentifiers = (\n scenario: string,\n tenantId: string,\n workspaceId: string,\n resourceType: string,\n id: string,\n roleSuffix: string,\n) => [\n demoScenarioIdentifier(scenario, roleSuffix),\n openhiResourceIdentifier({\n tenantId,\n workspaceId,\n resourceType,\n id,\n }),\n];\n\nconst buildWoundCareFixtures = (\n scenario: string,\n tenantId: string,\n workspaceId: string,\n idPrefix: string,\n): DemoWorkspaceDataPlaneFixtures => ({\n tenantId,\n workspaceId,\n scenario,\n patients: [\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-1`,\n `patient-1`,\n ),\n active: true,\n name: [{ family: \"Carter\", given: [\"Eleanor\"], use: \"official\" }],\n gender: \"female\",\n birthDate: \"1952-04-18\",\n },\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-2`,\n `patient-2`,\n ),\n active: true,\n name: [{ family: \"Nguyen\", given: [\"Hao\"], use: \"official\" }],\n gender: \"male\",\n birthDate: \"1968-11-02\",\n },\n ],\n practitioners: [\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-1`,\n `practitioner-1`,\n ),\n active: true,\n name: [{ family: \"Reyes\", given: [\"Maria\"], prefix: [\"Dr.\"] }],\n gender: \"female\",\n },\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-2`,\n `practitioner-2`,\n ),\n active: true,\n name: [{ family: \"Okafor\", given: [\"Chinedu\"], prefix: [\"Dr.\"] }],\n gender: \"male\",\n },\n ],\n observations: [\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-1`,\n `observation-1`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"39135-9\",\n display: \"Wound size\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n valueString: \"3.2cm x 2.1cm\",\n },\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-2`,\n `observation-2`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"72287-2\",\n display: \"Wound exudate amount\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n valueString: \"moderate\",\n },\n ],\n encounters: [\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-1`,\n `encounter-1`,\n ),\n status: \"finished\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n },\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-2`,\n `encounter-2`,\n ),\n status: \"finished\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n },\n ],\n accounts: [\n {\n resourceType: \"Account\",\n id: `${idPrefix}-account-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Account\",\n `${idPrefix}-account-1`,\n `account-1`,\n ),\n status: \"active\",\n name: \"Wound-care self-pay account\",\n subject: [{ reference: `Patient/${idPrefix}-patient-1` }],\n },\n ],\n});\n\nconst buildPrimaryCareFixtures = (\n scenario: string,\n tenantId: string,\n workspaceId: string,\n idPrefix: string,\n): DemoWorkspaceDataPlaneFixtures => ({\n tenantId,\n workspaceId,\n scenario,\n patients: [\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-1`,\n `patient-1`,\n ),\n active: true,\n name: [{ family: \"Bennett\", given: [\"Sophia\"], use: \"official\" }],\n gender: \"female\",\n birthDate: \"1985-06-09\",\n },\n {\n resourceType: \"Patient\",\n id: `${idPrefix}-patient-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Patient\",\n `${idPrefix}-patient-2`,\n `patient-2`,\n ),\n active: true,\n name: [{ family: \"Patel\", given: [\"Arjun\"], use: \"official\" }],\n gender: \"male\",\n birthDate: \"1979-02-21\",\n },\n ],\n practitioners: [\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-1`,\n `practitioner-1`,\n ),\n active: true,\n name: [{ family: \"Lin\", given: [\"Wei\"], prefix: [\"Dr.\"] }],\n gender: \"female\",\n },\n {\n resourceType: \"Practitioner\",\n id: `${idPrefix}-practitioner-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Practitioner\",\n `${idPrefix}-practitioner-2`,\n `practitioner-2`,\n ),\n active: true,\n name: [{ family: \"Kowalski\", given: [\"Piotr\"], prefix: [\"Dr.\"] }],\n gender: \"male\",\n },\n ],\n observations: [\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-1`,\n `observation-1`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"8480-6\",\n display: \"Systolic blood pressure\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n valueQuantity: { value: 122, unit: \"mm[Hg]\" },\n },\n {\n resourceType: \"Observation\",\n id: `${idPrefix}-observation-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Observation\",\n `${idPrefix}-observation-2`,\n `observation-2`,\n ),\n status: \"final\",\n code: {\n coding: [\n {\n system: \"http://loinc.org\",\n code: \"8462-4\",\n display: \"Diastolic blood pressure\",\n },\n ],\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n valueQuantity: { value: 78, unit: \"mm[Hg]\" },\n },\n ],\n encounters: [\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-1`,\n `encounter-1`,\n ),\n status: \"finished\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-1` },\n },\n {\n resourceType: \"Encounter\",\n id: `${idPrefix}-encounter-2`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Encounter\",\n `${idPrefix}-encounter-2`,\n `encounter-2`,\n ),\n status: \"in-progress\",\n class: {\n system: \"http://terminology.hl7.org/CodeSystem/v3-ActCode\",\n code: \"AMB\",\n display: \"ambulatory\",\n },\n subject: { reference: `Patient/${idPrefix}-patient-2` },\n },\n ],\n accounts: [\n {\n resourceType: \"Account\",\n id: `${idPrefix}-account-1`,\n identifier: fixtureIdentifiers(\n scenario,\n tenantId,\n workspaceId,\n \"Account\",\n `${idPrefix}-account-1`,\n `account-1`,\n ),\n status: \"active\",\n name: \"Primary-care insurance account\",\n subject: [{ reference: `Patient/${idPrefix}-patient-1` }],\n },\n ],\n});\n\n/**\n * Per-workspace fixtures the data-plane phase writes on every fire.\n * The placeholder tenant carries no fixtures. The mixed tenant carries\n * one fixture group per workspace; the two single-workspace tenants\n * carry one each. Total: 4 fixture groups × ≈ 9 resources = ~36\n * data-plane records.\n *\n * Ids embed the tenant + workspace slug so they remain unambiguous\n * across the four workspaces (the FHIR resource id is the only thing\n * that survives into the partition key, so a duplicate id across\n * workspaces would still collide on read paths that scan-by-id).\n */\nexport const DEMO_DATA_PLANE_FIXTURES: ReadonlyArray<DemoWorkspaceDataPlaneFixtures> =\n [\n buildWoundCareFixtures(\n \"demo-wound-care\",\n \"demo-wound-care-tenant\",\n \"demo-wound-care-workspace\",\n \"demo-wound-care\",\n ),\n buildPrimaryCareFixtures(\n \"demo-primary-care\",\n \"demo-primary-care-tenant\",\n \"demo-primary-care-workspace\",\n \"demo-primary-care\",\n ),\n buildWoundCareFixtures(\n \"demo-mixed\",\n \"demo-mixed-tenant\",\n \"demo-mixed-workspace-wound-care\",\n \"demo-mixed-wound-care\",\n ),\n buildPrimaryCareFixtures(\n \"demo-mixed\",\n \"demo-mixed-tenant\",\n \"demo-mixed-workspace-primary-care\",\n \"demo-mixed-primary-care\",\n ),\n ];\n\n/**\n * Regression guard: the fixture groups must each be tied to a Tenant +\n * Workspace combination that exists in {@link DEMO_TENANT_SPECS}.\n * Imported `DEMO_TENANT_SPECS` and `PLACEHOLDER_TENANT_ID` keep the\n * guard self-contained — no test plumbing required.\n */\nconst _validateFixturesAgainstTenantSpecs = (): void => {\n for (const group of DEMO_DATA_PLANE_FIXTURES) {\n if (group.tenantId === PLACEHOLDER_TENANT_ID) {\n throw new Error(\n \"The placeholder tenant must not carry data-plane fixtures.\",\n );\n }\n const tenant = DEMO_TENANT_SPECS.find((s) => s.tenantId === group.tenantId);\n if (!tenant) {\n throw new Error(\n `Fixture references unknown tenantId \"${group.tenantId}\". ` +\n \"Add a matching entry to DEMO_TENANT_SPECS first.\",\n );\n }\n const workspace = tenant.workspaces.find(\n (ws) => ws.id === group.workspaceId,\n );\n if (!workspace) {\n throw new Error(\n `Fixture references unknown workspaceId \"${group.workspaceId}\" ` +\n `for tenant \"${group.tenantId}\".`,\n );\n }\n }\n};\n_validateFixturesAgainstTenantSpecs();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,IAAAA,oBAIO;AA1BP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE,sBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;;;ACpBP,uBAA2C;AAD3C,SAAS,0BAAiD;AAenD,IAAM,+BAA+B;AAGrC,IAAM,kBAAkB;AASxB,IAAM,6BAA6B;AAOnC,IAAM,cAAc,EAAE,OAAO,uBAAuB;AAkBpD,IAAM,wBAAwB;AAG9B,IAAM,2BAA2B;AAwBjC,IAAM,YAAwC;AAAA,EACnD,EAAE,IAAI,eAAe,OAAO,2BAA2B;AAAA,EACvD,EAAE,IAAI,eAAe,OAAO,2BAA2B;AAAA,EACvD,EAAE,IAAI,eAAe,OAAO,2BAA2B;AAAA,EACvD,EAAE,IAAI,aAAa,OAAO,yBAAyB;AAAA,EACnD,EAAE,IAAI,YAAY,OAAO,wBAAwB;AAAA,EACjD,EAAE,IAAI,YAAY,OAAO,wBAAwB;AAAA,EACjD,EAAE,IAAI,eAAe,OAAO,2BAA2B;AAAA,EACvD,EAAE,IAAI,aAAa,OAAO,yBAAyB;AAAA,EACnD,EAAE,IAAI,eAAe,OAAO,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUvD,EAAE,IAAI,yBAAyB,OAAO,qCAAqC;AAC7E;AAsDO,IAAM,oBAAmD;AAAA,EAC9D;AAAA,IACE,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,MACV;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,MACV;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,MACV;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,MACV;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,YAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,mBAAmB,CAAC,WAAmB,aAClD,mBAAmB,SAAS,IAAI,QAAQ;AAOnC,IAAM,uBAAuB,CAClC,WACA,UACA,aACW,uBAAuB,SAAS,IAAI,QAAQ,IAAI,QAAQ;AAK9D,IAAM,yBAAyB,CACpC,UACA,gBACuC;AAAA,EACvC,QAAQ;AAAA,EACR,OAAO,GAAG,QAAQ,IAAI,UAAU;AAClC;AASO,IAAM,2BAA2B,CAAC,YAKc;AAAA,EACrD,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,OAAO,WAAW,OAAO,QAAQ,IAAI,OAAO,WAAW,IAAI,OAAO,YAAY,IAAI,OAAO,EAAE;AAC7F;AAOO,IAAM,2BAA2B,CACtC,OACA,cACoC;AACpC,OAAK;AACL,OAAK;AACL,SAAO,CAAC,mBAAmB,YAAY;AACzC;;;AC3MA,IAAM,qBAAqB,CACzB,UACA,UACA,aACA,cACA,IACA,eACG;AAAA,EACH,uBAAuB,UAAU,UAAU;AAAA,EAC3C,yBAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,IAAM,yBAAyB,CAC7B,UACA,UACA,aACA,cACoC;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,UAAU,OAAO,CAAC,SAAS,GAAG,KAAK,WAAW,CAAC;AAAA,MAChE,QAAQ;AAAA,MACR,WAAW;AAAA,IACb;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,UAAU,OAAO,CAAC,KAAK,GAAG,KAAK,WAAW,CAAC;AAAA,MAC5D,QAAQ;AAAA,MACR,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,eAAe;AAAA,IACb;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,SAAS,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;AAAA,MAC7D,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,UAAU,OAAO,CAAC,SAAS,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;AAAA,MAChE,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,QAAQ;AAAA,UACN;AAAA,YACE,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,MACtD,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,QAAQ;AAAA,UACN;AAAA,YACE,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,MACtD,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,IACxD;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,IACxD;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS,CAAC,EAAE,WAAW,WAAW,QAAQ,aAAa,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;AAEA,IAAM,2BAA2B,CAC/B,UACA,UACA,aACA,cACoC;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,WAAW,OAAO,CAAC,QAAQ,GAAG,KAAK,WAAW,CAAC;AAAA,MAChE,QAAQ;AAAA,MACR,WAAW;AAAA,IACb;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,SAAS,OAAO,CAAC,OAAO,GAAG,KAAK,WAAW,CAAC;AAAA,MAC7D,QAAQ;AAAA,MACR,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,eAAe;AAAA,IACb;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,OAAO,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;AAAA,MACzD,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,CAAC,EAAE,QAAQ,YAAY,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;AAAA,MAChE,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,QAAQ;AAAA,UACN;AAAA,YACE,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,MACtD,eAAe,EAAE,OAAO,KAAK,MAAM,SAAS;AAAA,IAC9C;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,QAAQ;AAAA,UACN;AAAA,YACE,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,MACtD,eAAe,EAAE,OAAO,IAAI,MAAM,SAAS;AAAA,IAC7C;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,IACxD;AAAA,IACA;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,MACA,SAAS,EAAE,WAAW,WAAW,QAAQ,aAAa;AAAA,IACxD;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,cAAc;AAAA,MACd,IAAI,GAAG,QAAQ;AAAA,MACf,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAG,QAAQ;AAAA,QACX;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS,CAAC,EAAE,WAAW,WAAW,QAAQ,aAAa,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;AAcO,IAAM,2BACX;AAAA,EACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAQF,IAAM,sCAAsC,MAAY;AACtD,aAAW,SAAS,0BAA0B;AAC5C,QAAI,MAAM,aAAa,uBAAuB;AAC5C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAS,kBAAkB,KAAK,CAAC,MAAM,EAAE,aAAa,MAAM,QAAQ;AAC1E,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR,wCAAwC,MAAM,QAAQ;AAAA,MAExD;AAAA,IACF;AACA,UAAM,YAAY,OAAO,WAAW;AAAA,MAClC,CAAC,OAAO,GAAG,OAAO,MAAM;AAAA,IAC1B;AACA,QAAI,CAAC,WAAW;AACd,YAAM,IAAI;AAAA,QACR,2CAA2C,MAAM,WAAW,iBAC3C,MAAM,QAAQ;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;AACA,oCAAoC;;;AFrb7B,IAAM,sCACX;AA0CF,IAAM,eAAe,CAAC,QAAyB;AAC7C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,SAAO,OAAO,GAAG;AACnB;AA0CA,IAAM,SAAS,OACb,UACA,OACA,OACA,cACA,YACA,OACqB;AACrB,MAAI;AACF,UAAM,GAAG;AACT,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,aAAS,KAAK,EAAE,OAAO,OAAO,cAAc,YAAY,OAAO,IAAI,CAAC;AACpE,WAAO;AAAA,EACT;AACF;AAQA,IAAM,wBAAwB,CAAC,aAAgD;AAC7E,QAAM,UAAU,SACb;AAAA,IACC,CAAC,MACC,GAAG,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,YAAY,IAAI,EAAE,UAAU,KAAK;AAAA,MAC1D,EAAE;AAAA,IACJ,CAAC;AAAA,EACL,EACC,KAAK,IAAI;AACZ,SAAO,IAAI;AAAA,IACT,mBAAmB,SAAS,MAAM,kCAAkC,OAAO;AAAA,EAC7E;AACF;AASA,IAAM,gBAAgB,CAAC,SAAmC;AACxD,aAAW,OAAO,OAAO,KAAK,iBAAiB,GAE5C;AACD,QAAI,uBAAuB,GAAG,EAAE,SAAS,MAAM;AAC7C,aAAO,kBAAkB,GAAG;AAAA,IAC9B;AAAA,EACF;AACA,QAAM,IAAI,MAAM,gCAAgC,IAAI,IAAI;AAC1D;AASA,IAAM,yBAAyB,YAA2B;AACxD,QAAM,eAA8B;AAAA,IAClC,UAAU;AAAA,IACV,aAAa;AAAA,IACb,OAAM,oBAAI,KAAK,CAAC,GAAE,YAAY;AAAA,IAC9B,SAAS;AAAA,IACT,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,EACV;AACA,aAAW,MAAM,OAAO,OAAO,iBAAiB,GAAG;AACjD,QAAI;AACF,YAAM,qBAAqB,EAAE,SAAS,cAAc,GAAG,CAAC;AAAA,IAC1D,SAAS,KAAK;AACZ,UAAI,eAAe,eAAe;AAChC,cAAM,IAAI;AAAA,UACR,kDAAkD,EAAE;AAAA,QAEtD;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAOA,IAAM,qBAAqB,CACzB,UAC6B;AAAA,EAC7B,MAAM,KAAK;AAAA,EACX,QAAQ;AAAA,EACR,YAAY;AAAA,IACV,uBAAuB,KAAK,UAAU,QAAQ;AAAA,IAC9C,yBAAyB;AAAA,MACvB,UAAU,KAAK;AAAA,MACf,aAAa;AAAA,MACb,cAAc;AAAA,MACd,IAAI,KAAK;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,IAAM,wBAAwB,CAC5B,MACA,eAC6B;AAAA,EAC7B,MAAM,UAAU;AAAA,EAChB,QAAQ;AAAA,EACR,YAAY;AAAA,IACV,uBAAuB,KAAK,UAAU,UAAU,UAAU;AAAA,IAC1D,yBAAyB;AAAA,MACvB,UAAU,KAAK;AAAA,MACf,aAAa;AAAA,MACb,cAAc;AAAA,MACd,IAAI,UAAU;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EACA,QAAQ,EAAE,WAAW,UAAU,KAAK,QAAQ,IAAI,MAAM,SAAS;AACjE;AAEA,IAAM,yBAAyB,CAC7B,MACA,MACA,kBAC6B;AAAA,EAC7B,YAAY;AAAA,IACV,uBAAuB,KAAK,UAAU,cAAc,KAAK,EAAE,EAAE;AAAA,IAC7D,yBAAyB;AAAA,MACvB,UAAU,KAAK;AAAA,MACf,aAAa;AAAA,MACb,cAAc;AAAA,MACd,IAAI;AAAA,IACN,CAAC;AAAA,EACH;AAAA,EACA,MAAM,EAAE,WAAW,QAAQ,KAAK,EAAE,IAAI,MAAM,OAAO;AAAA,EACnD,QAAQ,EAAE,WAAW,UAAU,KAAK,QAAQ,IAAI,MAAM,SAAS;AAAA,EAC/D,QAAQ;AACV;AAEA,IAAM,6BAA6B,CACjC,UACA,UACA,MACA,UACA,sBAC6B;AAAA,EAC7B,YAAY;AAAA,IACV,uBAAuB,UAAU,kBAAkB,KAAK,EAAE,IAAI,QAAQ,EAAE;AAAA,IACxE,yBAAyB;AAAA,MACvB;AAAA,MACA,aAAa;AAAA,MACb,cAAc;AAAA,MACd,IAAI;AAAA,IACN,CAAC;AAAA,EACH;AAAA,EACA,MAAM,EAAE,WAAW,QAAQ,KAAK,EAAE,IAAI,MAAM,OAAO;AAAA,EACnD,MAAM,EAAE,WAAW,QAAQ,cAAc,QAAQ,CAAC,IAAI,MAAM,OAAO;AAAA,EACnE,QAAQ,EAAE,WAAW,UAAU,QAAQ,IAAI,MAAM,SAAS;AAAA,EAC1D,QAAQ;AACV;AAGA,IAAM,mBAAmB,CACvB,MACA,gBAC6B;AAAA,EAC7B,cAAc;AAAA,EACd,IAAI,KAAK;AAAA,EACT,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAAA,EAC3B,QAAQ;AAAA,EACR;AAAA,EACA,eAAe,EAAE,WAAW,UAAU,qBAAqB,GAAG;AAAA,EAC9D,kBAAkB,EAAE,WAAW,aAAa,wBAAwB,GAAG;AACzE;AAOA,IAAM,aAAa,OACjB,SACA,MACA,eACkB;AAClB,QAAM,UAAU,wBAAwB;AACxC,QAAM,WAAW,iBAAiB,MAAM,UAAU;AAClD,QAAM,UAAU,KAAK,UAAU,eAAe,QAA4B,CAAC;AAC3E,QAAM,QAAQ,SAAS,KACpB,IAAI;AAAA,IACH,IAAI,KAAK;AAAA,IACT;AAAA,IACA,UAAU,KAAK,UAAU,QAAQ;AAAA,IACjC;AAAA,IACA,KAAK;AAAA,IACL,aAAa,QAAQ,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtD,CAAC,EACA,GAAG;AACR;AAwBA,IAAM,yBAAyB,OAC7B,aACA,OACA,aACkB;AAClB,QAAM,mBAAkC;AAAA,IACtC,GAAG;AAAA,IACH,UAAU,MAAM;AAAA,IAChB,aAAa,MAAM;AAAA,EACrB;AACA,QAAM,QAAQ,GAAG,MAAM,QAAQ,IAAI,MAAM,WAAW;AACpD,aAAW,WAAW,MAAM,UAAU;AACpC,UAAM;AAAA,MAAO;AAAA,MAAU;AAAA,MAAW;AAAA,MAAO;AAAA,MAAW,QAAQ,MAAM;AAAA,MAAI,MACpE,uBAAuB;AAAA,QACrB,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AACA,aAAW,gBAAgB,MAAM,eAAe;AAC9C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,MACE,4BAA4B;AAAA,QAC1B,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,IACL;AAAA,EACF;AACA,aAAW,eAAe,MAAM,cAAc;AAC5C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,MAAM;AAAA,MAClB,MACE,2BAA2B;AAAA,QACzB,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,IACL;AAAA,EACF;AACA,aAAW,aAAa,MAAM,YAAY;AACxC,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,MACE,yBAAyB;AAAA,QACvB,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,IACL;AAAA,EACF;AACA,aAAW,WAAW,MAAM,UAAU;AACpC,UAAM;AAAA,MAAO;AAAA,MAAU;AAAA,MAAW;AAAA,MAAO;AAAA,MAAW,QAAQ,MAAM;AAAA,MAAI,MACpE,uBAAuB;AAAA,QACrB,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AACF;AA8BO,IAAM,gBAAgB,OAAO,WAIf;AACnB,QAAM,EAAE,aAAa,UAAU,QAAQ,IAAI;AAC3C,QAAM,WAA+B,CAAC;AAYtC,aAAW,QAAQ,mBAAmB;AACpC,UAAM,gBAA+B;AAAA,MACnC,GAAG;AAAA,MACH,UAAU,KAAK;AAAA,IACjB;AAEA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA,KAAK;AAAA,MACL,MACE,sBAAsB;AAAA,QACpB,SAAS;AAAA,QACT,MAAM,EAAE,IAAI,KAAK,UAAU,UAAU,mBAAmB,IAAI,EAAE;AAAA,MAChE,CAAC;AAAA,IACL;AAEA,eAAW,aAAa,KAAK,YAAY;AACvC,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA,UAAU;AAAA,QACV,MACE,yBAAyB;AAAA,UACvB,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,IAAI,UAAU;AAAA,YACd,UAAU,sBAAsB,MAAM,SAAS;AAAA,UACjD;AAAA,QACF,CAAC;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAqBA,aAAW,QAAQ,UAAU;AAC3B,QAAI;AACJ,QAAI;AACF,mBAAa,MAAM,QAAQ,WAAW,KAAK,KAAK;AAAA,IAClD,SAAS,KAAK;AACZ,eAAS,KAAK;AAAA,QACZ,OAAO;AAAA,QACP,OAAO,KAAK;AAAA,QACZ,cAAc;AAAA,QACd,YAAY,KAAK;AAAA,QACjB,OAAO;AAAA,MACT,CAAC;AACD;AAAA,IACF;AAEA,UAAM;AAAA,MAAO;AAAA,MAAU;AAAA,MAAW,KAAK;AAAA,MAAI;AAAA,MAAQ,KAAK;AAAA,MAAI,MAC1D,WAAW,aAAa,MAAM,UAAU;AAAA,IAC1C;AAEA,eAAW,QAAQ,mBAAmB;AACpC,YAAM,gBAA+B;AAAA,QACnC,GAAG;AAAA,QACH,UAAU,KAAK;AAAA,MACjB;AACA,YAAM,YAAY,GAAG,KAAK,EAAE,IAAI,KAAK,QAAQ;AAE7C,YAAM,eAAe,iBAAiB,KAAK,IAAI,KAAK,QAAQ;AAC5D,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MACE,0BAA0B;AAAA,UACxB,SAAS;AAAA,UACT,MAAM;AAAA,YACJ,IAAI;AAAA,YACJ,UAAU,uBAAuB,MAAM,MAAM,YAAY;AAAA,UAC3D;AAAA,QACF,CAAC;AAAA,MACL;AAEA,iBAAW,YAAY,yBAAyB,MAAM,KAAK,QAAQ,GAAG;AACpE,cAAM,OAAO,qBAAqB,KAAK,IAAI,KAAK,UAAU,QAAQ;AAClE,cAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,MACE,8BAA8B;AAAA,YAC5B,SAAS;AAAA,YACT,MAAM;AAAA,cACJ,IAAI;AAAA,cACJ,UAAU;AAAA,gBACR,KAAK;AAAA,gBACL,KAAK;AAAA,gBACL;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACL;AAAA,MACF;AAAA,IACF;AAQA,UAAM,kBAAiC;AAAA,MACrC,GAAG;AAAA,MACH,UAAU;AAAA,IACZ;AACA,UAAM,mBAAmBC,oBAAmB;AAC5C,UAAM,eAAe;AAAA,MACnB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AACA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,GAAG,KAAK,EAAE,IAAI,wBAAwB;AAAA,MACtC;AAAA,MACA;AAAA,MACA,MACE,8BAA8B;AAAA,QAC5B,SAAS;AAAA,QACT,MAAM;AAAA,UACJ,IAAI;AAAA,UACJ,UAAU;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACL;AAAA,EACF;AAaA,aAAW,SAAS,0BAA0B;AAC5C,QAAI;AACF,YAAM,uBAAuB,aAAa,OAAO,QAAQ;AAAA,IAC3D,SAAS,KAAK;AACZ,eAAS,KAAK;AAAA,QACZ,OAAO;AAAA,QACP,OAAO,GAAG,MAAM,QAAQ,IAAI,MAAM,WAAW;AAAA,QAC7C,cAAc;AAAA,QACd,YAAY,MAAM;AAAA,QAClB,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,sBAAsB,QAAQ;AAAA,EACtC;AACF;AAOO,IAAM,kBAAkB,OAC7B,OACA,MACA,aACkB;AAClB,QAAM,aAAS,sCAAmB,OAAO,2CAA0B;AAEnE,QAAM,eAAe,MAAM,KAAK,YAAY,eAAe;AAAA,IACzD,cAAc;AAAA,IACd,SAAS,OAAO,SAAS;AAAA,IACzB,SAAS,OAAO,SAAS;AAAA,EAC3B,CAAC;AACD,MAAI,CAAC,aAAa,UAAU;AAC1B;AAAA,EACF;AAEA,QAAM,cAA6B;AAAA,IACjC,UAAU;AAAA,IACV,aAAa;AAAA,IACb,MAAM,OAAO,SAAS;AAAA,IACtB,SAAS;AAAA,IACT,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,EACV;AAEA,MAAI;AACF,UAAM,KAAK,YAAY;AACvB,UAAM,KAAK,cAAc;AAAA,MACvB;AAAA,MACA;AAAA,MACA,SAAS,KAAK;AAAA,IAChB,CAAC;AAAA,EACH,SAAS,KAAK;AAMZ,UAAM,KAAK,YAAY,WAAW;AAAA,MAChC,cAAc;AAAA,MACd,SAAS,OAAO,SAAS;AAAA,MACzB,SAAS,OAAO,SAAS;AAAA,MACzB,QAAQ,aAAa,GAAG;AAAA,IAC1B,CAAC;AACD,UAAM;AAAA,EACR;AACF;AAWO,IAAM,sCAAsC;AAUnD,IAAM,mBAAmB;AAYlB,IAAM,iBAAiB,CAAC,UAA0B;AACvD,MAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,UAAM,IAAI;AAAA,MACR,+DAA+D,OAAO,KAAK,CAAC;AAAA,IAC9E;AAAA,EACF;AACA,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,MAAI,UAAU,MAAM,UAAU,MAAM,YAAY,GAAG,GAAG;AACpD,UAAM,IAAI;AAAA,MACR,0BAA0B,KAAK;AAAA,IACjC;AAAA,EACF;AACA,QAAM,YAAY,MAAM,MAAM,GAAG,KAAK;AACtC,QAAM,aAAa,MAAM,MAAM,QAAQ,CAAC;AACxC,MAAI,UAAU,WAAW,KAAK,WAAW,WAAW,GAAG;AACrD,UAAM,IAAI;AAAA,MACR,0BAA0B,KAAK;AAAA,IACjC;AAAA,EACF;AACA,MAAI,CAAC,iBAAiB,KAAK,SAAS,KAAK,CAAC,iBAAiB,KAAK,UAAU,GAAG;AAC3E,UAAM,IAAI;AAAA,MACR,0BAA0B,KAAK;AAAA,IAEjC;AAAA,EACF;AACA,SAAO,GAAG,mCAAmC,GAAG,SAAS,OAAO,UAAU;AAC5E;AASA,IAAI;AAEJ,IAAM,eAAe,MAAiB;AACpC,MAAI,CAAC,iBAAiB;AACpB,sBAAkB,IAAI,UAAU,CAAC,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAOO,IAAM,2BAA2B,MAAY;AAClD,oBAAkB;AACpB;AASO,IAAM,wBAAwB,OAAO,UAAmC;AAC7E,QAAM,OAAO,eAAe,KAAK;AACjC,QAAM,SAAS,aAAa;AAC5B,MAAI;AACF,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B,IAAI,oBAAoB,EAAE,MAAM,MAAM,gBAAgB,KAAK,CAAC;AAAA,IAC9D;AACA,UAAM,QAAQ,OAAO,WAAW;AAChC,QAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,YAAM,IAAI;AAAA,QACR,yCAAyC,IAAI;AAAA,MAC/C;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,eAAe,mBAAmB;AACpC,YAAM,IAAI;AAAA,QACR,yCAAyC,IAAI,6CACX,IAAI;AAAA,MACxC;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAuBO,IAAM,+BAA+B,MAA0B;AACpE,QAAM,SAAS,IAAI,8BAA8B,CAAC,CAAC;AACnD,QAAM,aAAa,QAAQ,IAAI,mCAAmC;AAClE,MAAI,CAAC,cAAc,WAAW,KAAK,MAAM,IAAI;AAC3C,UAAM,IAAI;AAAA,MACR,GAAG,mCAAmC;AAAA,IAExC;AAAA,EACF;AACA,QAAM,oBAAoB,CACxB,UACuB;AACvB,eAAW,QAAQ,SAAS,CAAC,GAAG;AAC9B,UAAI,KAAK,SAAS,SAAS,OAAO,KAAK,UAAU,UAAU;AACzD,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,QAAM,cAAc,OAClB,OACA,aACkB;AAClB,UAAM,OAAO;AAAA,MACX,IAAI,4BAA4B;AAAA,QAC9B,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,UAAU;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AAAA,IACL,YAAY,OAAO,UAAmC;AAIpD,YAAM,WAAW,MAAM,sBAAsB,KAAK;AAClD,UAAI;AACF,cAAM,UAAU,MAAM,OAAO;AAAA,UAC3B,IAAI,uBAAuB;AAAA,YACzB,YAAY;AAAA,YACZ,UAAU;AAAA,YACV,eAAe;AAAA,YACf,gBAAgB;AAAA,cACd,EAAE,MAAM,SAAS,OAAO,MAAM;AAAA,cAC9B,EAAE,MAAM,kBAAkB,OAAO,OAAO;AAAA,YAC1C;AAAA,UACF,CAAC;AAAA,QACH;AACA,cAAM,YAAY,OAAO,QAAQ;AACjC,cAAM,MAAM,kBAAkB,QAAQ,MAAM,UAAU;AACtD,YAAI,CAAC,KAAK;AACR,gBAAM,IAAI;AAAA,YACR,iCAAiC,KAAK;AAAA,UACxC;AAAA,QACF;AACA,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,YAAI,eAAe,yBAAyB;AAK1C,gBAAM,YAAY,OAAO,QAAQ;AACjC,gBAAM,MAAM,MAAM,OAAO;AAAA,YACvB,IAAI,oBAAoB;AAAA,cACtB,YAAY;AAAA,cACZ,UAAU;AAAA,YACZ,CAAC;AAAA,UACH;AACA,gBAAM,MAAM,kBAAkB,IAAI,cAAc;AAChD,cAAI,CAAC,KAAK;AACR,kBAAM,IAAI;AAAA,cACR,8BAA8B,KAAK;AAAA,YACrC;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,yBAAyB,MAAgC;AAC7D,QAAM,WAAW,IAAI,eAAe,CAAC,CAAC;AACtC,QAAM,UAAU,6BAA6B;AAC7C,SAAO;AAAA,IACL,iBAAa,uCAAoB,QAAQ;AAAA,IACzC,aAAa;AAAA,IACb;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,UAAU,OAAO,UAC5B,gBAAgB,OAAO,uBAAuB,GAAG,SAAS;","names":["import_workflows","PLATFORM_ROLE_CODE","PLATFORM_ROLE_CODE"]}
|
package/lib/index.js
CHANGED
|
@@ -3121,7 +3121,17 @@ var DEV_USERS = [
|
|
|
3121
3121
|
{ id: "dev-drew", email: "drew@codedrifters.com" },
|
|
3122
3122
|
{ id: "dev-jessica", email: "jessica@codedrifters.com" },
|
|
3123
3123
|
{ id: "dev-jared", email: "jared@codedrifters.com" },
|
|
3124
|
-
{ id: "dev-goddess", email: "goddess@codedrifters.com" }
|
|
3124
|
+
{ id: "dev-goddess", email: "goddess@codedrifters.com" },
|
|
3125
|
+
// Dedicated end-to-end test principal for admin-console Playwright
|
|
3126
|
+
// specs (issue #1275). Reuses the standard DEV_USERS plumbing
|
|
3127
|
+
// (Cognito user + DynamoDB User + per-tenant Memberships +
|
|
3128
|
+
// tenant-admin RAs + platform-scoped system-admin RA) and the
|
|
3129
|
+
// existing SSM-sourced password convention from #1249 — the
|
|
3130
|
+
// operator provisions the SecureString at
|
|
3131
|
+
// /openhi/seed/users/e2e-admin-console_at_codedrifters.com/password
|
|
3132
|
+
// out of band, and the seeded Cognito user picks it up via
|
|
3133
|
+
// AdminSetUserPassword on every seed run.
|
|
3134
|
+
{ id: "dev-e2e-admin-console", email: "e2e-admin-console@codedrifters.com" }
|
|
3125
3135
|
];
|
|
3126
3136
|
var DEMO_TENANT_SPECS = [
|
|
3127
3137
|
{
|