@intentius/chant-lexicon-aws 0.79.0 → 0.80.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.
@@ -124,8 +124,8 @@ export interface EcsUpdateServiceOutput {
124
124
  * capability-level compensation for the common case (recorded here so the
125
125
  * capability is never rollback-silent); a component whose service swap needs
126
126
  * a specific prior task definition/count restored (rather than a re-apply of
127
- * the same input) supplies its own explicit rollback phase instead, as the
128
- * ALB/ECS pilot does with `rollback-previous`.
127
+ * the same input) supplies its own explicit rollback phase instead, such as
128
+ * `rollback-previous` with that `taskDefinition`.
129
129
  */
130
130
  export declare function createEcsUpdateServiceCapability(executor?: CloudExecutor): Capability<EcsUpdateServiceInput, EcsUpdateServiceOutput>;
131
131
  /** Default `ecs-update-service` capability, backed by the real `CloudExecutor`. */
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "04f2deaead1d0523d682f765b4c3e7f95af5e96de6db8713db3f09acac92c4f6",
4
+ "manifest.json": "d41955abfee73ff321172c907028877048d322f6305bfd90673ce5768245d5d9",
5
5
  "meta.json": "31795ba62a1da7503a8d55192c212ac44c56a56cdb2d4fde47966a1abc8b021b",
6
6
  "types/index.d.ts": "1038589dccfc1e690e3373014bff4d319e4ed3b421db2448ed4e70d51eb9d1f2",
7
7
  "rules/hardcoded-region.ts": "5a0eaf7ab391231fe6cd51426ece29539cb4b36f31c8dd060956638fed55722a",
@@ -73,5 +73,5 @@
73
73
  "skills/chant-aws-eks.md": "8789255709ff004ad0a875fd5999edcdc66fc6e33d710db058d9f42703bcfdfe",
74
74
  "skills/chant-aws-carve-terraform.md": "8780b94ce60aded935938d5ef20ea667738354051da1bf9ca6d70d9886f65d21"
75
75
  },
76
- "composite": "b6f987ad78908ed06fcb7830a9eb7874a4c7a7c141e12dd342371ef704bc7c09"
76
+ "composite": "76fcd6b2effef2df35ff5e01a3dbd11def617990258cf782bf42774e8d96d320"
77
77
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "AWS",
6
6
  "intrinsics": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-aws",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "description": "AWS CloudFormation lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -80,7 +80,7 @@
80
80
  "typescript": "^5.9.3"
81
81
  },
82
82
  "peerDependencies": {
83
- "@intentius/chant": "^0.79.0",
83
+ "@intentius/chant": "^0.80.0",
84
84
  "typescript": "^5.9.3"
85
85
  }
86
86
  }
@@ -259,8 +259,8 @@ export interface EcsUpdateServiceOutput {
259
259
  * capability-level compensation for the common case (recorded here so the
260
260
  * capability is never rollback-silent); a component whose service swap needs
261
261
  * a specific prior task definition/count restored (rather than a re-apply of
262
- * the same input) supplies its own explicit rollback phase instead, as the
263
- * ALB/ECS pilot does with `rollback-previous`.
262
+ * the same input) supplies its own explicit rollback phase instead, such as
263
+ * `rollback-previous` with that `taskDefinition`.
264
264
  */
