@openhi/constructs 0.0.151 → 0.0.153
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-AWYZJFPL.mjs → chunk-CFJDATDK.mjs} +8 -1
- package/lib/chunk-CFJDATDK.mjs.map +1 -0
- package/lib/{chunk-WGA43MMY.mjs → chunk-ZVDVKCNC.mjs} +276 -85
- package/lib/chunk-ZVDVKCNC.mjs.map +1 -0
- package/lib/{events-CMG8xanm.d.mts → events-DTgo2dcW.d.mts} +2 -14
- package/lib/{events-CMG8xanm.d.ts → events-DTgo2dcW.d.ts} +2 -14
- package/lib/index.d.mts +54 -7
- package/lib/index.d.ts +53 -18
- package/lib/index.js +80 -20
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +61 -4
- package/lib/index.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +6 -0
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +1 -1
- package/lib/rest-api-lambda.handler.js +6 -0
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1 -1
- package/lib/seed-demo-data.handler.d.mts +59 -11
- package/lib/seed-demo-data.handler.d.ts +59 -11
- package/lib/seed-demo-data.handler.js +281 -83
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +10 -4
- package/package.json +5 -5
- package/lib/chunk-AWYZJFPL.mjs.map +0 -1
- package/lib/chunk-WGA43MMY.mjs.map +0 -1
|
@@ -22,6 +22,9 @@ import {
|
|
|
22
22
|
getDynamoControlService
|
|
23
23
|
} from "./chunk-VZCPGQXA.mjs";
|
|
24
24
|
|
|
25
|
+
// src/data/operations/control/membership-constraints/platform-scope-tenant-id.ts
|
|
26
|
+
var PLATFORM_SCOPE_TENANT_ID = "platform";
|
|
27
|
+
|
|
25
28
|
// src/data/operations/control/membership/membership-create-operation.ts
|
|
26
29
|
import {
|
|
27
30
|
assertLinkedDataIdentityCardinality,
|
|
@@ -170,6 +173,9 @@ import { extractSummary as extractSummary2 } from "@openhi/types";
|
|
|
170
173
|
var TENANT_LANE_SK_PREFIX = "MEMBERSHIP#TENANT#";
|
|
171
174
|
async function assertUserHasTenantMembershipOperation(params) {
|
|
172
175
|
const { userId, tenantId, tableName } = params;
|
|
176
|
+
if (tenantId === PLATFORM_SCOPE_TENANT_ID) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
173
179
|
const service = getDynamoControlService(tableName);
|
|
174
180
|
const result = await service.entities.membershipUserProjection.query.record({ userId }).begins({ sk: TENANT_LANE_SK_PREFIX }).go();
|
|
175
181
|
const matched = (result.data ?? []).some((row) => row.tenantId === tenantId);
|
|
@@ -380,10 +386,11 @@ async function createWorkspaceOperation(params) {
|
|
|
380
386
|
}
|
|
381
387
|
|
|
382
388
|
export {
|
|
389
|
+
PLATFORM_SCOPE_TENANT_ID,
|
|
383
390
|
extractDenormalizedReferenceDisplay,
|
|
384
391
|
createMembershipOperation,
|
|
385
392
|
createRoleAssignmentOperation,
|
|
386
393
|
createTenantOperation,
|
|
387
394
|
createWorkspaceOperation
|
|
388
395
|
};
|
|
389
|
-
//# sourceMappingURL=chunk-
|
|
396
|
+
//# sourceMappingURL=chunk-CFJDATDK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/operations/control/membership-constraints/platform-scope-tenant-id.ts","../src/data/operations/control/membership/membership-create-operation.ts","../src/data/operations/control/denormalized-display-names.ts","../src/data/operations/control/membership-constraints/assert-workspace-in-tenant-operation.ts","../src/data/operations/control/roleassignment/roleassignment-create-operation.ts","../src/data/operations/control/membership-constraints/assert-user-has-tenant-membership-operation.ts","../src/data/operations/control/tenant/tenant-create-operation.ts","../src/data/operations/control/workspace/workspace-create-operation.ts","../src/data/operations/data/organization/organization-provision-for-workspace-operation.ts"],"sourcesContent":["/**\n * Sentinel `tenantId` used on platform-scoped `RoleAssignment` records.\n * A platform-scoped RA (e.g. `system-admin`) spans every tenant, but\n * the `RoleAssignment` entity requires a `tenantId` on its key for\n * sharding — there is no real tenant to point at. The `\"platform\"`\n * literal is a reserved value that never matches a real `Tenant.id`\n * and signals \"this RA scopes across all tenants\".\n *\n * The constant lives in the data layer so both the\n * {@link assertUserHasTenantMembershipOperation} write-time constraint\n * and the seed-demo-data workflow can read it without the constraint\n * helper having to reach back into the workflow package — which would\n * create a backwards data-layer-to-workflow dependency. The\n * `workflows/control-plane/seed-demo-data/events.ts` module re-exports\n * the constant under the same name for back-compat with existing\n * imports that point at the workflow's barrel.\n *\n * Renaming this constant is a wire-format break — the seed-demo-data\n * handler emits `RoleAssignment` records keyed on this value, and the\n * in-band records written under it become unreachable if the sentinel\n * changes.\n */\nexport const PLATFORM_SCOPE_TENANT_ID = \"platform\";\n","import {\n assertLinkedDataIdentityCardinality,\n extractSummary,\n type Extension,\n type FhirResourceLike,\n LinkedDataIdentityCardinalityError,\n} from \"@openhi/types\";\nimport {\n buildMembershipUserProjectionItem,\n extractReferenceSlug,\n} from \"./membership-user-projection\";\nimport { buildMembershipWorkspaceProjectionItem } from \"./membership-workspace-projection\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { ValidationError } from \"../../../errors\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport { extractDenormalizedReferenceDisplay } from \"../denormalized-display-names\";\nimport { assertWorkspaceInTenantOperation } from \"../membership-constraints/assert-workspace-in-tenant-operation\";\nimport {\n executeMultiWrite,\n type MultiWriteTriple,\n} from \"../multi-write-operation\";\n\nexport interface MembershipCreateParams {\n context: OpenHiContext;\n body: { id?: string; resource?: Record<string, unknown> | string };\n tableName?: string;\n}\n\nexport interface MembershipCreateResult {\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n meta: { lastUpdated: string; versionId: string };\n}\n\nexport async function createMembershipOperation(\n params: MembershipCreateParams,\n): Promise<MembershipCreateResult> {\n const { context, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `membership-${Date.now()}`;\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const lastUpdated = context.date ?? new Date().toISOString();\n const vid = `1`;\n\n const resource = { resourceType: \"Membership\", id, ...parsedResource };\n\n let linkedDataIdentityRef: string | undefined;\n try {\n const ext = assertLinkedDataIdentityCardinality(\n resource as { extension?: Array<Extension> },\n );\n linkedDataIdentityRef = ext?.valueReference?.reference;\n } catch (e) {\n if (e instanceof LinkedDataIdentityCardinalityError) {\n throw new ValidationError(e.message, { cause: e });\n }\n throw e;\n }\n\n // TR-024 denormalized display-name attributes. The authoritative\n // write-time source per TR-024 rule 2 is the canonical Tenant / User\n // record's `displayName`. Until the carrier-record-lookup pass lands\n // (#1010 follow-up), the foundational fallback (#1009) reads the\n // FHIR Reference.display values supplied on the resource so the\n // adjacency-list projection writer here can compose SKs from\n // top-level attributes.\n const resourceRecord = resource as Record<string, unknown>;\n const denormalizedTenantName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"tenant\",\n );\n const denormalizedUserName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"user\",\n );\n const denormalizedWorkspaceName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"workspace\",\n );\n\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n // ADR-018 patterns #2 / #3 / #4 — user- and workspace-partition\n // projection rows. The projection's discriminating fields come from\n // the same FHIR Reference shape the canonical row reads, so the\n // canonical and projection writes always agree on the underlying\n // user / workspace identifiers. Missing identifiers (no user\n // reference at all) skip the user-projection; tenant-scoped\n // Memberships (no workspaceId) skip the workspace-projection — the\n // canonical row still lands either way.\n const userIdFromResource = extractReferenceSlug(resourceRecord, \"user\");\n const workspaceIdFromResource = extractReferenceSlug(\n resourceRecord,\n \"workspace\",\n );\n\n // ADR 2026-03-13-02 § 2 Consequences/Negative — when the resource\n // names a workspace, that workspace must belong to the referenced\n // tenant. Prefer the resource's `tenant.reference` (the issue's\n // \"referenced tenant\" wording) and fall back to the context tenant\n // when the body omits the reference. The check fires before any\n // write so the constraint is enforced atomically with respect to\n // the canonical row.\n if (workspaceIdFromResource !== undefined) {\n const tenantIdFromResource = extractReferenceSlug(resourceRecord, \"tenant\");\n const referencedTenantId = tenantIdFromResource ?? context.tenantId;\n await assertWorkspaceInTenantOperation({\n tenantId: referencedTenantId,\n workspaceId: workspaceIdFromResource,\n tableName,\n });\n }\n\n const userProjectionItem =\n userIdFromResource !== undefined\n ? buildMembershipUserProjectionItem({\n tenantId: context.tenantId,\n userId: userIdFromResource,\n workspaceId: workspaceIdFromResource,\n membershipId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedTenantName,\n denormalizedUserName,\n denormalizedWorkspaceName,\n })\n : undefined;\n\n const workspaceProjectionItem =\n userIdFromResource !== undefined && workspaceIdFromResource !== undefined\n ? buildMembershipWorkspaceProjectionItem({\n tenantId: context.tenantId,\n workspaceId: workspaceIdFromResource,\n userId: userIdFromResource,\n membershipId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedUserName,\n })\n : undefined;\n\n const canonicalItem = {\n tenantId: context.tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n linkedDataIdentityRef,\n denormalizedTenantName,\n denormalizedUserName,\n };\n\n const triples: Array<MultiWriteTriple> = [\n { entity: \"membership\", action: \"put\", item: canonicalItem },\n ];\n if (userProjectionItem) {\n triples.push({\n entity: \"membershipUserProjection\",\n action: \"put\",\n item: userProjectionItem as unknown as Record<string, unknown>,\n });\n }\n if (workspaceProjectionItem) {\n triples.push({\n entity: \"membershipWorkspaceProjection\",\n action: \"put\",\n item: workspaceProjectionItem as unknown as Record<string, unknown>,\n });\n }\n\n await executeMultiWrite({ service, triples });\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","/**\n * Helpers that capture denormalized display-name attributes from an\n * incoming FHIR Reference on Membership and RoleAssignment resources.\n *\n * **Foundational implementation for TR-024.** ADR-018 § Implementation\n * Notes and TR-024 § Recommendation pin the authoritative write-time\n * source as the carrier entity's canonical `displayName` — i.e. the\n * operations layer reads the canonical Tenant / User / Role record\n * by id and copies its display name into the relationship's\n * `denormalized<CarrierEntity>Name` field on the same\n * `TransactWriteItems`. That contract is owned by the operations-layer\n * multi-write helper filed as #1010.\n *\n * This module is the **foundational fallback** that issue #1009 uses\n * before #1010 lands: it reads the display string a client already\n * supplies on the resource's `Reference.display` field. The fallback\n * keeps Membership / RoleAssignment write paths populating the new\n * top-level attributes today (so adjacency-list projection writers\n * downstream can rely on them) while letting #1010 own the canonical-\n * row lookup without re-plumbing the write paths.\n *\n * @see TR-024 — Denormalized display-name attributes\n * @see ADR-018 § Implementation Notes\n */\n\n/**\n * Returns the trimmed display string from `resource[fieldName].display`\n * when present and non-empty; otherwise returns `undefined`. Used by the\n * Membership and RoleAssignment create / update operations to populate\n * top-level `denormalized<CarrierEntity>Name` attributes from incoming\n * FHIR Reference fields.\n *\n * Field name maps to the FHIR field on the resource:\n * - `Membership.tenant` → `\"tenant\"`\n * - `Membership.user` → `\"user\"`\n * - `RoleAssignment.tenant` → `\"tenant\"`\n * - `RoleAssignment.user` → `\"user\"`\n * - `RoleAssignment.role` → `\"role\"`\n *\n * Guards against malformed payloads (non-object `field`, non-string\n * `display`, empty strings after trim) so a single bad write never\n * blocks an entity put — matching the same defensive posture\n * `gsi1skAttribute` takes for the `resource` JSON parse.\n */\nexport function extractDenormalizedReferenceDisplay(\n resource: Record<string, unknown>,\n fieldName: string,\n): string | undefined {\n const field = resource[fieldName];\n if (!field || typeof field !== \"object\") {\n return undefined;\n }\n const display = (field as { display?: unknown }).display;\n if (typeof display !== \"string\") {\n return undefined;\n }\n const trimmed = display.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n","import { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { ConflictError } from \"../../../errors\";\n\n/** Inputs to {@link assertWorkspaceInTenantOperation}. */\nexport interface AssertWorkspaceInTenantParams {\n /** Tenant the workspace must belong to. */\n readonly tenantId: string;\n /** Workspace id that must exist under `tenantId`. */\n readonly workspaceId: string;\n /** Optional table-name override; resolved via env when omitted. */\n readonly tableName?: string;\n}\n\n/**\n * Throws {@link ConflictError} when no `Workspace` with id `workspaceId`\n * exists under `tenantId`.\n *\n * Implementation: a single base-table `get` against the Workspace entity\n * (`PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK = CURRENT`). The\n * workspace's tenant is encoded into the PK by construction, so a hit\n * *is* the constraint check — there is no separate `managingOrganization`\n * comparison required.\n *\n * @see ADR 2026-03-13-02 § 2 — Consequences/Negative\n */\nexport async function assertWorkspaceInTenantOperation(\n params: AssertWorkspaceInTenantParams,\n): Promise<void> {\n const { tenantId, workspaceId, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const { data: item } = await service.entities.workspace\n .get({ tenantId, id: workspaceId, sk: \"CURRENT\" })\n .go();\n\n if (!item) {\n throw new ConflictError(\n `Workspace ${workspaceId} does not belong to tenant ${tenantId}; the workspace must be created in the referenced tenant before this resource can reference it.`,\n { details: { tenantId, workspaceId } },\n );\n }\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport {\n buildRoleAssignmentUserProjectionItem,\n extractReferenceSlug,\n} from \"./roleassignment-user-projection\";\nimport { buildRoleAssignmentWorkspaceProjectionItem } from \"./roleassignment-workspace-projection\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { OpenHiContext } from \"../../../openhi-context\";\nimport { extractDenormalizedReferenceDisplay } from \"../denormalized-display-names\";\nimport { assertUserHasTenantMembershipOperation } from \"../membership-constraints/assert-user-has-tenant-membership-operation\";\nimport { assertWorkspaceInTenantOperation } from \"../membership-constraints/assert-workspace-in-tenant-operation\";\nimport {\n executeMultiWrite,\n type MultiWriteTriple,\n} from \"../multi-write-operation\";\n\nexport interface RoleAssignmentCreateParams {\n context: OpenHiContext;\n body: { id?: string; resource?: Record<string, unknown> | string };\n tableName?: string;\n}\n\nexport interface RoleAssignmentCreateResult {\n id: string;\n resource: { resourceType: string; id: string; [key: string]: unknown };\n meta: { lastUpdated: string; versionId: string };\n}\n\nexport async function createRoleAssignmentOperation(\n params: RoleAssignmentCreateParams,\n): Promise<RoleAssignmentCreateResult> {\n const { context, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `roleassignment-${Date.now()}`;\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const lastUpdated = context.date ?? new Date().toISOString();\n const vid = `1`;\n\n const resource = { resourceType: \"RoleAssignment\", id, ...parsedResource };\n\n // TR-024 denormalized display-name attributes. The authoritative\n // write-time source per TR-024 rule 2 is the canonical Tenant / User /\n // Role record's `displayName`. Until the carrier-record-lookup pass\n // lands (#1010 follow-up), the foundational fallback (#1009) reads\n // the FHIR Reference.display values supplied on the resource so the\n // adjacency-list projection writer here can compose SKs from\n // top-level attributes.\n const resourceRecord = resource as Record<string, unknown>;\n const denormalizedTenantName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"tenant\",\n );\n const denormalizedUserName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"user\",\n );\n const denormalizedRoleName = extractDenormalizedReferenceDisplay(\n resourceRecord,\n \"role\",\n );\n\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n // ADR-018 patterns #5 / #9 — user- and workspace-partition projection\n // rows. The projection's discriminating fields come from the same\n // FHIR Reference shape the canonical row reads, so the canonical and\n // projection writes always agree on the underlying user / role /\n // workspace identifiers. Missing user or role references skip the\n // user-projection; tenant-scoped RoleAssignments (no workspaceId)\n // skip the workspace-projection — the canonical row still lands\n // either way.\n const userIdFromResource = extractReferenceSlug(resourceRecord, \"user\");\n const roleIdFromResource = extractReferenceSlug(resourceRecord, \"role\");\n const workspaceIdFromResource = extractReferenceSlug(\n resourceRecord,\n \"workspace\",\n );\n\n // ADR 2026-03-13-02 § 2 Consequences/Negative — a RoleAssignment may\n // only be created when the referenced user already holds a\n // tenant-level Membership in the referenced tenant, and (when\n // workspace-scoped) that workspace must belong to the same tenant.\n // Prefer the resource's `tenant.reference` (the issue's \"referenced\n // tenant\" wording) and fall back to the context tenant when the\n // body omits the reference. Constraints fire before any write so\n // the rejection is atomic with respect to the canonical row.\n if (userIdFromResource !== undefined) {\n const tenantIdFromResource = extractReferenceSlug(resourceRecord, \"tenant\");\n const referencedTenantId = tenantIdFromResource ?? context.tenantId;\n await assertUserHasTenantMembershipOperation({\n userId: userIdFromResource,\n tenantId: referencedTenantId,\n tableName,\n });\n if (workspaceIdFromResource !== undefined) {\n await assertWorkspaceInTenantOperation({\n tenantId: referencedTenantId,\n workspaceId: workspaceIdFromResource,\n tableName,\n });\n }\n }\n\n const userProjectionItem =\n userIdFromResource !== undefined && roleIdFromResource !== undefined\n ? buildRoleAssignmentUserProjectionItem({\n tenantId: context.tenantId,\n userId: userIdFromResource,\n workspaceId: workspaceIdFromResource,\n roleId: roleIdFromResource,\n roleAssignmentId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedTenantName,\n denormalizedUserName,\n denormalizedRoleName,\n })\n : undefined;\n\n const workspaceProjectionItem =\n userIdFromResource !== undefined &&\n roleIdFromResource !== undefined &&\n workspaceIdFromResource !== undefined\n ? buildRoleAssignmentWorkspaceProjectionItem({\n tenantId: context.tenantId,\n workspaceId: workspaceIdFromResource,\n userId: userIdFromResource,\n roleId: roleIdFromResource,\n roleAssignmentId: id,\n summary,\n vid,\n lastUpdated,\n denormalizedUserName,\n denormalizedRoleName,\n })\n : undefined;\n\n const canonicalItem = {\n tenantId: context.tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n denormalizedTenantName,\n denormalizedUserName,\n denormalizedRoleName,\n };\n\n const triples: Array<MultiWriteTriple> = [\n { entity: \"roleAssignment\", action: \"put\", item: canonicalItem },\n ];\n if (userProjectionItem) {\n triples.push({\n entity: \"roleAssignmentUserProjection\",\n action: \"put\",\n item: userProjectionItem as unknown as Record<string, unknown>,\n });\n }\n if (workspaceProjectionItem) {\n triples.push({\n entity: \"roleAssignmentWorkspaceProjection\",\n action: \"put\",\n item: workspaceProjectionItem as unknown as Record<string, unknown>,\n });\n }\n\n await executeMultiWrite({ service, triples });\n\n return {\n id,\n resource,\n meta: { lastUpdated, versionId: vid },\n };\n}\n","import { PLATFORM_SCOPE_TENANT_ID } from \"./platform-scope-tenant-id\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport { ConflictError } from \"../../../errors\";\n\n/**\n * SK prefix for the tenant-level Membership sub-lane (ADR-018 pattern #3).\n *\n * Tenant-level Memberships project under the user partition with\n * `SK = MEMBERSHIP#TENANT#<normalizedTenantName>#TID#<tenantId>#<membershipId>`.\n * Because the `<tenantId>` segment appears *after* `<normalizedTenantName>`,\n * a `begins_with` filter cannot narrow to a single tenant — we read every\n * tenant-lane row for the user and filter on the projection-row's\n * `tenantId` attribute in memory.\n */\nconst TENANT_LANE_SK_PREFIX = \"MEMBERSHIP#TENANT#\";\n\n/** Inputs to {@link assertUserHasTenantMembershipOperation}. */\nexport interface AssertUserHasTenantMembershipParams {\n /** The user that must already be a member of `tenantId`. */\n readonly userId: string;\n /** The tenant the user must hold a tenant-level Membership in. */\n readonly tenantId: string;\n /** Optional table-name override; resolved via env when omitted. */\n readonly tableName?: string;\n}\n\n/**\n * Throws {@link ConflictError} when `userId` has no tenant-level\n * `Membership` in `tenantId`.\n *\n * Implementation: queries the ADR-018 user-partition projection\n * (`MembershipUserProjectionEntity`) under\n * `PK = USER#ID#<userId>, SK begins_with 'MEMBERSHIP#TENANT#'` — a single\n * strongly-consistent base-table query with no GSI hop — and filters the\n * returned rows for `tenantId` in memory.\n *\n * The filter cannot be expressed as an additional `begins_with` because\n * the projection SK encodes `<normalizedTenantName>` *before* `<tenantId>`\n * (`MEMBERSHIP#TENANT#<normalizedTenantName>#TID#<tenantId>#<id>`). The\n * scan cost is bounded by the user's tenant-membership fan-out, which is\n * small by construction.\n *\n * @see ADR 2026-03-13-02 § 2 — Consequences/Negative\n * @see ADR-018 § Access Pattern Coverage (pattern #3)\n */\nexport async function assertUserHasTenantMembershipOperation(\n params: AssertUserHasTenantMembershipParams,\n): Promise<void> {\n const { userId, tenantId, tableName } = params;\n\n // The platform-scope sentinel is not a real tenant — it is a\n // reserved `tenantId` value used on platform-scoped RoleAssignments\n // (e.g. `system-admin`). No `Membership` is ever created under it,\n // and a Membership there would have no semantic meaning. Skip the\n // tenant-membership constraint so platform-scoped RAs can be\n // created without requiring an impossible-to-create prerequisite\n // Membership row.\n if (tenantId === PLATFORM_SCOPE_TENANT_ID) {\n return;\n }\n\n const service = getDynamoControlService(tableName);\n\n const result = await service.entities.membershipUserProjection.query\n .record({ userId })\n .begins({ sk: TENANT_LANE_SK_PREFIX })\n .go();\n\n const matched = (result.data ?? []).some((row) => row.tenantId === tenantId);\n if (!matched) {\n throw new ConflictError(\n `User ${userId} has no tenant-level Membership in tenant ${tenantId}; a Membership must exist before a RoleAssignment can be created.`,\n { details: { userId, tenantId } },\n );\n }\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\n\nexport interface CreateTenantParams {\n context: OpenHiContext;\n body: {\n id?: string;\n resource?: Record<string, unknown> | string;\n };\n tableName?: string;\n}\n\nexport interface CreateTenantResult {\n id: string;\n resource: Record<string, unknown>;\n meta: { lastUpdated: string; versionId: string };\n}\n\n/**\n * Creates a Tenant. Generates an id if not provided.\n */\nexport async function createTenantOperation(\n params: CreateTenantParams,\n): Promise<CreateTenantResult> {\n const { context, body, tableName } = params;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `tenant-${Date.now()}`;\n const lastUpdated = context.date;\n const vid =\n lastUpdated.replace(/[-:T.Z]/g, \"\").slice(0, 12) || Date.now().toString(36);\n\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const resource = { resourceType: \"Tenant\", id, ...parsedResource };\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.tenant\n .put({\n tenantId: id,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n return { id, resource, meta: { lastUpdated, versionId: vid } };\n}\n","import { extractSummary, type FhirResourceLike } from \"@openhi/types\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\nimport { provisionOrganizationForWorkspaceOperation } from \"../../data/organization/organization-provision-for-workspace-operation\";\n\nexport interface CreateWorkspaceParams {\n context: OpenHiContext;\n body: {\n id?: string;\n resource?: Record<string, unknown> | string;\n };\n tableName?: string;\n}\n\nexport interface CreateWorkspaceResult {\n id: string;\n resource: Record<string, unknown>;\n meta: { lastUpdated: string; versionId: string };\n}\n\n/**\n * Creates a Workspace scoped to the context tenant. Generates an id if not provided.\n */\nexport async function createWorkspaceOperation(\n params: CreateWorkspaceParams,\n): Promise<CreateWorkspaceResult> {\n const { context, body, tableName } = params;\n const { tenantId } = context;\n const service = getDynamoControlService(tableName);\n\n const id = body.id ?? `workspace-${Date.now()}`;\n const lastUpdated = context.date;\n const vid =\n lastUpdated.replace(/[-:T.Z]/g, \"\").slice(0, 12) || Date.now().toString(36);\n\n const parsedResource =\n typeof body.resource === \"string\"\n ? (JSON.parse(body.resource) as Record<string, unknown>)\n : (body.resource ?? {});\n\n const resource = { resourceType: \"Workspace\", id, ...parsedResource };\n const summary = JSON.stringify(extractSummary(resource as FhirResourceLike));\n\n await service.entities.workspace\n .put({\n tenantId,\n id,\n resource: JSON.stringify(resource),\n summary,\n vid,\n lastUpdated,\n })\n .go();\n\n const workspaceName =\n typeof parsedResource.name === \"string\" ? parsedResource.name : undefined;\n await provisionOrganizationForWorkspaceOperation({\n context,\n workspaceId: id,\n workspaceName,\n tableName,\n });\n\n return { id, resource, meta: { lastUpdated, versionId: vid } };\n}\n","import type { Organization, Reference } from \"@openhi/types\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-data-service\";\nimport type { OpenHiContext } from \"../../../openhi-context\";\nimport {\n createDataEntityRecord,\n DATA_ENTITY_SK,\n type SingleResourceResult,\n} from \"../../data-operations-common\";\n\n/**\n * Provision the slim, id-share Organization that represents a Workspace on the data plane.\n *\n * - `Organization.id === workspaceId` (id-share with the Workspace).\n * - `name` is populated from the Workspace's name when provided.\n * - `partOf` is populated when the Workspace's Tenant has an Organization (id-share at the\n * tenant level: `Tenant.id === TenantOrganization.id`). Tenant-level provisioning is out of\n * scope for #1001 — the lookup returns `undefined` until Tenant Organizations exist.\n *\n * Idempotent: re-running with the same params overwrites the same PK with the same payload.\n *\n * @see https://github.com/codedrifters/openhi/issues/1001\n */\nexport interface ProvisionOrganizationForWorkspaceParams {\n context: OpenHiContext;\n /** Workspace id; the provisioned Organization's id equals this value. */\n workspaceId: string;\n /** Name to record on the Organization. Derived from the Workspace's `name` by the caller. */\n workspaceName?: string;\n /** Optional table name override; resolved by data service from DYNAMO_TABLE_NAME when omitted. */\n tableName?: string;\n}\n\nexport type ProvisionOrganizationForWorkspaceResult =\n SingleResourceResult<Organization>;\n\nexport async function provisionOrganizationForWorkspaceOperation(\n params: ProvisionOrganizationForWorkspaceParams,\n): Promise<ProvisionOrganizationForWorkspaceResult> {\n const { context, workspaceId, workspaceName, tableName } = params;\n const { tenantId, date } = context;\n const service = getDynamoDataService(tableName);\n\n const partOf = await resolveTenantOrganizationReference(service, tenantId);\n\n const resource: Organization = {\n resourceType: \"Organization\",\n id: workspaceId,\n ...(workspaceName !== undefined && workspaceName !== \"\"\n ? { name: workspaceName }\n : {}),\n ...(partOf !== undefined ? { partOf } : {}),\n meta: {\n lastUpdated: date,\n versionId: \"1\",\n },\n };\n\n return createDataEntityRecord<Organization>(\n service.entities.organization as Parameters<\n typeof createDataEntityRecord\n >[0],\n tenantId,\n workspaceId,\n workspaceId,\n resource,\n date,\n );\n}\n\n/**\n * Resolve the Tenant's Organization reference for `Organization.partOf`. Returns `undefined`\n * when the Tenant has no Organization yet (the default until Tenant Organization provisioning\n * is wired up in a follow-up).\n *\n * Lookup convention: the Tenant's Organization is stored at\n * `(tenantId, workspaceId=tenantId, id=tenantId)` — a self-scope key that mirrors the\n * Workspace id-share pattern one level up. This scoping convention will be ratified by the\n * Tenant Organization provisioning issue; the OrganizationEntity comment notes strict\n * isolation requires both `tenantId` and `workspaceId`, so the self-scope is the\n * simplest deterministic location.\n */\nasync function resolveTenantOrganizationReference(\n service: ReturnType<typeof getDynamoDataService>,\n tenantId: string,\n): Promise<Reference | undefined> {\n const result = await (\n service.entities.organization as {\n get(params: {\n tenantId: string;\n workspaceId: string;\n id: string;\n sk: string;\n }): { go(): Promise<{ data: unknown }> };\n }\n )\n .get({\n tenantId,\n workspaceId: tenantId,\n id: tenantId,\n sk: DATA_ENTITY_SK,\n })\n .go();\n\n if (result.data === null || result.data === undefined) {\n return undefined;\n }\n\n return { reference: `Organization/${tenantId}` };\n}\n"],"mappings":"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","names":["extractSummary","extractSummary","extractReferenceSlug","extractSummary","extractSummary","extractSummary","extractSummary"]}
|
|
@@ -10,11 +10,12 @@ import {
|
|
|
10
10
|
require_lib
|
|
11
11
|
} from "./chunk-ZM4GDHHC.mjs";
|
|
12
12
|
import {
|
|
13
|
+
PLATFORM_SCOPE_TENANT_ID,
|
|
13
14
|
createMembershipOperation,
|
|
14
15
|
createRoleAssignmentOperation,
|
|
15
16
|
createTenantOperation,
|
|
16
17
|
createWorkspaceOperation
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CFJDATDK.mjs";
|
|
18
19
|
import {
|
|
19
20
|
NotFoundError
|
|
20
21
|
} from "./chunk-FYHBHHWK.mjs";
|
|
@@ -27,7 +28,6 @@ import {
|
|
|
27
28
|
|
|
28
29
|
// src/workflows/control-plane/seed-demo-data/seed-demo-data.handler.ts
|
|
29
30
|
var import_workflows2 = __toESM(require_lib());
|
|
30
|
-
import { createHash } from "crypto";
|
|
31
31
|
import {
|
|
32
32
|
AdminCreateUserCommand,
|
|
33
33
|
AdminGetUserCommand,
|
|
@@ -36,6 +36,11 @@ import {
|
|
|
36
36
|
UsernameExistsException
|
|
37
37
|
} from "@aws-sdk/client-cognito-identity-provider";
|
|
38
38
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
39
|
+
import {
|
|
40
|
+
GetParameterCommand,
|
|
41
|
+
ParameterNotFound,
|
|
42
|
+
SSMClient
|
|
43
|
+
} from "@aws-sdk/client-ssm";
|
|
39
44
|
import {
|
|
40
45
|
PLATFORM_ROLE_CODE as PLATFORM_ROLE_CODE2,
|
|
41
46
|
PLATFORM_ROLE_CONCEPTS,
|
|
@@ -50,7 +55,6 @@ var SEED_DEMO_DATA_CONSUMER_NAME = "seed-demo-data";
|
|
|
50
55
|
var DEMO_URN_SYSTEM = "urn:openhi:demo";
|
|
51
56
|
var OPENHI_RESOURCE_URN_SYSTEM = "http://openhi.org/";
|
|
52
57
|
var DEMO_PERIOD = { start: "2026-01-01T00:00:00Z" };
|
|
53
|
-
var PLATFORM_SCOPE_TENANT_ID = "platform";
|
|
54
58
|
var PLACEHOLDER_TENANT_ID = "placeholder-tenant-id";
|
|
55
59
|
var PLACEHOLDER_WORKSPACE_ID = "placeholder-workspace-id";
|
|
56
60
|
var DEV_USERS = [
|
|
@@ -561,6 +565,25 @@ var errorMessage = (err) => {
|
|
|
561
565
|
}
|
|
562
566
|
return String(err);
|
|
563
567
|
};
|
|
568
|
+
var tryRun = async (failures, phase, scope, resourceType, resourceId, fn) => {
|
|
569
|
+
try {
|
|
570
|
+
await fn();
|
|
571
|
+
return true;
|
|
572
|
+
} catch (err) {
|
|
573
|
+
failures.push({ phase, scope, resourceType, resourceId, error: err });
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
var aggregateFailureError = (failures) => {
|
|
578
|
+
const summary = failures.map(
|
|
579
|
+
(f) => `${f.phase} ${f.scope}/${f.resourceType}/${f.resourceId}: ${errorMessage(
|
|
580
|
+
f.error
|
|
581
|
+
)}`
|
|
582
|
+
).join("; ");
|
|
583
|
+
return new Error(
|
|
584
|
+
`seed-demo-data: ${failures.length} item(s) failed across phases: ${summary}`
|
|
585
|
+
);
|
|
586
|
+
};
|
|
564
587
|
var idForRoleCode = (code) => {
|
|
565
588
|
for (const key of Object.keys(PLATFORM_ROLE_IDS)) {
|
|
566
589
|
if (PLATFORM_ROLE_CONCEPTS[key].code === code) {
|
|
@@ -670,95 +693,180 @@ var upsertUser = async (context, user, cognitoSub) => {
|
|
|
670
693
|
lastUpdated: context.date ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
671
694
|
}).go();
|
|
672
695
|
};
|
|
673
|
-
var seedWorkspaceDataPlane = async (baseContext, group) => {
|
|
696
|
+
var seedWorkspaceDataPlane = async (baseContext, group, failures) => {
|
|
674
697
|
const workspaceContext = {
|
|
675
698
|
...baseContext,
|
|
676
699
|
tenantId: group.tenantId,
|
|
677
700
|
workspaceId: group.workspaceId
|
|
678
701
|
};
|
|
702
|
+
const scope = `${group.tenantId}/${group.workspaceId}`;
|
|
679
703
|
for (const patient of group.patients) {
|
|
680
|
-
await
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
704
|
+
await tryRun(
|
|
705
|
+
failures,
|
|
706
|
+
"phase-3",
|
|
707
|
+
scope,
|
|
708
|
+
"Patient",
|
|
709
|
+
patient.id ?? "",
|
|
710
|
+
() => createPatientOperation({
|
|
711
|
+
context: workspaceContext,
|
|
712
|
+
body: patient
|
|
713
|
+
})
|
|
714
|
+
);
|
|
684
715
|
}
|
|
685
716
|
for (const practitioner of group.practitioners) {
|
|
686
|
-
await
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
717
|
+
await tryRun(
|
|
718
|
+
failures,
|
|
719
|
+
"phase-3",
|
|
720
|
+
scope,
|
|
721
|
+
"Practitioner",
|
|
722
|
+
practitioner.id ?? "",
|
|
723
|
+
() => createPractitionerOperation({
|
|
724
|
+
context: workspaceContext,
|
|
725
|
+
body: practitioner
|
|
726
|
+
})
|
|
727
|
+
);
|
|
690
728
|
}
|
|
691
729
|
for (const observation of group.observations) {
|
|
692
|
-
await
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
730
|
+
await tryRun(
|
|
731
|
+
failures,
|
|
732
|
+
"phase-3",
|
|
733
|
+
scope,
|
|
734
|
+
"Observation",
|
|
735
|
+
observation.id ?? "",
|
|
736
|
+
() => createObservationOperation({
|
|
737
|
+
context: workspaceContext,
|
|
738
|
+
body: observation
|
|
739
|
+
})
|
|
740
|
+
);
|
|
696
741
|
}
|
|
697
742
|
for (const encounter of group.encounters) {
|
|
698
|
-
await
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
743
|
+
await tryRun(
|
|
744
|
+
failures,
|
|
745
|
+
"phase-3",
|
|
746
|
+
scope,
|
|
747
|
+
"Encounter",
|
|
748
|
+
encounter.id ?? "",
|
|
749
|
+
() => createEncounterOperation({
|
|
750
|
+
context: workspaceContext,
|
|
751
|
+
body: encounter
|
|
752
|
+
})
|
|
753
|
+
);
|
|
702
754
|
}
|
|
703
755
|
for (const account of group.accounts) {
|
|
704
|
-
await
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
756
|
+
await tryRun(
|
|
757
|
+
failures,
|
|
758
|
+
"phase-3",
|
|
759
|
+
scope,
|
|
760
|
+
"Account",
|
|
761
|
+
account.id ?? "",
|
|
762
|
+
() => createAccountOperation({
|
|
763
|
+
context: workspaceContext,
|
|
764
|
+
body: account
|
|
765
|
+
})
|
|
766
|
+
);
|
|
708
767
|
}
|
|
709
768
|
};
|
|
710
769
|
var seedDemoGraph = async (params) => {
|
|
711
770
|
const { baseContext, devUsers, cognito } = params;
|
|
771
|
+
const failures = [];
|
|
712
772
|
for (const spec of DEMO_TENANT_SPECS) {
|
|
713
773
|
const tenantContext = {
|
|
714
774
|
...baseContext,
|
|
715
775
|
tenantId: spec.tenantId
|
|
716
776
|
};
|
|
717
|
-
await
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
777
|
+
await tryRun(
|
|
778
|
+
failures,
|
|
779
|
+
"phase-1",
|
|
780
|
+
spec.tenantId,
|
|
781
|
+
"Tenant",
|
|
782
|
+
spec.tenantId,
|
|
783
|
+
() => createTenantOperation({
|
|
723
784
|
context: tenantContext,
|
|
724
|
-
body: {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
785
|
+
body: { id: spec.tenantId, resource: tenantResourceBody(spec) }
|
|
786
|
+
})
|
|
787
|
+
);
|
|
788
|
+
for (const workspace of spec.workspaces) {
|
|
789
|
+
await tryRun(
|
|
790
|
+
failures,
|
|
791
|
+
"phase-1",
|
|
792
|
+
spec.tenantId,
|
|
793
|
+
"Workspace",
|
|
794
|
+
workspace.id,
|
|
795
|
+
() => createWorkspaceOperation({
|
|
796
|
+
context: tenantContext,
|
|
797
|
+
body: {
|
|
798
|
+
id: workspace.id,
|
|
799
|
+
resource: workspaceResourceBody(spec, workspace)
|
|
800
|
+
}
|
|
801
|
+
})
|
|
802
|
+
);
|
|
729
803
|
}
|
|
730
804
|
}
|
|
731
805
|
for (const user of devUsers) {
|
|
732
|
-
|
|
733
|
-
|
|
806
|
+
let cognitoSub;
|
|
807
|
+
try {
|
|
808
|
+
cognitoSub = await cognito.ensureUser(user.email);
|
|
809
|
+
} catch (err) {
|
|
810
|
+
failures.push({
|
|
811
|
+
phase: "phase-2",
|
|
812
|
+
scope: user.id,
|
|
813
|
+
resourceType: "CognitoUser",
|
|
814
|
+
resourceId: user.email,
|
|
815
|
+
error: err
|
|
816
|
+
});
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
await tryRun(
|
|
820
|
+
failures,
|
|
821
|
+
"phase-2",
|
|
822
|
+
user.id,
|
|
823
|
+
"User",
|
|
824
|
+
user.id,
|
|
825
|
+
() => upsertUser(baseContext, user, cognitoSub)
|
|
826
|
+
);
|
|
734
827
|
for (const spec of DEMO_TENANT_SPECS) {
|
|
735
828
|
const tenantContext = {
|
|
736
829
|
...baseContext,
|
|
737
830
|
tenantId: spec.tenantId
|
|
738
831
|
};
|
|
832
|
+
const userScope = `${user.id}@${spec.tenantId}`;
|
|
739
833
|
const membershipId = demoMembershipId(user.id, spec.tenantId);
|
|
740
|
-
await
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {
|
|
748
|
-
const raId = demoRoleAssignmentId(user.id, spec.tenantId, roleCode);
|
|
749
|
-
await createRoleAssignmentOperation({
|
|
834
|
+
await tryRun(
|
|
835
|
+
failures,
|
|
836
|
+
"phase-2",
|
|
837
|
+
userScope,
|
|
838
|
+
"Membership",
|
|
839
|
+
membershipId,
|
|
840
|
+
() => createMembershipOperation({
|
|
750
841
|
context: tenantContext,
|
|
751
842
|
body: {
|
|
752
|
-
id:
|
|
753
|
-
resource:
|
|
754
|
-
spec.scenario,
|
|
755
|
-
spec.tenantId,
|
|
756
|
-
user,
|
|
757
|
-
roleCode,
|
|
758
|
-
raId
|
|
759
|
-
)
|
|
843
|
+
id: membershipId,
|
|
844
|
+
resource: membershipResourceBody(spec, user, membershipId)
|
|
760
845
|
}
|
|
761
|
-
})
|
|
846
|
+
})
|
|
847
|
+
);
|
|
848
|
+
for (const roleCode of demoRolesForUserInTenant(user, spec.tenantId)) {
|
|
849
|
+
const raId = demoRoleAssignmentId(user.id, spec.tenantId, roleCode);
|
|
850
|
+
await tryRun(
|
|
851
|
+
failures,
|
|
852
|
+
"phase-2",
|
|
853
|
+
userScope,
|
|
854
|
+
"RoleAssignment",
|
|
855
|
+
raId,
|
|
856
|
+
() => createRoleAssignmentOperation({
|
|
857
|
+
context: tenantContext,
|
|
858
|
+
body: {
|
|
859
|
+
id: raId,
|
|
860
|
+
resource: roleAssignmentResourceBody(
|
|
861
|
+
spec.scenario,
|
|
862
|
+
spec.tenantId,
|
|
863
|
+
user,
|
|
864
|
+
roleCode,
|
|
865
|
+
raId
|
|
866
|
+
)
|
|
867
|
+
}
|
|
868
|
+
})
|
|
869
|
+
);
|
|
762
870
|
}
|
|
763
871
|
}
|
|
764
872
|
const platformContext = {
|
|
@@ -771,22 +879,42 @@ var seedDemoGraph = async (params) => {
|
|
|
771
879
|
PLATFORM_SCOPE_TENANT_ID,
|
|
772
880
|
platformRoleCode
|
|
773
881
|
);
|
|
774
|
-
await
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
platformRaId
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
882
|
+
await tryRun(
|
|
883
|
+
failures,
|
|
884
|
+
"phase-2",
|
|
885
|
+
`${user.id}@${PLATFORM_SCOPE_TENANT_ID}`,
|
|
886
|
+
"RoleAssignment",
|
|
887
|
+
platformRaId,
|
|
888
|
+
() => createRoleAssignmentOperation({
|
|
889
|
+
context: platformContext,
|
|
890
|
+
body: {
|
|
891
|
+
id: platformRaId,
|
|
892
|
+
resource: roleAssignmentResourceBody(
|
|
893
|
+
"platform",
|
|
894
|
+
PLATFORM_SCOPE_TENANT_ID,
|
|
895
|
+
user,
|
|
896
|
+
platformRoleCode,
|
|
897
|
+
platformRaId
|
|
898
|
+
)
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
);
|
|
787
902
|
}
|
|
788
903
|
for (const group of DEMO_DATA_PLANE_FIXTURES) {
|
|
789
|
-
|
|
904
|
+
try {
|
|
905
|
+
await seedWorkspaceDataPlane(baseContext, group, failures);
|
|
906
|
+
} catch (err) {
|
|
907
|
+
failures.push({
|
|
908
|
+
phase: "phase-3",
|
|
909
|
+
scope: `${group.tenantId}/${group.workspaceId}`,
|
|
910
|
+
resourceType: "Workspace",
|
|
911
|
+
resourceId: group.workspaceId,
|
|
912
|
+
error: err
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (failures.length > 0) {
|
|
917
|
+
throw aggregateFailureError(failures);
|
|
790
918
|
}
|
|
791
919
|
};
|
|
792
920
|
var runSeedDemoData = async (event, deps, devUsers) => {
|
|
@@ -825,10 +953,66 @@ var runSeedDemoData = async (event, deps, devUsers) => {
|
|
|
825
953
|
throw err;
|
|
826
954
|
}
|
|
827
955
|
};
|
|
828
|
-
var
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
956
|
+
var SEED_USER_PASSWORD_PARAMETER_PREFIX = "/openhi/seed/users/";
|
|
957
|
+
var SSM_PATH_SEGMENT = /^[A-Za-z0-9_.-]+$/;
|
|
958
|
+
var emailToSsmPath = (email) => {
|
|
959
|
+
if (typeof email !== "string" || email.length === 0) {
|
|
960
|
+
throw new Error(
|
|
961
|
+
`emailToSsmPath: email must be a non-empty string (received "${String(email)}").`
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
const atIdx = email.indexOf("@");
|
|
965
|
+
if (atIdx === -1 || atIdx !== email.lastIndexOf("@")) {
|
|
966
|
+
throw new Error(
|
|
967
|
+
`emailToSsmPath: email "${email}" must contain exactly one "@" character.`
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
const localPart = email.slice(0, atIdx);
|
|
971
|
+
const domainPart = email.slice(atIdx + 1);
|
|
972
|
+
if (localPart.length === 0 || domainPart.length === 0) {
|
|
973
|
+
throw new Error(
|
|
974
|
+
`emailToSsmPath: email "${email}" must have a non-empty local-part and domain.`
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
if (!SSM_PATH_SEGMENT.test(localPart) || !SSM_PATH_SEGMENT.test(domainPart)) {
|
|
978
|
+
throw new Error(
|
|
979
|
+
`emailToSsmPath: email "${email}" contains characters that would produce an invalid SSM parameter path (only A-Z, a-z, 0-9, '.', '-', and '_' are allowed).`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
return `${SEED_USER_PASSWORD_PARAMETER_PREFIX}${localPart}_at_${domainPart}/password`;
|
|
983
|
+
};
|
|
984
|
+
var cachedSsmClient;
|
|
985
|
+
var getSsmClient = () => {
|
|
986
|
+
if (!cachedSsmClient) {
|
|
987
|
+
cachedSsmClient = new SSMClient({});
|
|
988
|
+
}
|
|
989
|
+
return cachedSsmClient;
|
|
990
|
+
};
|
|
991
|
+
var __resetSsmClientForTests = () => {
|
|
992
|
+
cachedSsmClient = void 0;
|
|
993
|
+
};
|
|
994
|
+
var fetchSeedUserPassword = async (email) => {
|
|
995
|
+
const path = emailToSsmPath(email);
|
|
996
|
+
const client = getSsmClient();
|
|
997
|
+
try {
|
|
998
|
+
const result = await client.send(
|
|
999
|
+
new GetParameterCommand({ Name: path, WithDecryption: true })
|
|
1000
|
+
);
|
|
1001
|
+
const value = result.Parameter?.Value;
|
|
1002
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1003
|
+
throw new Error(
|
|
1004
|
+
`fetchSeedUserPassword: SSM parameter "${path}" returned an empty value.`
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
return value;
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
if (err instanceof ParameterNotFound) {
|
|
1010
|
+
throw new Error(
|
|
1011
|
+
`fetchSeedUserPassword: SSM parameter "${path}" not found. Provision a SecureString at "${path}" with the dev user's password before re-running seed-demo-data.`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
throw err;
|
|
1015
|
+
}
|
|
832
1016
|
};
|
|
833
1017
|
var productionCognitoProvisioner = () => {
|
|
834
1018
|
const client = new CognitoIdentityProviderClient({});
|
|
@@ -846,8 +1030,19 @@ var productionCognitoProvisioner = () => {
|
|
|
846
1030
|
}
|
|
847
1031
|
return void 0;
|
|
848
1032
|
};
|
|
1033
|
+
const setPassword = async (email, password) => {
|
|
1034
|
+
await client.send(
|
|
1035
|
+
new AdminSetUserPasswordCommand({
|
|
1036
|
+
UserPoolId: userPoolId,
|
|
1037
|
+
Username: email,
|
|
1038
|
+
Password: password,
|
|
1039
|
+
Permanent: true
|
|
1040
|
+
})
|
|
1041
|
+
);
|
|
1042
|
+
};
|
|
849
1043
|
return {
|
|
850
1044
|
ensureUser: async (email) => {
|
|
1045
|
+
const password = await fetchSeedUserPassword(email);
|
|
851
1046
|
try {
|
|
852
1047
|
const created = await client.send(
|
|
853
1048
|
new AdminCreateUserCommand({
|
|
@@ -860,14 +1055,7 @@ var productionCognitoProvisioner = () => {
|
|
|
860
1055
|
]
|
|
861
1056
|
})
|
|
862
1057
|
);
|
|
863
|
-
await
|
|
864
|
-
new AdminSetUserPasswordCommand({
|
|
865
|
-
UserPoolId: userPoolId,
|
|
866
|
-
Username: email,
|
|
867
|
-
Password: devPasswordForEmail(email),
|
|
868
|
-
Permanent: true
|
|
869
|
-
})
|
|
870
|
-
);
|
|
1058
|
+
await setPassword(email, password);
|
|
871
1059
|
const sub = subFromAttributes(created.User?.Attributes);
|
|
872
1060
|
if (!sub) {
|
|
873
1061
|
throw new Error(
|
|
@@ -877,6 +1065,7 @@ var productionCognitoProvisioner = () => {
|
|
|
877
1065
|
return sub;
|
|
878
1066
|
} catch (err) {
|
|
879
1067
|
if (err instanceof UsernameExistsException) {
|
|
1068
|
+
await setPassword(email, password);
|
|
880
1069
|
const got = await client.send(
|
|
881
1070
|
new AdminGetUserCommand({
|
|
882
1071
|
UserPoolId: userPoolId,
|
|
@@ -914,7 +1103,6 @@ export {
|
|
|
914
1103
|
DEMO_URN_SYSTEM,
|
|
915
1104
|
OPENHI_RESOURCE_URN_SYSTEM,
|
|
916
1105
|
DEMO_PERIOD,
|
|
917
|
-
PLATFORM_SCOPE_TENANT_ID,
|
|
918
1106
|
PLACEHOLDER_TENANT_ID,
|
|
919
1107
|
PLACEHOLDER_WORKSPACE_ID,
|
|
920
1108
|
DEV_USERS,
|
|
@@ -928,8 +1116,11 @@ export {
|
|
|
928
1116
|
SEED_DEMO_DATA_USER_POOL_ID_ENV_VAR,
|
|
929
1117
|
seedDemoGraph,
|
|
930
1118
|
runSeedDemoData,
|
|
931
|
-
|
|
1119
|
+
SEED_USER_PASSWORD_PARAMETER_PREFIX,
|
|
1120
|
+
emailToSsmPath,
|
|
1121
|
+
__resetSsmClientForTests,
|
|
1122
|
+
fetchSeedUserPassword,
|
|
932
1123
|
productionCognitoProvisioner,
|
|
933
1124
|
handler
|
|
934
1125
|
};
|
|
935
|
-
//# sourceMappingURL=chunk-
|
|
1126
|
+
//# sourceMappingURL=chunk-ZVDVKCNC.mjs.map
|