@intentius/chant-lexicon-aws 0.14.0 → 0.15.0

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.
Files changed (85) hide show
  1. package/dist/components/__tests__/mock-cloud-executor.d.ts +106 -0
  2. package/dist/components/__tests__/mock-cloud-executor.d.ts.map +1 -0
  3. package/dist/components/apply.d.ts +242 -0
  4. package/dist/components/apply.d.ts.map +1 -0
  5. package/dist/components/builders.d.ts +37 -0
  6. package/dist/components/builders.d.ts.map +1 -0
  7. package/dist/components/capability-plugin.d.ts +23 -0
  8. package/dist/components/capability-plugin.d.ts.map +1 -0
  9. package/dist/components/cloud-executor.d.ts +350 -0
  10. package/dist/components/cloud-executor.d.ts.map +1 -0
  11. package/dist/components/config-bom.d.ts +135 -0
  12. package/dist/components/config-bom.d.ts.map +1 -0
  13. package/dist/components/host-delivery.d.ts +110 -0
  14. package/dist/components/host-delivery.d.ts.map +1 -0
  15. package/dist/components/index.d.ts +19 -0
  16. package/dist/components/index.d.ts.map +1 -0
  17. package/dist/components/job-submission.d.ts +89 -0
  18. package/dist/components/job-submission.d.ts.map +1 -0
  19. package/dist/components/publish.d.ts +194 -0
  20. package/dist/components/publish.d.ts.map +1 -0
  21. package/dist/components/safety.d.ts +49 -0
  22. package/dist/components/safety.d.ts.map +1 -0
  23. package/dist/components/wait-aws.d.ts +65 -0
  24. package/dist/components/wait-aws.d.ts.map +1 -0
  25. package/dist/generated/index.d.ts +7 -4
  26. package/dist/generated/index.d.ts.map +1 -1
  27. package/dist/import/live-export.d.ts.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/integrity.json +4 -4
  31. package/dist/lint/audit-catalog.d.ts +10 -0
  32. package/dist/lint/audit-catalog.d.ts.map +1 -0
  33. package/dist/manifest.json +1 -1
  34. package/dist/meta.json +141 -30
  35. package/dist/op/activities/floci.d.ts +54 -0
  36. package/dist/op/activities/floci.d.ts.map +1 -0
  37. package/dist/op/activities/index.d.ts +8 -0
  38. package/dist/op/activities/index.d.ts.map +1 -0
  39. package/dist/ownership.d.ts +10 -0
  40. package/dist/ownership.d.ts.map +1 -0
  41. package/dist/plugin.d.ts.map +1 -1
  42. package/dist/serializer.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +145 -38
  44. package/package.json +12 -2
  45. package/src/components/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  46. package/src/components/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  47. package/src/components/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  48. package/src/components/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  49. package/src/components/__fixtures__/synthesized-template.json +53 -0
  50. package/src/components/__tests__/mock-cloud-executor.ts +425 -0
  51. package/src/components/apply.test.ts +382 -0
  52. package/src/components/apply.ts +499 -0
  53. package/src/components/builders.test.ts +49 -0
  54. package/src/components/builders.ts +59 -0
  55. package/src/components/capability-plugin.ts +73 -0
  56. package/src/components/cloud-executor.test.ts +25 -0
  57. package/src/components/cloud-executor.ts +814 -0
  58. package/src/components/config-bom.test.ts +273 -0
  59. package/src/components/config-bom.ts +310 -0
  60. package/src/components/host-delivery.test.ts +121 -0
  61. package/src/components/host-delivery.ts +193 -0
  62. package/src/components/index.ts +19 -0
  63. package/src/components/job-submission.test.ts +97 -0
  64. package/src/components/job-submission.ts +138 -0
  65. package/src/components/pilots-e2e.test.ts +460 -0
  66. package/src/components/presets-e2e.test.ts +166 -0
  67. package/src/components/publish.test.ts +318 -0
  68. package/src/components/publish.ts +356 -0
  69. package/src/components/safety.test.ts +47 -0
  70. package/src/components/safety.ts +78 -0
  71. package/src/components/wait-aws.test.ts +79 -0
  72. package/src/components/wait-aws.ts +132 -0
  73. package/src/generated/index.d.ts +145 -38
  74. package/src/generated/index.ts +12 -9
  75. package/src/generated/lexicon-aws.json +141 -30
  76. package/src/import/live-export.ts +2 -1
  77. package/src/index.ts +5 -0
  78. package/src/lint/audit-catalog.ts +45 -0
  79. package/src/op/activities/floci.test.ts +67 -0
  80. package/src/op/activities/floci.ts +149 -0
  81. package/src/op/activities/index.ts +16 -0
  82. package/src/ownership.test.ts +18 -0
  83. package/src/ownership.ts +15 -0
  84. package/src/plugin.ts +3 -0
  85. package/src/serializer.ts +2 -1
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createSnapshotBeforeCapability, createRollbackPreviousCapability } from "./safety";
3
+ import { createMockCloudExecutor } from "./__tests__/mock-cloud-executor";
4
+
5
+ const ctx = { env: "dev", component: "orders" };
6
+
7
+ describe("snapshot-before (#557)", () => {
8
+ it("dispatches to the executor's snapshot.create with the resource + kind, returning the snapshotId", async () => {
9
+ const mock = createMockCloudExecutor();
10
+ const out = await createSnapshotBeforeCapability(mock.executor).run(ctx, {
11
+ resource: "orders-table",
12
+ resourceKind: "dynamodb-table",
13
+ });
14
+ expect(out.snapshotId).toBe("snap-mock-dynamodb-table-orders-table");
15
+ expect(mock.calls).toEqual([
16
+ { client: "snapshot", method: "create", args: { resource: "orders-table", resourceKind: "dynamodb-table" } },
17
+ ]);
18
+ });
19
+
20
+ it("passes the kind through for rds/ebs so the executor selects the right mechanism", async () => {
21
+ const mock = createMockCloudExecutor();
22
+ await createSnapshotBeforeCapability(mock.executor).run(ctx, { resource: "prod-db", resourceKind: "rds-instance" });
23
+ expect(mock.calls[0]!.args).toEqual({ resource: "prod-db", resourceKind: "rds-instance" });
24
+ });
25
+
26
+ it("declares no rollback — the snapshot it takes IS the rollback artifact", () => {
27
+ expect(createSnapshotBeforeCapability(createMockCloudExecutor().executor).rollback).toBeUndefined();
28
+ });
29
+ });
30
+
31
+ describe("rollback-previous (#557)", () => {
32
+ it("restores the resource from the given snapshot via the executor, returning restored: true", async () => {
33
+ const mock = createMockCloudExecutor();
34
+ const out = await createRollbackPreviousCapability(mock.executor).run(ctx, {
35
+ resource: "orders-table",
36
+ snapshotId: "arn:aws:dynamodb:us-east-2:1:table/orders-table/backup/abc",
37
+ });
38
+ expect(out).toEqual({ restored: true });
39
+ expect(mock.calls).toEqual([
40
+ { client: "snapshot", method: "restore", args: { resource: "orders-table", snapshotId: "arn:aws:dynamodb:us-east-2:1:table/orders-table/backup/abc" } },
41
+ ]);
42
+ });
43
+
44
+ it("declares no rollback — it is the rollback", () => {
45
+ expect(createRollbackPreviousCapability(createMockCloudExecutor().executor).rollback).toBeUndefined();
46
+ });
47
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * safety / rollback family — explicit snapshot-and-restore capabilities, used
3
+ * alongside the per-capability `rollback` compensation for cases that need an
4
+ * up-front capture (e.g. before a `cfn-deploy` with `onReplace: "snapshot-first"`).
5
+ *
6
+ * Both dispatch to the kind-appropriate AWS backup/restore mechanism through the
7
+ * injectable `CloudExecutor` (DynamoDB backup, RDS DB snapshot, EBS snapshot).
8
+ */
9
+
10
+ import type { Capability } from "@intentius/chant/components/capability";
11
+ import { defaultCloudExecutor, type CloudExecutor } from "./cloud-executor";
12
+
13
+ // ── snapshot-before ──────────────────────────────────────────────────────────
14
+
15
+ export interface SnapshotBeforeInput {
16
+ /** Resource identifier to snapshot (e.g. a DynamoDB table name, an RDS instance id). */
17
+ resource: string;
18
+ /** Resource kind, used to select the right snapshot mechanism. */
19
+ resourceKind: "dynamodb-table" | "rds-instance" | "opensearch-domain" | "ebs-volume";
20
+ }
21
+
22
+ export interface SnapshotBeforeOutput {
23
+ /** Snapshot identifier, for `rollback-previous` to restore from. */
24
+ snapshotId: string;
25
+ }
26
+
27
+ /**
28
+ * Capture a restorable snapshot before a risky/destructive apply step, via the
29
+ * kind-appropriate AWS backup mechanism through the injectable `CloudExecutor`
30
+ * (DynamoDB on-demand backup, RDS DB snapshot, or EBS volume snapshot). Returns
31
+ * the backup/snapshot id `rollback-previous` restores from.
32
+ */
33
+ export function createSnapshotBeforeCapability(executor: CloudExecutor = defaultCloudExecutor()): Capability<SnapshotBeforeInput, SnapshotBeforeOutput> {
34
+ return {
35
+ kind: "snapshot-before",
36
+ async run(_ctx, input) {
37
+ return executor.snapshot.create({ resource: input.resource, resourceKind: input.resourceKind });
38
+ },
39
+ };
40
+ }
41
+
42
+ /** Default `snapshot-before` capability, backed by the real `CloudExecutor`. */
43
+ export const snapshotBeforeCapability: Capability<SnapshotBeforeInput, SnapshotBeforeOutput> =
44
+ createSnapshotBeforeCapability();
45
+
46
+ // ── rollback-previous ────────────────────────────────────────────────────────
47
+
48
+ export interface RollbackPreviousInput {
49
+ /** Snapshot identifier to restore (typically `"@snapshot-before.snapshotId"`). */
50
+ snapshotId: string;
51
+ /** Resource identifier being restored. */
52
+ resource: string;
53
+ }
54
+
55
+ export interface RollbackPreviousOutput {
56
+ /** True once the restore completed. */
57
+ restored: boolean;
58
+ }
59
+
60
+ /**
61
+ * Restore a resource from a prior `snapshot-before` capture — an explicit,
62
+ * caller-composed rollback (not the auto-triggered per-capability compensation).
63
+ * Dispatches by the snapshot-id shape through the `CloudExecutor` and waits for
64
+ * the restore to become available.
65
+ */
66
+ export function createRollbackPreviousCapability(executor: CloudExecutor = defaultCloudExecutor()): Capability<RollbackPreviousInput, RollbackPreviousOutput> {
67
+ return {
68
+ kind: "rollback-previous",
69
+ async run(_ctx, input) {
70
+ await executor.snapshot.restore({ resource: input.resource, snapshotId: input.snapshotId });
71
+ return { restored: true };
72
+ },
73
+ };
74
+ }
75
+
76
+ /** Default `rollback-previous` capability, backed by the real `CloudExecutor`. */
77
+ export const rollbackPreviousCapability: Capability<RollbackPreviousInput, RollbackPreviousOutput> =
78
+ createRollbackPreviousCapability();
@@ -0,0 +1,79 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createWaitForStackCapability,
4
+ createWaitSteadyStateCapability,
5
+ createWaitJobCapability,
6
+ } from "./wait-aws";
7
+ import { createMockCloudExecutor } from "./__tests__/mock-cloud-executor";
8
+
9
+ describe("wait-for-stack (#557)", () => {
10
+ it("polls until the stack is terminal and returns the final status", async () => {
11
+ const mock = createMockCloudExecutor({ stacks: { "orders-table": { terminalStatus: "CREATE_COMPLETE" } } });
12
+ const capability = createWaitForStackCapability(mock.executor);
13
+ const output = await capability.run({ env: "dev", component: "orders-table" }, { stack: "orders-table" });
14
+ expect(output.stackStatus).toBe("CREATE_COMPLETE");
15
+ });
16
+
17
+ it("declares no rollback — a wait is read-only", () => {
18
+ const capability = createWaitForStackCapability(createMockCloudExecutor().executor);
19
+ expect(capability.rollback).toBeUndefined();
20
+ });
21
+ });
22
+
23
+ describe("wait-steady-state (#557)", () => {
24
+ it("returns runningCount once the ECS service reports stable", async () => {
25
+ const mock = createMockCloudExecutor({
26
+ ecsServices: { "prod/search": { runningCount: 3, desiredCount: 3, stable: true } },
27
+ });
28
+ const capability = createWaitSteadyStateCapability(mock.executor);
29
+ const output = await capability.run(
30
+ { env: "dev", component: "search-service" },
31
+ { service: "search", cluster: "prod" },
32
+ );
33
+ expect(output.runningCount).toBe(3);
34
+ });
35
+
36
+ it("defaults the cluster to 'default' when not given (matches the ALB/ECS pilot's bare `{ service }` step)", async () => {
37
+ const mock = createMockCloudExecutor({
38
+ ecsServices: { "default/search": { runningCount: 2, desiredCount: 2, stable: true } },
39
+ });
40
+ const capability = createWaitSteadyStateCapability(mock.executor);
41
+ const output = await capability.run({ env: "dev", component: "search-service" }, { service: "search" });
42
+ expect(output.runningCount).toBe(2);
43
+ });
44
+
45
+ it("times out if the service never stabilizes", async () => {
46
+ const mock = createMockCloudExecutor({
47
+ ecsServices: { "prod/search": { runningCount: 1, desiredCount: 3, stable: false } },
48
+ });
49
+ const capability = createWaitSteadyStateCapability(mock.executor);
50
+ await expect(
51
+ capability.run(
52
+ { env: "dev", component: "search-service" },
53
+ { service: "search", cluster: "prod", intervalMs: 1, timeoutMs: 5 },
54
+ ),
55
+ ).rejects.toThrow(/timed out/);
56
+ });
57
+ });
58
+
59
+ describe("wait-job (#561 — EMR job-run poll)", () => {
60
+ it("returns the terminal state once the job run completes", async () => {
61
+ const mock = createMockCloudExecutor({ jobRuns: { "run-1": { terminalState: "COMPLETED" } } });
62
+ const capability = createWaitJobCapability(mock.executor);
63
+ const output = await capability.run({ env: "dev", component: "emr-job" }, { runId: "run-1" });
64
+ expect(output.state).toBe("COMPLETED");
65
+ });
66
+
67
+ it("fails when the job run ends in a non-COMPLETED terminal state", async () => {
68
+ const mock = createMockCloudExecutor({ jobRuns: { "run-1": { terminalState: "FAILED" } } });
69
+ const capability = createWaitJobCapability(mock.executor);
70
+ await expect(capability.run({ env: "dev", component: "emr-job" }, { runId: "run-1" })).rejects.toThrow(
71
+ /ended in terminal state "FAILED"/,
72
+ );
73
+ });
74
+
75
+ it("declares no rollback — a job-run poll is read-only", () => {
76
+ const capability = createWaitJobCapability(createMockCloudExecutor().executor);
77
+ expect(capability.rollback).toBeUndefined();
78
+ });
79
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * wait / verify family — the cloud-specific members, owned by the aws lexicon:
3
+ * `wait-for-stack` (CloudFormation), `wait-steady-state` (ECS), `wait-job`
4
+ * (EMR). The agnostic waits (`wait-cluster-healthy`, `wait-endpoint`,
5
+ * `health-gate`) stay in core. All poll a real cloud through the injectable aws
6
+ * `CloudExecutor` (./cloud-executor.ts); none has a rollback — a wait is a
7
+ * read-only observation, never a mutation.
8
+ */
9
+
10
+ import type { Capability } from "@intentius/chant/components/capability";
11
+ import { defaultCloudExecutor, sleep, type CloudExecutor } from "./cloud-executor";
12
+
13
+ // ── wait-for-stack ───────────────────────────────────────────────────────────
14
+
15
+ export interface WaitForStackInput {
16
+ /** CloudFormation stack name. */
17
+ stack: string;
18
+ /** Poll interval in ms. Default: 10000. */
19
+ intervalMs?: number;
20
+ /** Overall timeout in ms. */
21
+ timeoutMs?: number;
22
+ }
23
+
24
+ export interface WaitForStackOutput {
25
+ /** Terminal stack status (`CREATE_COMPLETE`, `UPDATE_COMPLETE`, ...). */
26
+ stackStatus: string;
27
+ }
28
+
29
+ /** Poll a CloudFormation stack until it reaches a terminal status. No rollback: a wait is a read-only observation. */
30
+ export function createWaitForStackCapability(
31
+ executor: CloudExecutor = defaultCloudExecutor(),
32
+ ): Capability<WaitForStackInput, WaitForStackOutput> {
33
+ return {
34
+ kind: "wait-for-stack",
35
+ async run(_ctx, input) {
36
+ const { stackStatus } = await executor.cloudformation.waitForStack(input.stack, {
37
+ intervalMs: input.intervalMs,
38
+ timeoutMs: input.timeoutMs,
39
+ });
40
+ return { stackStatus };
41
+ },
42
+ };
43
+ }
44
+
45
+ /** Default `wait-for-stack` capability, backed by the real aws `CloudExecutor`. */
46
+ export const waitForStackCapability: Capability<WaitForStackInput, WaitForStackOutput> = createWaitForStackCapability();
47
+
48
+ // ── wait-steady-state ────────────────────────────────────────────────────────
49
+
50
+ export interface WaitSteadyStateInput {
51
+ /** Service identifier (e.g. an ECS service name). */
52
+ service: string;
53
+ /** ECS cluster name/ARN the service lives in. Default: "default". */
54
+ cluster?: string;
55
+ /** Poll interval in ms. Default: 10000. */
56
+ intervalMs?: number;
57
+ /** Overall timeout in ms. Default: 10 minutes. */
58
+ timeoutMs?: number;
59
+ }
60
+
61
+ export interface WaitSteadyStateOutput {
62
+ /** Running task/instance count once steady state is reached. */
63
+ runningCount: number;
64
+ }
65
+
66
+ /** Poll an ECS service until running count matches desired and no deployment is in flight. No rollback: read-only. */
67
+ export function createWaitSteadyStateCapability(
68
+ executor: CloudExecutor = defaultCloudExecutor(),
69
+ ): Capability<WaitSteadyStateInput, WaitSteadyStateOutput> {
70
+ return {
71
+ kind: "wait-steady-state",
72
+ async run(_ctx, input) {
73
+ const cluster = input.cluster ?? "default";
74
+ const intervalMs = input.intervalMs ?? 10_000;
75
+ const deadline = Date.now() + (input.timeoutMs ?? 10 * 60_000);
76
+ while (true) {
77
+ const state = await executor.ecs.describeService(cluster, input.service);
78
+ if (state.stable) return { runningCount: state.runningCount };
79
+ if (Date.now() > deadline) {
80
+ throw new Error(`wait-steady-state "${input.service}" timed out before reaching steady state`);
81
+ }
82
+ await sleep(intervalMs);
83
+ }
84
+ },
85
+ };
86
+ }
87
+
88
+ /** Default `wait-steady-state` capability, backed by the real aws `CloudExecutor`. */
89
+ export const waitSteadyStateCapability: Capability<WaitSteadyStateInput, WaitSteadyStateOutput> =
90
+ createWaitSteadyStateCapability();
91
+
92
+ // ── wait-job ─────────────────────────────────────────────────────────────────
93
+
94
+ export interface WaitJobInput {
95
+ /** Job/step run id (e.g. from `emr-start-job-run` or `emr-submit-step`). */
96
+ runId: string;
97
+ /** Poll interval in ms. Default: 10000. */
98
+ intervalMs?: number;
99
+ /** Overall timeout in ms. */
100
+ timeoutMs?: number;
101
+ }
102
+
103
+ export interface WaitJobOutput {
104
+ /** Terminal job state (`COMPLETED`, `FAILED`, `CANCELLED`). */
105
+ state: string;
106
+ }
107
+
108
+ /**
109
+ * Poll a submitted job/step (e.g. an EMR job run started by `emr-start-job-run`)
110
+ * until it reaches a terminal state, failing the step if that state is not
111
+ * `COMPLETED`. No rollback: read-only observation.
112
+ */
113
+ export function createWaitJobCapability(
114
+ executor: CloudExecutor = defaultCloudExecutor(),
115
+ ): Capability<WaitJobInput, WaitJobOutput> {
116
+ return {
117
+ kind: "wait-job",
118
+ async run(_ctx, input) {
119
+ const { state } = await executor.emr.waitForJobRun(input.runId, {
120
+ intervalMs: input.intervalMs,
121
+ timeoutMs: input.timeoutMs,
122
+ });
123
+ if (state !== "COMPLETED") {
124
+ throw new Error(`wait-job "${input.runId}" ended in terminal state "${state}", expected "COMPLETED"`);
125
+ }
126
+ return { state };
127
+ },
128
+ };
129
+ }
130
+
131
+ /** Default `wait-job` capability, backed by the real aws `CloudExecutor`. */
132
+ export const waitJobCapability: Capability<WaitJobInput, WaitJobOutput> = createWaitJobCapability();