265
265
  export function createEcsUpdateServiceCapability(
266
266
  executor: CloudExecutor = defaultCloudExecutor(),
@@ -48,8 +48,9 @@ import {
48
48
  createWaitJobCapability,
49
49
  } from "./wait-aws";
50
50
  import { createEmrStartJobRunCapability } from "./job-submission";
51
+ import { createRollbackPreviousCapability } from "./safety";
51
52
 
52
- /** Build a fresh registry with every real #557/#558/#561 capability wired to one shared mock executor, plus the still-stubbed verbs the pilots also reference (run-migration, health-gate, rollback-previous — none of which #557/#558/#561 scopes). */
53
+ /** Build a fresh registry with every real #557/#558/#561 capability wired to one shared mock executor, plus the still-stubbed verbs the pilots also reference (run-migration, health-gate — neither of which #557/#558/#561 scopes). */
53
54
  function buildRegistry(mock: MockCloudExecutor): CapabilityRegistry {
54
55
  const registry = new CapabilityRegistry();
55
56
  registry.register(createDockerBuildCapability(mock.executor));
@@ -79,10 +80,8 @@ function buildRegistry(mock: MockCloudExecutor): CapabilityRegistry {
79
80
  // Non-AWS-leaf / non-pilot-scoped verbs the pilots still reference — out of
80
81
  // #557/#558/#561's scope (still typed stubs in ../verbs/*.ts), so faked here
81
82
  // to succeed so the happy-path E2E run can reach completion.
82
- // `rollback-previous` backs the ALB/ECS pilot's own compensation phase.
83
83
  registry.register({ kind: "run-migration", run: async () => ({ applied: true, version: "1" }) });
84
84
  registry.register({ kind: "health-gate", run: async () => ({ healthy: true }) });
85
- registry.register({ kind: "rollback-previous", run: async () => ({ restored: true }) });
86
85
  return registry;
87
86
  }
88
87
 
@@ -447,11 +446,20 @@ describe("Every mutating #557 capability declares a rollback, or the pilot suppl
447
446
  expect(createWaitClusterHealthyCapability(mock.executor).rollback).toBeUndefined();
448
447
  });
449
448
 
450
- it("the ALB/ECS pilot supplies its own explicit rollback phase (rollback-previous) precisely because ecs-update-service's own rollback is a best-effort re-apply, not a true prior-state restore", () => {
451
- // Documents the pilot-level opt-out this issue's acceptance criteria asks
452
- // for: the component, not the capability, owns the compensation here.
453
- expect(searchService.rollback).toBeDefined();
454
- expect(searchService.rollback![0]!.steps[0]).toMatchObject({ kind: "rollback-previous" });
449
+ it("the ALB/ECS pilot declares no rollback-previous phase (#2576): with no taskDefinition it restored nothing", async () => {
450
+ // The phase it used to declare passed only `service` and `cluster`. The
451
+ // real capability turns that into `rollbackService` with no task
452
+ // definition, which the real executor sends as an `aws ecs update-service`
453
+ // that changes nothing. An earlier release is `chant components rollback`.
454
+ expect(searchService.rollback).toBeUndefined();
455
+ const mock = createMockCloudExecutor();
456
+ await createRollbackPreviousCapability(mock.executor).run(
457
+ { env: "dev", component: "search-service" },
458
+ { service: "search", cluster: "prod" },
459
+ );
460
+ expect(mock.calls).toEqual([
461
+ { client: "ecs", method: "rollbackService", args: { cluster: "prod", service: "search", taskDefinition: undefined, desiredCount: undefined } },
462
+ ]);
455
463
  });
456
464
 
457
465
  it("the DynamoDB pilot declares no component-level rollback — a blocked replacement is a stop, not something to compensate (documented opt-out)", () => {
@@ -25,6 +25,8 @@ import { createDockerBuildCapability } from "@intentius/chant/components/verbs/b
25
25
  import { createPublishImageCapability, createLoadImageOnHostCapability } from "./publish";
26
26
  import { createCfnDeployCapability, createEcsUpdateServiceCapability, createLambdaDeployCapability } from "./apply";
27
27
  import { createWaitSteadyStateCapability } from "./wait-aws";
28
+ import { starterCapabilityPlugin } from "@intentius/chant/components/starter-plugin";
29
+ import { awsCapabilityPlugin } from "./capability-plugin";
28
30
 
29
31
  /** Same registry-building convention as ../pilots/pilots-e2e.test.ts: real capabilities wired to one shared mock executor, plus fakes for verbs this suite's presets reference but that stay typed stubs (out of scope for #566, matching the pilots' own accounting). */
30
32
  function buildRegistry(mock: MockCloudExecutor): CapabilityRegistry {
@@ -37,7 +39,6 @@ function buildRegistry(mock: MockCloudExecutor): CapabilityRegistry {
37
39
  registry.register(createLambdaDeployCapability(mock.executor));
38
40
  registry.register(createWaitSteadyStateCapability(mock.executor));
39
41
  registry.register({ kind: "health-gate", run: async () => ({ healthy: true }) });
40
- registry.register({ kind: "rollback-previous", run: async () => ({ restored: true }) });
41
42
  registry.register({ kind: "copy-to-host", run: async () => ({ bytesCopied: 128 }) });
42
43
  registry.register({ kind: "remote-exec", run: async () => ({ exitCode: 0, stdout: "" }) });
43
44
  registry.register({ kind: "wait-endpoint", run: async () => ({ status: 200 }) });
@@ -164,3 +165,40 @@ describe("Preset library end-to-end through the real interpret driver (#566 acce
164
165
  expect(callsByClient.some((c) => c.startsWith("ecr."))).toBe(false);
165
166
  });
166
167
  });
168
+
169
+ /** Every step kind a component composes, in `deploy` and `rollback`, nested fan-out phases included. */
170
+ function stepKinds(component: DriverComponent): string[] {
171
+ const kinds: string[] = [];
172
+ const walk = (steps: unknown[]): void => {
173
+ for (const step of steps) {
174
+ const s = step as { kind?: string; steps?: unknown[] };
175
+ if (Array.isArray(s.steps)) walk(s.steps);
176
+ else if (s.kind && s.kind !== "gate") kinds.push(s.kind);
177
+ }
178
+ };
179
+ for (const phase of [...component.deploy, ...(component.rollback ?? [])]) walk(phase.steps);
180
+ return kinds;
181
+ }
182
+
183
+ describe("presets compose only registered step kinds (#2576)", () => {
184
+ const registered = new Set(
185
+ [...starterCapabilityPlugin.capabilities(), ...awsCapabilityPlugin.capabilities()].map((c) => c.kind),
186
+ );
187
+ const components: Array<[string, DriverComponent]> = [
188
+ ["EcsFargateComponent", projectToJson(EcsFargateComponent({ name: "svc", healthPath: "/healthz", sharedAlbStack: "shared-alb" })) as unknown as DriverComponent],
189
+ ["LambdaComponent", projectToJson(LambdaComponent({ name: "fn", functionName: "fn" })) as unknown as DriverComponent],
190
+ ["SingleHostComposeComponent", projectToJson(SingleHostComposeComponent({ name: "host", healthPath: "/", healthPort: 80 })) as unknown as DriverComponent],
191
+ ["the ALB/ECS pilot", projectToJson(searchService) as unknown as DriverComponent],
192
+ ];
193
+
194
+ it.each(components)("%s uses no step kind the starter and aws plugins leave unregistered", (_name, component) => {
195
+ expect(stepKinds(component).filter((kind) => !registered.has(kind))).toEqual([]);
196
+ });
197
+
198
+ it("the ECS preset and the ALB/ECS pilot declare no rollback-previous step", () => {
199
+ for (const [, component] of components) {
200
+ expect(stepKinds(component)).not.toContain("rollback-previous");
201
+ }
202
+ expect(components[0]![1].rollback).toBeUndefined();
203
+ });
204
+ });