@openhi/constructs 0.0.110 → 0.0.112
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-23PUSHBV.mjs +24 -0
- package/lib/chunk-23PUSHBV.mjs.map +1 -0
- package/lib/chunk-2O3CXY2C.mjs +79 -0
- package/lib/chunk-2O3CXY2C.mjs.map +1 -0
- package/lib/{chunk-7FUAMZOF.mjs → chunk-53OHXLIL.mjs} +3 -3
- package/lib/chunk-6NBGYGFL.mjs +1803 -0
- package/lib/chunk-6NBGYGFL.mjs.map +1 -0
- package/lib/chunk-7RZHFI77.mjs +22 -0
- package/lib/chunk-7RZHFI77.mjs.map +1 -0
- package/lib/{chunk-7Q2IJ2J5.mjs → chunk-CUUKXDB2.mjs} +6 -6
- package/lib/chunk-FYHBHHWK.mjs +47 -0
- package/lib/chunk-FYHBHHWK.mjs.map +1 -0
- package/lib/{chunk-MULKGFIJ.mjs → chunk-GBDIGTNV.mjs} +165 -10
- package/lib/chunk-GBDIGTNV.mjs.map +1 -0
- package/lib/chunk-HQ67J7BP.mjs +199 -0
- package/lib/chunk-HQ67J7BP.mjs.map +1 -0
- package/lib/{chunk-AJ3G3THO.mjs → chunk-KO64HPWQ.mjs} +2 -2
- package/lib/{chunk-BB5MK4L3.mjs → chunk-KSFC72TT.mjs} +3 -3
- package/lib/{chunk-2TPJ6HOF.mjs → chunk-NZRW7ROK.mjs} +72 -54
- package/lib/chunk-NZRW7ROK.mjs.map +1 -0
- package/lib/chunk-QJDHVMKT.mjs +117 -0
- package/lib/chunk-QJDHVMKT.mjs.map +1 -0
- package/lib/{chunk-IS4VQRI4.mjs → chunk-QMBJ4VHC.mjs} +12 -47
- package/lib/chunk-QMBJ4VHC.mjs.map +1 -0
- package/lib/chunk-TRY7JGWO.mjs +16 -0
- package/lib/chunk-TRY7JGWO.mjs.map +1 -0
- package/lib/chunk-W4KR4CSL.mjs +236 -0
- package/lib/chunk-W4KR4CSL.mjs.map +1 -0
- package/lib/{chunk-AGF3RAAZ.mjs → chunk-WPCBVDFZ.mjs} +2 -2
- package/lib/chunk-WQWFVEVX.mjs +66 -0
- package/lib/chunk-WQWFVEVX.mjs.map +1 -0
- package/lib/{chunk-SYBADQXI.mjs → chunk-ZM4GDHHC.mjs} +77 -2
- package/lib/chunk-ZM4GDHHC.mjs.map +1 -0
- package/lib/data-store-postgres-replication.handler.js +26 -17
- package/lib/data-store-postgres-replication.handler.js.map +1 -1
- package/lib/data-store-postgres-replication.handler.mjs +5 -65
- package/lib/data-store-postgres-replication.handler.mjs.map +1 -1
- package/lib/delete-chunk.handler.d.mts +29 -0
- package/lib/delete-chunk.handler.d.ts +29 -0
- package/lib/delete-chunk.handler.js +2716 -0
- package/lib/delete-chunk.handler.js.map +1 -0
- package/lib/delete-chunk.handler.mjs +47 -0
- package/lib/delete-chunk.handler.mjs.map +1 -0
- package/lib/events-CjS-sm0W.d.mts +107 -0
- package/lib/events-CjS-sm0W.d.ts +107 -0
- package/lib/events-Da_cFgtc.d.mts +208 -0
- package/lib/events-Da_cFgtc.d.ts +208 -0
- package/lib/finalize.handler.d.mts +35 -0
- package/lib/finalize.handler.d.ts +35 -0
- package/lib/finalize.handler.js +875 -0
- package/lib/finalize.handler.js.map +1 -0
- package/lib/finalize.handler.mjs +166 -0
- package/lib/finalize.handler.mjs.map +1 -0
- package/lib/index.d.mts +189 -2
- package/lib/index.d.ts +500 -3
- package/lib/index.js +1753 -174
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +571 -17
- package/lib/index.mjs.map +1 -1
- package/lib/list-chunks.handler.d.mts +28 -0
- package/lib/list-chunks.handler.d.ts +28 -0
- package/lib/list-chunks.handler.js +2746 -0
- package/lib/list-chunks.handler.js.map +1 -0
- package/lib/list-chunks.handler.mjs +54 -0
- package/lib/list-chunks.handler.mjs.map +1 -0
- package/lib/platform-deploy-bridge.handler.js +76 -1
- package/lib/platform-deploy-bridge.handler.js.map +1 -1
- package/lib/platform-deploy-bridge.handler.mjs +1 -1
- package/lib/pre-token-generation.handler.js +1106 -155
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +6 -4
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +1529 -142
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +8 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rename-finalize.handler.d.mts +30 -0
- package/lib/rename-finalize.handler.d.ts +30 -0
- package/lib/rename-finalize.handler.js +795 -0
- package/lib/rename-finalize.handler.js.map +1 -0
- package/lib/rename-finalize.handler.mjs +90 -0
- package/lib/rename-finalize.handler.mjs.map +1 -0
- package/lib/rename-list-targets.handler.d.mts +26 -0
- package/lib/rename-list-targets.handler.d.ts +26 -0
- package/lib/rename-list-targets.handler.js +2985 -0
- package/lib/rename-list-targets.handler.js.map +1 -0
- package/lib/rename-list-targets.handler.mjs +431 -0
- package/lib/rename-list-targets.handler.mjs.map +1 -0
- package/lib/rename-rewrite-chunk.handler.d.mts +35 -0
- package/lib/rename-rewrite-chunk.handler.d.ts +35 -0
- package/lib/rename-rewrite-chunk.handler.js +2021 -0
- package/lib/rename-rewrite-chunk.handler.js.map +1 -0
- package/lib/rename-rewrite-chunk.handler.mjs +27 -0
- package/lib/rename-rewrite-chunk.handler.mjs.map +1 -0
- package/lib/rest-api-lambda.handler.js +4087 -921
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1827 -81
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.js +1588 -124
- package/lib/seed-demo-data.handler.js.map +1 -1
- package/lib/seed-demo-data.handler.mjs +10 -6
- package/lib/seed-system-data.handler.js +1179 -155
- package/lib/seed-system-data.handler.js.map +1 -1
- package/lib/seed-system-data.handler.mjs +5 -4
- package/lib/seed-system-data.handler.mjs.map +1 -1
- package/package.json +1 -1
- package/lib/chunk-2TPJ6HOF.mjs.map +0 -1
- package/lib/chunk-IS4VQRI4.mjs.map +0 -1
- package/lib/chunk-MULKGFIJ.mjs.map +0 -1
- package/lib/chunk-QR5JVSCF.mjs +0 -862
- package/lib/chunk-QR5JVSCF.mjs.map +0 -1
- package/lib/chunk-SYBADQXI.mjs.map +0 -1
- /package/lib/{chunk-7FUAMZOF.mjs.map → chunk-53OHXLIL.mjs.map} +0 -0
- /package/lib/{chunk-7Q2IJ2J5.mjs.map → chunk-CUUKXDB2.mjs.map} +0 -0
- /package/lib/{chunk-AJ3G3THO.mjs.map → chunk-KO64HPWQ.mjs.map} +0 -0
- /package/lib/{chunk-BB5MK4L3.mjs.map → chunk-KSFC72TT.mjs.map} +0 -0
- /package/lib/{chunk-AGF3RAAZ.mjs.map → chunk-WPCBVDFZ.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../workflows/src/envelope-version.ts","../../workflows/src/envelope.ts","../../workflows/src/sources.ts","../../workflows/src/detail-types/registry.ts","../../workflows/src/detail-types/control-plane.ts","../../workflows/src/detail-types/platform.ts","../../workflows/src/detail-types/index.ts","../../workflows/src/publisher.ts","../../workflows/src/consumer.ts","../../workflows/src/dedup/env.ts","../../workflows/src/dedup/workflow-dedup-client.ts","../../workflows/src/dedup/index.ts","../../workflows/src/index.ts","../src/workflows/control-plane/owning-delete-cascade/delete-chunk.handler.ts","../src/data/dynamo/dynamo-control-service.ts","../src/data/dynamo/dynamo-client.ts","../src/data/dynamo/entities/control/configuration-entity.ts","../src/data/dynamo/entities/control/control-entity-common.ts","../src/data/dynamo/shard.ts","../src/data/dynamo/entities/control/configuration-user-projection-entity.ts","../src/data/dynamo/entities/control/configuration-workspace-projection-entity.ts","../src/data/dynamo/entities/control/membership-entity.ts","../src/data/dynamo/entities/control/membership-user-projection-entity.ts","../src/data/dynamo/entities/control/membership-workspace-projection-entity.ts","../src/data/dynamo/entities/control/role-entity.ts","../src/data/dynamo/entities/control/roleassignment-entity.ts","../src/data/dynamo/entities/control/roleassignment-user-projection-entity.ts","../src/data/dynamo/entities/control/roleassignment-workspace-projection-entity.ts","../src/data/dynamo/entities/control/tenant-entity.ts","../src/data/dynamo/entities/control/user-entity.ts","../src/data/dynamo/entities/control/workspace-entity.ts","../src/data/errors/domain-errors.ts","../src/data/operations/control/multi-write-operation.ts","../src/data/operations/control/owning-delete/owning-delete-batch-delete-operation.ts","../src/data/operations/control/owning-delete/owning-delete-list-child-projections-operation.ts"],"sourcesContent":["/**\n * Envelope version pinned by the publisher SDK on every emitted envelope.\n *\n * Shape is `\"<major>.<minor>\"` per TR-016 §Open Items #3 (P1).\n * Additive optional fields bump the minor; breaking changes bump the\n * major and require a documented overlap window in the consumer SDK.\n */\nexport const ENVELOPE_VERSION = \"1.0\";\n\nconst ENVELOPE_VERSION_PATTERN = /^\\d+\\.\\d+$/;\n\n/**\n * Lowest envelope major version this SDK's consumer parser accepts.\n *\n * Bump this when a deprecated major reaches end-of-life and the\n * overlap window closes.\n */\nconst MIN_SUPPORTED_MAJOR = 1;\n\n/**\n * Highest envelope major version this SDK's consumer parser accepts.\n *\n * Bump this in lockstep with `ENVELOPE_VERSION` whenever a new major\n * ships; the consumer always supports its own publish major.\n */\nconst MAX_SUPPORTED_MAJOR = 1;\n\n/**\n * Return `true` when `version` is shaped `<major>.<minor>` and its\n * major lies within `[MIN_SUPPORTED_MAJOR, MAX_SUPPORTED_MAJOR]`.\n */\nexport function isSupportedEnvelopeVersion(version: string): boolean {\n if (!ENVELOPE_VERSION_PATTERN.test(version)) {\n return false;\n }\n const major = Number.parseInt(version.split(\".\")[0], 10);\n return major >= MIN_SUPPORTED_MAJOR && major <= MAX_SUPPORTED_MAJOR;\n}\n","import type { OhiJwtClaims } from \"@openhi/types\";\n\n/**\n * Discriminated-union actor field per ADR-016 §Decision #3.\n *\n * User-initiated workflows project the four ADR-014 JWT claims (with\n * `ohi_tid` / `ohi_wid` required — a workflow event without a tenant\n * + workspace context belongs to the `system` variant); bootstrap\n * workflows run before a User exists and carry the bootstrap-role\n * name instead. The `system` value is free-form pending the\n * onboarding-bootstrap IAM role TR (TR-016 §Open Items #4).\n */\nexport type WorkflowActor = WorkflowUserActor | WorkflowSystemActor;\n\n/** User-actor variant — required projection of the ADR-014 JWT claims. */\nexport interface WorkflowUserActor {\n readonly ohi_tid: string;\n readonly ohi_wid: string;\n readonly ohi_uid: string;\n readonly ohi_uname: string;\n}\n\n/** Bootstrap-actor variant — carries the bootstrap-role name. */\nexport interface WorkflowSystemActor {\n readonly system: string;\n}\n\n/**\n * Type guard for the user-actor variant.\n */\nexport function isWorkflowUserActor(\n actor: WorkflowActor,\n): actor is WorkflowUserActor {\n return (actor as WorkflowUserActor).ohi_uid !== undefined;\n}\n\n/**\n * Type guard for the system-actor variant.\n */\nexport function isWorkflowSystemActor(\n actor: WorkflowActor,\n): actor is WorkflowSystemActor {\n return (actor as WorkflowSystemActor).system !== undefined;\n}\n\n/**\n * Standard workflow event envelope per ADR-016 §Decision #3 / TR-016.\n *\n * The generic `TPayload` parameter narrows the per-workflow payload.\n *\n * Field naming note: the payload lives under `payload` (not `detail`)\n * deliberately. EventBridge's outer event already has its own `detail`\n * field that carries this whole envelope; nesting another `detail`\n * inside it produced double-`detail` paths in rule patterns\n * (`detail.detail.<x>`) that consistently bit consumers writing\n * filters. The unambiguous name is worth the rename.\n */\nexport interface WorkflowEvent<TPayload = Record<string, unknown>> {\n /** Per-event UUID. Dedup keys on `(eventId, attempt)`. */\n readonly eventId: string;\n /** 1-indexed delivery attempt. */\n readonly attempt: number;\n /** Originating cross-plane chain identifier. */\n readonly correlationId: string;\n /** Immediate predecessor event id, or null for a chain origin. */\n readonly causationId: string | null;\n /** Discriminated actor — JWT-claim projection or bootstrap-role marker. */\n readonly actor: WorkflowActor;\n /** ISO-8601 timestamp marking when the envelope was constructed. */\n readonly occurredAt: string;\n /** Semver-shaped envelope version (e.g. `\"1.0\"`). */\n readonly envelopeVersion: string;\n /** Per-workflow payload narrowed by the generic parameter. */\n readonly payload: TPayload;\n}\n\n/**\n * Promote a raw `OhiJwtClaims` projection to a fully-required\n * `WorkflowUserActor`.\n *\n * Throws `MissingActorContextError` when `ohi_tid` or `ohi_wid` is\n * absent — those claims are optional on the JWT but required on the\n * workflow user-actor.\n */\nexport function workflowUserActorFromClaims(\n claims: OhiJwtClaims,\n): WorkflowUserActor {\n if (claims.ohi_tid === undefined || claims.ohi_wid === undefined) {\n throw new MissingActorContextError(\n \"workflowUserActorFromClaims: ohi_tid and ohi_wid are required on the workflow user-actor; the caller's JWT is missing one or both. Use a system-actor for pre-provisioning bootstrap workflows.\",\n );\n }\n return {\n ohi_tid: claims.ohi_tid,\n ohi_wid: claims.ohi_wid,\n ohi_uid: claims.ohi_uid,\n ohi_uname: claims.ohi_uname,\n };\n}\n\n/** Thrown when JWT claims lack a field required by the workflow user-actor. */\nexport class MissingActorContextError extends Error {\n /** @param message - human-readable description of the missing claim. */\n constructor(message: string) {\n super(message);\n this.name = \"MissingActorContextError\";\n }\n}\n","/**\n * Per-bus `Source` constants for the three OpenHI EventBridge buses.\n *\n * Every workflow event carries one of these values in its EventBridge\n * `Source` field. Bus selection follows ADR-016 §Decision #1.\n *\n * @see https://github.com/codedrifters/openhi-planning/blob/main/docs/src/content/docs/requirements/architectural-decisions/ADR-016-workflow-boundary-strategy.md\n */\n\n/** Source for control-plane workflow events (Tenant / Workspace / User / Membership / Role / RoleAssignment / Configuration). */\nexport const OPENHI_CONTROL_SOURCE = \"openhi.control\" as const;\n\n/** Source for data-plane workflow events (committed writes to the FHIR data store). */\nexport const OPENHI_DATA_SOURCE = \"openhi.data\" as const;\n\n/** Source for ops-plane workflow events (platform telemetry, deployments, build events). */\nexport const OPENHI_OPS_SOURCE = \"openhi.ops\" as const;\n\n/**\n * Discriminated union over the three OpenHI EventBridge sources.\n *\n * A publisher that passes any other string for the EventBridge `Source`\n * field is rejected at compile time.\n */\nexport type OpenHiSource =\n | typeof OPENHI_CONTROL_SOURCE\n | typeof OPENHI_DATA_SOURCE\n | typeof OPENHI_OPS_SOURCE;\n\n/**\n * Default EventBridge bus name for each OpenHI source.\n *\n * Deployments may override per-source via\n * `PublisherOptions.busNameByPlane`.\n */\nexport const DEFAULT_BUS_NAME_BY_SOURCE: Record<OpenHiSource, string> = {\n [OPENHI_CONTROL_SOURCE]: \"openhi-control-event-bus\",\n [OPENHI_DATA_SOURCE]: \"openhi-data-event-bus\",\n [OPENHI_OPS_SOURCE]: \"openhi-ops-event-bus\",\n};\n","import type { OpenHiSource } from \"../sources\";\n\n/**\n * One entry in the workflow detail-type registry.\n *\n * Each registered detail-type binds a typed `detail` payload to:\n * - the EventBridge `Source` (and therefore the bus) it ships on,\n * - the `detail-type` string consumers subscribe to,\n * - per-event metadata (`crossPlane`, `dedupRequired`) read by\n * downstream tooling (AsyncAPI generator, placement matrix).\n *\n * The `_detail` property is a phantom marker type only — never\n * read, never written, never instantiated. It carries the\n * compile-time TDetail through the entry so callers of\n * `publishWorkflowEvent` / `parseWorkflowEvent` see the typed\n * payload at the call site.\n */\nexport interface WorkflowDetailTypeEntry<TDetail> {\n /** Versioned detail-type string, e.g. `tenant.onboarded.v1`. */\n readonly detailType: string;\n /** The bus this detail-type ships on (compile-time enforced). */\n readonly source: OpenHiSource;\n /** Phantom marker carrying the payload shape. */\n readonly _detail?: TDetail;\n /** Marks events that cross plane boundaries (placement-matrix metadata). */\n readonly crossPlane?: boolean;\n /** Marks events that retryable consumers MUST dedupe on. */\n readonly dedupRequired?: boolean;\n}\n\n/**\n * Declare a registry entry with type inference.\n *\n * Call sites:\n *\n * ```ts\n * export const TenantOnboardedV1 = defineDetailType<TenantOnboardedV1Detail>({\n * detailType: \"tenant.onboarded.v1\",\n * source: OPENHI_CONTROL_SOURCE,\n * dedupRequired: true,\n * });\n * ```\n *\n * Detail-type strings follow `<area>.<event>.<version>` (TR-016 §Open\n * Items #2). The conformance regex below is the platform-wide format.\n */\nexport function defineDetailType<TDetail>(\n entry: Omit<WorkflowDetailTypeEntry<TDetail>, \"_detail\">,\n): WorkflowDetailTypeEntry<TDetail> {\n if (!isWellFormedDetailType(entry.detailType)) {\n throw new InvalidDetailTypeRegistrationError(\n `Detail-type \"${entry.detailType}\" does not match the platform-wide format <area>.<event>.v<integer>. See TR-016 §Open Items #2.`,\n );\n }\n return entry;\n}\n\n/**\n * Pattern enforced on every registered detail-type:\n * `<area>.<event>.v<integer>` where each segment is lowercase\n * alphanumeric with optional dashes.\n *\n * Multi-level areas (e.g. `fhir.audit-event.recorded.v1`) are\n * intentionally not yet allowed; TR-016 §Open Items #2 defers that.\n */\nconst DETAIL_TYPE_PATTERN =\n /^[a-z0-9]+(?:-[a-z0-9]+)*\\.[a-z0-9]+(?:-[a-z0-9]+)*\\.v\\d+$/;\n\n/** Return `true` when `detailType` matches the platform-wide format. */\nexport function isWellFormedDetailType(detailType: string): boolean {\n return DETAIL_TYPE_PATTERN.test(detailType);\n}\n\n/** Thrown by `defineDetailType` when the supplied string violates the format. */\nexport class InvalidDetailTypeRegistrationError extends Error {\n /** @param message - human-readable description of the violation. */\n constructor(message: string) {\n super(message);\n this.name = \"InvalidDetailTypeRegistrationError\";\n }\n}\n","import { OPENHI_DATA_SOURCE, OPENHI_OPS_SOURCE } from \"../sources\";\nimport { defineDetailType } from \"./registry\";\n\n/**\n * Owning-entity types covered by the TR-022 hard-delete cascade.\n * The cascade pipeline today targets {@link OWNING_ENTITY_TYPE.Workspace}\n * and {@link OWNING_ENTITY_TYPE.User} — the two owning entities whose\n * deletion orphans adjacency-list projections under their partition.\n * Tenant hard-delete is intentionally out of scope (it cascades a much\n * wider graph and is handled outside this pipeline).\n */\nexport const OWNING_ENTITY_TYPE = {\n Workspace: \"Workspace\",\n User: \"User\",\n} as const;\n/** Union of the values of {@link OWNING_ENTITY_TYPE}. */\nexport type OwningEntityType =\n (typeof OWNING_ENTITY_TYPE)[keyof typeof OWNING_ENTITY_TYPE];\n\n/**\n * Payload (`detail.payload`) of `control-plane.owning-delete.v1` —\n * published on the `openhi.data` data event bus when the Firehose\n * transform Lambda observes a stream record showing\n * `lifecycleState: active → deleting` on a canonical Workspace or\n * User record. The owning-delete cascade state machine subscribes to\n * this detail-type.\n *\n * The full EventBridge `detail` carries this payload nested inside a\n * `WorkflowEvent<ControlPlaneOwningDeleteV1Detail>` envelope per\n * ADR-016 standard envelope shape — `eventId`, `attempt`,\n * `correlationId`, `causationId`, `actor`, `occurredAt` live on the\n * envelope, not on this payload.\n *\n * @see .state/adr-018-implementation-guide.md section 4 (TR-022 Hard-Delete-Cascade Contract)\n */\nexport interface ControlPlaneOwningDeleteV1Detail {\n readonly ownerType: OwningEntityType;\n readonly ownerId: string;\n /** Present for Workspace owners; absent for User (cross-tenant identity). */\n readonly tenantId?: string;\n}\n\n/**\n * Registry entry for `control-plane.owning-delete.v1`.\n *\n * The platform-wide detail-type format\n * (`<area>.<event>.v<integer>` enforced by `isWellFormedDetailType`)\n * does not allow the bare `ControlPlaneOwningDelete` shape the\n * ADR-018 implementation guide pseudocode uses; the registered string\n * is the format-compliant equivalent. The EventBridge rule pattern on\n * the cascade state machine matches this exact string.\n *\n * `dedupRequired: true` — the cascade state machine MUST dedupe on\n * `(eventId, attempt)` via `WorkflowDedupClient` so EventBridge retries\n * (which the data event bus delivers at-least-once) never start two\n * concurrent cascades on the same owning entity.\n */\nexport const ControlPlaneOwningDeleteV1 =\n defineDetailType<ControlPlaneOwningDeleteV1Detail>({\n detailType: \"control-plane.owning-delete.v1\",\n source: OPENHI_DATA_SOURCE,\n dedupRequired: true,\n });\n\n/**\n * Payload of `control-plane.owning-delete-complete.v1` — terminal\n * event published on the `openhi.ops` ops event bus when the cascade\n * state machine has successfully deleted every child projection and\n * the canonical owning record itself. Observability sinks subscribe\n * to this event to confirm cascade completion and emit metrics.\n *\n * @see .state/adr-018-implementation-guide.md section 4 (Terminal events on EventBridge)\n */\nexport interface ControlPlaneOwningDeleteCompleteV1Detail {\n readonly ownerType: OwningEntityType;\n readonly ownerId: string;\n readonly tenantId?: string;\n /** Number of `TransactWriteItems` chunks the cascade issued. */\n readonly chunkCount: number;\n /** Total number of projection rows removed by the cascade. */\n readonly projectionsRemoved: number;\n /** Wall-clock duration of the cascade, in milliseconds. */\n readonly durationMs: number;\n /** ISO-8601 UTC timestamp of cascade completion. */\n readonly completedAt: string;\n}\n\n/** Registry entry for `control-plane.owning-delete-complete.v1`. */\nexport const ControlPlaneOwningDeleteCompleteV1 =\n defineDetailType<ControlPlaneOwningDeleteCompleteV1Detail>({\n detailType: \"control-plane.owning-delete-complete.v1\",\n source: OPENHI_OPS_SOURCE,\n dedupRequired: true,\n });\n\n/**\n * Payload of `control-plane.owning-delete-failed.v1` — terminal event\n * published on the `openhi.ops` ops event bus when the cascade state\n * machine fails irrecoverably. The canonical owning record is left at\n * `lifecycleState = \"deleted-failed\"` for operator-driven recovery;\n * alerting subscribers fan out to oncall.\n *\n * @see .state/adr-018-implementation-guide.md section 4 (Terminal events on EventBridge)\n */\nexport interface ControlPlaneOwningDeleteFailedV1Detail {\n readonly ownerType: OwningEntityType;\n readonly ownerId: string;\n readonly tenantId?: string;\n /** Step Functions execution ARN — operators dereference for root cause. */\n readonly executionArn: string;\n readonly chunkCount: number;\n /** Last opaque cursor the state machine successfully processed, or null. */\n readonly lastProcessedCursor: string | null;\n /** Short failure cause string from the Step Functions Catch block. */\n readonly failureCause: string;\n readonly failedAt: string;\n}\n\n/** Registry entry for `control-plane.owning-delete-failed.v1`. */\nexport const ControlPlaneOwningDeleteFailedV1 =\n defineDetailType<ControlPlaneOwningDeleteFailedV1Detail>({\n detailType: \"control-plane.owning-delete-failed.v1\",\n source: OPENHI_OPS_SOURCE,\n dedupRequired: true,\n });\n\n/**\n * Renamable entity types covered by the TR-023 rename cascade.\n *\n * Per ADR-018 only the three carrier entities whose display name is\n * denormalized onto Membership / RoleAssignment projections trigger a\n * cascade — Tenant, User, Role. A Workspace rename is intentionally\n * **not** in scope: Workspace's display name is denormalized onto the\n * Membership user-projection workspace sub-lane, but TR-024 § Open Item\n * #4 defers a formal Workspace-rename cascade — the SK falls back to a\n * sentinel until a future TR commits the contract.\n */\nexport const RENAMABLE_ENTITY_TYPE = {\n Tenant: \"Tenant\",\n User: \"User\",\n Role: \"Role\",\n} as const;\n/** Union of the values of {@link RENAMABLE_ENTITY_TYPE}. */\nexport type RenamableEntityType =\n (typeof RENAMABLE_ENTITY_TYPE)[keyof typeof RENAMABLE_ENTITY_TYPE];\n\n/**\n * Payload (`detail.payload`) of `control-plane.rename.v1` — published\n * on the `openhi.data` data event bus when the Firehose transform\n * Lambda observes a stream record showing a display-name change on a\n * canonical Tenant, User, or Role record. The rename-cascade state\n * machine subscribes to this detail-type.\n *\n * The full EventBridge `detail` carries this payload nested inside a\n * `WorkflowEvent<ControlPlaneRenameV1Detail>` envelope per ADR-016\n * standard envelope shape — `eventId`, `attempt`, `correlationId`,\n * `causationId`, `actor`, `occurredAt` live on the envelope, not on\n * this payload.\n *\n * The platform-wide detail-type format\n * (`<area>.<event>.v<integer>` enforced by `isWellFormedDetailType`)\n * does not allow the bare `ControlPlaneRename` shape the ADR-018\n * implementation guide pseudocode uses; the registered string is the\n * format-compliant equivalent (mirroring the\n * `control-plane.owning-delete.v1` naming).\n *\n * @see .state/adr-018-implementation-guide.md section 5 (TR-023 Rename-Cascade Consumer Contract)\n */\nexport interface ControlPlaneRenameV1Detail {\n readonly entityType: RenamableEntityType;\n readonly entityId: string;\n /** Present for User and Role; absent for Tenant (Tenant is the partition root). */\n readonly tenantId?: string;\n readonly oldName: string;\n readonly newName: string;\n /** Pre-computed via `extractLabel` so consumers do not re-normalize. */\n readonly oldNormalizedName: string;\n readonly newNormalizedName: string;\n}\n\n/**\n * Registry entry for `control-plane.rename.v1`.\n *\n * `dedupRequired: true` — the cascade state machine MUST dedupe on\n * `(eventId, attempt)` via `WorkflowDedupClient` so EventBridge retries\n * (which the data event bus delivers at-least-once) never start two\n * concurrent cascades on the same rename.\n */\nexport const ControlPlaneRenameV1 =\n defineDetailType<ControlPlaneRenameV1Detail>({\n detailType: \"control-plane.rename.v1\",\n source: OPENHI_DATA_SOURCE,\n dedupRequired: true,\n });\n\n/**\n * Payload of `control-plane.rename-complete.v1` — terminal event\n * published on the `openhi.ops` ops event bus when the cascade state\n * machine has successfully rewritten every affected projection row.\n * UI clients subscribe so they can refresh stale list views.\n *\n * @see .state/adr-018-implementation-guide.md section 5\n */\nexport interface ControlPlaneRenameCompleteV1Detail {\n readonly entityType: RenamableEntityType;\n readonly entityId: string;\n readonly tenantId?: string;\n readonly newName: string;\n /** Number of `TransactWriteItems` chunks the cascade issued. */\n readonly chunkCount: number;\n /** Total number of projection rows rewritten by the cascade. */\n readonly itemsRewritten: number;\n /** Wall-clock duration of the cascade, in milliseconds. */\n readonly durationMs: number;\n /** ISO-8601 UTC timestamp of cascade completion. */\n readonly completedAt: string;\n}\n\n/** Registry entry for `control-plane.rename-complete.v1`. */\nexport const ControlPlaneRenameCompleteV1 =\n defineDetailType<ControlPlaneRenameCompleteV1Detail>({\n detailType: \"control-plane.rename-complete.v1\",\n source: OPENHI_OPS_SOURCE,\n dedupRequired: true,\n });\n\n/**\n * Payload of `control-plane.rename-failed.v1` — terminal event\n * published on the `openhi.ops` ops event bus when the cascade state\n * machine fails irrecoverably. Alerting subscribers fan out to oncall.\n *\n * @see .state/adr-018-implementation-guide.md section 5\n */\nexport interface ControlPlaneRenameFailedV1Detail {\n readonly entityType: RenamableEntityType;\n readonly entityId: string;\n readonly tenantId?: string;\n /** Step Functions execution ARN — operators dereference for root cause. */\n readonly executionArn: string;\n readonly chunkCount: number;\n /** Short failure cause string from the Step Functions Catch block. */\n readonly failureCause: string;\n readonly failedAt: string;\n}\n\n/** Registry entry for `control-plane.rename-failed.v1`. */\nexport const ControlPlaneRenameFailedV1 =\n defineDetailType<ControlPlaneRenameFailedV1Detail>({\n detailType: \"control-plane.rename-failed.v1\",\n source: OPENHI_OPS_SOURCE,\n dedupRequired: true,\n });\n","import { OPENHI_CONTROL_SOURCE } from \"../sources\";\nimport { defineDetailType } from \"./registry\";\n\n/**\n * `detail` payload for `platform.deployment-completed.v1`.\n *\n * Projected by the platform-deploy bridge from a CloudFormation\n * `Stack Status Change` event (`CREATE_COMPLETE` / `UPDATE_COMPLETE`)\n * on a tagged OpenHI platform stack. Downstream control-plane\n * workflows (e.g. seed-system-roles, seed-demo-data) subscribe\n * to this detail-type on the control event bus.\n */\nexport interface PlatformDeploymentCompletedV1Detail {\n /** CloudFormation stack name (`AWS::CloudFormation::Stack` `StackName`). */\n readonly stackName: string;\n /** Full CloudFormation stack ARN. */\n readonly stackId: string;\n /** AWS region the stack deployed into (e.g. `us-east-1`). */\n readonly region: string;\n /** 12-digit AWS account id the stack deployed into. */\n readonly accountId: string;\n /** Terminal stack status that triggered the bridge. */\n readonly status: \"CREATE_COMPLETE\" | \"UPDATE_COMPLETE\";\n /** Free-form reason text from CloudFormation; absent on most events. */\n readonly statusReason?: string;\n /**\n * Projected subset of stack tags. The bridge resolves tags via\n * `cloudformation:DescribeStacks` because the source EventBridge\n * event omits them.\n */\n readonly stackTags: ReadonlyArray<{\n readonly key: string;\n readonly value: string;\n }>;\n /** ISO-8601 timestamp from the source EventBridge `time` field. */\n readonly cloudformationEventTime: string;\n}\n\n/**\n * Registry entry for `platform.deployment-completed.v1`.\n *\n * Published on the control event bus (`OPENHI_CONTROL_SOURCE`) per\n * the workflow placement matrix (codedrifters/openhi#953 row 4):\n * the AWS-native source is the ops-plane default bus, but the bridge\n * republishes onto the control bus because the downstream consumers\n * are control-plane workflows.\n *\n * `dedupRequired: true` — at-least-once redelivery from EventBridge\n * means retryable consumers MUST dedupe on `(eventId, attempt)` via\n * `WorkflowDedupClient`.\n */\nexport const PlatformDeploymentCompletedV1 =\n defineDetailType<PlatformDeploymentCompletedV1Detail>({\n detailType: \"platform.deployment-completed.v1\",\n source: OPENHI_CONTROL_SOURCE,\n dedupRequired: true,\n });\n\n/**\n * `detail` payload for `platform.system-data-seeded.v1`.\n *\n * Published by the `seed-system-data` workflow after it has\n * idempotently re-asserted every platform-singleton control-plane\n * record (today: the three canonical Roles; future: additional system\n * data) on the back of a `platform.deployment-completed.v1` event.\n *\n * Downstream control-plane workflows that depend on the\n * platform-singleton records existing — `seed-demo-data`, for\n * example — subscribe to this detail-type instead of the raw\n * deploy-completion event so the dependency is enforced by a\n * happens-before edge rather than by EventBridge retry timing.\n */\nexport interface PlatformSystemDataSeededV1Detail {\n /**\n * EventBridge `eventId` of the originating\n * `platform.deployment-completed.v1` event that triggered the\n * system-data seeding. Propagated for correlation in logs and\n * downstream causation chains.\n */\n readonly sourceEventId: string;\n /**\n * Full CloudFormation stack ARN of the deploy that triggered the\n * system-data seeding. Mirrors the field on the originating\n * `PlatformDeploymentCompletedV1Detail`; downstream consumers can\n * filter by stack-id prefix without re-reading the source event.\n */\n readonly sourceStackId: string;\n /**\n * Number of platform-singleton records re-asserted on this run.\n * Useful for sanity checks and observability — divergence between\n * deploys signals either a generator-emitted catalog change or a\n * partial-failure recovery from the replay tooling.\n */\n readonly seededRecordCount: number;\n}\n\n/**\n * Registry entry for `platform.system-data-seeded.v1`.\n *\n * Published onto the control event bus (`OPENHI_CONTROL_SOURCE`).\n * `dedupRequired: true` — downstream consumers MUST dedup on\n * `(eventId, attempt)` via `WorkflowDedupClient`, same as every other\n * retryable consumer.\n */\nexport const PlatformSystemDataSeededV1 =\n defineDetailType<PlatformSystemDataSeededV1Detail>({\n detailType: \"platform.system-data-seeded.v1\",\n source: OPENHI_CONTROL_SOURCE,\n dedupRequired: true,\n });\n","export * from \"./control-plane\";\nexport * from \"./platform\";\nexport * from \"./registry\";\n","import { randomUUID } from \"node:crypto\";\nimport {\n EventBridgeClient,\n PutEventsCommand,\n} from \"@aws-sdk/client-eventbridge\";\n\nimport type { WorkflowDetailTypeEntry } from \"./detail-types/registry\";\nimport type { WorkflowActor, WorkflowEvent } from \"./envelope\";\nimport { ENVELOPE_VERSION } from \"./envelope-version\";\nimport { DEFAULT_BUS_NAME_BY_SOURCE, type OpenHiSource } from \"./sources\";\n\n/**\n * Caller-supplied envelope context the publisher consumes.\n *\n * The actor is required on every publish — pre-provisioning bootstrap\n * workflows pass a `{ system: <role-name> }` actor.\n *\n * `correlationId` and `causationId` propagate from an upstream event\n * when this publisher is consuming-then-publishing; pass them through\n * verbatim from the inbound envelope's fields. Both are optional;\n * when omitted the publisher treats the publish as a chain origin\n * (`correlationId` = fresh UUID, `causationId` = null).\n */\nexport interface PublishContext {\n readonly actor: WorkflowActor;\n readonly correlationId?: string;\n readonly causationId?: string | null;\n}\n\n/**\n * Per-call output of a successful publish.\n */\nexport interface PublishResult {\n readonly eventId: string;\n}\n\n/**\n * Publisher overrides applied to every call against a single client.\n *\n * `eventIdGenerator`, `correlationIdGenerator`, and `now` are\n * test-only seams; production callers omit them and the publisher\n * uses `crypto.randomUUID()` and `new Date()`.\n */\nexport interface PublisherOptions {\n /** Override the default bus name for one or more sources. */\n readonly busNameByPlane?: Partial<Record<OpenHiSource, string>>;\n /** Test seam — supply a deterministic UUID generator for `eventId`. */\n readonly eventIdGenerator?: () => string;\n /** Test seam — supply a deterministic UUID generator for new `correlationId` values. */\n readonly correlationIdGenerator?: () => string;\n /** Test seam — supply a deterministic clock for `occurredAt`. */\n readonly now?: () => Date;\n}\n\n/**\n * Tree-shaped publisher client per ADR-016 Recommendation.\n *\n * The `publish` primitive accepts any registered detail-type and\n * returns a typed `PublishResult`. Downstream tree shaping\n * (`client.<bus>.<area>.<event>.publish(payload, ctx)`) is built from\n * the detail-type registry once entries are registered; until then,\n * callers invoke `client.publish(entry, payload, ctx)` directly.\n */\nexport interface WorkflowsClient {\n /**\n * Construct a workflow envelope around `payload` and publish it to\n * the EventBridge bus configured for `entry.source`.\n */\n publish<TPayload>(\n entry: WorkflowDetailTypeEntry<TPayload>,\n payload: TPayload,\n ctx: PublishContext,\n ): Promise<PublishResult>;\n}\n\n/**\n * Factory that returns a `WorkflowsClient` bound to a single\n * `EventBridgeClient`.\n */\nexport function workflowsClient(\n bridge: EventBridgeClient,\n options: PublisherOptions = {},\n): WorkflowsClient {\n return {\n publish: (entry, payload, ctx) =>\n publishWorkflowEvent(bridge, entry, payload, ctx, options),\n };\n}\n\n/**\n * Construct a workflow envelope and publish it via\n * `EventBridge.PutEvents`.\n *\n * Exposed as a stand-alone function for callers that prefer the\n * primitive over the `WorkflowsClient` indirection.\n */\nexport async function publishWorkflowEvent<TPayload>(\n bridge: EventBridgeClient,\n entry: WorkflowDetailTypeEntry<TPayload>,\n payload: TPayload,\n ctx: PublishContext,\n options: PublisherOptions = {},\n): Promise<PublishResult> {\n const eventIdGenerator = options.eventIdGenerator ?? (() => randomUUID());\n const correlationIdGenerator =\n options.correlationIdGenerator ?? (() => randomUUID());\n const now = options.now ?? (() => new Date());\n\n const envelope: WorkflowEvent<TPayload> = {\n eventId: eventIdGenerator(),\n attempt: 1,\n correlationId: ctx.correlationId ?? correlationIdGenerator(),\n causationId: ctx.causationId ?? null,\n actor: ctx.actor,\n occurredAt: now().toISOString(),\n envelopeVersion: ENVELOPE_VERSION,\n payload,\n };\n\n const busName =\n options.busNameByPlane?.[entry.source] ??\n DEFAULT_BUS_NAME_BY_SOURCE[entry.source];\n\n const result = await bridge.send(\n new PutEventsCommand({\n Entries: [\n {\n EventBusName: busName,\n Source: entry.source,\n DetailType: entry.detailType,\n Detail: JSON.stringify(envelope),\n },\n ],\n }),\n );\n\n if ((result.FailedEntryCount ?? 0) > 0) {\n const first = result.Entries?.[0];\n throw new WorkflowPublishError(\n `EventBridge rejected ${entry.detailType} publish on bus ${busName}: ${first?.ErrorCode ?? \"unknown\"} — ${first?.ErrorMessage ?? \"no error message\"}`,\n );\n }\n\n return { eventId: envelope.eventId };\n}\n\n/** Thrown when EventBridge rejects a `PutEvents` entry. */\nexport class WorkflowPublishError extends Error {\n /** @param message - human-readable description of the failed publish. */\n constructor(message: string) {\n super(message);\n this.name = \"WorkflowPublishError\";\n }\n}\n","import type { WorkflowDetailTypeEntry } from \"./detail-types/registry\";\nimport type { WorkflowEvent } from \"./envelope\";\nimport { isSupportedEnvelopeVersion } from \"./envelope-version\";\n\n/**\n * Structural shape of the EventBridge event objects this SDK's\n * consumer parses.\n *\n * Matches `@types/aws-lambda`'s `EventBridgeEvent<string, unknown>`\n * by structural compatibility without requiring callers to import\n * that types package — consumers may pass either an\n * `EventBridgeEvent` from `aws-lambda` or any record-shaped object\n * carrying the same keys.\n */\nexport interface EventBridgeEventLike {\n readonly source: string;\n readonly \"detail-type\": string;\n readonly detail: unknown;\n}\n\n/**\n * The `(eventId, attempt)` tuple every retryable consumer hands to\n * the `WorkflowDedupTable` client.\n */\nexport interface DedupKey {\n readonly eventId: string;\n readonly attempt: number;\n}\n\n/**\n * Output of `parseWorkflowEvent` — the validated envelope plus the\n * dedup tuple.\n */\nexport interface ParsedWorkflowEvent<TPayload> {\n readonly envelope: WorkflowEvent<TPayload>;\n readonly dedupKey: DedupKey;\n}\n\n/**\n * Parse an EventBridge event into a typed envelope and surface the\n * `(eventId, attempt)` tuple the dedup-table client consumes.\n *\n * Validates:\n * - `event.source` matches `expected.source`\n * - `event[\"detail-type\"]` matches `expected.detailType`\n * - the envelope's `envelopeVersion` is within the SDK's supported range\n * - every required envelope field is present and well-shaped\n */\nexport function parseWorkflowEvent<TPayload>(\n event: EventBridgeEventLike,\n expected: WorkflowDetailTypeEntry<TPayload>,\n): ParsedWorkflowEvent<TPayload> {\n if (event.source !== expected.source) {\n throw new InvalidWorkflowEventError(\n `EventBridge source \"${event.source}\" does not match expected detail-type's source \"${expected.source}\".`,\n );\n }\n\n if (event[\"detail-type\"] !== expected.detailType) {\n throw new InvalidWorkflowEventError(\n `EventBridge detail-type \"${event[\"detail-type\"]}\" does not match expected \"${expected.detailType}\".`,\n );\n }\n\n const candidate = asEnvelopeCandidate(event.detail);\n\n if (!isSupportedEnvelopeVersion(candidate.envelopeVersion)) {\n throw new UnsupportedEnvelopeVersionError(\n `Envelope version \"${candidate.envelopeVersion}\" is outside the SDK's supported range.`,\n );\n }\n\n const envelope: WorkflowEvent<TPayload> = {\n eventId: candidate.eventId,\n attempt: candidate.attempt,\n correlationId: candidate.correlationId,\n causationId: candidate.causationId,\n actor: candidate.actor,\n occurredAt: candidate.occurredAt,\n envelopeVersion: candidate.envelopeVersion,\n payload: candidate.payload as TPayload,\n };\n\n return {\n envelope,\n dedupKey: { eventId: envelope.eventId, attempt: envelope.attempt },\n };\n}\n\n/**\n * Validate that the EventBridge `detail` (which carries the workflow\n * envelope) has every required field with a plausible type. Returns a\n * typed `WorkflowEvent<unknown>` so the caller can narrow `payload`\n * once routing has succeeded.\n */\nfunction asEnvelopeCandidate(detail: unknown): WorkflowEvent<unknown> {\n if (detail === null || typeof detail !== \"object\") {\n throw new InvalidWorkflowEventError(\n \"EventBridge detail is not a non-null object.\",\n );\n }\n\n const obj = detail as Record<string, unknown>;\n\n assertString(obj, \"eventId\");\n assertPositiveInteger(obj, \"attempt\");\n assertString(obj, \"correlationId\");\n assertCausationId(obj);\n assertActor(obj);\n assertString(obj, \"occurredAt\");\n assertString(obj, \"envelopeVersion\");\n\n if (!(\"payload\" in obj)) {\n throw new InvalidWorkflowEventError(\n \"Envelope is missing required field: payload.\",\n );\n }\n\n return obj as unknown as WorkflowEvent<unknown>;\n}\n\nfunction assertString(\n obj: Record<string, unknown>,\n field: string,\n): asserts obj is Record<string, unknown> & Record<typeof field, string> {\n const value = obj[field];\n if (typeof value !== \"string\" || value.length === 0) {\n throw new InvalidWorkflowEventError(\n `Envelope field \"${field}\" must be a non-empty string.`,\n );\n }\n}\n\nfunction assertPositiveInteger(\n obj: Record<string, unknown>,\n field: string,\n): void {\n const value = obj[field];\n if (typeof value !== \"number\" || !Number.isInteger(value) || value < 1) {\n throw new InvalidWorkflowEventError(\n `Envelope field \"${field}\" must be a 1-indexed integer.`,\n );\n }\n}\n\nfunction assertCausationId(obj: Record<string, unknown>): void {\n if (!(\"causationId\" in obj)) {\n throw new InvalidWorkflowEventError(\n \"Envelope is missing required field: causationId.\",\n );\n }\n const value = obj.causationId;\n if (value !== null && (typeof value !== \"string\" || value.length === 0)) {\n throw new InvalidWorkflowEventError(\n 'Envelope field \"causationId\" must be a non-empty string or null.',\n );\n }\n}\n\nfunction assertActor(obj: Record<string, unknown>): void {\n const actor = obj.actor;\n if (actor === null || typeof actor !== \"object\") {\n throw new InvalidWorkflowEventError(\n 'Envelope field \"actor\" must be an object.',\n );\n }\n const actorObj = actor as Record<string, unknown>;\n const isUserActor =\n typeof actorObj.ohi_uid === \"string\" &&\n typeof actorObj.ohi_uname === \"string\" &&\n typeof actorObj.ohi_tid === \"string\" &&\n typeof actorObj.ohi_wid === \"string\";\n const isSystemActor = typeof actorObj.system === \"string\";\n if (!isUserActor && !isSystemActor) {\n throw new InvalidWorkflowEventError(\n 'Envelope field \"actor\" must be either a user-actor (ohi_tid, ohi_wid, ohi_uid, ohi_uname) or a system-actor ({ system: string }).',\n );\n }\n}\n\n/** Thrown when the event does not match the expected detail-type entry. */\nexport class InvalidWorkflowEventError extends Error {\n /** @param message - human-readable description of the validation failure. */\n constructor(message: string) {\n super(message);\n this.name = \"InvalidWorkflowEventError\";\n }\n}\n\n/** Thrown when the envelope version is outside the SDK's supported range. */\nexport class UnsupportedEnvelopeVersionError extends Error {\n /** @param message - human-readable description of the unsupported version. */\n constructor(message: string) {\n super(message);\n this.name = \"UnsupportedEnvelopeVersionError\";\n }\n}\n","/**\n * Environment-variable name the construct's `grantConsumer` integration\n * injects into a consumer Lambda; the runtime `WorkflowDedupClient`\n * reads it to discover the shared dedup table without a prop or import.\n *\n * The constant is the single cross-package contract between\n * `@openhi/constructs` (which emits the env var) and `@openhi/workflows`\n * (which consumes it). Renaming or removing it is a breaking change.\n */\nexport const WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR =\n \"OPENHI_WORKFLOW_DEDUP_TABLE_NAME\";\n\n/** Default TTL for dedup rows: 14 days, expressed in seconds (per TR-015). */\nexport const WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS = 14 * 24 * 60 * 60;\n\n/** Maximum length of a `consumerName` (per TR-015). */\nexport const WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH = 64;\n","import {\n ConditionalCheckFailedException,\n DynamoDBClient,\n PutItemCommand,\n UpdateItemCommand,\n} from \"@aws-sdk/client-dynamodb\";\n\nimport {\n WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS,\n WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH,\n WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR,\n} from \"./env\";\n\n/**\n * Inputs to `recordIfAbsent`.\n *\n * `eventId` and `attempt` are the dedup tuple every retryable\n * consumer derives from the standard envelope (see `parseWorkflowEvent`\n * and the `DedupKey` type); call sites typically spread the dedupKey\n * directly alongside the consumer name.\n */\nexport interface RecordIfAbsentInput {\n /** Stable logical name of the consumer. At most 64 chars; no whitespace. */\n readonly consumerName: string;\n /** Per-event UUID from the standard envelope. */\n readonly eventId: string;\n /** 1-indexed delivery attempt from the standard envelope. */\n readonly attempt: number;\n /** Override the 14-day default TTL. Must be a positive integer. */\n readonly ttlSeconds?: number;\n}\n\n/**\n * Result shape per TR-015. `recorded` is true on first delivery and\n * false on a duplicate; on a duplicate `alreadyProcessed` is also\n * true so callers can pattern-match without re-checking the boolean.\n */\nexport type RecordIfAbsentResult =\n | { readonly recorded: true }\n | { readonly recorded: false; readonly alreadyProcessed: true };\n\n/**\n * Inputs to `markFailed`.\n *\n * Updates the existing dedup row with `failed: true`, `failureReason`,\n * `failedAt` so the replay tooling (TR-016 follow-up) can re-publish\n * the originating event with a fresh `attempt`.\n */\nexport interface MarkFailedInput {\n /** Stable logical name of the consumer. */\n readonly consumerName: string;\n /** Per-event UUID. */\n readonly eventId: string;\n /** 1-indexed delivery attempt. */\n readonly attempt: number;\n /** Short string describing why the consumer gave up. */\n readonly reason: string;\n}\n\n/**\n * Runtime SDK every retryable workflow consumer calls before\n * performing its side-effect. See TR-015 for the contract.\n */\nexport interface WorkflowDedupClient {\n /**\n * Conditionally record a dedup token for the supplied consumer name\n * and dedup tuple. See `RecordIfAbsentResult` for the return shape.\n */\n recordIfAbsent(input: RecordIfAbsentInput): Promise<RecordIfAbsentResult>;\n /**\n * Mark the existing dedup row as permanently failed. Fire-and-forget\n * semantics for the caller; unexpected DynamoDB errors propagate.\n */\n markFailed(input: MarkFailedInput): Promise<void>;\n}\n\n/** Options shared by the factory and the standalone primitives. */\nexport interface WorkflowDedupClientOptions {\n /**\n * Table name. Defaults to `process.env[WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR]`\n * (populated by the `WorkflowDedupTable` construct's `grantConsumer`).\n */\n readonly tableName?: string;\n /** Override the 14-day default TTL for every `recordIfAbsent` call. */\n readonly defaultTtlSeconds?: number;\n /** Test seam — deterministic clock for `recordedAt` / `expiresAt`. */\n readonly now?: () => Date;\n}\n\n/** Factory that returns a `WorkflowDedupClient` bound to a single DynamoDB client. */\nexport function workflowDedupClient(\n dynamodb: DynamoDBClient,\n options: WorkflowDedupClientOptions = {},\n): WorkflowDedupClient {\n return {\n recordIfAbsent: (input) => recordIfAbsent(dynamodb, input, options),\n markFailed: (input) => markFailed(dynamodb, input, options),\n };\n}\n\n/**\n * Standalone primitive — exposed for callers that prefer it over the\n * `WorkflowDedupClient` indirection.\n */\nexport async function recordIfAbsent(\n dynamodb: DynamoDBClient,\n input: RecordIfAbsentInput,\n options: WorkflowDedupClientOptions = {},\n): Promise<RecordIfAbsentResult> {\n assertConsumerName(input.consumerName);\n assertPositiveInteger(input.attempt, \"attempt\");\n const ttlSeconds =\n input.ttlSeconds ??\n options.defaultTtlSeconds ??\n WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS;\n if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {\n throw new WorkflowDedupInvalidInputError(\n `ttlSeconds must be a positive integer; got ${ttlSeconds}.`,\n );\n }\n\n const tableName = resolveTableName(options.tableName);\n const now = (options.now ?? defaultNow)();\n const sk = encodeSortKey(input.eventId, input.attempt);\n const expiresAt = Math.floor(now.getTime() / 1000) + ttlSeconds;\n\n try {\n await dynamodb.send(\n new PutItemCommand({\n TableName: tableName,\n Item: {\n consumerName: { S: input.consumerName },\n sk: { S: sk },\n eventId: { S: input.eventId },\n attempt: { N: String(input.attempt) },\n recordedAt: { S: now.toISOString() },\n expiresAt: { N: String(expiresAt) },\n },\n ConditionExpression:\n \"attribute_not_exists(consumerName) AND attribute_not_exists(sk)\",\n }),\n );\n return { recorded: true };\n } catch (err) {\n if (err instanceof ConditionalCheckFailedException) {\n return { recorded: false, alreadyProcessed: true };\n }\n throw err;\n }\n}\n\n/** Standalone primitive — flips `failed: true` on an existing dedup row. */\nexport async function markFailed(\n dynamodb: DynamoDBClient,\n input: MarkFailedInput,\n options: WorkflowDedupClientOptions = {},\n): Promise<void> {\n assertConsumerName(input.consumerName);\n assertPositiveInteger(input.attempt, \"attempt\");\n if (input.reason.length === 0) {\n throw new WorkflowDedupInvalidInputError(\"reason must be non-empty.\");\n }\n\n const tableName = resolveTableName(options.tableName);\n const now = (options.now ?? defaultNow)();\n const sk = encodeSortKey(input.eventId, input.attempt);\n\n await dynamodb.send(\n new UpdateItemCommand({\n TableName: tableName,\n Key: {\n consumerName: { S: input.consumerName },\n sk: { S: sk },\n },\n UpdateExpression:\n \"SET #failed = :failed, #failureReason = :reason, #failedAt = :failedAt\",\n ExpressionAttributeNames: {\n \"#failed\": \"failed\",\n \"#failureReason\": \"failureReason\",\n \"#failedAt\": \"failedAt\",\n },\n ExpressionAttributeValues: {\n \":failed\": { BOOL: true },\n \":reason\": { S: input.reason },\n \":failedAt\": { S: now.toISOString() },\n },\n }),\n );\n}\n\n/** Compose the composite sort key per the TR-015 encoding. */\nexport function encodeSortKey(eventId: string, attempt: number): string {\n if (eventId.length === 0) {\n throw new WorkflowDedupInvalidInputError(\"eventId must be non-empty.\");\n }\n return `${eventId}#${attempt}`;\n}\n\nfunction resolveTableName(explicit?: string): string {\n const name = explicit ?? process.env[WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR];\n if (!name) {\n throw new WorkflowDedupTableNameMissingError(\n `Workflow dedup table name not set. Pass options.tableName or set ${WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR}.`,\n );\n }\n return name;\n}\n\nfunction assertConsumerName(consumerName: string): void {\n if (consumerName.length === 0) {\n throw new WorkflowDedupInvalidInputError(\"consumerName must be non-empty.\");\n }\n if (consumerName.length > WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH) {\n throw new WorkflowDedupInvalidInputError(\n `consumerName must be ≤${WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH} chars; got ${consumerName.length}.`,\n );\n }\n if (/\\s/.test(consumerName)) {\n throw new WorkflowDedupInvalidInputError(\n \"consumerName must not contain whitespace.\",\n );\n }\n}\n\nfunction assertPositiveInteger(value: number, field: string): void {\n if (!Number.isInteger(value) || value < 1) {\n throw new WorkflowDedupInvalidInputError(\n `${field} must be a 1-indexed integer; got ${value}.`,\n );\n }\n}\n\nfunction defaultNow(): Date {\n return new Date();\n}\n\n/** Thrown when the dedup table name cannot be resolved. */\nexport class WorkflowDedupTableNameMissingError extends Error {\n /** @param message - human-readable description. */\n constructor(message: string) {\n super(message);\n this.name = \"WorkflowDedupTableNameMissingError\";\n }\n}\n\n/** Thrown when an input violates a TR-015 invariant. */\nexport class WorkflowDedupInvalidInputError extends Error {\n /** @param message - human-readable description. */\n constructor(message: string) {\n super(message);\n this.name = \"WorkflowDedupInvalidInputError\";\n }\n}\n","export {\n WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS,\n WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH,\n WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR,\n} from \"./env\";\nexport {\n WorkflowDedupInvalidInputError,\n WorkflowDedupTableNameMissingError,\n encodeSortKey,\n markFailed,\n recordIfAbsent,\n workflowDedupClient,\n} from \"./workflow-dedup-client\";\nexport type {\n MarkFailedInput,\n RecordIfAbsentInput,\n RecordIfAbsentResult,\n WorkflowDedupClient,\n WorkflowDedupClientOptions,\n} from \"./workflow-dedup-client\";\n","export {\n ENVELOPE_VERSION,\n isSupportedEnvelopeVersion,\n} from \"./envelope-version\";\nexport {\n MissingActorContextError,\n isWorkflowSystemActor,\n isWorkflowUserActor,\n workflowUserActorFromClaims,\n} from \"./envelope\";\nexport type {\n WorkflowActor,\n WorkflowEvent,\n WorkflowSystemActor,\n WorkflowUserActor,\n} from \"./envelope\";\nexport {\n DEFAULT_BUS_NAME_BY_SOURCE,\n OPENHI_CONTROL_SOURCE,\n OPENHI_DATA_SOURCE,\n OPENHI_OPS_SOURCE,\n} from \"./sources\";\nexport type { OpenHiSource } from \"./sources\";\nexport {\n ControlPlaneOwningDeleteCompleteV1,\n ControlPlaneOwningDeleteFailedV1,\n ControlPlaneOwningDeleteV1,\n ControlPlaneRenameCompleteV1,\n ControlPlaneRenameFailedV1,\n ControlPlaneRenameV1,\n InvalidDetailTypeRegistrationError,\n OWNING_ENTITY_TYPE,\n PlatformDeploymentCompletedV1,\n PlatformSystemDataSeededV1,\n RENAMABLE_ENTITY_TYPE,\n defineDetailType,\n isWellFormedDetailType,\n} from \"./detail-types\";\nexport type {\n ControlPlaneOwningDeleteCompleteV1Detail,\n ControlPlaneOwningDeleteFailedV1Detail,\n ControlPlaneOwningDeleteV1Detail,\n ControlPlaneRenameCompleteV1Detail,\n ControlPlaneRenameFailedV1Detail,\n ControlPlaneRenameV1Detail,\n OwningEntityType,\n PlatformDeploymentCompletedV1Detail,\n PlatformSystemDataSeededV1Detail,\n RenamableEntityType,\n WorkflowDetailTypeEntry,\n} from \"./detail-types\";\nexport {\n WorkflowPublishError,\n publishWorkflowEvent,\n workflowsClient,\n} from \"./publisher\";\nexport type {\n PublishContext,\n PublishResult,\n PublisherOptions,\n WorkflowsClient,\n} from \"./publisher\";\nexport {\n InvalidWorkflowEventError,\n UnsupportedEnvelopeVersionError,\n parseWorkflowEvent,\n} from \"./consumer\";\nexport type {\n DedupKey,\n EventBridgeEventLike,\n ParsedWorkflowEvent,\n} from \"./consumer\";\nexport {\n WORKFLOW_DEDUP_DEFAULT_TTL_SECONDS,\n WORKFLOW_DEDUP_MAX_CONSUMER_NAME_LENGTH,\n WORKFLOW_DEDUP_TABLE_NAME_ENV_VAR,\n WorkflowDedupInvalidInputError,\n WorkflowDedupTableNameMissingError,\n encodeSortKey,\n markFailed,\n recordIfAbsent,\n workflowDedupClient,\n} from \"./dedup\";\nexport type {\n MarkFailedInput,\n RecordIfAbsentInput,\n RecordIfAbsentResult,\n WorkflowDedupClient,\n WorkflowDedupClientOptions,\n} from \"./dedup\";\n","/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/owning-delete-cascade/delete-chunk-handler.md\n *\n * Cascade Step Functions Map-iteration handler. Receives one chunk of\n * <=100 projection rows from the list-and-chunk step and issues a\n * single `TransactWriteItems` via `executeMultiWrite` (#1010).\n *\n * Idempotency: ElectroDB / DynamoDB SDK forwards the `chunkToken` as\n * the `ClientRequestToken` for the transaction, so a Map iteration\n * replayed by Step Functions retry lands on the same transaction id.\n * The state machine's `Catch` block also absorbs\n * `TransactionCanceledException` as a no-op success — common when a\n * prior replay already removed the rows.\n *\n * The handler never touches the canonical owning record; the finalize\n * step owns the canonical delete.\n */\n\nimport type { CascadeChunkInput } from \"./events\";\nimport { deleteOwningChildChunkOperation } from \"../../../data/operations/control/owning-delete/owning-delete-batch-delete-operation\";\nimport {\n type OwningDeleteProjectionEntity,\n OWNING_DELETE_PROJECTION_ENTITY,\n} from \"../../../data/operations/control/owning-delete/owning-delete-list-child-projections-operation\";\n\nexport interface DeleteChunkOutput {\n readonly ownerType: string;\n readonly ownerId: string;\n readonly tenantId?: string;\n readonly rowsDeleted: number;\n}\n\nfunction isKnownEntity(value: string): value is OwningDeleteProjectionEntity {\n for (const known of Object.values(OWNING_DELETE_PROJECTION_ENTITY)) {\n if (known === value) {\n return true;\n }\n }\n return false;\n}\n\nexport const handler = async (\n input: CascadeChunkInput,\n): Promise<DeleteChunkOutput> => {\n const rows = input.rows.map((row) => {\n if (!isKnownEntity(row.entity)) {\n throw new Error(\n `owning-delete cascade delete-chunk: unknown projection entity '${row.entity}'`,\n );\n }\n return {\n entity: row.entity,\n key: { ...row.key },\n };\n });\n\n const result = await deleteOwningChildChunkOperation({\n rows,\n token: input.chunkToken,\n });\n\n return {\n ownerType: input.ownerType,\n ownerId: input.ownerId,\n tenantId: input.tenantId,\n rowsDeleted: result.rowsDeleted,\n };\n};\n","import { Service } from \"electrodb\";\nimport { defaultTableName, dynamoClient } from \"./dynamo-client\";\nimport { ConfigurationEntity } from \"./entities/control/configuration-entity\";\nimport { ConfigurationUserProjectionEntity } from \"./entities/control/configuration-user-projection-entity\";\nimport { ConfigurationWorkspaceProjectionEntity } from \"./entities/control/configuration-workspace-projection-entity\";\nimport { MembershipEntity } from \"./entities/control/membership-entity\";\nimport { MembershipUserProjectionEntity } from \"./entities/control/membership-user-projection-entity\";\nimport { MembershipWorkspaceProjectionEntity } from \"./entities/control/membership-workspace-projection-entity\";\nimport { RoleEntity } from \"./entities/control/role-entity\";\nimport { RoleAssignmentEntity } from \"./entities/control/roleassignment-entity\";\nimport { RoleAssignmentUserProjectionEntity } from \"./entities/control/roleassignment-user-projection-entity\";\nimport { RoleAssignmentWorkspaceProjectionEntity } from \"./entities/control/roleassignment-workspace-projection-entity\";\nimport { TenantEntity } from \"./entities/control/tenant-entity\";\nimport { UserEntity } from \"./entities/control/user-entity\";\nimport { WorkspaceEntity } from \"./entities/control/workspace-entity\";\n\n/**\n * Control-plane entities only (service \"control\"). Same table as data plane; use\n * DynamoDataService for data-plane entities.\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n */\n\nconst controlPlaneEntities = {\n configuration: ConfigurationEntity,\n configurationUserProjection: ConfigurationUserProjectionEntity,\n configurationWorkspaceProjection: ConfigurationWorkspaceProjectionEntity,\n membership: MembershipEntity,\n membershipUserProjection: MembershipUserProjectionEntity,\n membershipWorkspaceProjection: MembershipWorkspaceProjectionEntity,\n role: RoleEntity,\n roleAssignment: RoleAssignmentEntity,\n roleAssignmentUserProjection: RoleAssignmentUserProjectionEntity,\n roleAssignmentWorkspaceProjection: RoleAssignmentWorkspaceProjectionEntity,\n tenant: TenantEntity,\n user: UserEntity,\n workspace: WorkspaceEntity,\n};\n\nconst controlPlaneService = new Service(controlPlaneEntities, {\n table: defaultTableName,\n client: dynamoClient,\n});\n\n/**\n * Control-plane service: entities for configuration and control. Use with the\n * data store table (PK, SK, GSI1; UserEntity also uses GSI2).\n *\n * `transaction` exposes ElectroDB's `service.transaction.write` /\n * `service.transaction.get` builders so the operations-layer multi-write\n * helper (#1010, ADR-018) can compose `TransactWriteItems` across the\n * control-plane entities.\n */\nexport const DynamoControlService = {\n entities: controlPlaneService.entities,\n transaction: controlPlaneService.transaction,\n};\n\nexport type DynamoControlServiceType = typeof DynamoControlService;\n\n/**\n * Returns the control-plane service. Table name is resolved from tableName (optional override),\n * then DYNAMO_TABLE_NAME, then \"jesttesttable\".\n */\nexport function getDynamoControlService(\n tableName?: string,\n): DynamoControlServiceType {\n const resolved = tableName ?? defaultTableName;\n const service = new Service(controlPlaneEntities, {\n table: resolved,\n client: dynamoClient,\n });\n return {\n entities: service.entities,\n transaction: service.transaction,\n };\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\n\n/**\n * DynamoDB table name for the data store. Set via DYNAMO_TABLE_NAME at runtime\n * (e.g. from Lambda env); defaults for local/test.\n */\nexport const defaultTableName =\n process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * DynamoDB client. When MOCK_DYNAMODB_ENDPOINT is set (e.g. local DynamoDB or\n * jest-dynalite), uses that endpoint with no SSL and region \"local\".\n */\nexport const dynamoClient = new DynamoDBClient({\n ...(process.env.MOCK_DYNAMODB_ENDPOINT && {\n endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,\n sslEnabled: false,\n region: \"local\",\n }),\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute } from \"./control-entity-common\";\n\n/**\n * Configuration data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Partially tenant-isolated, control plane. Cascade of scope\n * levels: resolution order user → workspace → tenant → baseline. Sentinels: tenantId \"BASELINE\" for\n * baseline tier; workspaceId/userId/roleId \"-\" for absent dimension.\n *\n * Key structure: PK = CONFIG#TID#<tenantId>#WID#<workspaceId>#UID#<userId>#RID#<roleId>,\n * SK = KEY#<key>#SK#<sk>. Uniqueness: one Configuration per (tenantId, workspaceId, userId, roleId, key).\n * Standard attributes and key-building conventions align with single-table design.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Configuration entries in a tenant/workspace\n * across the four shards.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/entities/configuration.md\n * @see sites/www-docs/content/architecture/control-plane/configuration.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/entity-standards.md — Key-building conventions (keys built inside entity)\n */\nexport const ConfigurationEntity = new Entity({\n model: {\n entity: \"configuration\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key. \"CURRENT\" for current version; version history in S3. */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant scope. Use \"BASELINE\" when the config is baseline default (no tenant). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n default: \"BASELINE\",\n },\n /** Workspace scope. Use \"-\" when absent. */\n workspaceId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** User scope. Use \"-\" when absent. */\n userId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** Role scope. Use \"-\" when absent. */\n roleId: {\n type: \"string\" as const,\n required: true,\n default: \"-\",\n },\n /** Config type (category), e.g. endpoints, branding, display. */\n key: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL and for the Configuration resource. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, key, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). Tracks current version; S3 history key. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\", \"userId\", \"roleId\"],\n template:\n \"CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"key\", \"sk\"],\n template: \"KEY#${key}#SK#${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Configuration entries for a\n * (tenant, workspace) across the four shards. Use for \"list configs scoped to this tenant\"\n * (workspaceId = \"-\") or \"list configs scoped to this workspace\". Does not support\n * hierarchical resolution in one query; use base table GetItem in fallback order\n * (user → workspace → tenant → baseline) for that.\n * SK is `<key>#<id>` — Configuration's `key` is a required entity attribute (the\n * config category: endpoints, branding, display, …) and the natural sort/lookup\n * dimension. `casing: \"none\"` preserves the literal key value.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"workspaceId\", \"gsi1Shard\"],\n template:\n \"TID#${tenantId}#WID#${workspaceId}#RT#Configuration#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"key\", \"id\"],\n template: \"${key}#${id}\",\n },\n },\n },\n});\n","import { extractLabel, normalizeLabel } from \"@openhi/types\";\nimport { computeShard } from \"../../shard\";\n\n/**\n * Shared GSI1 shard attribute for control-plane entities.\n *\n * Control-plane entities (User, Tenant, Workspace, Membership, Role, RoleAssignment,\n * Configuration) use the same `TID#/WID#/RT#/SHARD#` PK shape on GSI1 as data-plane\n * FHIR resources per ADR-011. The shard index is derived deterministically from `id`\n * via `computeShard` so updates always land on the same shard. Stored as a string\n * because it appears as a literal segment in the GSI1 PK template; the underlying\n * value is 0..3.\n *\n * Not `required` because the value is derived via `watch`/`set`; ElectroDB's\n * required-field check runs before watch propagation, so callers must not fail\n * validation on a derived field.\n */\nexport const gsi1ShardAttribute = {\n type: \"string\" as const,\n watch: [\"id\"] as const,\n set: (_val?: string, item?: { id?: string }) => {\n if (typeof item?.id !== \"string\" || item.id.length === 0) {\n return undefined;\n }\n return String(computeShard(item.id));\n },\n};\n\n/**\n * Shared GSI1 sort-key attribute for control-plane entities.\n *\n * Derives the GSI1SK value at write time from the entity's `resource` JSON\n * string, applying the same label-extraction strategy as the data plane\n * (DR-004 / `@openhi/types` `extractLabel`). When the resource carries a\n * natural label (Tenant.name, Workspace.name, Configuration.key, …) the\n * sort key is `<normalizedLabel>#<id>` so list endpoints sort alphabetically\n * and `BEGINS_WITH` queries serve prefix searches. When no label is\n * extractable, falls back to `<entity.lastUpdated>#<id>` for stable\n * reverse-chronological ordering.\n *\n * Falls back gracefully on malformed `resource` payloads — JSON parse\n * failures and missing fields both route to the lastUpdated fallback so a\n * single bad write never blocks an entity put. The entity-level\n * `lastUpdated` is preferred over `resource.meta.lastUpdated` so the\n * fallback uses the authoritative timestamp the entity write supplies.\n *\n * Not `required` because the value is derived via `watch`/`set`.\n */\nexport const gsi1skAttribute = {\n type: \"string\" as const,\n watch: [\"resource\", \"lastUpdated\", \"id\"] as const,\n set: (\n _val?: string,\n item?: { resource?: string; lastUpdated?: string; id?: string },\n ) => {\n const id = typeof item?.id === \"string\" ? item.id : \"\";\n const lastUpdated =\n typeof item?.lastUpdated === \"string\" ? item.lastUpdated : \"\";\n const fallback = `${lastUpdated}#${id}`;\n\n if (typeof item?.resource !== \"string\" || item.resource.length === 0) {\n return fallback;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(item.resource);\n } catch {\n return fallback;\n }\n if (!parsed || typeof parsed !== \"object\") return fallback;\n const resourceType = (parsed as { resourceType?: unknown }).resourceType;\n if (typeof resourceType !== \"string\") return fallback;\n\n const label = extractLabel(parsed as Parameters<typeof extractLabel>[0]);\n return label !== undefined ? `${label}#${id}` : fallback;\n },\n};\n\n/**\n * Extract a roleId from a RoleAssignment resource payload. Looks first at\n * a flat top-level `roleId` string, then at a FHIR-style `role.reference`\n * (e.g. `Role/<id>`). Returns `undefined` when neither shape is present\n * or the field is malformed — callers fall back to the generic GSI1SK\n * derivation in that case so a single bad write never blocks an entity put.\n */\nfunction extractRoleId(resource: Record<string, unknown>): string | undefined {\n const flat = resource.roleId;\n if (typeof flat === \"string\" && flat.length > 0) return flat;\n\n const role = resource.role;\n if (role && typeof role === \"object\") {\n const reference = (role as { reference?: unknown }).reference;\n if (typeof reference === \"string\" && reference.length > 0) {\n const slash = reference.lastIndexOf(\"/\");\n const tail = slash >= 0 ? reference.slice(slash + 1) : reference;\n if (tail.length > 0) return tail;\n }\n }\n return undefined;\n}\n\n/**\n * RoleAssignment-specific GSI1 sort-key attribute (ADR-018 pattern #8 —\n * \"users with a specific role in a tenant, sorted by user name\").\n *\n * Composes the canonical-row GSI1SK as the discriminator-first shape\n * `<roleId>#<normalizedUserName>#<id>` so a GSI1 query partitioned on\n * the tenant can `begins_with('<roleId>#')` to enumerate every user\n * assigned to a given role, sorted by user name.\n *\n * - `<roleId>` is read from a flat `resource.roleId` field, falling back\n * to the slug after the final `/` in `resource.role.reference` (the\n * FHIR Reference shape). Sorting on `roleId` rather than the role's\n * display name means a Role rename does not cascade onto this index\n * (TR-024 / ADR-018 § Implementation Notes).\n * - `<normalizedUserName>` is `normalizeLabel(denormalizedUserName)` —\n * the top-level denormalized field promoted in #1009 (TR-024 rule 3:\n * canonical-record symmetry).\n *\n * Falls back to `gsi1skAttribute`'s `<lastUpdated>#<id>` shape when\n * either component is missing or malformed, so pre-TR-024 rows and\n * malformed payloads still produce a valid sort key.\n *\n * Not `required` because the value is derived via `watch`/`set`.\n *\n * @see ADR-018 § Access Pattern Coverage — pattern #8\n * @see TR-024 — Denormalized display-name attributes\n */\nexport const roleAssignmentGsi1skAttribute = {\n type: \"string\" as const,\n watch: [\"resource\", \"denormalizedUserName\", \"lastUpdated\", \"id\"] as const,\n set: (\n _val?: string,\n item?: {\n resource?: string;\n denormalizedUserName?: string;\n lastUpdated?: string;\n id?: string;\n },\n ) => {\n const id = typeof item?.id === \"string\" ? item.id : \"\";\n const lastUpdated =\n typeof item?.lastUpdated === \"string\" ? item.lastUpdated : \"\";\n const fallback = `${lastUpdated}#${id}`;\n\n if (typeof item?.resource !== \"string\" || item.resource.length === 0) {\n return fallback;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(item.resource);\n } catch {\n return fallback;\n }\n if (!parsed || typeof parsed !== \"object\") return fallback;\n\n const roleId = extractRoleId(parsed as Record<string, unknown>);\n if (roleId === undefined) return fallback;\n\n const denormalizedUserName =\n typeof item.denormalizedUserName === \"string\"\n ? item.denormalizedUserName\n : \"\";\n const normalizedUserName =\n denormalizedUserName.length > 0\n ? normalizeLabel(denormalizedUserName)\n : \"\";\n if (normalizedUserName.length === 0) return fallback;\n\n return `${roleId}#${normalizedUserName}#${id}`;\n },\n};\n\n/**\n * Membership-specific GSI1 sort-key attribute (ADR-018 pattern #1 —\n * \"users in a tenant, sorted by user name\").\n *\n * Composes the canonical-row GSI1SK as `<normalizedUserName>#<id>` so a\n * GSI1 query partitioned on the tenant range-scans by user-name prefix\n * and returns memberships sorted alphabetically by user name. No role\n * discriminator goes in front — pattern #1 is user-name-first.\n *\n * - `<normalizedUserName>` is `normalizeLabel(denormalizedUserName)` —\n * the top-level denormalized field promoted in #1009 (TR-024 rule 3:\n * canonical-record symmetry).\n *\n * Falls back to `gsi1skAttribute`'s `<lastUpdated>#<id>` shape when\n * `denormalizedUserName` is missing, so pre-TR-024 rows and malformed\n * payloads still produce a valid sort key.\n *\n * Not `required` because the value is derived via `watch`/`set`.\n *\n * @see ADR-018 § Access Pattern Coverage — pattern #1\n * @see TR-024 — Denormalized display-name attributes\n */\nexport const membershipGsi1skAttribute = {\n type: \"string\" as const,\n watch: [\"denormalizedUserName\", \"lastUpdated\", \"id\"] as const,\n set: (\n _val?: string,\n item?: {\n denormalizedUserName?: string;\n lastUpdated?: string;\n id?: string;\n },\n ) => {\n const id = typeof item?.id === \"string\" ? item.id : \"\";\n const lastUpdated =\n typeof item?.lastUpdated === \"string\" ? item.lastUpdated : \"\";\n const fallback = `${lastUpdated}#${id}`;\n\n const denormalizedUserName =\n typeof item?.denormalizedUserName === \"string\"\n ? item.denormalizedUserName\n : \"\";\n const normalizedUserName =\n denormalizedUserName.length > 0\n ? normalizeLabel(denormalizedUserName)\n : \"\";\n if (normalizedUserName.length === 0) {\n return fallback;\n }\n\n return `${normalizedUserName}#${id}`;\n },\n};\n","/**\n * Shard selection for the data-plane single-table GSI1 partitioning per ADR-011.\n *\n * GSI1's partition key embeds a `SHARD#<n>` segment with `n = computeShard(id)`.\n * The hash is deterministic so updates to the same resource id always land on\n * the same shard (no cross-shard migration on update); reads fan out to all\n * shards in parallel and merge by SK.\n *\n * @see sites/www-docs/content/architecture/adr/ — ADR-011 (single-table DynamoDB)\n */\n\n/** Number of shards in the GSI1 partition key. Fixed at 4 in v1; raising it later is a backfill, not a schema migration. */\nexport const SHARD_COUNT = 4;\n\n/**\n * Returns a deterministic shard index in [0, SHARD_COUNT) for the given resource id.\n *\n * Implementation: 32-bit FNV-1a over the UTF-16 code units of the id, modulo SHARD_COUNT.\n * The function is pure and stable; the same id always returns the same shard.\n *\n * ESLint's `no-bitwise` rule is disabled inside this function because FNV-1a is\n * defined in terms of XOR and unsigned-right-shift — the bitwise ops are the\n * algorithm, not an accidental logical-operator confusion.\n */\nexport function computeShard(id: string): number {\n /* eslint-disable no-bitwise */\n let hash = 0x811c9dc5;\n for (let i = 0; i < id.length; i++) {\n hash ^= id.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0) % SHARD_COUNT;\n /* eslint-enable no-bitwise */\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * Configuration user-projection entity (single-table store, no GSI).\n *\n * **ADR-018 adjacency-list projection — pattern #10 (user-scope half).**\n * For every user-scoped Configuration write the operations-layer\n * multi-write helper writes one projection row under the user partition\n * so the user-rooted access pattern #10 is served by a single\n * base-table `Query` with no GSI hop:\n *\n * | Pattern | When | PK | SK |\n * |---|---|---|---|\n * | #10 user-scope | Configuration is user-scoped (`userId !== \"-\"`) | `USER#ID#<userId>` | `CONFIGURATION#<normalizedConfigName>#<configurationId>` |\n *\n * `<normalizedConfigName>` derives from Configuration's `key` attribute\n * (the canonical name dimension — Configuration carries no `displayName`\n * per TR-024 § Open Item #5, so `key` is the natural sort source). The\n * SK shape is operation-owned: the operations-layer projection writer\n * composes the SK string via `buildConfigurationUserProjectionSk` and\n * supplies it on the `sk` attribute. This entity stores the SK verbatim —\n * no `watch`/derived computation here — so the SK grammar (and any\n * future revision) lives in one place: the operations layer.\n *\n * Projection attribute set per ADR-018 § Projection attribute set and\n * the implementation guide § 2: `summary`, `vid`, `lastUpdated` (so\n * `Query(PK = USER#ID#<userId>, SK begins_with 'CONFIGURATION#')` is\n * self-sufficient — no BatchGet hop to the canonical record), plus the\n * projection-discriminating fields (`configurationId`, `userId`,\n * `tenantId`, `scope`).\n *\n * **Cross-tenant partition.** Unlike Membership/RoleAssignment-workspace\n * partitions, the Configuration user-projection's PK carries no tenant\n * prefix — a user's user-scoped Configurations are cross-tenant by\n * design (a user may carry preferences that follow them across tenant\n * memberships). This mirrors the RoleAssignment user-projection partition.\n *\n * **No GSI projection.** Per ADR-018 § Decision, cross-cutting reads\n * are served by the main-table partition `USER#ID#<userId>`; the\n * GSI1/GSI2 catalog is unchanged. Tenant-scoped Configurations\n * continue to use the canonical GSI1 path (ADR-011) unchanged.\n *\n * @see ADR-018 § Access Pattern Coverage (#10 — user-scope half)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n * @see .claude/rules/data-layer-layout.md — projection writers live in operations, not here\n */\nexport const ConfigurationUserProjectionEntity = new Entity({\n model: {\n entity: \"configurationUserProjection\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /**\n * User partition discriminator. Renders as `USER#ID#<userId>` on the\n * base-table PK. Always required — the projection has no meaning\n * outside a user partition.\n */\n userId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Pre-composed sort key — built by the operations-layer projection\n * writer via `buildConfigurationUserProjectionSk`. The entity stores\n * the value verbatim so the SK grammar (pattern #10 user-scope) is\n * owned by the operations layer, not duplicated here.\n */\n sk: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Configuration canonical-record id. Stored as a discriminating\n * field so consumers can hydrate the canonical row via the\n * Configuration get-by-id operation when the projection's `summary`\n * is insufficient.\n */\n configurationId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Tenant the Configuration is associated with. The canonical row\n * keys off `(tenantId, workspaceId, userId, roleId)`; the projection\n * carries `tenantId` so consumers reconstructing the canonical PK\n * have the tenant segment without a hop.\n */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Scope marker. Always `\"user\"` on this projection — recorded\n * explicitly so future scope-bearing projections (workspace,\n * tenant, role) can share filter semantics in a unified\n * cross-projection list query if one ever lands.\n */\n scope: {\n type: \"string\" as const,\n required: true,\n default: \"user\",\n },\n /**\n * Configuration's `key` attribute (config category, e.g. endpoints,\n * branding, display). Mirrored from the canonical row so consumers\n * reading the projection get the natural display label without a\n * BatchGet hop. Doubles as the source of `<normalizedConfigName>` in\n * the SK.\n */\n displayName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Summary projection (key display fields as JSON string) — mirrored\n * from the canonical Configuration row so user-partition queries do\n * not need a BatchGet hop.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id mirrored from the canonical Configuration row. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n /** Last-updated timestamp mirrored from the canonical Configuration row. */\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n },\n indexes: {\n /**\n * Base table: PK = USER#ID#<userId>, SK = operation-supplied. A\n * single `Query(PK = USER#ID#<userId>, SK begins_with\n * 'CONFIGURATION#')` returns the user's user-scoped Configurations\n * sorted by `<normalizedConfigName>` (then `<configurationId>` as\n * the tiebreaker).\n */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"userId\"],\n template: \"USER#ID#${userId}\",\n },\n sk: {\n field: \"SK\",\n casing: \"none\" as const,\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\n\n/**\n * Configuration workspace-projection entity (single-table store, no GSI).\n *\n * **ADR-018 adjacency-list projection — pattern #10 (workspace-scope half).**\n * For every workspace-scoped Configuration the operations-layer\n * multi-write helper writes one projection row under the workspace\n * partition so the workspace-rooted access pattern #10 is served by a\n * single base-table `Query` with no GSI hop:\n *\n * | Pattern | When | PK | SK |\n * |---|---|---|---|\n * | #10 workspace-scope | Configuration is workspace-scoped (`workspaceId !== \"-\"`, `userId === \"-\"`) | `TID#<tenantId>#WORKSPACE#ID#<workspaceId>` | `CONFIGURATION#<normalizedConfigName>#<configurationId>` |\n *\n * The PK co-locates with the canonical Workspace record\n * (`SK = CURRENT`) and the Membership / RoleAssignment workspace-\n * projections (patterns #2, #9), so an admin workspace dashboard can\n * hydrate workspace metadata + member projections + role-assignment\n * projections + workspace-scoped Configurations in a single `Query`.\n *\n * `<normalizedConfigName>` derives from Configuration's `key` attribute\n * (the canonical name dimension — Configuration carries no `displayName`\n * per TR-024 § Open Item #5, so `key` is the natural sort source). The\n * SK shape is operation-owned: the operations-layer projection writer\n * composes the SK string via `buildConfigurationWorkspaceProjectionSk`\n * and supplies it on the `sk` attribute. This entity stores the SK\n * verbatim — no `watch`/derived computation here — so the SK grammar\n * (and any future revision) lives in one place: the operations layer.\n *\n * Projection attribute set per ADR-018 § Projection attribute set and\n * the implementation guide § 2: `summary`, `vid`, `lastUpdated` (so\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'CONFIGURATION#')`\n * is self-sufficient — no BatchGet hop to the canonical record), plus\n * the projection-discriminating fields (`configurationId`, `workspaceId`,\n * `tenantId`, `scope`).\n *\n * **Tenant-prefixed partition.** Unlike the Configuration user-\n * projection (whose PK is `USER#ID#<userId>` with no tenant prefix —\n * a user's user-scoped Configurations are cross-tenant by design),\n * the workspace-projection PK carries the tenant prefix because\n * Workspaces are tenant-scoped per ADR-011. This mirrors the\n * Membership / RoleAssignment workspace-projection partitions.\n *\n * **No GSI projection.** Per ADR-018 § Decision, cross-cutting reads\n * are served by the main-table partition\n * `TID#<tenantId>#WORKSPACE#ID#<workspaceId>`; the GSI1/GSI2 catalog\n * is unchanged. Tenant-scoped Configurations continue to use the\n * canonical GSI1 path (ADR-011) unchanged.\n *\n * @see ADR-018 § Access Pattern Coverage (#10 — workspace-scope half)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n * @see .claude/rules/data-layer-layout.md — projection writers live in operations, not here\n */\nexport const ConfigurationWorkspaceProjectionEntity = new Entity({\n model: {\n entity: \"configurationWorkspaceProjection\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /**\n * Tenant the workspace belongs to. Renders as the leading segment\n * of the base-table PK. Always required — the workspace partition\n * is tenant-scoped per ADR-011.\n */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Workspace partition discriminator. Renders as the trailing\n * segment of the base-table PK\n * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —\n * the projection has no meaning outside a workspace partition.\n */\n workspaceId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Pre-composed sort key — built by the operations-layer projection\n * writer via `buildConfigurationWorkspaceProjectionSk`. The entity\n * stores the value verbatim so the SK grammar (pattern #10\n * workspace-scope) is owned by the operations layer, not\n * duplicated here.\n */\n sk: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Configuration canonical-record id. Stored as a discriminating\n * field so consumers can hydrate the canonical row via the\n * Configuration get-by-id operation when the projection's `summary`\n * is insufficient.\n */\n configurationId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Scope marker. Always `\"workspace\"` on this projection — recorded\n * explicitly so future scope-bearing projections (user, tenant,\n * role) can share filter semantics in a unified cross-projection\n * list query if one ever lands.\n */\n scope: {\n type: \"string\" as const,\n required: true,\n default: \"workspace\",\n },\n /**\n * Configuration's `key` attribute (config category, e.g. endpoints,\n * branding, display). Mirrored from the canonical row so consumers\n * reading the projection get the natural display label without a\n * BatchGet hop. Doubles as the source of `<normalizedConfigName>`\n * in the SK.\n */\n displayName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Summary projection (key display fields as JSON string) — mirrored\n * from the canonical Configuration row so workspace-partition\n * queries do not need a BatchGet hop.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id mirrored from the canonical Configuration row. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n /** Last-updated timestamp mirrored from the canonical Configuration row. */\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n },\n indexes: {\n /**\n * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,\n * SK = operation-supplied. A single\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'CONFIGURATION#')`\n * returns the workspace's workspace-scoped Configurations sorted by\n * `<normalizedConfigName>` (then `<configurationId>` as the\n * tiebreaker).\n */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\"],\n template: \"TID#${tenantId}#WORKSPACE#ID#${workspaceId}\",\n },\n sk: {\n field: \"SK\",\n casing: \"none\" as const,\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport {\n gsi1ShardAttribute,\n membershipGsi1skAttribute,\n} from \"./control-entity-common\";\n\n/**\n * Membership data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Membership links a User\n * to a Tenant (and optionally a Workspace). One record per (tenantId, id).\n *\n * Key structure: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT.\n * Uniqueness: one Membership per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Memberships in a tenant across the four\n * shards. Membership is tenant-scoped (not workspace-scoped), so the GSI1 PK uses `WID#-` as a\n * sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const MembershipEntity = new Entity({\n model: {\n entity: \"membership\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant in which the user has membership (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; membership id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Membership resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n /**\n * Derived GSI1 sort key — `<normalizedUserName>#<id>` per ADR-018\n * pattern #1 so a GSI1 query partitioned on the tenant range-scans\n * by user-name prefix and returns memberships sorted by user name.\n * Falls back to `<lastUpdated>#<id>` when `denormalizedUserName`\n * is missing.\n */\n gsi1sk: membershipGsi1skAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized `linked-data-identity` Reference (e.g. `Practitioner/abc`).\n * Populated from the FHIR extension on the Membership resource at write\n * time so future GSIs can index data-plane identity lookups without\n * deserializing the full resource JSON. See ADR 2026-03-13-02 §6.\n */\n linkedDataIdentityRef: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized display name of the linked Tenant, captured at row\n * last-write time. Promoted to a top-level attribute so the ADR-018\n * adjacency-list projection SKs (pattern #3 — `MEMBERSHIP#TENANT#<normalizedTenantName>#…`)\n * can be composed from a top-level field instead of digging into the\n * `resource` JSON. Optional on the schema so pre-TR-024 rows do not\n * break; the operations-layer multi-write helper (#1010) makes the\n * field load-bearing at write time per TR-024 rule 2 (write-time\n * source = canonical Tenant.displayName).\n * @see TR-024 — Denormalized display-name attributes\n */\n denormalizedTenantName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized display name of the linked User, captured at row\n * last-write time. Promoted to a top-level attribute so the ADR-018\n * adjacency-list canonical-record GSI1SK (pattern #1 —\n * `<normalizedUserName>#<id>`) and workspace-projection SK (pattern #2)\n * can be composed from a top-level field. Optional on the schema so\n * pre-TR-024 rows do not break; the operations-layer multi-write helper\n * (#1010) makes the field load-bearing at write time per TR-024 rule 2\n * (write-time source = canonical User.displayName).\n * @see TR-024 — Denormalized display-name attributes\n */\n denormalizedUserName: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#MEMBERSHIP#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#MEMBERSHIP#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the\n * four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.\n * SK is derived via `membershipGsi1skAttribute` — composes\n * `<normalizedUserName>#<id>` per ADR-018 pattern #1 (users in a\n * tenant, sorted by user name); falls back to `<lastUpdated>#<id>`\n * when `denormalizedUserName` is missing. `casing: \"none\"` preserves\n * the normalized label and ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#Membership#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"gsi1sk\"],\n template: \"${gsi1sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\n\n/**\n * Membership user-projection entity (single-table store, no GSI).\n *\n * **ADR-018 adjacency-list projection.** For every Membership write the\n * operations-layer multi-write helper writes one of two projection rows\n * under the user partition so the user-rooted access patterns #3 and #4\n * are served by a single base-table `Query` with no GSI hop:\n *\n * | Pattern | When | PK | SK |\n * |---|---|---|---|\n * | #3 — tenant sub-lane | `workspaceId` absent | `USER#ID#<userId>` | `MEMBERSHIP#TENANT#<normalizedTenantName>#TID#<tenantId>#<id>` |\n * | #4 — workspace sub-lane | `workspaceId` set | `USER#ID#<userId>` | `MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#WID#<workspaceId>#<id>` |\n *\n * Both shapes share the user-partition `PK = USER#ID#<userId>`. The SK\n * shape is operation-owned: the operations-layer projection writer\n * composes the SK string via the `buildMembershipUserProjectionSk*`\n * helpers and supplies it on the `sk` attribute. This entity stores the\n * SK verbatim — no `watch`/derived computation here — so the SK grammar\n * (and any future revision) lives in one place: the operations layer.\n *\n * Projection attribute set per ADR-018 § Projection attribute set and\n * the implementation guide § 2: `summary`, `vid`, `lastUpdated` (so\n * `Query(PK = USER#ID#<userId>, SK begins_with 'MEMBERSHIP#')` is\n * self-sufficient — no BatchGet hop to the canonical record), plus the\n * projection-discriminating fields (`tenantId`, `userId`, `workspaceId?`,\n * `membershipId`) and TR-024 denormalized display names\n * (`denormalizedTenantName`, `denormalizedUserName`,\n * `denormalizedWorkspaceName?`).\n *\n * **No GSI projection.** Per ADR-018 § Decision, cross-cutting reads\n * are served by the main-table partition `USER#ID#<userId>`; the\n * GSI1/GSI2 catalog is unchanged.\n *\n * @see ADR-018 § Access Pattern Coverage (#3, #4)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n * @see .claude/rules/data-layer-layout.md — projection writers live in operations, not here\n */\nexport const MembershipUserProjectionEntity = new Entity({\n model: {\n entity: \"membershipUserProjection\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /**\n * User partition discriminator. Renders as `USER#ID#<userId>` on the\n * base-table PK. Always required — the projection has no meaning\n * outside a user partition.\n */\n userId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Pre-composed sort key — built by the operations-layer projection\n * writer via `buildMembershipUserProjectionSk*` helpers. The entity\n * stores the value verbatim so the SK grammar (patterns #3 and #4)\n * is owned by the operations layer, not duplicated here.\n */\n sk: {\n type: \"string\" as const,\n required: true,\n },\n /** Tenant in which the membership applies. Always required. */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Workspace the membership scopes to. Present iff the projection\n * row is a pattern-#4 workspace sub-lane row; absent for pattern-#3\n * tenant sub-lane rows.\n */\n workspaceId: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Membership canonical-record id. Stored as a discriminating field\n * so consumers can hydrate the canonical row via\n * `MembershipEntity.get({ tenantId, id: membershipId })` when the\n * projection's `summary` is insufficient.\n */\n membershipId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id,\n * displayName, status) — mirrored from the canonical Membership row\n * so user-partition queries do not need a BatchGet hop.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id mirrored from the canonical Membership row. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n /** Last-updated timestamp mirrored from the canonical Membership row. */\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Denormalized Tenant display name — required to compose pattern-#3\n * SK (`MEMBERSHIP#TENANT#<normalizedTenantName>#…`). Optional on the\n * schema because pre-TR-024 rows may not carry a display name; the\n * operations layer falls back gracefully when missing.\n */\n denormalizedTenantName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized User display name — mirrored from the canonical\n * Membership row per TR-024 rule 3 (canonical-record symmetry).\n * Carried on the projection so consumers can render the user's\n * display name without a hop to the User record.\n */\n denormalizedUserName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized Workspace display name — required to compose\n * pattern-#4 SK (`MEMBERSHIP#WORKSPACE#TID#<tenantId>#<normalizedWorkspaceName>#…`).\n * Optional on the schema (TR-024 § Open Item #4 defers a formal\n * Workspace-rename cascade); the operations layer falls back to a\n * sentinel when missing so the SK still has a valid shape.\n */\n denormalizedWorkspaceName: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /**\n * Base table: PK = USER#ID#<userId>, SK = operation-supplied.\n * Both pattern #3 and pattern #4 use this same index — the SK string\n * encodes the lane discriminator (`MEMBERSHIP#TENANT#…` vs\n * `MEMBERSHIP#WORKSPACE#…`) so a single `Query(PK = USER#ID#<userId>,\n * SK begins_with 'MEMBERSHIP#')` returns both lanes interleaved.\n */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"userId\"],\n template: \"USER#ID#${userId}\",\n },\n sk: {\n field: \"SK\",\n casing: \"none\" as const,\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\n\n/**\n * Membership workspace-projection entity (single-table store, no GSI).\n *\n * **ADR-018 adjacency-list projection.** For every workspace-scoped\n * Membership the operations-layer multi-write helper writes one\n * projection row under the workspace partition so the workspace-rooted\n * access pattern #2 is served by a single base-table `Query` with no\n * GSI hop:\n *\n * | Pattern | When | PK | SK |\n * |---|---|---|---|\n * | #2 — users in a workspace | `workspaceId` set | `TID#<tenantId>#WORKSPACE#ID#<workspaceId>` | `MEMBERSHIP#<normalizedUserName>#USER#<userId>#<id>` |\n *\n * The PK co-locates with the canonical Workspace record\n * (`SK = CURRENT`) so a single `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>)`\n * returns workspace metadata + every member projection in one round\n * trip. The SK shape is operation-owned: the operations-layer\n * projection writer composes the SK string via the\n * `buildMembershipWorkspaceProjectionSk` helper and supplies it on the\n * `sk` attribute. This entity stores the SK verbatim — no\n * `watch`/derived computation here — so the SK grammar lives in one\n * place: the operations layer.\n *\n * Projection attribute set per ADR-018 § Projection attribute set and\n * the implementation guide § 2: `summary`, `vid`, `lastUpdated` (so\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'MEMBERSHIP#')`\n * is self-sufficient — no BatchGet hop to the canonical record), plus\n * the projection-discriminating fields (`tenantId`, `workspaceId`,\n * `userId`, `membershipId`) and TR-024 denormalized user display name\n * (`denormalizedUserName`).\n *\n * **No GSI projection.** Per ADR-018 § Decision, cross-cutting reads\n * are served by the main-table partition\n * `TID#<tenantId>#WORKSPACE#ID#<workspaceId>`; the GSI1/GSI2 catalog\n * is unchanged.\n *\n * @see ADR-018 § Access Pattern Coverage (#2)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n * @see .claude/rules/data-layer-layout.md — projection writers live in operations, not here\n */\nexport const MembershipWorkspaceProjectionEntity = new Entity({\n model: {\n entity: \"membershipWorkspaceProjection\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /**\n * Tenant the workspace belongs to. Renders as the leading segment\n * of the base-table PK. Always required — the workspace partition\n * is tenant-scoped per ADR-011.\n */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Workspace partition discriminator. Renders as the trailing\n * segment of the base-table PK\n * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —\n * the projection has no meaning outside a workspace partition.\n */\n workspaceId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Pre-composed sort key — built by the operations-layer projection\n * writer via `buildMembershipWorkspaceProjectionSk`. The entity\n * stores the value verbatim so the SK grammar (pattern #2) is\n * owned by the operations layer, not duplicated here.\n */\n sk: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * User the membership links. Stored as a discriminating field so\n * consumers can hydrate the canonical User row via\n * `UserEntity.get({ id: userId, sk: \"CURRENT\" })` when the\n * projection's `summary` is insufficient.\n */\n userId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Membership canonical-record id. Stored as a discriminating field\n * so consumers can hydrate the canonical row via\n * `MembershipEntity.get({ tenantId, id: membershipId })` when the\n * projection's `summary` is insufficient.\n */\n membershipId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id,\n * displayName, status) — mirrored from the canonical Membership row\n * so workspace-partition queries do not need a BatchGet hop.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id mirrored from the canonical Membership row. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n /** Last-updated timestamp mirrored from the canonical Membership row. */\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Denormalized User display name — required to compose the\n * pattern-#2 SK (`MEMBERSHIP#<normalizedUserName>#…`). Optional on\n * the schema because pre-TR-024 rows may not carry a display name;\n * the operations layer falls back to a sentinel when missing so\n * the SK still has a valid shape. The TR-023 rename-cascade\n * pipeline rewrites the SK on a User rename.\n */\n denormalizedUserName: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /**\n * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,\n * SK = operation-supplied. Pattern #2 uses this index — the SK\n * encodes the entity-type prefix (`MEMBERSHIP#…`) so a\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'MEMBERSHIP#')`\n * returns every member projection for the workspace in normalized-\n * user-name sort order.\n */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\"],\n template: \"TID#${tenantId}#WORKSPACE#ID#${workspaceId}\",\n },\n sk: {\n field: \"SK\",\n casing: \"none\" as const,\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute, gsi1skAttribute } from \"./control-entity-common\";\n\n/**\n * Role data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Non-tenant-isolated, control plane. Role is a\n * platform-wide role catalog (e.g. tenant-admin, tenant-user, system-admin); not scoped by tenant.\n * RoleAssignment references Role to assign a role to a User in a Tenant/Workspace context.\n *\n * Key structure: PK = ROLE#ID#<id>, SK = CURRENT.\n * The ROLE# prefix prevents key collisions with other non-tenant-isolated entities (User, etc.)\n * sharing the same table (ADR 2026-03-11-01 — preferred pattern for all control plane entities).\n * Uniqueness: one Role per id.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Roles across the four shards. Non-tenant-\n * isolated, so the PK uses `TID#-#WID#-` sentinels.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const RoleEntity = new Entity({\n model: {\n entity: \"role\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** FHIR Resource.id; role id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Role resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */\n gsi1sk: gsi1skAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = ROLE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"id\"],\n template: \"ROLE#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.\n * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.\n * SK is derived via `gsi1skAttribute` — uses the resource's natural label when\n * extractable, else `<lastUpdated>#<id>` (DR-004). `casing: \"none\"` preserves the\n * normalized label and ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#Role#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"gsi1sk\"],\n template: \"${gsi1sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport {\n gsi1ShardAttribute,\n roleAssignmentGsi1skAttribute,\n} from \"./control-entity-common\";\n\n/**\n * RoleAssignment data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. RoleAssignment assigns\n * a Role to a User in a Tenant (and optionally Workspace) context.\n *\n * Key structure: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT.\n * Uniqueness: one RoleAssignment per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all RoleAssignments in a tenant across the four\n * shards. Tenant-scoped only (workspace context lives inside the resource), so the GSI1 PK uses\n * `WID#-` as a sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-02-control-plane-roles-and-user-tenant-workspace-linkage.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const RoleAssignmentEntity = new Entity({\n model: {\n entity: \"roleassignment\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant in which the role assignment applies (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; role assignment id. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full RoleAssignment resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n /**\n * Derived GSI1 sort key — discriminator-first\n * `<roleId>#<normalizedUserName>#<id>` per ADR-018 pattern #8 so a\n * GSI1 query partitioned on the tenant can `begins_with('<roleId>#')`\n * to enumerate every user assigned to a given role, sorted by user\n * name. Falls back to `<lastUpdated>#<id>` when either component is\n * missing.\n */\n gsi1sk: roleAssignmentGsi1skAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized display name of the linked Tenant, captured at row\n * last-write time. Promoted to a top-level attribute so the ADR-018\n * adjacency-list user-projection SK (pattern #5 —\n * `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#<roleId>#TID#<tenantId>#<id>`)\n * can be composed from a top-level field instead of digging into the\n * `resource` JSON. Optional on the schema so pre-TR-024 rows do not\n * break; the operations-layer multi-write helper (#1010) makes the\n * field load-bearing at write time per TR-024 rule 2 (write-time\n * source = canonical Tenant.displayName).\n * @see TR-024 — Denormalized display-name attributes\n */\n denormalizedTenantName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized display name of the linked User, captured at row\n * last-write time. Promoted to a top-level attribute so the ADR-018\n * adjacency-list canonical-record GSI1SK (pattern #8 —\n * `<roleId>#<normalizedUserName>#<id>`) and workspace-projection SK\n * (pattern #9) can be composed from a top-level field. Optional on\n * the schema so pre-TR-024 rows do not break; the operations-layer\n * multi-write helper (#1010) makes the field load-bearing at write\n * time per TR-024 rule 2 (write-time source = canonical\n * User.displayName).\n * @see TR-024 — Denormalized display-name attributes\n */\n denormalizedUserName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized display name of the linked Role, captured at row\n * last-write time. Promoted to a top-level attribute so the ADR-018\n * adjacency-list user-projection SK (pattern #5 —\n * `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#…`) can be composed from\n * a top-level field. Optional on the schema so pre-TR-024 rows do not\n * break; the operations-layer multi-write helper (#1010) makes the\n * field load-bearing at write time per TR-024 rule 2 (write-time\n * source = canonical Role.displayName).\n * @see TR-024 — Denormalized display-name attributes\n */\n denormalizedRoleName: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#ROLEASSIGNMENT#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#ROLEASSIGNMENT#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the\n * four shards. Tenant-scoped only, so `WID#-` is a sentinel.\n * SK is derived via `roleAssignmentGsi1skAttribute` — composes the\n * discriminator-first `<roleId>#<normalizedUserName>#<id>` shape per\n * ADR-018 pattern #8 (users with a specific role in a tenant, sorted\n * by user name); falls back to `<lastUpdated>#<id>` when either\n * component is missing. `casing: \"none\"` preserves the normalized\n * label and ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#RoleAssignment#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"gsi1sk\"],\n template: \"${gsi1sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\n\n/**\n * RoleAssignment user-projection entity (single-table store, no GSI).\n *\n * **ADR-018 adjacency-list projection.** For every RoleAssignment write\n * the operations-layer multi-write helper writes one projection row\n * under the user partition so the user-rooted access pattern #5 is\n * served by a single base-table `Query` with no GSI hop. The SK encodes\n * a tenant-vs-workspace discriminator sub-prefix so both sub-lanes share\n * the user partition:\n *\n * | Sub-lane | When | PK | SK |\n * |---|---|---|---|\n * | tenant-level | `workspaceId` absent | `USER#ID#<userId>` | `ROLEASSIGNMENT#TENANT#<normalizedRoleName>#<roleId>#TID#<tenantId>#<id>` |\n * | workspace-level | `workspaceId` set | `USER#ID#<userId>` | `ROLEASSIGNMENT#WORKSPACE#<normalizedRoleName>#<roleId>#TID#<tenantId>#WID#<workspaceId>#<id>` |\n *\n * The SK shape is operation-owned: the operations-layer projection\n * writer composes the SK string via the\n * `buildRoleAssignmentUserProjectionSk*` helpers and supplies it on the\n * `sk` attribute. This entity stores the SK verbatim — no\n * `watch`/derived computation here — so the SK grammar (and any future\n * revision) lives in one place: the operations layer.\n *\n * Projection attribute set per ADR-018 § Projection attribute set and\n * the implementation guide § 2: `summary`, `vid`, `lastUpdated` (so\n * `Query(PK = USER#ID#<userId>, SK begins_with 'ROLEASSIGNMENT#')` is\n * self-sufficient — no BatchGet hop to the canonical record), plus the\n * projection-discriminating fields (`tenantId`, `roleId`,\n * `roleAssignmentId`, `userId`, `workspaceId?`) and TR-024 denormalized\n * display names (`denormalizedTenantName`, `denormalizedUserName`,\n * `denormalizedRoleName`).\n *\n * **No GSI projection.** Per ADR-018 § Decision, cross-cutting reads\n * are served by the main-table partition `USER#ID#<userId>`; the\n * GSI1/GSI2 catalog is unchanged.\n *\n * @see ADR-018 § Access Pattern Coverage (#5)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n * @see .claude/rules/data-layer-layout.md — projection writers live in operations, not here\n */\nexport const RoleAssignmentUserProjectionEntity = new Entity({\n model: {\n entity: \"roleAssignmentUserProjection\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /**\n * User partition discriminator. Renders as `USER#ID#<userId>` on the\n * base-table PK. Always required — the projection has no meaning\n * outside a user partition.\n */\n userId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Pre-composed sort key — built by the operations-layer projection\n * writer via `buildRoleAssignmentUserProjectionSk*` helpers. The\n * entity stores the value verbatim so the SK grammar (tenant-lane\n * vs workspace-lane) is owned by the operations layer, not\n * duplicated here.\n */\n sk: {\n type: \"string\" as const,\n required: true,\n },\n /** Tenant in which the role assignment applies. Always required. */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Workspace the role assignment scopes to. Present iff the\n * projection row is the workspace-level sub-lane; absent for\n * tenant-level sub-lane rows.\n */\n workspaceId: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Role the assignment grants. Stored as a discriminating field so\n * `Query(PK = USER#ID#<userId>, SK begins_with 'ROLEASSIGNMENT#…')`\n * results carry the role id without a hop to the canonical row.\n */\n roleId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * RoleAssignment canonical-record id. Stored as a discriminating\n * field so consumers can hydrate the canonical row via\n * `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`\n * when the projection's `summary` is insufficient.\n */\n roleAssignmentId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id,\n * displayName, status) — mirrored from the canonical RoleAssignment\n * row so user-partition queries do not need a BatchGet hop.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id mirrored from the canonical RoleAssignment row. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n /** Last-updated timestamp mirrored from the canonical RoleAssignment row. */\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Denormalized Tenant display name — mirrored from the canonical\n * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).\n * Optional on the schema because pre-TR-024 rows may not carry a\n * display name; the operations layer falls back gracefully when\n * missing.\n */\n denormalizedTenantName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized User display name — mirrored from the canonical\n * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).\n * Carried on the projection so consumers can render the user's\n * display name without a hop to the User record.\n */\n denormalizedUserName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized Role display name — required to compose the SK's\n * `<normalizedRoleName>` segment. Optional on the schema (pre-TR-024\n * rows fall back to a sentinel) but expected to be present at write\n * time per TR-024 rule 2 (write-time source =\n * canonical Role.displayName).\n */\n denormalizedRoleName: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /**\n * Base table: PK = USER#ID#<userId>, SK = operation-supplied. Both\n * sub-lanes (tenant-level and workspace-level) use this same index —\n * the SK string encodes the lane discriminator\n * (`ROLEASSIGNMENT#TENANT#…` vs `ROLEASSIGNMENT#WORKSPACE#…`) so a\n * single `Query(PK = USER#ID#<userId>, SK begins_with\n * 'ROLEASSIGNMENT#')` returns both lanes interleaved.\n */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"userId\"],\n template: \"USER#ID#${userId}\",\n },\n sk: {\n field: \"SK\",\n casing: \"none\" as const,\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\n\n/**\n * RoleAssignment workspace-projection entity (single-table store, no GSI).\n *\n * **ADR-018 adjacency-list projection.** For every workspace-scoped\n * RoleAssignment the operations-layer multi-write helper writes one\n * projection row under the workspace partition so the workspace-rooted\n * access pattern #9 is served by a single base-table `Query` with no\n * GSI hop:\n *\n * | Pattern | When | PK | SK |\n * |---|---|---|---|\n * | #9 — users with a specific role in a workspace | `workspaceId` set | `TID#<tenantId>#WORKSPACE#ID#<workspaceId>` | `ROLEASSIGNMENT#<roleId>#<normalizedUserName>#USER#<userId>#<id>` |\n *\n * The SK is **discriminator-first** on the raw `<roleId>` (mirroring the\n * canonical GSI1SK from pattern #8): role id discriminates first so a\n * `begins_with('ROLEASSIGNMENT#<roleId>#')` filter returns every user\n * assigned to that role in the workspace, sorted alphabetically by\n * normalized user name. Omitting the `<roleId>#` segment\n * (`begins_with('ROLEASSIGNMENT#')`) returns every role assignment in\n * the workspace interleaved.\n *\n * The PK co-locates with the canonical Workspace record (`SK = CURRENT`)\n * and the Membership workspace-projection rows (pattern #2) so a single\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>)` returns\n * workspace metadata + every member projection + every role-assignment\n * projection in one round trip — the admin workspace-dashboard read shape.\n *\n * The SK shape is operation-owned: the operations-layer projection\n * writer composes the SK string via the\n * `buildRoleAssignmentWorkspaceProjectionSk` helper and supplies it on\n * the `sk` attribute. This entity stores the SK verbatim — no\n * `watch`/derived computation here — so the SK grammar lives in one\n * place: the operations layer.\n *\n * Projection attribute set per ADR-018 § Projection attribute set and\n * the implementation guide § 2: `summary`, `vid`, `lastUpdated` (so\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'ROLEASSIGNMENT#')`\n * is self-sufficient — no BatchGet hop to the canonical record), plus\n * the projection-discriminating fields (`tenantId`, `workspaceId`,\n * `roleId`, `roleAssignmentId`, `userId`) and TR-024 denormalized\n * display names (`denormalizedUserName`, `denormalizedRoleName`).\n *\n * **Rename-cascade interaction (TR-023, Phase 6).** The SK uses the\n * raw `<roleId>` (rename-stable) for the discriminator and\n * `<normalizedUserName>` for the secondary sort. A Role rename does NOT\n * rewrite this SK; a User rename DOES (cascaded by the rename pipeline).\n *\n * **No GSI projection.** Per ADR-018 § Decision, cross-cutting reads\n * are served by the main-table partition\n * `TID#<tenantId>#WORKSPACE#ID#<workspaceId>`; the GSI1/GSI2 catalog\n * is unchanged.\n *\n * @see ADR-018 § Access Pattern Coverage (#9)\n * @see .state/adr-018-implementation-guide.md § 1 (SK grammar) and § 2 (attribute set)\n * @see .claude/rules/data-layer-layout.md — projection writers live in operations, not here\n */\nexport const RoleAssignmentWorkspaceProjectionEntity = new Entity({\n model: {\n entity: \"roleAssignmentWorkspaceProjection\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /**\n * Tenant the workspace belongs to. Renders as the leading segment\n * of the base-table PK. Always required — the workspace partition\n * is tenant-scoped per ADR-011.\n */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Workspace partition discriminator. Renders as the trailing\n * segment of the base-table PK\n * (`TID#<tenantId>#WORKSPACE#ID#<workspaceId>`). Always required —\n * the projection has no meaning outside a workspace partition.\n */\n workspaceId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Pre-composed sort key — built by the operations-layer projection\n * writer via `buildRoleAssignmentWorkspaceProjectionSk`. The entity\n * stores the value verbatim so the SK grammar (pattern #9) is\n * owned by the operations layer, not duplicated here.\n */\n sk: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * User the role assignment grants the role to. Stored as a\n * discriminating field so consumers can hydrate the canonical User\n * row via `UserEntity.get({ id: userId, sk: \"CURRENT\" })` when the\n * projection's `summary` is insufficient.\n */\n userId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Role the assignment grants. Stored as a discriminating field —\n * also rendered into the SK as the discriminator-first segment so\n * `begins_with('ROLEASSIGNMENT#<roleId>#')` filters one role.\n */\n roleId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * RoleAssignment canonical-record id. Stored as a discriminating\n * field so consumers can hydrate the canonical row via\n * `RoleAssignmentEntity.get({ tenantId, id: roleAssignmentId })`\n * when the projection's `summary` is insufficient.\n */\n roleAssignmentId: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id,\n * displayName, status) — mirrored from the canonical RoleAssignment\n * row so workspace-partition queries do not need a BatchGet hop.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id mirrored from the canonical RoleAssignment row. */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n /** Last-updated timestamp mirrored from the canonical RoleAssignment row. */\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Denormalized User display name — required to compose the\n * pattern-#9 SK (`ROLEASSIGNMENT#<roleId>#<normalizedUserName>#…`).\n * Optional on the schema because pre-TR-024 rows may not carry a\n * display name; the operations layer falls back to a sentinel when\n * missing so the SK still has a valid shape. The TR-023 rename-\n * cascade pipeline rewrites the SK on a User rename.\n */\n denormalizedUserName: {\n type: \"string\" as const,\n required: false,\n },\n /**\n * Denormalized Role display name — mirrored from the canonical\n * RoleAssignment row per TR-024 rule 3 (canonical-record symmetry).\n * Carried on the projection so consumers can render the role's\n * display name without a hop to the Role record. Not part of the\n * SK (pattern #9 sorts on `<normalizedUserName>`, not role name) —\n * a Role rename does NOT rewrite this SK.\n */\n denormalizedRoleName: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /**\n * Base table: PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>,\n * SK = operation-supplied. Pattern #9 uses this index — the SK\n * encodes the entity-type prefix and discriminator-first roleId\n * (`ROLEASSIGNMENT#<roleId>#…`) so\n * `Query(PK = TID#<tenantId>#WORKSPACE#ID#<workspaceId>, SK begins_with 'ROLEASSIGNMENT#<roleId>#')`\n * returns every user-assignment for that role in the workspace, sorted\n * by normalized user name.\n */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\"],\n template: \"TID#${tenantId}#WORKSPACE#ID#${workspaceId}\",\n },\n sk: {\n field: \"SK\",\n casing: \"none\" as const,\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute, gsi1skAttribute } from \"./control-entity-common\";\n\n/**\n * Tenant data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Tenant IS the top scope;\n * the workspace dimension is not applicable and uses the sentinel `TENANT`. The tenant's own `id`\n * is stored as `tenantId` to drive the partition key.\n *\n * Key structure: PK = TENANT#ID#<tenantId>, SK = CURRENT.\n * Uniqueness: one Tenant per tenantId (id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Tenants across the four shards. Tenant has\n * no parent tenant or workspace, so the PK uses `TID#-#WID#-` sentinels.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-01-tenant-and-workspace-fhir-types-control-plane.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const TenantEntity = new Entity({\n model: {\n entity: \"tenant\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** The tenant's own id (= resource id). Drives the partition key. */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL. Equals tenantId. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Tenant resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */\n gsi1sk: gsi1skAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TENANT#ID#<tenantId>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\"],\n template: \"TENANT#ID#${tenantId}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.\n * Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`\n * sentinels precede `RT#Tenant#SHARD#<n>`. SK is derived via `gsi1skAttribute` —\n * `<normalizedName>#<id>` when the resource carries a `name`, else `<lastUpdated>#<id>`\n * (DR-004). `casing: \"none\"` preserves the normalized label and ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#Tenant#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"gsi1sk\"],\n template: \"${gsi1sk}\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute, gsi1skAttribute } from \"./control-entity-common\";\n\n/**\n * User data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Non-tenant-isolated, control plane. User is a\n * platform-wide identity; association with tenants and workspaces is through Membership and\n * RoleAssignment, not the User entity's own key.\n *\n * Key structure: PK = USER#ID#<id>, SK = CURRENT.\n * The USER# prefix prevents key collisions with other non-tenant-isolated entities (Role, etc.)\n * sharing the same table (ADR 2026-03-11-01 — preferred pattern for all control plane entities).\n * Uniqueness: one User per id.\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Users across the four shards. Non-tenant-\n * isolated, so the PK uses `TID#-#WID#-` sentinels.\n * GSI2 — Cognito sub-lookup per ADR-011: resolves a UserEntity from a Cognito `sub` claim\n * (`USER#SUB#<cognitoSub>` PK, `CURRENT` SK). The `cognitoSub` attribute is populated by the\n * Post Confirmation Lambda (Epic #765 / #770); kept optional here until that write path lands.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-11-01-user-type-definition-fhir-and-data-layer.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const UserEntity = new Entity({\n model: {\n entity: \"user\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** FHIR Resource.id; platform user id (ohi_uid). */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full User resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Immutable Cognito-issued `sub` claim. Drives GSI2 (sub-lookup). Optional until the\n * Post Confirmation Lambda (#770) lands; required thereafter.\n */\n cognitoSub: {\n type: \"string\" as const,\n required: false,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */\n gsi1sk: gsi1skAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n /**\n * TR-022 / ADR-018 lifecycle state for the cascade pipeline.\n *\n * - `active` (or undefined) — normal, readable state.\n * - `deleting` — intermediate state set synchronously by the\n * hard-delete API entry point. The owning-delete cascade state\n * machine fans out from this transition (DynamoDB stream →\n * `control-plane.owning-delete.v1` → Step Functions). Readers MUST\n * short-circuit on `deleting` so partial cascades stay invisible.\n * - `deleted-failed` — terminal failure state set by the cascade\n * finalize Lambda when the cascade run fails irrecoverably.\n * Operators recover by re-running the cascade or by direct\n * intervention.\n *\n * The cascade finalize step deletes the canonical record conditional\n * on `lifecycleState = \"deleting\"`; on replay the conditional check\n * fails and the finalize step treats that as a no-op success.\n */\n lifecycleState: {\n type: [\"active\", \"deleting\", \"deleted-failed\"] as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = USER#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"id\"],\n template: \"USER#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.\n * Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.\n * SK is derived via `gsi1skAttribute` — uses the resource's natural label when\n * extractable (string `name`/`title` via introspection), else `<lastUpdated>#<id>`\n * (DR-004). `casing: \"none\"` preserves the normalized label and ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"gsi1Shard\"],\n template: \"TID#-#WID#-#RT#User#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"gsi1sk\"],\n template: \"${gsi1sk}\",\n },\n },\n\n /**\n * GSI2 — Cognito sub-lookup per ADR-011: resolves the UserEntity from a Cognito `sub` claim.\n * `condition` skips the index when `cognitoSub` is missing so legacy items without a sub are\n * not indexed.\n */\n gsi2: {\n index: \"GSI2\",\n condition: (attrs: { cognitoSub?: string }) =>\n typeof attrs.cognitoSub === \"string\" && attrs.cognitoSub.length > 0,\n pk: {\n field: \"GSI2PK\",\n casing: \"none\" as const,\n composite: [\"cognitoSub\"],\n template: \"USER#SUB#${cognitoSub}\",\n },\n sk: {\n field: \"GSI2SK\",\n casing: \"none\" as const,\n composite: [],\n template: \"CURRENT\",\n },\n },\n },\n});\n","import { Entity } from \"electrodb\";\nimport { gsi1ShardAttribute, gsi1skAttribute } from \"./control-entity-common\";\n\n/**\n * Workspace data-store entity (single-table store).\n *\n * **Classification (ADR 2026-03-03-01):** Tenant-isolated, control plane. Each workspace belongs\n * to exactly one tenant; both tenantId and workspace id are in the partition key.\n *\n * Key structure: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT.\n * Uniqueness: one Workspace per (tenantId, id).\n *\n * GSI1 — Unified Sharded List per ADR-011: lists all Workspaces in a tenant across the four\n * shards. Workspace is itself the workspace identity, so the GSI1 PK uses `WID#-` as a sentinel.\n *\n * @see sites/www-docs/content/architecture/adr/2026-03-03-01-tenant-isolated-vs-non-tenant-isolated-entities.md\n * @see sites/www-docs/content/architecture/adr/2026-03-13-01-tenant-and-workspace-fhir-types-control-plane.md\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/single-table-design.md\n */\nexport const WorkspaceEntity = new Entity({\n model: {\n entity: \"workspace\",\n service: \"control\",\n version: \"01\",\n },\n attributes: {\n /** Sort key sentinel. Always \"CURRENT\". */\n sk: {\n type: \"string\" as const,\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant that contains this workspace (required). */\n tenantId: {\n type: \"string\" as const,\n required: true,\n },\n /** FHIR Resource.id; logical id in URL. */\n id: {\n type: \"string\" as const,\n required: true,\n },\n /** Full Workspace resource serialized as JSON string. */\n resource: {\n type: \"string\" as const,\n required: true,\n },\n /**\n * Summary projection (key display fields as JSON string: id, displayName, status).\n * Populated on every write via extractSummary(resource); GSI1 INCLUDE surfaces it on lists.\n */\n summary: {\n type: \"string\" as const,\n required: true,\n },\n /** Version id (e.g. ULID). */\n vid: {\n type: \"string\" as const,\n required: true,\n },\n lastUpdated: {\n type: \"string\" as const,\n required: true,\n },\n gsi1Shard: gsi1ShardAttribute,\n /** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */\n gsi1sk: gsi1skAttribute,\n deleted: {\n type: \"boolean\" as const,\n required: false,\n },\n /**\n * TR-022 / ADR-018 lifecycle state for the cascade pipeline.\n *\n * - `active` (or undefined) — normal, readable state.\n * - `deleting` — intermediate state set synchronously by the\n * hard-delete API entry point. The owning-delete cascade state\n * machine fans out from this transition (DynamoDB stream →\n * `control-plane.owning-delete.v1` → Step Functions). Readers MUST\n * short-circuit on `deleting` so partial cascades stay invisible.\n * - `deleted-failed` — terminal failure state set by the cascade\n * finalize Lambda when the cascade run fails irrecoverably.\n * Operators recover by re-running the cascade or by direct\n * intervention.\n *\n * The cascade finalize step deletes the canonical record conditional\n * on `lifecycleState = \"deleting\"`; on replay the conditional check\n * fails and the finalize step treats that as a no-op success.\n */\n lifecycleState: {\n type: [\"active\", \"deleting\", \"deleted-failed\"] as const,\n required: false,\n },\n bundleId: {\n type: \"string\" as const,\n required: false,\n },\n msgId: {\n type: \"string\" as const,\n required: false,\n },\n },\n indexes: {\n /** Base table: PK = TID#<tenantId>#WORKSPACE#ID#<id>, SK = CURRENT. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"id\"],\n template: \"TID#${tenantId}#WORKSPACE#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n template: \"${sk}\",\n },\n },\n\n /**\n * GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the\n * four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.\n * SK is derived via `gsi1skAttribute` — `<normalizedName>#<id>` when the resource\n * carries a `name`, else `<lastUpdated>#<id>` (DR-004). `casing: \"none\"` preserves\n * the normalized label and ISO-8601 `T`/`Z`.\n */\n gsi1: {\n index: \"GSI1\",\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"gsi1Shard\"],\n template: \"TID#${tenantId}#WID#-#RT#Workspace#SHARD#${gsi1Shard}\",\n },\n sk: {\n field: \"GSI1SK\",\n casing: \"none\" as const,\n composite: [\"gsi1sk\"],\n template: \"${gsi1sk}\",\n },\n },\n },\n});\n","/**\n * Typed domain errors for the data operations layer.\n * Data operations throw these; adapters (REST, GraphQL, Step Function) map them to\n * HTTP status, GraphQL errors, or workflow envelopes. No HTTP or OperationOutcome here.\n *\n * Full documentation (error types, HTTP mapping, FHIR OperationOutcome.issue.code alignment):\n * @see sites/www-docs/content/packages/@openhi/constructs/data/errors.md\n */\n\n/**\n * Base class for domain errors thrown by data operations.\n * Adapters use instanceof checks or domainErrorToHttpStatus() to map to transport.\n */\nexport class DomainError extends Error {\n /** Stable code for adapter mapping (e.g. \"NOT_FOUND\", \"VALIDATION\", \"CONFLICT\"). */\n readonly code: string;\n\n /** Optional details for validation messages or conflict context. */\n readonly details?: unknown;\n\n constructor(\n message: string,\n code: string,\n options?: { cause?: unknown; details?: unknown },\n ) {\n super(message, options);\n this.name = this.constructor.name;\n this.code = code;\n this.details = options?.details;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a requested resource or entity is not found (e.g. get by id returns nothing). */\nexport class NotFoundError extends DomainError {\n constructor(\n message: string,\n options?: { cause?: unknown; details?: unknown },\n ) {\n super(message, \"NOT_FOUND\", options);\n }\n}\n\n/** Thrown when input fails validation (e.g. missing required field, invalid format). */\nexport class ValidationError extends DomainError {\n constructor(\n message: string,\n options?: { cause?: unknown; details?: unknown },\n ) {\n super(message, \"VALIDATION\", options);\n }\n}\n\n/** Thrown when an operation conflicts with current state (e.g. duplicate key, version conflict). */\nexport class ConflictError extends DomainError {\n constructor(\n message: string,\n options?: { cause?: unknown; details?: unknown },\n ) {\n super(message, \"CONFLICT\", options);\n }\n}\n\n/** Thrown when the caller is authenticated but not authorized for the requested action. */\nexport class ForbiddenError extends DomainError {\n constructor(\n message: string,\n options?: { cause?: unknown; details?: unknown },\n ) {\n super(message, \"FORBIDDEN\", options);\n }\n}\n\n/**\n * Maps a thrown value to the HTTP status code that adapters should use for that domain error.\n * Returns null if the value is not a known domain error (adapter may use 500 or handle otherwise).\n * REST adapters use this to choose status; response body/OperationOutcome remain adapter responsibility.\n */\nexport function domainErrorToHttpStatus(err: unknown): number | null {\n if (err instanceof NotFoundError) return 404;\n if (err instanceof ValidationError) return 400;\n if (err instanceof ConflictError) return 409;\n if (err instanceof ForbiddenError) return 403;\n return null;\n}\n","/**\n * Operations-layer multi-write helper for the control plane.\n *\n * Per ADR-018 § Implementation Notes and the ADR-018 implementation\n * guide § 3, every cross-entity mutation in the control plane (canonical\n * record + adjacency-list projections) MUST flow through this helper.\n * Lambda handlers call operations; operations call this helper; nothing\n * under `data/operations/control/` may call `service.transaction.write`\n * directly after #1010 lands.\n *\n * The helper accepts an array of `{ entity, action, item }` triples,\n * maps each one to ElectroDB's transaction-write builder, and submits\n * the batch via `TransactWriteItems` — all-or-nothing atomicity. It\n * enforces the 100-item DynamoDB transaction ceiling at the helper\n * boundary and surfaces conditional-check / transaction-conflict\n * failures as typed domain errors so REST adapters can map to the\n * right HTTP status (409 / 400 / 500).\n *\n * Idempotency belongs to the caller — every call attempts a fresh\n * `TransactWriteItems`. Callers compose conditional expressions\n * (e.g. `vid` matches, `attribute_not_exists(PK)`) per triple via\n * ElectroDB's `where` builder; the helper does not invent conditions.\n *\n * Per ADR-018 Open Item Follow-up #2 the implementation uses ElectroDB's\n * `service.transaction.write` rather than a hand-rolled\n * `TransactWriteItemsCommand`. Reasons: (a) ElectroDB derives the\n * `TransactWriteItem` payload (Item attributes, GSI keys, watch-driven\n * fields) from the entity schema — hand-rolling would duplicate the\n * watch/derive logic and silently drift on schema changes; (b) it\n * preserves the existing single-write call-site idiom\n * (`service.entities.<x>.put({...}).go()` reads almost identically to\n * `entities.<x>.put({...}).commit()` inside the txn builder), keeping\n * the operations layer ergonomic; (c) ElectroDB's `canceled` flag and\n * `ElectroValidationError` propagation give us a single typed handle on\n * cancellation-vs-validation. We can drop to raw `TransactWriteItems`\n * later if a future operation needs DDB features ElectroDB has not yet\n * surfaced.\n *\n * @see ADR-018 § Implementation Notes\n * @see .state/adr-018-implementation-guide.md § 3 — Multi-Write Helper API Contract\n * @see .claude/rules/data-layer-layout.md\n */\n\nimport type { Entity, Service } from \"electrodb\";\nimport { ConflictError, DomainError, ValidationError } from \"../../errors\";\n\n/**\n * Maximum items allowed in a single DynamoDB `TransactWriteItems`\n * call (AWS, raised from 25 → 100 on 2024-09-23). The helper enforces\n * this ceiling at the boundary so callers do not silently lose the\n * tail of an oversized batch when DDB rejects the call.\n */\nexport const TRANSACT_WRITE_ITEM_LIMIT = 100 as const;\n\n/** Supported actions on a multi-write triple. */\nexport type MultiWriteAction = \"put\" | \"create\" | \"delete\";\n\n/**\n * A single entry in a multi-write call: which entity to act on, the\n * action, and the per-action payload.\n *\n * - `put` / `create` — `item` is the full record (matches\n * `entity.put(item)` / `entity.create(item)` shape).\n * - `delete` — `item` is the key composite (matches\n * `entity.delete(key)` shape).\n *\n * `entity` is a string key into the service's `entities` map (e.g.\n * `\"membership\"`, `\"tenant\"`). Using a string key avoids leaking the\n * transaction-bound entity reference across the helper boundary —\n * ElectroDB's `service.transaction.write(fn)` only honours entity\n * references that the closure-provided `entities` argument exposes.\n */\nexport interface MultiWriteTriple<EntityKey extends string = string> {\n readonly entity: EntityKey;\n readonly action: MultiWriteAction;\n readonly item: Record<string, unknown>;\n}\n\n/** A service object exposing the ElectroDB `transaction.write` builder. */\ntype ServiceLike = Pick<\n Service<Record<string, Entity<any, any, any, any>>>,\n \"transaction\"\n>;\n\n/** Parameters for {@link executeMultiWrite}. */\nexport interface ExecuteMultiWriteParams<EntityKey extends string = string> {\n /**\n * ElectroDB `Service` (control-plane or data-plane) exposing\n * `transaction.write` and the entity set the triples reference.\n * Inject via `getDynamoControlService(tableName)` /\n * `getDynamoDataService(tableName)` — matches the service-factory\n * pattern every other operation under `data/operations/` uses.\n */\n readonly service: ServiceLike;\n /**\n * The triples to commit atomically. Order is preserved on the wire\n * (ElectroDB submits them in the same order); duplicates are not\n * de-duplicated by the helper.\n */\n readonly triples: ReadonlyArray<MultiWriteTriple<EntityKey>>;\n /** Optional idempotency token forwarded to ElectroDB. */\n readonly token?: string;\n}\n\n/** Successful multi-write result. */\nexport interface ExecuteMultiWriteResult {\n /** Number of triples committed. */\n readonly itemsWritten: number;\n /**\n * `canceled` flag returned by ElectroDB. Always `false` on the\n * success path — failures throw {@link ConflictError} or\n * {@link ValidationError}. Surfaced for completeness so callers can\n * assert on it in tests if they wish.\n */\n readonly canceled: false;\n}\n\n/**\n * Submits `triples` as a single `TransactWriteItems` via ElectroDB's\n * `service.transaction.write` builder. All-or-nothing: every triple\n * lands or none does.\n *\n * Throws:\n * - {@link ValidationError} when `triples` is empty, exceeds the\n * {@link TRANSACT_WRITE_ITEM_LIMIT}, references an entity that the\n * service does not expose, or carries an unsupported `action`.\n * - {@link ConflictError} when DynamoDB cancels the transaction\n * (e.g. `ConditionalCheckFailed`, `TransactionConflict`,\n * `ProvisionedThroughputExceeded`). The original ElectroDB error\n * (carrying `CancellationReasons`) is attached as `cause`.\n * - The original {@link DomainError} on rethrow if the caller's own\n * validation hook surfaces one (e.g. nested ValidationError).\n *\n * Note on `update` / `patch` actions: not yet supported by the triple\n * shape because ElectroDB's transaction-write builder structures them\n * as `entity.update(key).set({...}).commit()` (key + delta), which\n * does not flatten cleanly into a single `item` payload. Add a\n * separate triple variant when the first consumer needs it; today\n * every ADR-018 multi-write is a `put` (canonical + projections) or a\n * `delete` (cascade rewriter). See ADR-018 § Open Item Follow-up #2.\n */\nexport async function executeMultiWrite<EntityKey extends string = string>(\n params: ExecuteMultiWriteParams<EntityKey>,\n): Promise<ExecuteMultiWriteResult> {\n const { service, triples, token } = params;\n\n if (triples.length === 0) {\n throw new ValidationError(\n \"executeMultiWrite called with zero triples; at least one triple is required\",\n );\n }\n if (triples.length > TRANSACT_WRITE_ITEM_LIMIT) {\n throw new ValidationError(\n `executeMultiWrite received ${triples.length} triples; DynamoDB TransactWriteItems is limited to ${TRANSACT_WRITE_ITEM_LIMIT} items per call`,\n {\n details: {\n itemsRequested: triples.length,\n limit: TRANSACT_WRITE_ITEM_LIMIT,\n },\n },\n );\n }\n\n for (const [index, triple] of triples.entries()) {\n if (!triple || typeof triple !== \"object\") {\n throw new ValidationError(\n `executeMultiWrite triple at index ${index} is not an object`,\n );\n }\n if (typeof triple.entity !== \"string\" || triple.entity.length === 0) {\n throw new ValidationError(\n `executeMultiWrite triple at index ${index} is missing a non-empty 'entity' key`,\n );\n }\n if (!isSupportedAction(triple.action)) {\n throw new ValidationError(\n `executeMultiWrite triple at index ${index} has unsupported action '${String(\n triple.action,\n )}'; supported: 'put' | 'create' | 'delete'`,\n );\n }\n if (!triple.item || typeof triple.item !== \"object\") {\n throw new ValidationError(\n `executeMultiWrite triple at index ${index} is missing an 'item' payload`,\n );\n }\n }\n\n let result: { canceled: boolean; data: unknown };\n try {\n result = await service.transaction\n .write((entities: Record<string, any>) =>\n triples.map((triple, index) => {\n const transactEntity = entities[triple.entity];\n if (transactEntity === undefined) {\n throw new ValidationError(\n `executeMultiWrite triple at index ${index} references unknown entity '${triple.entity}'; ensure the service exposes it`,\n );\n }\n switch (triple.action) {\n case \"put\":\n return transactEntity.put(triple.item).commit();\n case \"create\":\n return transactEntity.create(triple.item).commit();\n case \"delete\":\n return transactEntity.delete(triple.item).commit();\n default:\n // Exhaustiveness guard; pre-flight validates this already.\n throw new ValidationError(\n `executeMultiWrite triple at index ${index} has unsupported action '${String(\n (triple as MultiWriteTriple).action,\n )}'`,\n );\n }\n }),\n )\n .go(token === undefined ? undefined : { token });\n } catch (err) {\n // Pre-flight ValidationErrors thrown inside the builder bubble\n // through unchanged so callers see a 400, not a 409.\n if (err instanceof DomainError) {\n throw err;\n }\n throw new ConflictError(buildCancellationMessage(err), {\n cause: err,\n details: extractCancellationReasons(err),\n });\n }\n\n if (result.canceled) {\n throw new ConflictError(\n \"TransactWriteItems was canceled by DynamoDB (check CancellationReasons on the cause for details)\",\n { details: { canceled: true, data: result.data } },\n );\n }\n\n return { itemsWritten: triples.length, canceled: false };\n}\n\nfunction isSupportedAction(value: unknown): value is MultiWriteAction {\n return value === \"put\" || value === \"create\" || value === \"delete\";\n}\n\nfunction buildCancellationMessage(err: unknown): string {\n if (err instanceof Error && err.message) {\n return `TransactWriteItems failed: ${err.message}`;\n }\n return \"TransactWriteItems failed (no error message available)\";\n}\n\nfunction extractCancellationReasons(err: unknown): unknown {\n if (err && typeof err === \"object\") {\n const cancellationReasons = (err as { CancellationReasons?: unknown })\n .CancellationReasons;\n if (cancellationReasons !== undefined) {\n return { CancellationReasons: cancellationReasons };\n }\n }\n return undefined;\n}\n","/**\n * Atomic chunk delete for the TR-022 hard-delete cascade.\n *\n * Each cascade Map iteration receives up to\n * {@link OWNING_DELETE_CASCADE_CHUNK_SIZE} projection rows tagged with\n * their projection entity and submits them all in a single\n * `TransactWriteItems` via the operations-layer multi-write helper\n * (#1010). All-or-nothing semantics: every row in the chunk lands or\n * none does.\n *\n * Idempotency: per TR-022 Choice 5A, replayed chunks fail the\n * `attribute_exists(PK)` condition (or are absorbed by the helper's\n * cancellation handling) and the state-machine `Catch` treats\n * `TransactionCanceledException` as a no-op success. The outer loop\n * keeps walking the partition until `Query` returns zero items.\n *\n * @see .state/adr-018-implementation-guide.md section 4 (Step Functions Map shape)\n * @see ../multi-write-operation.ts (executeMultiWrite)\n * @see .claude/rules/data-layer-layout.md\n */\n\nimport {\n type OwningChildProjectionRow,\n type OwningDeleteProjectionEntity,\n} from \"./owning-delete-list-child-projections-operation\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\nimport {\n TRANSACT_WRITE_ITEM_LIMIT,\n executeMultiWrite,\n type MultiWriteTriple,\n} from \"../multi-write-operation\";\n\n/**\n * Maximum rows the cascade may submit in a single chunk. Mirrors the\n * AWS `TransactWriteItems` 100-item ceiling enforced by\n * {@link executeMultiWrite}.\n */\nexport const OWNING_DELETE_CASCADE_CHUNK_SIZE = TRANSACT_WRITE_ITEM_LIMIT;\n\n/** Inputs accepted by {@link deleteOwningChildChunkOperation}. */\nexport interface DeleteOwningChildChunkParams {\n /** Rows to delete in this transaction. Length must be 1..100. */\n readonly rows: ReadonlyArray<OwningChildProjectionRow>;\n /** Optional table-name override; resolved via env when omitted. */\n readonly tableName?: string;\n /** Optional idempotency token forwarded to ElectroDB. */\n readonly token?: string;\n}\n\n/** Result of {@link deleteOwningChildChunkOperation}. */\nexport interface DeleteOwningChildChunkResult {\n /** Number of rows committed to the transaction. */\n readonly rowsDeleted: number;\n /**\n * Per-entity breakdown of the rows deleted, for observability and\n * tests. Keyed by the projection-entity name.\n */\n readonly breakdown: Partial<Record<OwningDeleteProjectionEntity, number>>;\n}\n\n/**\n * Submit `rows` as a single `TransactWriteItems` via `executeMultiWrite`.\n *\n * Empty input is a no-op (returns `rowsDeleted: 0`) so the cascade\n * state machine never needs to pre-flight the chunk size — the Map\n * iteration body can call this operation unconditionally.\n */\nexport async function deleteOwningChildChunkOperation(\n params: DeleteOwningChildChunkParams,\n): Promise<DeleteOwningChildChunkResult> {\n const { rows, tableName, token } = params;\n\n if (rows.length === 0) {\n return { rowsDeleted: 0, breakdown: {} };\n }\n if (rows.length > OWNING_DELETE_CASCADE_CHUNK_SIZE) {\n throw new Error(\n `deleteOwningChildChunkOperation: chunk has ${rows.length} rows; limit is ${OWNING_DELETE_CASCADE_CHUNK_SIZE}`,\n );\n }\n\n const service = getDynamoControlService(tableName);\n\n const triples: Array<MultiWriteTriple> = rows.map((row) => ({\n entity: row.entity,\n action: \"delete\",\n item: { ...row.key },\n }));\n\n await executeMultiWrite({ service, triples, token });\n\n const breakdown: Partial<Record<OwningDeleteProjectionEntity, number>> = {};\n for (const row of rows) {\n breakdown[row.entity] = (breakdown[row.entity] ?? 0) + 1;\n }\n\n return { rowsDeleted: rows.length, breakdown };\n}\n\n/**\n * Split a flat row array into chunks of at most\n * {@link OWNING_DELETE_CASCADE_CHUNK_SIZE} items. Used by the cascade\n * state machine Pass state that prepares the Map's `ItemsPath` array.\n */\nexport function chunkOwningChildRows(\n rows: ReadonlyArray<OwningChildProjectionRow>,\n): Array<Array<OwningChildProjectionRow>> {\n const chunks: Array<Array<OwningChildProjectionRow>> = [];\n for (let i = 0; i < rows.length; i += OWNING_DELETE_CASCADE_CHUNK_SIZE) {\n chunks.push(rows.slice(i, i + OWNING_DELETE_CASCADE_CHUNK_SIZE));\n }\n return chunks;\n}\n","/**\n * Enumerate child projection rows under an owning entity's partition\n * for the TR-022 hard-delete cascade.\n *\n * The cascade state machine pages through this operation until it\n * returns zero items, batching each page into <=100-item chunks for the\n * delete step. The operation queries each of the projection entities\n * that key off the owner's partition and returns each row tagged with\n * the entity it came from plus the composite-key payload the\n * operations-layer multi-write helper needs to issue a delete.\n *\n * Owner-type to projection-entity matrix:\n *\n * | Owner | Partition | Entities queried |\n * |---|---|---|\n * | Workspace | `TID#<tenantId>#WORKSPACE#ID#<workspaceId>` | membershipWorkspaceProjection (#2), roleAssignmentWorkspaceProjection (#9), configurationWorkspaceProjection (#10) |\n * | User | `USER#ID#<userId>` | membershipUserProjection (#3, #4), roleAssignmentUserProjection (#5), configurationUserProjection (#10) |\n *\n * The owning canonical record itself (`SK = \"CURRENT\"`) is never\n * returned — the canonical row lives on a different entity (Workspace\n * / User) which is not queried here.\n *\n * Each page reads up to `limit` rows per entity; the operation merges\n * the per-entity responses and surfaces a single `cursor` map keyed by\n * entity name. The state machine outer loop passes this map back on\n * subsequent calls and stops when every per-entity cursor is exhausted.\n *\n * @see .state/adr-018-implementation-guide.md section 4 (Per-batch delete query)\n * @see .claude/rules/data-layer-layout.md (operations live under operations/, not dynamo/)\n */\n\nimport { OWNING_ENTITY_TYPE, type OwningEntityType } from \"@openhi/workflows\";\nimport { getDynamoControlService } from \"../../../dynamo/dynamo-control-service\";\n\n/**\n * Entity-name keys this operation may emit. Matches the keys exposed\n * by the control-plane service factory; downstream delete callers feed\n * them to `executeMultiWrite` as the `entity` field on each triple.\n */\nexport const OWNING_DELETE_PROJECTION_ENTITY = {\n MembershipUserProjection: \"membershipUserProjection\",\n MembershipWorkspaceProjection: \"membershipWorkspaceProjection\",\n RoleAssignmentUserProjection: \"roleAssignmentUserProjection\",\n RoleAssignmentWorkspaceProjection: \"roleAssignmentWorkspaceProjection\",\n ConfigurationUserProjection: \"configurationUserProjection\",\n ConfigurationWorkspaceProjection: \"configurationWorkspaceProjection\",\n} as const;\nexport type OwningDeleteProjectionEntity =\n (typeof OWNING_DELETE_PROJECTION_ENTITY)[keyof typeof OWNING_DELETE_PROJECTION_ENTITY];\n\n/** Inputs accepted by {@link listOwningChildProjectionsOperation}. */\nexport interface ListOwningChildProjectionsParams {\n /** The type of owning entity whose children the cascade is reaping. */\n readonly ownerType: OwningEntityType;\n /** Canonical id of the owning entity (Workspace id or User id). */\n readonly ownerId: string;\n /** Required for Workspace owners; ignored for User owners. */\n readonly tenantId?: string;\n /**\n * Per-entity cursor map carried forward from a prior page. Each\n * key is one of the {@link OWNING_DELETE_PROJECTION_ENTITY} values;\n * missing keys are treated as \"start from the beginning\". Pass the\n * value `null` to mark a per-entity stream as exhausted.\n */\n readonly cursors?: ListOwningChildProjectionsCursorMap;\n /**\n * Per-entity per-page limit (defaults to 100, matching the cascade\n * chunk size cap). The merged page can exceed this when more than\n * one entity has matching rows; the state machine chunker re-splits\n * the merged page into 100-item batches.\n */\n readonly limit?: number;\n /** Optional table-name override; resolved via env when omitted. */\n readonly tableName?: string;\n}\n\n/**\n * One projection row tagged for delete. Carries the entity name plus\n * the composite-key payload the operations-layer multi-write helper\n * expects on a `delete` triple — never the raw `PK`/`SK` strings, so\n * callers cannot accidentally delete an owning canonical record.\n */\nexport interface OwningChildProjectionRow {\n /** The projection entity this row belongs to. */\n readonly entity: OwningDeleteProjectionEntity;\n /**\n * Composite-key payload for `TransactWriteItems` delete. Shape\n * depends on `entity` — user-partition projections key on\n * `{ userId, sk }`; workspace-partition projections key on\n * `{ tenantId, workspaceId, sk }`.\n */\n readonly key: Record<string, string>;\n}\n\n/**\n * Per-entity cursor map. `null` for a key means the entity stream is\n * exhausted; an `undefined` / missing key means start from the\n * beginning.\n */\nexport type ListOwningChildProjectionsCursorMap = Partial<\n Record<OwningDeleteProjectionEntity, string | null>\n>;\n\n/** Page returned by {@link listOwningChildProjectionsOperation}. */\nexport interface ListOwningChildProjectionsResult {\n /** Projection rows in this page, merged across the per-entity queries. */\n readonly items: ReadonlyArray<OwningChildProjectionRow>;\n /** Per-entity cursors for the next page. */\n readonly cursors: ListOwningChildProjectionsCursorMap;\n /**\n * `true` when every per-entity stream returned no items and every\n * cursor is null — the cascade's outer loop terminates on this flag.\n */\n readonly exhausted: boolean;\n}\n\n/**\n * Build the owning partition key for the cascade query — exported so\n * tests and other operations can recompose the same partition string\n * the projection-entity queries here target via ElectroDB.\n */\nexport function buildOwningPartitionKey(params: {\n readonly ownerType: OwningEntityType;\n readonly ownerId: string;\n readonly tenantId?: string;\n}): string {\n switch (params.ownerType) {\n case OWNING_ENTITY_TYPE.Workspace:\n if (!params.tenantId || params.tenantId.length === 0) {\n throw new Error(\n \"buildOwningPartitionKey: tenantId is required for Workspace owners\",\n );\n }\n return `TID#${params.tenantId}#WORKSPACE#ID#${params.ownerId}`;\n case OWNING_ENTITY_TYPE.User:\n return `USER#ID#${params.ownerId}`;\n default: {\n const exhaustive: never = params.ownerType;\n throw new Error(\n `buildOwningPartitionKey: unsupported ownerType '${String(\n exhaustive,\n )}'`,\n );\n }\n }\n}\n\n/** Default per-entity per-page item limit. */\nconst DEFAULT_PAGE_SIZE = 100 as const;\n\n/** SK-prefix filter for each projection-entity query. */\nconst ENTITY_SK_PREFIXES: Record<OwningDeleteProjectionEntity, string> = {\n membershipUserProjection: \"MEMBERSHIP#\",\n membershipWorkspaceProjection: \"MEMBERSHIP#\",\n roleAssignmentUserProjection: \"ROLEASSIGNMENT#\",\n roleAssignmentWorkspaceProjection: \"ROLEASSIGNMENT#\",\n configurationUserProjection: \"CONFIGURATION#\",\n configurationWorkspaceProjection: \"CONFIGURATION#\",\n};\n\n/**\n * Entity-list per owner-type. Lookups for a Workspace skip user-\n * partition entities and vice versa, so the cascade never reaches\n * across the partition boundary by mistake.\n */\nconst ENTITIES_FOR_OWNER: Record<\n OwningEntityType,\n ReadonlyArray<OwningDeleteProjectionEntity>\n> = {\n Workspace: [\n \"membershipWorkspaceProjection\",\n \"roleAssignmentWorkspaceProjection\",\n \"configurationWorkspaceProjection\",\n ],\n User: [\n \"membershipUserProjection\",\n \"roleAssignmentUserProjection\",\n \"configurationUserProjection\",\n ],\n};\n\n/**\n * Issue one ElectroDB `Query` per applicable projection entity for the\n * owner's partition, return the merged page, and carry per-entity\n * cursors forward. Each entity is queried independently — when one\n * entity is exhausted but others still have rows, the next page reads\n * only the still-open streams. The merged result preserves entity\n * identity on every row so the delete step can issue a typed\n * `TransactWriteItems` via `executeMultiWrite`.\n */\nexport async function listOwningChildProjectionsOperation(\n params: ListOwningChildProjectionsParams,\n): Promise<ListOwningChildProjectionsResult> {\n const {\n ownerType,\n ownerId,\n tenantId,\n cursors = {},\n limit = DEFAULT_PAGE_SIZE,\n tableName,\n } = params;\n\n if (!ownerId || ownerId.length === 0) {\n throw new Error(\"listOwningChildProjectionsOperation: ownerId is required\");\n }\n if (\n ownerType === OWNING_ENTITY_TYPE.Workspace &&\n (!tenantId || tenantId.length === 0)\n ) {\n throw new Error(\n \"listOwningChildProjectionsOperation: tenantId is required for Workspace owners\",\n );\n }\n\n const service = getDynamoControlService(tableName);\n const entities = ENTITIES_FOR_OWNER[ownerType];\n\n const nextCursors: ListOwningChildProjectionsCursorMap = {};\n const items: Array<OwningChildProjectionRow> = [];\n\n for (const entityName of entities) {\n const priorCursor = cursors[entityName];\n if (priorCursor === null) {\n // Exhausted on a previous page — skip without re-querying.\n nextCursors[entityName] = null;\n continue;\n }\n const prefix = ENTITY_SK_PREFIXES[entityName];\n const page = await queryProjectionEntity({\n service,\n entityName,\n ownerType,\n ownerId,\n tenantId,\n prefix,\n cursor: priorCursor ?? null,\n limit,\n });\n for (const row of page.items) {\n items.push(row);\n }\n nextCursors[entityName] = page.cursor;\n }\n\n const exhausted = Object.values(nextCursors).every((value) => value === null);\n\n return { items, cursors: nextCursors, exhausted };\n}\n\ninterface QueryProjectionEntityParams {\n readonly service: ReturnType<typeof getDynamoControlService>;\n readonly entityName: OwningDeleteProjectionEntity;\n readonly ownerType: OwningEntityType;\n readonly ownerId: string;\n readonly tenantId?: string;\n readonly prefix: string;\n readonly cursor: string | null;\n readonly limit: number;\n}\n\ninterface QueryProjectionEntityResult {\n readonly items: ReadonlyArray<OwningChildProjectionRow>;\n readonly cursor: string | null;\n}\n\nasync function queryProjectionEntity(\n params: QueryProjectionEntityParams,\n): Promise<QueryProjectionEntityResult> {\n const {\n service,\n entityName,\n ownerType,\n ownerId,\n tenantId,\n prefix,\n cursor,\n limit,\n } = params;\n\n const goOptions = { cursor, limit };\n\n switch (entityName) {\n case \"membershipUserProjection\": {\n const result = await service.entities.membershipUserProjection.query\n .record({ userId: ownerId })\n .begins({ sk: prefix })\n .go(goOptions);\n const items = (result.data ?? []).map<OwningChildProjectionRow>(\n (row) => ({\n entity: entityName,\n key: { userId: row.userId, sk: row.sk },\n }),\n );\n return { items, cursor: result.cursor ?? null };\n }\n case \"membershipWorkspaceProjection\": {\n if (ownerType !== OWNING_ENTITY_TYPE.Workspace || !tenantId) {\n return { items: [], cursor: null };\n }\n const result = await service.entities.membershipWorkspaceProjection.query\n .record({ tenantId, workspaceId: ownerId })\n .begins({ sk: prefix })\n .go(goOptions);\n const items = (result.data ?? []).map<OwningChildProjectionRow>(\n (row) => ({\n entity: entityName,\n key: {\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n sk: row.sk,\n },\n }),\n );\n return { items, cursor: result.cursor ?? null };\n }\n case \"roleAssignmentUserProjection\": {\n const result = await service.entities.roleAssignmentUserProjection.query\n .record({ userId: ownerId })\n .begins({ sk: prefix })\n .go(goOptions);\n const items = (result.data ?? []).map<OwningChildProjectionRow>(\n (row) => ({\n entity: entityName,\n key: { userId: row.userId, sk: row.sk },\n }),\n );\n return { items, cursor: result.cursor ?? null };\n }\n case \"roleAssignmentWorkspaceProjection\": {\n if (ownerType !== OWNING_ENTITY_TYPE.Workspace || !tenantId) {\n return { items: [], cursor: null };\n }\n const result =\n await service.entities.roleAssignmentWorkspaceProjection.query\n .record({ tenantId, workspaceId: ownerId })\n .begins({ sk: prefix })\n .go(goOptions);\n const items = (result.data ?? []).map<OwningChildProjectionRow>(\n (row) => ({\n entity: entityName,\n key: {\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n sk: row.sk,\n },\n }),\n );\n return { items, cursor: result.cursor ?? null };\n }\n case \"configurationUserProjection\": {\n const result = await service.entities.configurationUserProjection.query\n .record({ userId: ownerId })\n .begins({ sk: prefix })\n .go(goOptions);\n const items = (result.data ?? []).map<OwningChildProjectionRow>(\n (row) => ({\n entity: entityName,\n key: { userId: row.userId, sk: row.sk },\n }),\n );\n return { items, cursor: result.cursor ?? null };\n }\n case \"configurationWorkspaceProjection\": {\n if (ownerType !== OWNING_ENTITY_TYPE.Workspace || !tenantId) {\n return { items: [], cursor: null };\n }\n const result =\n await service.entities.configurationWorkspaceProjection.query\n .record({ tenantId, workspaceId: ownerId })\n .begins({ sk: prefix })\n .go(goOptions);\n const items = (result.data ?? []).map<OwningChildProjectionRow>(\n (row) => ({\n entity: entityName,\n key: {\n tenantId: row.tenantId,\n workspaceId: row.workspaceId,\n sk: row.sk,\n },\n }),\n );\n return { items, cursor: result.cursor ?? null };\n }\n default: {\n const exhaustive: never = entityName;\n throw new Error(\n `listOwningChildProjectionsOperation: unsupported entity '${String(\n exhaustive,\n )}'`,\n );\n }\n }\n}\n"],"mappings":"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","names":["exports","exports","exports","exports","exports","exports","exports","exports","exports","exports","exports","exports","exports","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb","import_electrodb"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OWNING_DELETE_PROJECTION_ENTITY,
|
|
3
|
+
deleteOwningChildChunkOperation
|
|
4
|
+
} from "./chunk-W4KR4CSL.mjs";
|
|
5
|
+
import "./chunk-ZM4GDHHC.mjs";
|
|
6
|
+
import "./chunk-QJDHVMKT.mjs";
|
|
7
|
+
import "./chunk-FYHBHHWK.mjs";
|
|
8
|
+
import "./chunk-6NBGYGFL.mjs";
|
|
9
|
+
import "./chunk-TRY7JGWO.mjs";
|
|
10
|
+
import "./chunk-LZOMFHX3.mjs";
|
|
11
|
+
|
|
12
|
+
// src/workflows/control-plane/owning-delete-cascade/delete-chunk.handler.ts
|
|
13
|
+
function isKnownEntity(value) {
|
|
14
|
+
for (const known of Object.values(OWNING_DELETE_PROJECTION_ENTITY)) {
|
|
15
|
+
if (known === value) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
var handler = async (input) => {
|
|
22
|
+
const rows = input.rows.map((row) => {
|
|
23
|
+
if (!isKnownEntity(row.entity)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`owning-delete cascade delete-chunk: unknown projection entity '${row.entity}'`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
entity: row.entity,
|
|
30
|
+
key: { ...row.key }
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
const result = await deleteOwningChildChunkOperation({
|
|
34
|
+
rows,
|
|
35
|
+
token: input.chunkToken
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
ownerType: input.ownerType,
|
|
39
|
+
ownerId: input.ownerId,
|
|
40
|
+
tenantId: input.tenantId,
|
|
41
|
+
rowsDeleted: result.rowsDeleted
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export {
|
|
45
|
+
handler
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=delete-chunk.handler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflows/control-plane/owning-delete-cascade/delete-chunk.handler.ts"],"sourcesContent":["/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/owning-delete-cascade/delete-chunk-handler.md\n *\n * Cascade Step Functions Map-iteration handler. Receives one chunk of\n * <=100 projection rows from the list-and-chunk step and issues a\n * single `TransactWriteItems` via `executeMultiWrite` (#1010).\n *\n * Idempotency: ElectroDB / DynamoDB SDK forwards the `chunkToken` as\n * the `ClientRequestToken` for the transaction, so a Map iteration\n * replayed by Step Functions retry lands on the same transaction id.\n * The state machine's `Catch` block also absorbs\n * `TransactionCanceledException` as a no-op success — common when a\n * prior replay already removed the rows.\n *\n * The handler never touches the canonical owning record; the finalize\n * step owns the canonical delete.\n */\n\nimport type { CascadeChunkInput } from \"./events\";\nimport { deleteOwningChildChunkOperation } from \"../../../data/operations/control/owning-delete/owning-delete-batch-delete-operation\";\nimport {\n type OwningDeleteProjectionEntity,\n OWNING_DELETE_PROJECTION_ENTITY,\n} from \"../../../data/operations/control/owning-delete/owning-delete-list-child-projections-operation\";\n\nexport interface DeleteChunkOutput {\n readonly ownerType: string;\n readonly ownerId: string;\n readonly tenantId?: string;\n readonly rowsDeleted: number;\n}\n\nfunction isKnownEntity(value: string): value is OwningDeleteProjectionEntity {\n for (const known of Object.values(OWNING_DELETE_PROJECTION_ENTITY)) {\n if (known === value) {\n return true;\n }\n }\n return false;\n}\n\nexport const handler = async (\n input: CascadeChunkInput,\n): Promise<DeleteChunkOutput> => {\n const rows = input.rows.map((row) => {\n if (!isKnownEntity(row.entity)) {\n throw new Error(\n `owning-delete cascade delete-chunk: unknown projection entity '${row.entity}'`,\n );\n }\n return {\n entity: row.entity,\n key: { ...row.key },\n };\n });\n\n const result = await deleteOwningChildChunkOperation({\n rows,\n token: input.chunkToken,\n });\n\n return {\n ownerType: input.ownerType,\n ownerId: input.ownerId,\n tenantId: input.tenantId,\n rowsDeleted: result.rowsDeleted,\n };\n};\n"],"mappings":";;;;;;;;;;;;AAgCA,SAAS,cAAc,OAAsD;AAC3E,aAAW,SAAS,OAAO,OAAO,+BAA+B,GAAG;AAClE,QAAI,UAAU,OAAO;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,UAAU,OACrB,UAC+B;AAC/B,QAAM,OAAO,MAAM,KAAK,IAAI,CAAC,QAAQ;AACnC,QAAI,CAAC,cAAc,IAAI,MAAM,GAAG;AAC9B,YAAM,IAAI;AAAA,QACR,kEAAkE,IAAI,MAAM;AAAA,MAC9E;AAAA,IACF;AACA,WAAO;AAAA,MACL,QAAQ,IAAI;AAAA,MACZ,KAAK,EAAE,GAAG,IAAI,IAAI;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,SAAS,MAAM,gCAAgC;AAAA,IACnD;AAAA,IACA,OAAO,MAAM;AAAA,EACf,CAAC;AAED,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,IACf,UAAU,MAAM;AAAA,IAChB,aAAa,OAAO;AAAA,EACtB;AACF;","names":[]}
|