@openhi/constructs 0.0.105 → 0.0.107
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-36UPY7YQ.mjs +529 -0
- package/lib/chunk-36UPY7YQ.mjs.map +1 -0
- package/lib/chunk-AGF3RAAZ.mjs +20 -0
- package/lib/chunk-AGF3RAAZ.mjs.map +1 -0
- package/lib/{chunk-BXEG7IOZ.mjs → chunk-AO3E22CS.mjs} +2 -2
- package/lib/{chunk-WNUH2WDZ.mjs → chunk-CHPEQRXU.mjs} +2 -2
- package/lib/chunk-JUNL76HF.mjs +428 -0
- package/lib/chunk-JUNL76HF.mjs.map +1 -0
- package/lib/chunk-L6UAP4KP.mjs +27 -0
- package/lib/chunk-L6UAP4KP.mjs.map +1 -0
- package/lib/{chunk-3QS3WKRC.mjs → chunk-LZOMFHX3.mjs} +9 -2
- package/lib/chunk-SYBADQXI.mjs +607 -0
- package/lib/chunk-SYBADQXI.mjs.map +1 -0
- package/lib/chunk-VXX4I3EF.mjs +19 -0
- package/lib/chunk-VXX4I3EF.mjs.map +1 -0
- package/lib/{chunk-36YCDLLA.mjs → chunk-VYDIGFIX.mjs} +75 -481
- package/lib/chunk-VYDIGFIX.mjs.map +1 -0
- package/lib/chunk-YU2HRNUP.mjs +33 -0
- package/lib/chunk-YU2HRNUP.mjs.map +1 -0
- package/lib/chunk-YZZDUJHI.mjs +37 -0
- package/lib/chunk-YZZDUJHI.mjs.map +1 -0
- package/lib/cors-options-lambda.handler.mjs +1 -1
- package/lib/data-store-postgres-replication.handler.mjs +1 -1
- package/lib/events-BfrkMoBD.d.mts +44 -0
- package/lib/events-BfrkMoBD.d.ts +44 -0
- package/lib/events-DPodvl07.d.mts +207 -0
- package/lib/events-DPodvl07.d.ts +207 -0
- package/lib/firehose-archive-transform.handler.mjs +1 -1
- package/lib/index.d.mts +417 -9
- package/lib/index.d.ts +663 -10
- package/lib/index.js +2398 -111
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +779 -104
- package/lib/index.mjs.map +1 -1
- package/lib/openhi-context-CaBH8SFo.d.mts +39 -0
- package/lib/openhi-context-CaBH8SFo.d.ts +39 -0
- package/lib/platform-deploy-bridge.handler.d.mts +14 -0
- package/lib/platform-deploy-bridge.handler.d.ts +14 -0
- package/lib/platform-deploy-bridge.handler.js +762 -0
- package/lib/platform-deploy-bridge.handler.js.map +1 -0
- package/lib/platform-deploy-bridge.handler.mjs +134 -0
- package/lib/platform-deploy-bridge.handler.mjs.map +1 -0
- package/lib/post-authentication.handler.mjs +1 -1
- package/lib/post-confirmation.handler.mjs +1 -1
- package/lib/pre-token-generation.handler.js +76 -31
- package/lib/pre-token-generation.handler.js.map +1 -1
- package/lib/pre-token-generation.handler.mjs +5 -3
- package/lib/pre-token-generation.handler.mjs.map +1 -1
- package/lib/provision-default-workspace.handler.js +86 -41
- package/lib/provision-default-workspace.handler.js.map +1 -1
- package/lib/provision-default-workspace.handler.mjs +6 -4
- package/lib/provision-default-workspace.handler.mjs.map +1 -1
- package/lib/rest-api-lambda.handler.js +114 -59
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +40 -61
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/lib/seed-demo-data.handler.d.mts +107 -0
- package/lib/seed-demo-data.handler.d.ts +107 -0
- package/lib/seed-demo-data.handler.js +2037 -0
- package/lib/seed-demo-data.handler.js.map +1 -0
- package/lib/seed-demo-data.handler.mjs +23 -0
- package/lib/seed-demo-data.handler.mjs.map +1 -0
- package/lib/seed-system-data.handler.d.mts +64 -0
- package/lib/seed-system-data.handler.d.ts +64 -0
- package/lib/seed-system-data.handler.js +1631 -0
- package/lib/seed-system-data.handler.js.map +1 -0
- package/lib/seed-system-data.handler.mjs +135 -0
- package/lib/seed-system-data.handler.mjs.map +1 -0
- package/package.json +4 -2
- package/lib/chunk-36YCDLLA.mjs.map +0 -1
- /package/lib/{chunk-BXEG7IOZ.mjs.map → chunk-AO3E22CS.mjs.map} +0 -0
- /package/lib/{chunk-WNUH2WDZ.mjs.map → chunk-CHPEQRXU.mjs.map} +0 -0
- /package/lib/{chunk-3QS3WKRC.mjs.map → chunk-LZOMFHX3.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/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/platform-deploy-bridge/platform-deploy-bridge.handler.ts","../src/workflows/control-plane/platform-deploy-bridge/events.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_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 \"./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 InvalidDetailTypeRegistrationError,\n PlatformDeploymentCompletedV1,\n PlatformSystemDataSeededV1,\n defineDetailType,\n isWellFormedDetailType,\n} from \"./detail-types\";\nexport type {\n PlatformDeploymentCompletedV1Detail,\n PlatformSystemDataSeededV1Detail,\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","import {\n CloudFormationClient,\n DescribeStacksCommand,\n} from \"@aws-sdk/client-cloudformation\";\nimport { EventBridgeClient } from \"@aws-sdk/client-eventbridge\";\nimport {\n OPENHI_CONTROL_SOURCE,\n PlatformDeploymentCompletedV1,\n publishWorkflowEvent,\n type PlatformDeploymentCompletedV1Detail,\n} from \"@openhi/workflows\";\nimport type { EventBridgeEvent } from \"aws-lambda\";\nimport {\n BRIDGED_STATUSES,\n CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,\n CONTROL_EVENT_BUS_NAME_ENV_VAR,\n OPENHI_REPO_TAG_KEY_ENV_VAR,\n OPENHI_TAG_KEY_PREFIX_ENV_VAR,\n PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM,\n type BridgedStatus,\n type CloudFormationStackStatusChangeDetail,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/platform-deploy-bridge/index.md\n *\n * Bridge handler: consume a CloudFormation Stack Status Change event from\n * the default AWS bus, drop it if the stack is not OpenHi-tagged, and\n * republish the projected envelope onto the OpenHI control event bus.\n */\n\ntype CloudFormationStackStatusChangeEvent = EventBridgeEvent<\n typeof CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,\n CloudFormationStackStatusChangeDetail\n>;\n\nconst isBridgedStatus = (status: string): status is BridgedStatus =>\n (BRIDGED_STATUSES as ReadonlyArray<string>).includes(status);\n\nlet cfnClient: CloudFormationClient | undefined;\nlet ebClient: EventBridgeClient | undefined;\n\nconst getCfnClient = (): CloudFormationClient => {\n if (!cfnClient) {\n cfnClient = new CloudFormationClient({});\n }\n return cfnClient;\n};\n\nconst getEbClient = (): EventBridgeClient => {\n if (!ebClient) {\n ebClient = new EventBridgeClient({});\n }\n return ebClient;\n};\n\nconst parseStackArn = (\n stackArn: string,\n): { region: string; accountId: string } | undefined => {\n // arn:aws:cloudformation:<region>:<accountId>:stack/<name>/<uuid>\n const parts = stackArn.split(\":\");\n if (parts.length < 6 || parts[0] !== \"arn\" || parts[2] !== \"cloudformation\") {\n return undefined;\n }\n return { region: parts[3], accountId: parts[4] };\n};\n\nconst parseStackName = (stackArn: string): string | undefined => {\n const tail = stackArn.split(\":\").slice(5).join(\":\");\n const segments = tail.split(\"/\");\n return segments.length >= 2 ? segments[1] : undefined;\n};\n\nexport const handler = async (\n event: CloudFormationStackStatusChangeEvent,\n): Promise<void> => {\n const detail = event.detail;\n const status = detail?.[\"status-details\"]?.status;\n\n // Defensive: the EventBridge rule pre-filters by status, but a misconfigured\n // rule (or replay) could deliver other statuses. Drop silently.\n if (!status || !isBridgedStatus(status)) {\n return;\n }\n\n const stackId = detail[\"stack-id\"];\n if (!stackId) {\n console.warn(\"platform-deploy-bridge: event missing stack-id; dropping\");\n return;\n }\n\n const arnParts = parseStackArn(stackId);\n if (!arnParts) {\n console.warn(\n `platform-deploy-bridge: unparseable stack ARN ${stackId}; dropping`,\n );\n return;\n }\n\n // CloudFormation Stack Status Change events do not carry stack tags\n // inline — fetch them so the bridge can filter to OpenHi-tagged stacks\n // and project the relevant tags into the republished envelope.\n const cfn = getCfnClient();\n const described = await cfn.send(\n new DescribeStacksCommand({ StackName: stackId }),\n );\n const stack = described.Stacks?.[0];\n if (!stack) {\n console.warn(\n `platform-deploy-bridge: DescribeStacks returned no stack for ${stackId}; dropping`,\n );\n return;\n }\n\n const allTags = (stack.Tags ?? [])\n .filter((t): t is { Key: string; Value: string } =>\n Boolean(t.Key && t.Value !== undefined),\n )\n .map((t) => ({ key: t.Key, value: t.Value }));\n\n const repoTagKey = process.env[OPENHI_REPO_TAG_KEY_ENV_VAR];\n if (!repoTagKey) {\n throw new Error(\n `platform-deploy-bridge: ${OPENHI_REPO_TAG_KEY_ENV_VAR} env var is required`,\n );\n }\n const tagKeyPrefix = process.env[OPENHI_TAG_KEY_PREFIX_ENV_VAR];\n if (!tagKeyPrefix) {\n throw new Error(\n `platform-deploy-bridge: ${OPENHI_TAG_KEY_PREFIX_ENV_VAR} env var is required`,\n );\n }\n\n // Defense in depth — the EventBridge rule pre-filters by stack-id prefix\n // (this branch's stacks only), so any matching stack is an OpenHi stack.\n // Re-check the tag to catch the impossible-but-cheap case of a non-OpenHi\n // stack that happens to share the branchHash prefix.\n const isOpenHiStack = allTags.some((t) => t.key === repoTagKey);\n if (!isOpenHiStack) {\n return;\n }\n\n // Project only the openhi:* tags into the envelope so downstream\n // consumers do not see unrelated customer-applied or aws:* tags.\n const stackTags = allTags.filter((t) => t.key.startsWith(tagKeyPrefix));\n\n const stackName = stack.StackName ?? parseStackName(stackId);\n if (!stackName) {\n console.warn(\n `platform-deploy-bridge: could not resolve stack name for ${stackId}; dropping`,\n );\n return;\n }\n\n const detailPayload: PlatformDeploymentCompletedV1Detail = {\n stackName,\n stackId,\n region: arnParts.region,\n accountId: arnParts.accountId,\n status,\n ...(detail[\"status-details\"][\"status-reason\"] !== undefined\n ? { statusReason: detail[\"status-details\"][\"status-reason\"] }\n : {}),\n stackTags,\n cloudformationEventTime: event.time,\n };\n\n const controlBusName = process.env[CONTROL_EVENT_BUS_NAME_ENV_VAR];\n if (!controlBusName) {\n throw new Error(\n `platform-deploy-bridge: ${CONTROL_EVENT_BUS_NAME_ENV_VAR} env var is required`,\n );\n }\n\n await publishWorkflowEvent(\n getEbClient(),\n PlatformDeploymentCompletedV1,\n detailPayload,\n { actor: { system: PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM } },\n {\n busNameByPlane: { [OPENHI_CONTROL_SOURCE]: controlBusName },\n },\n );\n};\n","/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/platform-deploy-bridge/index.md\n */\n\n/** EventBridge `source` for the AWS-native CloudFormation events the bridge listens to. */\nexport const CLOUDFORMATION_EVENT_SOURCE = \"aws.cloudformation\" as const;\n\n/** EventBridge `detail-type` for terminal stack status events. */\nexport const CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE =\n \"CloudFormation Stack Status Change\" as const;\n\n/** Stack statuses the bridge republishes. Other statuses are pre-filtered at the rule. */\nexport const BRIDGED_STATUSES = [\"CREATE_COMPLETE\", \"UPDATE_COMPLETE\"] as const;\nexport type BridgedStatus = (typeof BRIDGED_STATUSES)[number];\n\n/** Env var the bridge handler reads to discover the control event bus name. */\nexport const CONTROL_EVENT_BUS_NAME_ENV_VAR = \"CONTROL_EVENT_BUS_NAME\";\n\n/**\n * Env var the bridge handler reads for the resolved\n * `openhi:repo-name`-shaped tag key. Resolved at synth time from the host\n * stack's `appName` + {@link OPENHI_TAG_SUFFIX_REPO_NAME}.\n */\nexport const OPENHI_REPO_TAG_KEY_ENV_VAR = \"OPENHI_REPO_TAG_KEY\";\n\n/**\n * Env var the bridge handler reads for the resolved `openhi:` tag prefix.\n * Resolved at synth time from the host stack's `appName`. Used to project\n * the relevant stack tags into the published envelope.\n */\nexport const OPENHI_TAG_KEY_PREFIX_ENV_VAR = \"OPENHI_TAG_KEY_PREFIX\";\n\n/** Free-form `actor.system` value per TR-016 § Decision points #1 (bootstrap-role pattern). */\nexport const PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM = \"platform-deploy-bridge\";\n\n/**\n * Subset of the CloudFormation Stack Status Change `detail` field the\n * bridge handler reads. AWS-side schema lives at\n * <https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/monitoring-cloudformation.html>.\n */\nexport interface CloudFormationStackStatusChangeDetail {\n readonly \"stack-id\": string;\n readonly \"logical-resource-id\"?: string;\n readonly \"physical-resource-id\"?: string;\n readonly \"status-details\": {\n readonly status: string;\n readonly \"status-reason\"?: string;\n };\n readonly \"resource-type\"?: string;\n readonly \"client-request-token\"?: string;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAAA,SAAA,6BAAA;AAxBa,IAAAA,SAAA,mBAAmB;AAEhC,QAAM,2BAA2B;AAQjC,QAAM,sBAAsB;AAQ5B,QAAM,sBAAsB;AAM5B,aAAgB,2BAA2B,SAAe;AACxD,UAAI,CAAC,yBAAyB,KAAK,OAAO,GAAG;AAC3C,eAAO;MACT;AACA,YAAM,QAAQ,OAAO,SAAS,QAAQ,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE;AACvD,aAAO,SAAS,uBAAuB,SAAS;IAClD;;;;;;;;;;ACPA,IAAAC,SAAA,sBAAA;AASA,IAAAA,SAAA,wBAAA;AA6CA,IAAAA,SAAA,8BAAA;AAtDA,aAAgB,oBACd,OAAoB;AAEpB,aAAQ,MAA4B,YAAY;IAClD;AAKA,aAAgB,sBACd,OAAoB;AAEpB,aAAQ,MAA8B,WAAW;IACnD;AAyCA,aAAgB,4BACd,QAAoB;AAEpB,UAAI,OAAO,YAAY,UAAa,OAAO,YAAY,QAAW;AAChE,cAAM,IAAI,yBACR,iMAAiM;MAErM;AACA,aAAO;QACL,SAAS,OAAO;QAChB,SAAS,OAAO;QAChB,SAAS,OAAO;QAChB,WAAW,OAAO;;IAEtB;AAGA,QAAa,2BAAb,cAA8C,MAAK;;MAEjD,YAAY,SAAe;AACzB,cAAM,OAAO;AACb,aAAK,OAAO;MACd;;AALF,IAAAA,SAAA,2BAAA;;;;;;;;;;AC3Fa,IAAAC,SAAA,wBAAwB;AAGxB,IAAAA,SAAA,qBAAqB;AAGrB,IAAAA,SAAA,oBAAoB;AAmBpB,IAAAA,SAAA,6BAA2D;MACtE,CAACA,SAAA,qBAAqB,GAAG;MACzB,CAACA,SAAA,kBAAkB,GAAG;MACtB,CAACA,SAAA,iBAAiB,GAAG;;;;;;;;;;;ACQvB,IAAAC,SAAA,mBAAA;AAuBA,IAAAA,SAAA,yBAAA;AAvBA,aAAgB,iBACd,OAAwD;AAExD,UAAI,CAAC,uBAAuB,MAAM,UAAU,GAAG;AAC7C,cAAM,IAAI,mCACR,gBAAgB,MAAM,UAAU,oGAAiG;MAErI;AACA,aAAO;IACT;AAUA,QAAM,sBACJ;AAGF,aAAgB,uBAAuB,YAAkB;AACvD,aAAO,oBAAoB,KAAK,UAAU;IAC5C;AAGA,QAAa,qCAAb,cAAwD,MAAK;;MAE3D,YAAY,SAAe;AACzB,cAAM,OAAO;AACb,aAAK,OAAO;MACd;;AALF,IAAAA,SAAA,qCAAA;;;;;;;;;;AC1EA,QAAA,YAAA;AACA,QAAA,aAAA;AAkDa,IAAAC,SAAA,iCACX,GAAA,WAAA,kBAAsD;MACpD,YAAY;MACZ,QAAQ,UAAA;MACR,eAAe;KAChB;AAgDU,IAAAA,SAAA,8BACX,GAAA,WAAA,kBAAmD;MACjD,YAAY;MACZ,QAAQ,UAAA;MACR,eAAe;KAChB;;;;;;;;;;;;;;;;;;;;;;;;;AC7GH,iBAAA,oBAAAC,QAAA;AACA,iBAAA,oBAAAA,QAAA;;;;;;;;;;AC8EA,IAAAC,SAAA,kBAAA;AAiBA,IAAAA,SAAA,uBAAAC;AAhGA,QAAA,gBAAA,QAAA,QAAA;AACA,QAAA,uBAAA,QAAA,6BAAA;AAOA,QAAA,qBAAA;AACA,QAAA,YAAA;AAsEA,aAAgB,gBACd,QACA,UAA4B,CAAA,GAAE;AAE9B,aAAO;QACL,SAAS,CAAC,OAAO,SAAS,QACxBA,sBAAqB,QAAQ,OAAO,SAAS,KAAK,OAAO;;IAE/D;AASO,mBAAeA,sBACpB,QACA,OACA,SACA,KACA,UAA4B,CAAA,GAAE;AAE9B,YAAM,mBAAmB,QAAQ,qBAAqB,OAAM,GAAA,cAAA,YAAU;AACtE,YAAM,yBACJ,QAAQ,2BAA2B,OAAM,GAAA,cAAA,YAAU;AACrD,YAAM,MAAM,QAAQ,QAAQ,MAAM,oBAAI,KAAI;AAE1C,YAAM,WAAoC;QACxC,SAAS,iBAAgB;QACzB,SAAS;QACT,eAAe,IAAI,iBAAiB,uBAAsB;QAC1D,aAAa,IAAI,eAAe;QAChC,OAAO,IAAI;QACX,YAAY,IAAG,EAAG,YAAW;QAC7B,iBAAiB,mBAAA;QACjB;;AAGF,YAAM,UACJ,QAAQ,iBAAiB,MAAM,MAAM,KACrC,UAAA,2BAA2B,MAAM,MAAM;AAEzC,YAAM,SAAS,MAAM,OAAO,KAC1B,IAAI,qBAAA,iBAAiB;QACnB,SAAS;UACP;YACE,cAAc;YACd,QAAQ,MAAM;YACd,YAAY,MAAM;YAClB,QAAQ,KAAK,UAAU,QAAQ;;;OAGpC,CAAC;AAGJ,WAAK,OAAO,oBAAoB,KAAK,GAAG;AACtC,cAAM,QAAQ,OAAO,UAAU,CAAC;AAChC,cAAM,IAAI,qBACR,wBAAwB,MAAM,UAAU,mBAAmB,OAAO,KAAK,OAAO,aAAa,SAAS,WAAM,OAAO,gBAAgB,kBAAkB,EAAE;MAEzJ;AAEA,aAAO,EAAE,SAAS,SAAS,QAAO;IACpC;AAGA,QAAa,uBAAb,cAA0C,MAAK;;MAE7C,YAAY,SAAe;AACzB,cAAM,OAAO;AACb,aAAK,OAAO;MACd;;AALF,IAAAD,SAAA,uBAAA;;;;;;;;;;ACnGA,IAAAE,SAAA,qBAAA;AA9CA,QAAA,qBAAA;AA8CA,aAAgB,mBACd,OACA,UAA2C;AAE3C,UAAI,MAAM,WAAW,SAAS,QAAQ;AACpC,cAAM,IAAI,0BACR,uBAAuB,MAAM,MAAM,mDAAmD,SAAS,MAAM,IAAI;MAE7G;AAEA,UAAI,MAAM,aAAa,MAAM,SAAS,YAAY;AAChD,cAAM,IAAI,0BACR,4BAA4B,MAAM,aAAa,CAAC,8BAA8B,SAAS,UAAU,IAAI;MAEzG;AAEA,YAAM,YAAY,oBAAoB,MAAM,MAAM;AAElD,UAAI,EAAC,GAAA,mBAAA,4BAA2B,UAAU,eAAe,GAAG;AAC1D,cAAM,IAAI,gCACR,qBAAqB,UAAU,eAAe,yCAAyC;MAE3F;AAEA,YAAM,WAAoC;QACxC,SAAS,UAAU;QACnB,SAAS,UAAU;QACnB,eAAe,UAAU;QACzB,aAAa,UAAU;QACvB,OAAO,UAAU;QACjB,YAAY,UAAU;QACtB,iBAAiB,UAAU;QAC3B,SAAS,UAAU;;AAGrB,aAAO;QACL;QACA,UAAU,EAAE,SAAS,SAAS,SAAS,SAAS,SAAS,QAAO;;IAEpE;AAQA,aAAS,oBAAoB,QAAe;AAC1C,UAAI,WAAW,QAAQ,OAAO,WAAW,UAAU;AACjD,cAAM,IAAI,0BACR,8CAA8C;MAElD;AAEA,YAAM,MAAM;AAEZ,mBAAa,KAAK,SAAS;AAC3B,4BAAsB,KAAK,SAAS;AACpC,mBAAa,KAAK,eAAe;AACjC,wBAAkB,GAAG;AACrB,kBAAY,GAAG;AACf,mBAAa,KAAK,YAAY;AAC9B,mBAAa,KAAK,iBAAiB;AAEnC,UAAI,EAAE,aAAa,MAAM;AACvB,cAAM,IAAI,0BACR,8CAA8C;MAElD;AAEA,aAAO;IACT;AAEA,aAAS,aACP,KACA,OAAa;AAEb,YAAM,QAAQ,IAAI,KAAK;AACvB,UAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,cAAM,IAAI,0BACR,mBAAmB,KAAK,+BAA+B;MAE3D;IACF;AAEA,aAAS,sBACP,KACA,OAAa;AAEb,YAAM,QAAQ,IAAI,KAAK;AACvB,UAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG;AACtE,cAAM,IAAI,0BACR,mBAAmB,KAAK,gCAAgC;MAE5D;IACF;AAEA,aAAS,kBAAkB,KAA4B;AACrD,UAAI,EAAE,iBAAiB,MAAM;AAC3B,cAAM,IAAI,0BACR,kDAAkD;MAEtD;AACA,YAAM,QAAQ,IAAI;AAClB,UAAI,UAAU,SAAS,OAAO,UAAU,YAAY,MAAM,WAAW,IAAI;AACvE,cAAM,IAAI,0BACR,kEAAkE;MAEtE;IACF;AAEA,aAAS,YAAY,KAA4B;AAC/C,YAAM,QAAQ,IAAI;AAClB,UAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,cAAM,IAAI,0BACR,2CAA2C;MAE/C;AACA,YAAM,WAAW;AACjB,YAAM,cACJ,OAAO,SAAS,YAAY,YAC5B,OAAO,SAAS,cAAc,YAC9B,OAAO,SAAS,YAAY,YAC5B,OAAO,SAAS,YAAY;AAC9B,YAAM,gBAAgB,OAAO,SAAS,WAAW;AACjD,UAAI,CAAC,eAAe,CAAC,eAAe;AAClC,cAAM,IAAI,0BACR,mIAAmI;MAEvI;IACF;AAGA,QAAa,4BAAb,cAA+C,MAAK;;MAElD,YAAY,SAAe;AACzB,cAAM,OAAO;AACb,aAAK,OAAO;MACd;;AALF,IAAAA,SAAA,4BAAA;AASA,QAAa,kCAAb,cAAqD,MAAK;;MAExD,YAAY,SAAe;AACzB,cAAM,OAAO;AACb,aAAK,OAAO;MACd;;AALF,IAAAA,SAAA,kCAAA;;;;;;;;;;ACrLa,IAAAC,SAAA,oCACX;AAGW,IAAAA,SAAA,qCAAqC,KAAK,KAAK,KAAK;AAGpD,IAAAA,SAAA,0CAA0C;;;;;;;;;;AC0EvD,IAAAC,SAAA,sBAAA;AAcA,IAAAA,SAAA,iBAAA;AAgDA,IAAAA,SAAA,aAAA;AAuCA,IAAAA,SAAA,gBAAA;AA/LA,QAAA,oBAAA,QAAA,0BAAA;AAOA,QAAA,QAAA;AAmFA,aAAgB,oBACd,UACA,UAAsC,CAAA,GAAE;AAExC,aAAO;QACL,gBAAgB,CAAC,UAAU,eAAe,UAAU,OAAO,OAAO;QAClE,YAAY,CAAC,UAAU,WAAW,UAAU,OAAO,OAAO;;IAE9D;AAMO,mBAAe,eACpB,UACA,OACA,UAAsC,CAAA,GAAE;AAExC,yBAAmB,MAAM,YAAY;AACrC,4BAAsB,MAAM,SAAS,SAAS;AAC9C,YAAM,aACJ,MAAM,cACN,QAAQ,qBACR,MAAA;AACF,UAAI,CAAC,OAAO,UAAU,UAAU,KAAK,cAAc,GAAG;AACpD,cAAM,IAAI,+BACR,8CAA8C,UAAU,GAAG;MAE/D;AAEA,YAAM,YAAY,iBAAiB,QAAQ,SAAS;AACpD,YAAM,OAAO,QAAQ,OAAO,YAAW;AACvC,YAAM,KAAK,cAAc,MAAM,SAAS,MAAM,OAAO;AACrD,YAAM,YAAY,KAAK,MAAM,IAAI,QAAO,IAAK,GAAI,IAAI;AAErD,UAAI;AACF,cAAM,SAAS,KACb,IAAI,kBAAA,eAAe;UACjB,WAAW;UACX,MAAM;YACJ,cAAc,EAAE,GAAG,MAAM,aAAY;YACrC,IAAI,EAAE,GAAG,GAAE;YACX,SAAS,EAAE,GAAG,MAAM,QAAO;YAC3B,SAAS,EAAE,GAAG,OAAO,MAAM,OAAO,EAAC;YACnC,YAAY,EAAE,GAAG,IAAI,YAAW,EAAE;YAClC,WAAW,EAAE,GAAG,OAAO,SAAS,EAAC;;UAEnC,qBACE;SACH,CAAC;AAEJ,eAAO,EAAE,UAAU,KAAI;MACzB,SAAS,KAAK;AACZ,YAAI,eAAe,kBAAA,iCAAiC;AAClD,iBAAO,EAAE,UAAU,OAAO,kBAAkB,KAAI;QAClD;AACA,cAAM;MACR;IACF;AAGO,mBAAe,WACpB,UACA,OACA,UAAsC,CAAA,GAAE;AAExC,yBAAmB,MAAM,YAAY;AACrC,4BAAsB,MAAM,SAAS,SAAS;AAC9C,UAAI,MAAM,OAAO,WAAW,GAAG;AAC7B,cAAM,IAAI,+BAA+B,2BAA2B;MACtE;AAEA,YAAM,YAAY,iBAAiB,QAAQ,SAAS;AACpD,YAAM,OAAO,QAAQ,OAAO,YAAW;AACvC,YAAM,KAAK,cAAc,MAAM,SAAS,MAAM,OAAO;AAErD,YAAM,SAAS,KACb,IAAI,kBAAA,kBAAkB;QACpB,WAAW;QACX,KAAK;UACH,cAAc,EAAE,GAAG,MAAM,aAAY;UACrC,IAAI,EAAE,GAAG,GAAE;;QAEb,kBACE;QACF,0BAA0B;UACxB,WAAW;UACX,kBAAkB;UAClB,aAAa;;QAEf,2BAA2B;UACzB,WAAW,EAAE,MAAM,KAAI;UACvB,WAAW,EAAE,GAAG,MAAM,OAAM;UAC5B,aAAa,EAAE,GAAG,IAAI,YAAW,EAAE;;OAEtC,CAAC;IAEN;AAGA,aAAgB,cAAc,SAAiB,SAAe;AAC5D,UAAI,QAAQ,WAAW,GAAG;AACxB,cAAM,IAAI,+BAA+B,4BAA4B;MACvE;AACA,aAAO,GAAG,OAAO,IAAI,OAAO;IAC9B;AAEA,aAAS,iBAAiB,UAAiB;AACzC,YAAM,OAAO,YAAY,QAAQ,IAAI,MAAA,iCAAiC;AACtE,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,mCACR,oEAAoE,MAAA,iCAAiC,GAAG;MAE5G;AACA,aAAO;IACT;AAEA,aAAS,mBAAmB,cAAoB;AAC9C,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,+BAA+B,iCAAiC;MAC5E;AACA,UAAI,aAAa,SAAS,MAAA,yCAAyC;AACjE,cAAM,IAAI,+BACR,8BAAyB,MAAA,uCAAuC,eAAe,aAAa,MAAM,GAAG;MAEzG;AACA,UAAI,KAAK,KAAK,YAAY,GAAG;AAC3B,cAAM,IAAI,+BACR,2CAA2C;MAE/C;IACF;AAEA,aAAS,sBAAsB,OAAe,OAAa;AACzD,UAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG;AACzC,cAAM,IAAI,+BACR,GAAG,KAAK,qCAAqC,KAAK,GAAG;MAEzD;IACF;AAEA,aAAS,aAAU;AACjB,aAAO,oBAAI,KAAI;IACjB;AAGA,QAAa,qCAAb,cAAwD,MAAK;;MAE3D,YAAY,SAAe;AACzB,cAAM,OAAO;AACb,aAAK,OAAO;MACd;;AALF,IAAAA,SAAA,qCAAA;AASA,QAAa,iCAAb,cAAoD,MAAK;;MAEvD,YAAY,SAAe;AACzB,cAAM,OAAO;AACb,aAAK,OAAO;MACd;;AALF,IAAAA,SAAA,iCAAA;;;;;;;;;;ACtPA,QAAA,QAAA;AACE,WAAA,eAAAC,UAAA,sCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,MAAA;IAAkC,EAAA,CAAA;AAClC,WAAA,eAAAA,UAAA,2CAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,MAAA;IAAuC,EAAA,CAAA;AACvC,WAAA,eAAAA,UAAA,qCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,MAAA;IAAiC,EAAA,CAAA;AAEnC,QAAA,0BAAA;AACE,WAAA,eAAAA,UAAA,kCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,wBAAA;IAA8B,EAAA,CAAA;AAC9B,WAAA,eAAAA,UAAA,sCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,wBAAA;IAAkC,EAAA,CAAA;AAClC,WAAA,eAAAA,UAAA,iBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,wBAAA;IAAa,EAAA,CAAA;AACb,WAAA,eAAAA,UAAA,cAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,wBAAA;IAAU,EAAA,CAAA;AACV,WAAA,eAAAA,UAAA,kBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,wBAAA;IAAc,EAAA,CAAA;AACd,WAAA,eAAAA,UAAA,uBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,wBAAA;IAAmB,EAAA,CAAA;;;;;;;;;;ACXrB,QAAA,qBAAA;AACE,WAAA,eAAAC,UAAA,oBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,mBAAA;IAAgB,EAAA,CAAA;AAChB,WAAA,eAAAA,UAAA,8BAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,mBAAA;IAA0B,EAAA,CAAA;AAE5B,QAAA,aAAA;AACE,WAAA,eAAAA,UAAA,4BAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,WAAA;IAAwB,EAAA,CAAA;AACxB,WAAA,eAAAA,UAAA,yBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,WAAA;IAAqB,EAAA,CAAA;AACrB,WAAA,eAAAA,UAAA,uBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,WAAA;IAAmB,EAAA,CAAA;AACnB,WAAA,eAAAA,UAAA,+BAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,WAAA;IAA2B,EAAA,CAAA;AAQ7B,QAAA,YAAA;AACE,WAAA,eAAAA,UAAA,8BAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,UAAA;IAA0B,EAAA,CAAA;AAC1B,WAAA,eAAAA,UAAA,yBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,UAAA;IAAqB,EAAA,CAAA;AACrB,WAAA,eAAAA,UAAA,sBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,UAAA;IAAkB,EAAA,CAAA;AAClB,WAAA,eAAAA,UAAA,qBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,UAAA;IAAiB,EAAA,CAAA;AAGnB,QAAA,iBAAA;AACE,WAAA,eAAAA,UAAA,sCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,eAAA;IAAkC,EAAA,CAAA;AAClC,WAAA,eAAAA,UAAA,iCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,eAAA;IAA6B,EAAA,CAAA;AAC7B,WAAA,eAAAA,UAAA,8BAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,eAAA;IAA0B,EAAA,CAAA;AAC1B,WAAA,eAAAA,UAAA,oBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,eAAA;IAAgB,EAAA,CAAA;AAChB,WAAA,eAAAA,UAAA,0BAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,eAAA;IAAsB,EAAA,CAAA;AAOxB,QAAA,cAAA;AACE,WAAA,eAAAA,UAAA,wBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,YAAA;IAAoB,EAAA,CAAA;AACpB,WAAA,eAAAA,UAAA,wBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,YAAA;IAAoB,EAAA,CAAA;AACpB,WAAA,eAAAA,UAAA,mBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,YAAA;IAAe,EAAA,CAAA;AAQjB,QAAA,aAAA;AACE,WAAA,eAAAA,UAAA,6BAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,WAAA;IAAyB,EAAA,CAAA;AACzB,WAAA,eAAAA,UAAA,mCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,WAAA;IAA+B,EAAA,CAAA;AAC/B,WAAA,eAAAA,UAAA,sBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,WAAA;IAAkB,EAAA,CAAA;AAOpB,QAAA,UAAA;AACE,WAAA,eAAAA,UAAA,sCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAkC,EAAA,CAAA;AAClC,WAAA,eAAAA,UAAA,2CAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAuC,EAAA,CAAA;AACvC,WAAA,eAAAA,UAAA,qCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAiC,EAAA,CAAA;AACjC,WAAA,eAAAA,UAAA,kCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAA8B,EAAA,CAAA;AAC9B,WAAA,eAAAA,UAAA,sCAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAkC,EAAA,CAAA;AAClC,WAAA,eAAAA,UAAA,iBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAa,EAAA,CAAA;AACb,WAAA,eAAAA,UAAA,cAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAU,EAAA,CAAA;AACV,WAAA,eAAAA,UAAA,kBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAc,EAAA,CAAA;AACd,WAAA,eAAAA,UAAA,uBAAA,EAAA,YAAA,MAAA,KAAA,WAAA;AAAA,aAAA,QAAA;IAAmB,EAAA,CAAA;;;;;ACjErB;AAAA;AAAA;AAAA;AAAA;AAAA,mCAGO;AACP,gCAAkC;AAClC,uBAKO;;;ACEA,IAAM,mBAAmB,CAAC,mBAAmB,iBAAiB;AAI9D,IAAM,iCAAiC;AAOvC,IAAM,8BAA8B;AAOpC,IAAM,gCAAgC;AAGtC,IAAM,sCAAsC;;;ADGnD,IAAM,kBAAkB,CAAC,WACtB,iBAA2C,SAAS,MAAM;AAE7D,IAAI;AACJ,IAAI;AAEJ,IAAM,eAAe,MAA4B;AAC/C,MAAI,CAAC,WAAW;AACd,gBAAY,IAAI,kDAAqB,CAAC,CAAC;AAAA,EACzC;AACA,SAAO;AACT;AAEA,IAAM,cAAc,MAAyB;AAC3C,MAAI,CAAC,UAAU;AACb,eAAW,IAAI,4CAAkB,CAAC,CAAC;AAAA,EACrC;AACA,SAAO;AACT;AAEA,IAAM,gBAAgB,CACpB,aACsD;AAEtD,QAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,MAAI,MAAM,SAAS,KAAK,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,kBAAkB;AAC3E,WAAO;AAAA,EACT;AACA,SAAO,EAAE,QAAQ,MAAM,CAAC,GAAG,WAAW,MAAM,CAAC,EAAE;AACjD;AAEA,IAAM,iBAAiB,CAAC,aAAyC;AAC/D,QAAM,OAAO,SAAS,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG;AAClD,QAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,SAAO,SAAS,UAAU,IAAI,SAAS,CAAC,IAAI;AAC9C;AAEO,IAAM,UAAU,OACrB,UACkB;AAClB,QAAM,SAAS,MAAM;AACrB,QAAM,SAAS,SAAS,gBAAgB,GAAG;AAI3C,MAAI,CAAC,UAAU,CAAC,gBAAgB,MAAM,GAAG;AACvC;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,UAAU;AACjC,MAAI,CAAC,SAAS;AACZ,YAAQ,KAAK,0DAA0D;AACvE;AAAA,EACF;AAEA,QAAM,WAAW,cAAc,OAAO;AACtC,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN,iDAAiD,OAAO;AAAA,IAC1D;AACA;AAAA,EACF;AAKA,QAAM,MAAM,aAAa;AACzB,QAAM,YAAY,MAAM,IAAI;AAAA,IAC1B,IAAI,mDAAsB,EAAE,WAAW,QAAQ,CAAC;AAAA,EAClD;AACA,QAAM,QAAQ,UAAU,SAAS,CAAC;AAClC,MAAI,CAAC,OAAO;AACV,YAAQ;AAAA,MACN,gEAAgE,OAAO;AAAA,IACzE;AACA;AAAA,EACF;AAEA,QAAM,WAAW,MAAM,QAAQ,CAAC,GAC7B;AAAA,IAAO,CAAC,MACP,QAAQ,EAAE,OAAO,EAAE,UAAU,MAAS;AAAA,EACxC,EACC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE;AAE9C,QAAM,aAAa,QAAQ,IAAI,2BAA2B;AAC1D,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR,2BAA2B,2BAA2B;AAAA,IACxD;AAAA,EACF;AACA,QAAM,eAAe,QAAQ,IAAI,6BAA6B;AAC9D,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR,2BAA2B,6BAA6B;AAAA,IAC1D;AAAA,EACF;AAMA,QAAM,gBAAgB,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,UAAU;AAC9D,MAAI,CAAC,eAAe;AAClB;AAAA,EACF;AAIA,QAAM,YAAY,QAAQ,OAAO,CAAC,MAAM,EAAE,IAAI,WAAW,YAAY,CAAC;AAEtE,QAAM,YAAY,MAAM,aAAa,eAAe,OAAO;AAC3D,MAAI,CAAC,WAAW;AACd,YAAQ;AAAA,MACN,4DAA4D,OAAO;AAAA,IACrE;AACA;AAAA,EACF;AAEA,QAAM,gBAAqD;AAAA,IACzD;AAAA,IACA;AAAA,IACA,QAAQ,SAAS;AAAA,IACjB,WAAW,SAAS;AAAA,IACpB;AAAA,IACA,GAAI,OAAO,gBAAgB,EAAE,eAAe,MAAM,SAC9C,EAAE,cAAc,OAAO,gBAAgB,EAAE,eAAe,EAAE,IAC1D,CAAC;AAAA,IACL;AAAA,IACA,yBAAyB,MAAM;AAAA,EACjC;AAEA,QAAM,iBAAiB,QAAQ,IAAI,8BAA8B;AACjE,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI;AAAA,MACR,2BAA2B,8BAA8B;AAAA,IAC3D;AAAA,EACF;AAEA,YAAM;AAAA,IACJ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,EAAE,OAAO,EAAE,QAAQ,oCAAoC,EAAE;AAAA,IACzD;AAAA,MACE,gBAAgB,EAAE,CAAC,sCAAqB,GAAG,eAAe;AAAA,IAC5D;AAAA,EACF;AACF;","names":["exports","exports","exports","exports","exports","exports","exports","publishWorkflowEvent","exports","exports","exports","exports","exports"]}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BRIDGED_STATUSES,
|
|
3
|
+
CONTROL_EVENT_BUS_NAME_ENV_VAR,
|
|
4
|
+
OPENHI_REPO_TAG_KEY_ENV_VAR,
|
|
5
|
+
OPENHI_TAG_KEY_PREFIX_ENV_VAR,
|
|
6
|
+
PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM
|
|
7
|
+
} from "./chunk-VXX4I3EF.mjs";
|
|
8
|
+
import {
|
|
9
|
+
require_lib
|
|
10
|
+
} from "./chunk-SYBADQXI.mjs";
|
|
11
|
+
import {
|
|
12
|
+
__toESM
|
|
13
|
+
} from "./chunk-LZOMFHX3.mjs";
|
|
14
|
+
|
|
15
|
+
// src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge.handler.ts
|
|
16
|
+
var import_workflows = __toESM(require_lib());
|
|
17
|
+
import {
|
|
18
|
+
CloudFormationClient,
|
|
19
|
+
DescribeStacksCommand
|
|
20
|
+
} from "@aws-sdk/client-cloudformation";
|
|
21
|
+
import { EventBridgeClient } from "@aws-sdk/client-eventbridge";
|
|
22
|
+
var isBridgedStatus = (status) => BRIDGED_STATUSES.includes(status);
|
|
23
|
+
var cfnClient;
|
|
24
|
+
var ebClient;
|
|
25
|
+
var getCfnClient = () => {
|
|
26
|
+
if (!cfnClient) {
|
|
27
|
+
cfnClient = new CloudFormationClient({});
|
|
28
|
+
}
|
|
29
|
+
return cfnClient;
|
|
30
|
+
};
|
|
31
|
+
var getEbClient = () => {
|
|
32
|
+
if (!ebClient) {
|
|
33
|
+
ebClient = new EventBridgeClient({});
|
|
34
|
+
}
|
|
35
|
+
return ebClient;
|
|
36
|
+
};
|
|
37
|
+
var parseStackArn = (stackArn) => {
|
|
38
|
+
const parts = stackArn.split(":");
|
|
39
|
+
if (parts.length < 6 || parts[0] !== "arn" || parts[2] !== "cloudformation") {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
return { region: parts[3], accountId: parts[4] };
|
|
43
|
+
};
|
|
44
|
+
var parseStackName = (stackArn) => {
|
|
45
|
+
const tail = stackArn.split(":").slice(5).join(":");
|
|
46
|
+
const segments = tail.split("/");
|
|
47
|
+
return segments.length >= 2 ? segments[1] : void 0;
|
|
48
|
+
};
|
|
49
|
+
var handler = async (event) => {
|
|
50
|
+
const detail = event.detail;
|
|
51
|
+
const status = detail?.["status-details"]?.status;
|
|
52
|
+
if (!status || !isBridgedStatus(status)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const stackId = detail["stack-id"];
|
|
56
|
+
if (!stackId) {
|
|
57
|
+
console.warn("platform-deploy-bridge: event missing stack-id; dropping");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const arnParts = parseStackArn(stackId);
|
|
61
|
+
if (!arnParts) {
|
|
62
|
+
console.warn(
|
|
63
|
+
`platform-deploy-bridge: unparseable stack ARN ${stackId}; dropping`
|
|
64
|
+
);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const cfn = getCfnClient();
|
|
68
|
+
const described = await cfn.send(
|
|
69
|
+
new DescribeStacksCommand({ StackName: stackId })
|
|
70
|
+
);
|
|
71
|
+
const stack = described.Stacks?.[0];
|
|
72
|
+
if (!stack) {
|
|
73
|
+
console.warn(
|
|
74
|
+
`platform-deploy-bridge: DescribeStacks returned no stack for ${stackId}; dropping`
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const allTags = (stack.Tags ?? []).filter(
|
|
79
|
+
(t) => Boolean(t.Key && t.Value !== void 0)
|
|
80
|
+
).map((t) => ({ key: t.Key, value: t.Value }));
|
|
81
|
+
const repoTagKey = process.env[OPENHI_REPO_TAG_KEY_ENV_VAR];
|
|
82
|
+
if (!repoTagKey) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`platform-deploy-bridge: ${OPENHI_REPO_TAG_KEY_ENV_VAR} env var is required`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const tagKeyPrefix = process.env[OPENHI_TAG_KEY_PREFIX_ENV_VAR];
|
|
88
|
+
if (!tagKeyPrefix) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`platform-deploy-bridge: ${OPENHI_TAG_KEY_PREFIX_ENV_VAR} env var is required`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const isOpenHiStack = allTags.some((t) => t.key === repoTagKey);
|
|
94
|
+
if (!isOpenHiStack) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const stackTags = allTags.filter((t) => t.key.startsWith(tagKeyPrefix));
|
|
98
|
+
const stackName = stack.StackName ?? parseStackName(stackId);
|
|
99
|
+
if (!stackName) {
|
|
100
|
+
console.warn(
|
|
101
|
+
`platform-deploy-bridge: could not resolve stack name for ${stackId}; dropping`
|
|
102
|
+
);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const detailPayload = {
|
|
106
|
+
stackName,
|
|
107
|
+
stackId,
|
|
108
|
+
region: arnParts.region,
|
|
109
|
+
accountId: arnParts.accountId,
|
|
110
|
+
status,
|
|
111
|
+
...detail["status-details"]["status-reason"] !== void 0 ? { statusReason: detail["status-details"]["status-reason"] } : {},
|
|
112
|
+
stackTags,
|
|
113
|
+
cloudformationEventTime: event.time
|
|
114
|
+
};
|
|
115
|
+
const controlBusName = process.env[CONTROL_EVENT_BUS_NAME_ENV_VAR];
|
|
116
|
+
if (!controlBusName) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`platform-deploy-bridge: ${CONTROL_EVENT_BUS_NAME_ENV_VAR} env var is required`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
await (0, import_workflows.publishWorkflowEvent)(
|
|
122
|
+
getEbClient(),
|
|
123
|
+
import_workflows.PlatformDeploymentCompletedV1,
|
|
124
|
+
detailPayload,
|
|
125
|
+
{ actor: { system: PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM } },
|
|
126
|
+
{
|
|
127
|
+
busNameByPlane: { [import_workflows.OPENHI_CONTROL_SOURCE]: controlBusName }
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
export {
|
|
132
|
+
handler
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=platform-deploy-bridge.handler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflows/control-plane/platform-deploy-bridge/platform-deploy-bridge.handler.ts"],"sourcesContent":["import {\n CloudFormationClient,\n DescribeStacksCommand,\n} from \"@aws-sdk/client-cloudformation\";\nimport { EventBridgeClient } from \"@aws-sdk/client-eventbridge\";\nimport {\n OPENHI_CONTROL_SOURCE,\n PlatformDeploymentCompletedV1,\n publishWorkflowEvent,\n type PlatformDeploymentCompletedV1Detail,\n} from \"@openhi/workflows\";\nimport type { EventBridgeEvent } from \"aws-lambda\";\nimport {\n BRIDGED_STATUSES,\n CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,\n CONTROL_EVENT_BUS_NAME_ENV_VAR,\n OPENHI_REPO_TAG_KEY_ENV_VAR,\n OPENHI_TAG_KEY_PREFIX_ENV_VAR,\n PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM,\n type BridgedStatus,\n type CloudFormationStackStatusChangeDetail,\n} from \"./events\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/workflows/control-plane/platform-deploy-bridge/index.md\n *\n * Bridge handler: consume a CloudFormation Stack Status Change event from\n * the default AWS bus, drop it if the stack is not OpenHi-tagged, and\n * republish the projected envelope onto the OpenHI control event bus.\n */\n\ntype CloudFormationStackStatusChangeEvent = EventBridgeEvent<\n typeof CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,\n CloudFormationStackStatusChangeDetail\n>;\n\nconst isBridgedStatus = (status: string): status is BridgedStatus =>\n (BRIDGED_STATUSES as ReadonlyArray<string>).includes(status);\n\nlet cfnClient: CloudFormationClient | undefined;\nlet ebClient: EventBridgeClient | undefined;\n\nconst getCfnClient = (): CloudFormationClient => {\n if (!cfnClient) {\n cfnClient = new CloudFormationClient({});\n }\n return cfnClient;\n};\n\nconst getEbClient = (): EventBridgeClient => {\n if (!ebClient) {\n ebClient = new EventBridgeClient({});\n }\n return ebClient;\n};\n\nconst parseStackArn = (\n stackArn: string,\n): { region: string; accountId: string } | undefined => {\n // arn:aws:cloudformation:<region>:<accountId>:stack/<name>/<uuid>\n const parts = stackArn.split(\":\");\n if (parts.length < 6 || parts[0] !== \"arn\" || parts[2] !== \"cloudformation\") {\n return undefined;\n }\n return { region: parts[3], accountId: parts[4] };\n};\n\nconst parseStackName = (stackArn: string): string | undefined => {\n const tail = stackArn.split(\":\").slice(5).join(\":\");\n const segments = tail.split(\"/\");\n return segments.length >= 2 ? segments[1] : undefined;\n};\n\nexport const handler = async (\n event: CloudFormationStackStatusChangeEvent,\n): Promise<void> => {\n const detail = event.detail;\n const status = detail?.[\"status-details\"]?.status;\n\n // Defensive: the EventBridge rule pre-filters by status, but a misconfigured\n // rule (or replay) could deliver other statuses. Drop silently.\n if (!status || !isBridgedStatus(status)) {\n return;\n }\n\n const stackId = detail[\"stack-id\"];\n if (!stackId) {\n console.warn(\"platform-deploy-bridge: event missing stack-id; dropping\");\n return;\n }\n\n const arnParts = parseStackArn(stackId);\n if (!arnParts) {\n console.warn(\n `platform-deploy-bridge: unparseable stack ARN ${stackId}; dropping`,\n );\n return;\n }\n\n // CloudFormation Stack Status Change events do not carry stack tags\n // inline — fetch them so the bridge can filter to OpenHi-tagged stacks\n // and project the relevant tags into the republished envelope.\n const cfn = getCfnClient();\n const described = await cfn.send(\n new DescribeStacksCommand({ StackName: stackId }),\n );\n const stack = described.Stacks?.[0];\n if (!stack) {\n console.warn(\n `platform-deploy-bridge: DescribeStacks returned no stack for ${stackId}; dropping`,\n );\n return;\n }\n\n const allTags = (stack.Tags ?? [])\n .filter((t): t is { Key: string; Value: string } =>\n Boolean(t.Key && t.Value !== undefined),\n )\n .map((t) => ({ key: t.Key, value: t.Value }));\n\n const repoTagKey = process.env[OPENHI_REPO_TAG_KEY_ENV_VAR];\n if (!repoTagKey) {\n throw new Error(\n `platform-deploy-bridge: ${OPENHI_REPO_TAG_KEY_ENV_VAR} env var is required`,\n );\n }\n const tagKeyPrefix = process.env[OPENHI_TAG_KEY_PREFIX_ENV_VAR];\n if (!tagKeyPrefix) {\n throw new Error(\n `platform-deploy-bridge: ${OPENHI_TAG_KEY_PREFIX_ENV_VAR} env var is required`,\n );\n }\n\n // Defense in depth — the EventBridge rule pre-filters by stack-id prefix\n // (this branch's stacks only), so any matching stack is an OpenHi stack.\n // Re-check the tag to catch the impossible-but-cheap case of a non-OpenHi\n // stack that happens to share the branchHash prefix.\n const isOpenHiStack = allTags.some((t) => t.key === repoTagKey);\n if (!isOpenHiStack) {\n return;\n }\n\n // Project only the openhi:* tags into the envelope so downstream\n // consumers do not see unrelated customer-applied or aws:* tags.\n const stackTags = allTags.filter((t) => t.key.startsWith(tagKeyPrefix));\n\n const stackName = stack.StackName ?? parseStackName(stackId);\n if (!stackName) {\n console.warn(\n `platform-deploy-bridge: could not resolve stack name for ${stackId}; dropping`,\n );\n return;\n }\n\n const detailPayload: PlatformDeploymentCompletedV1Detail = {\n stackName,\n stackId,\n region: arnParts.region,\n accountId: arnParts.accountId,\n status,\n ...(detail[\"status-details\"][\"status-reason\"] !== undefined\n ? { statusReason: detail[\"status-details\"][\"status-reason\"] }\n : {}),\n stackTags,\n cloudformationEventTime: event.time,\n };\n\n const controlBusName = process.env[CONTROL_EVENT_BUS_NAME_ENV_VAR];\n if (!controlBusName) {\n throw new Error(\n `platform-deploy-bridge: ${CONTROL_EVENT_BUS_NAME_ENV_VAR} env var is required`,\n );\n }\n\n await publishWorkflowEvent(\n getEbClient(),\n PlatformDeploymentCompletedV1,\n detailPayload,\n { actor: { system: PLATFORM_DEPLOY_BRIDGE_ACTOR_SYSTEM } },\n {\n busNameByPlane: { [OPENHI_CONTROL_SOURCE]: controlBusName },\n },\n );\n};\n"],"mappings":"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","names":[]}
|
|
@@ -25,7 +25,7 @@ __export(pre_token_generation_handler_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(pre_token_generation_handler_exports);
|
|
26
26
|
|
|
27
27
|
// src/data/operations/control/user/user-create-operation.ts
|
|
28
|
-
var
|
|
28
|
+
var import_types2 = require("@openhi/types");
|
|
29
29
|
|
|
30
30
|
// src/data/dynamo/dynamo-control-service.ts
|
|
31
31
|
var import_electrodb8 = require("electrodb");
|
|
@@ -44,6 +44,9 @@ var dynamoClient = new import_client_dynamodb.DynamoDBClient({
|
|
|
44
44
|
// src/data/dynamo/entities/control/configuration-entity.ts
|
|
45
45
|
var import_electrodb = require("electrodb");
|
|
46
46
|
|
|
47
|
+
// src/data/dynamo/entities/control/control-entity-common.ts
|
|
48
|
+
var import_types = require("@openhi/types");
|
|
49
|
+
|
|
47
50
|
// src/data/dynamo/shard.ts
|
|
48
51
|
var SHARD_COUNT = 4;
|
|
49
52
|
function computeShard(id) {
|
|
@@ -66,6 +69,29 @@ var gsi1ShardAttribute = {
|
|
|
66
69
|
return String(computeShard(item.id));
|
|
67
70
|
}
|
|
68
71
|
};
|
|
72
|
+
var gsi1skAttribute = {
|
|
73
|
+
type: "string",
|
|
74
|
+
watch: ["resource", "lastUpdated", "id"],
|
|
75
|
+
set: (_val, item) => {
|
|
76
|
+
const id = typeof item?.id === "string" ? item.id : "";
|
|
77
|
+
const lastUpdated = typeof item?.lastUpdated === "string" ? item.lastUpdated : "";
|
|
78
|
+
const fallback = `${lastUpdated}#${id}`;
|
|
79
|
+
if (typeof item?.resource !== "string" || item.resource.length === 0) {
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(item.resource);
|
|
85
|
+
} catch {
|
|
86
|
+
return fallback;
|
|
87
|
+
}
|
|
88
|
+
if (!parsed || typeof parsed !== "object") return fallback;
|
|
89
|
+
const resourceType = parsed.resourceType;
|
|
90
|
+
if (typeof resourceType !== "string") return fallback;
|
|
91
|
+
const label = (0, import_types.extractLabel)(parsed);
|
|
92
|
+
return label !== void 0 ? `${label}#${id}` : fallback;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
69
95
|
|
|
70
96
|
// src/data/dynamo/entities/control/configuration-entity.ts
|
|
71
97
|
var ConfigurationEntity = new import_electrodb.Entity({
|
|
@@ -171,8 +197,9 @@ var ConfigurationEntity = new import_electrodb.Entity({
|
|
|
171
197
|
* (workspaceId = "-") or "list configs scoped to this workspace". Does not support
|
|
172
198
|
* hierarchical resolution in one query; use base table GetItem in fallback order
|
|
173
199
|
* (user → workspace → tenant → baseline) for that.
|
|
174
|
-
* SK is `<
|
|
175
|
-
*
|
|
200
|
+
* SK is `<key>#<id>` — Configuration's `key` is a required entity attribute (the
|
|
201
|
+
* config category: endpoints, branding, display, …) and the natural sort/lookup
|
|
202
|
+
* dimension. `casing: "none"` preserves the literal key value.
|
|
176
203
|
*/
|
|
177
204
|
gsi1: {
|
|
178
205
|
index: "GSI1",
|
|
@@ -184,8 +211,8 @@ var ConfigurationEntity = new import_electrodb.Entity({
|
|
|
184
211
|
sk: {
|
|
185
212
|
field: "GSI1SK",
|
|
186
213
|
casing: "none",
|
|
187
|
-
composite: ["
|
|
188
|
-
template: "${
|
|
214
|
+
composite: ["key", "id"],
|
|
215
|
+
template: "${key}#${id}"
|
|
189
216
|
}
|
|
190
217
|
}
|
|
191
218
|
}
|
|
@@ -239,6 +266,8 @@ var MembershipEntity = new import_electrodb2.Entity({
|
|
|
239
266
|
required: true
|
|
240
267
|
},
|
|
241
268
|
gsi1Shard: gsi1ShardAttribute,
|
|
269
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
270
|
+
gsi1sk: gsi1skAttribute,
|
|
242
271
|
deleted: {
|
|
243
272
|
type: "boolean",
|
|
244
273
|
required: false
|
|
@@ -269,8 +298,9 @@ var MembershipEntity = new import_electrodb2.Entity({
|
|
|
269
298
|
/**
|
|
270
299
|
* GSI1 — Unified Sharded List per ADR-011: list all Memberships for a tenant across the
|
|
271
300
|
* four shards. Membership is tenant-scoped only, so `WID#-` is a sentinel.
|
|
272
|
-
* SK is
|
|
273
|
-
* `casing: "none"`
|
|
301
|
+
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
302
|
+
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
303
|
+
* normalized label and ISO-8601 `T`/`Z`.
|
|
274
304
|
*/
|
|
275
305
|
gsi1: {
|
|
276
306
|
index: "GSI1",
|
|
@@ -282,8 +312,8 @@ var MembershipEntity = new import_electrodb2.Entity({
|
|
|
282
312
|
sk: {
|
|
283
313
|
field: "GSI1SK",
|
|
284
314
|
casing: "none",
|
|
285
|
-
composite: ["
|
|
286
|
-
template: "${
|
|
315
|
+
composite: ["gsi1sk"],
|
|
316
|
+
template: "${gsi1sk}"
|
|
287
317
|
}
|
|
288
318
|
}
|
|
289
319
|
}
|
|
@@ -332,6 +362,8 @@ var RoleEntity = new import_electrodb3.Entity({
|
|
|
332
362
|
required: true
|
|
333
363
|
},
|
|
334
364
|
gsi1Shard: gsi1ShardAttribute,
|
|
365
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
366
|
+
gsi1sk: gsi1skAttribute,
|
|
335
367
|
deleted: {
|
|
336
368
|
type: "boolean",
|
|
337
369
|
required: false
|
|
@@ -362,8 +394,9 @@ var RoleEntity = new import_electrodb3.Entity({
|
|
|
362
394
|
/**
|
|
363
395
|
* GSI1 — Unified Sharded List per ADR-011: list all Roles across the four shards.
|
|
364
396
|
* Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#Role#SHARD#<n>`.
|
|
365
|
-
* SK is
|
|
366
|
-
* `casing: "none"`
|
|
397
|
+
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
398
|
+
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
399
|
+
* normalized label and ISO-8601 `T`/`Z`.
|
|
367
400
|
*/
|
|
368
401
|
gsi1: {
|
|
369
402
|
index: "GSI1",
|
|
@@ -375,8 +408,8 @@ var RoleEntity = new import_electrodb3.Entity({
|
|
|
375
408
|
sk: {
|
|
376
409
|
field: "GSI1SK",
|
|
377
410
|
casing: "none",
|
|
378
|
-
composite: ["
|
|
379
|
-
template: "${
|
|
411
|
+
composite: ["gsi1sk"],
|
|
412
|
+
template: "${gsi1sk}"
|
|
380
413
|
}
|
|
381
414
|
}
|
|
382
415
|
}
|
|
@@ -430,6 +463,8 @@ var RoleAssignmentEntity = new import_electrodb4.Entity({
|
|
|
430
463
|
required: true
|
|
431
464
|
},
|
|
432
465
|
gsi1Shard: gsi1ShardAttribute,
|
|
466
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
467
|
+
gsi1sk: gsi1skAttribute,
|
|
433
468
|
deleted: {
|
|
434
469
|
type: "boolean",
|
|
435
470
|
required: false
|
|
@@ -460,8 +495,9 @@ var RoleAssignmentEntity = new import_electrodb4.Entity({
|
|
|
460
495
|
/**
|
|
461
496
|
* GSI1 — Unified Sharded List per ADR-011: list all RoleAssignments for a tenant across the
|
|
462
497
|
* four shards. Tenant-scoped only, so `WID#-` is a sentinel.
|
|
463
|
-
* SK is
|
|
464
|
-
* `casing: "none"`
|
|
498
|
+
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
499
|
+
* extractable, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves the
|
|
500
|
+
* normalized label and ISO-8601 `T`/`Z`.
|
|
465
501
|
*/
|
|
466
502
|
gsi1: {
|
|
467
503
|
index: "GSI1",
|
|
@@ -473,8 +509,8 @@ var RoleAssignmentEntity = new import_electrodb4.Entity({
|
|
|
473
509
|
sk: {
|
|
474
510
|
field: "GSI1SK",
|
|
475
511
|
casing: "none",
|
|
476
|
-
composite: ["
|
|
477
|
-
template: "${
|
|
512
|
+
composite: ["gsi1sk"],
|
|
513
|
+
template: "${gsi1sk}"
|
|
478
514
|
}
|
|
479
515
|
}
|
|
480
516
|
}
|
|
@@ -528,6 +564,8 @@ var TenantEntity = new import_electrodb5.Entity({
|
|
|
528
564
|
required: true
|
|
529
565
|
},
|
|
530
566
|
gsi1Shard: gsi1ShardAttribute,
|
|
567
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
568
|
+
gsi1sk: gsi1skAttribute,
|
|
531
569
|
deleted: {
|
|
532
570
|
type: "boolean",
|
|
533
571
|
required: false
|
|
@@ -558,8 +596,9 @@ var TenantEntity = new import_electrodb5.Entity({
|
|
|
558
596
|
/**
|
|
559
597
|
* GSI1 — Unified Sharded List per ADR-011: list all Tenants across the four shards.
|
|
560
598
|
* Tenant lives at the platform tier (no parent tenant or workspace), so `TID#-#WID#-`
|
|
561
|
-
* sentinels precede `RT#Tenant#SHARD#<n>`. SK is
|
|
562
|
-
*
|
|
599
|
+
* sentinels precede `RT#Tenant#SHARD#<n>`. SK is derived via `gsi1skAttribute` —
|
|
600
|
+
* `<normalizedName>#<id>` when the resource carries a `name`, else `<lastUpdated>#<id>`
|
|
601
|
+
* (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
|
|
563
602
|
*/
|
|
564
603
|
gsi1: {
|
|
565
604
|
index: "GSI1",
|
|
@@ -571,8 +610,8 @@ var TenantEntity = new import_electrodb5.Entity({
|
|
|
571
610
|
sk: {
|
|
572
611
|
field: "GSI1SK",
|
|
573
612
|
casing: "none",
|
|
574
|
-
composite: ["
|
|
575
|
-
template: "${
|
|
613
|
+
composite: ["gsi1sk"],
|
|
614
|
+
template: "${gsi1sk}"
|
|
576
615
|
}
|
|
577
616
|
}
|
|
578
617
|
}
|
|
@@ -629,6 +668,8 @@ var UserEntity = new import_electrodb6.Entity({
|
|
|
629
668
|
required: true
|
|
630
669
|
},
|
|
631
670
|
gsi1Shard: gsi1ShardAttribute,
|
|
671
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
672
|
+
gsi1sk: gsi1skAttribute,
|
|
632
673
|
deleted: {
|
|
633
674
|
type: "boolean",
|
|
634
675
|
required: false
|
|
@@ -659,8 +700,9 @@ var UserEntity = new import_electrodb6.Entity({
|
|
|
659
700
|
/**
|
|
660
701
|
* GSI1 — Unified Sharded List per ADR-011: list all Users across the four shards.
|
|
661
702
|
* Non-tenant-isolated, so `TID#-#WID#-` sentinels precede `RT#User#SHARD#<n>`.
|
|
662
|
-
* SK is
|
|
663
|
-
*
|
|
703
|
+
* SK is derived via `gsi1skAttribute` — uses the resource's natural label when
|
|
704
|
+
* extractable (string `name`/`title` via introspection), else `<lastUpdated>#<id>`
|
|
705
|
+
* (DR-004). `casing: "none"` preserves the normalized label and ISO-8601 `T`/`Z`.
|
|
664
706
|
*/
|
|
665
707
|
gsi1: {
|
|
666
708
|
index: "GSI1",
|
|
@@ -672,8 +714,8 @@ var UserEntity = new import_electrodb6.Entity({
|
|
|
672
714
|
sk: {
|
|
673
715
|
field: "GSI1SK",
|
|
674
716
|
casing: "none",
|
|
675
|
-
composite: ["
|
|
676
|
-
template: "${
|
|
717
|
+
composite: ["gsi1sk"],
|
|
718
|
+
template: "${gsi1sk}"
|
|
677
719
|
}
|
|
678
720
|
},
|
|
679
721
|
/**
|
|
@@ -748,6 +790,8 @@ var WorkspaceEntity = new import_electrodb7.Entity({
|
|
|
748
790
|
required: true
|
|
749
791
|
},
|
|
750
792
|
gsi1Shard: gsi1ShardAttribute,
|
|
793
|
+
/** Derived GSI1 sort key — name-based when extractable; else `<lastUpdated>#<id>`. */
|
|
794
|
+
gsi1sk: gsi1skAttribute,
|
|
751
795
|
deleted: {
|
|
752
796
|
type: "boolean",
|
|
753
797
|
required: false
|
|
@@ -778,8 +822,9 @@ var WorkspaceEntity = new import_electrodb7.Entity({
|
|
|
778
822
|
/**
|
|
779
823
|
* GSI1 — Unified Sharded List per ADR-011: list all Workspaces for a tenant across the
|
|
780
824
|
* four shards. Workspace is itself the workspace identity, so `WID#-` is a sentinel.
|
|
781
|
-
* SK is `<
|
|
782
|
-
* `casing: "none"`
|
|
825
|
+
* SK is derived via `gsi1skAttribute` — `<normalizedName>#<id>` when the resource
|
|
826
|
+
* carries a `name`, else `<lastUpdated>#<id>` (DR-004). `casing: "none"` preserves
|
|
827
|
+
* the normalized label and ISO-8601 `T`/`Z`.
|
|
783
828
|
*/
|
|
784
829
|
gsi1: {
|
|
785
830
|
index: "GSI1",
|
|
@@ -791,8 +836,8 @@ var WorkspaceEntity = new import_electrodb7.Entity({
|
|
|
791
836
|
sk: {
|
|
792
837
|
field: "GSI1SK",
|
|
793
838
|
casing: "none",
|
|
794
|
-
composite: ["
|
|
795
|
-
template: "${
|
|
839
|
+
composite: ["gsi1sk"],
|
|
840
|
+
template: "${gsi1sk}"
|
|
796
841
|
}
|
|
797
842
|
}
|
|
798
843
|
}
|
|
@@ -844,13 +889,13 @@ async function findUserBySubOperation(params) {
|
|
|
844
889
|
}
|
|
845
890
|
|
|
846
891
|
// src/data/operations/data-operations-common.ts
|
|
847
|
-
var
|
|
892
|
+
var import_types3 = require("@openhi/types");
|
|
848
893
|
|
|
849
894
|
// src/lib/compression.ts
|
|
850
895
|
var import_node_zlib = require("zlib");
|
|
851
896
|
|
|
852
897
|
// src/data/operations/control/user/user-update-operation.ts
|
|
853
|
-
var
|
|
898
|
+
var import_types4 = require("@openhi/types");
|
|
854
899
|
|
|
855
900
|
// src/data/operations/control/user/user-resource-helpers.ts
|
|
856
901
|
function parseUserResource(resource) {
|