@intentius/chant-lexicon-aws 0.13.1 → 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 (86) 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 +68 -40
  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 +893 -737
  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 +905 -659
  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 +905 -659
  74. package/src/generated/index.ts +79 -51
  75. package/src/generated/lexicon-aws.json +893 -737
  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/lsp/completions.test.ts +4 -2
  80. package/src/op/activities/floci.test.ts +67 -0
  81. package/src/op/activities/floci.ts +149 -0
  82. package/src/op/activities/index.ts +16 -0
  83. package/src/ownership.test.ts +18 -0
  84. package/src/ownership.ts +15 -0
  85. package/src/plugin.ts +3 -0
  86. package/src/serializer.ts +2 -1
@@ -1,5 +1,6 @@
1
1
  import type { ExportedTemplate, ResourceSelector } from "@intentius/chant/lexicon";
2
2
  import { hasOwnershipMarker, tagArrayToMap } from "@intentius/chant/ownership";
3
+ import { AWS_TAG_OWNERSHIP_KEYS } from "../ownership";
3
4
  import { CFParser } from "./parser";
4
5
 
5
6
  /**
@@ -30,7 +31,7 @@ export function parseStackTemplate(
30
31
  if (selector?.name !== undefined && r.logicalId !== selector.name) return false;
31
32
  if (owned) {
32
33
  const tags = (r.properties as { Tags?: Array<{ Key?: string; Value?: unknown }> }).Tags;
33
- if (!hasOwnershipMarker(tagArrayToMap(tags), "aws-tag")) return false;
34
+ if (!hasOwnershipMarker(tagArrayToMap(tags), AWS_TAG_OWNERSHIP_KEYS)) return false;
34
35
  }
35
36
  return true;
36
37
  }),
package/src/index.ts CHANGED
@@ -93,3 +93,8 @@ export type {
93
93
  export { generate, writeGeneratedFiles } from "./codegen/generate";
94
94
  export { packageLexicon } from "./codegen/package";
95
95
  export type { PackageOptions, PackageResult } from "./codegen/package";
96
+
97
+ // Component/release capabilities — the AWS operational leaves (cfn-deploy,
98
+ // emr-*, code-deploy, …) contributed to core's capability-plugin seam. Core
99
+ // loads `awsCapabilityPlugin` when a project's chant.config lists this lexicon.
100
+ export { awsCapabilityPlugin } from "./components/capability-plugin";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The aws lexicon's `chant audit` catalog — metadata (title/tier/fix/authority/
3
+ * category) for the WAW* CloudFormation post-synth rules this lexicon ships.
4
+ * Contributed to core's aggregate via `awsPlugin.auditCatalog()` (#687, epic
5
+ * #350); core keeps only the generic catalog machinery + cross-cutting ids.
6
+ */
7
+
8
+ import { auditRule, type RuleMeta, type Authority } from "@intentius/chant/audit/catalog";
9
+
10
+ /** AWS Well-Architected Security Pillar — the authority chant cites for AWS security findings. */
11
+ const AWS_SEC: Authority = {
12
+ name: "AWS — Security Pillar (Well-Architected)",
13
+ url: "https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html",
14
+ };
15
+
16
+ /** Every WAW* audit rule's catalog entry. */
17
+ export const awsAuditCatalog: Record<string, RuleMeta> = {
18
+ WAW010: auditRule("WAW010", "report-only", "guidance", "Redundant DependsOn", "Remove DependsOn already implied by a Ref/GetAtt.", { category: "best-practice" }),
19
+ WAW011: auditRule("WAW011", "report-only", "guidance", "Deprecated Lambda runtime", "Upgrade to a supported Lambda runtime.", { category: "best-practice" }),
20
+ WAW013: auditRule("WAW013", "merge-worthy", "guidance", "Child stack exports nothing", "Add stackOutput() exports the parent can reference.", { category: "correctness" }),
21
+ WAW014: auditRule("WAW014", "report-only", "guidance", "Nested stack outputs never referenced", "Reference the outputs or split into a separate build.", { category: "best-practice" }),
22
+ WAW015: auditRule("WAW015", "merge-worthy", "guidance", "Circular dependency between nested stacks", "Break the cycle between nested stacks.", { category: "correctness" }),
23
+ WAW016: auditRule("WAW016", "report-only", "guidance", "Deprecated property", "Replace the deprecated CloudFormation property.", { category: "best-practice" }),
24
+ WAW017: auditRule("WAW017", "report-only", "guidance", "Missing tags on a taggable resource", "Add tags for cost allocation and compliance.", { category: "best-practice" }),
25
+ WAW018: auditRule("WAW018", "merge-worthy", "guidance", "S3 bucket missing public access block", "Add a PublicAccessBlockConfiguration blocking all public access.", { authority: [AWS_SEC] }),
26
+ WAW019: auditRule("WAW019", "merge-worthy", "guidance", "Security group allows unrestricted ingress on a sensitive port", "Restrict the CIDR on SSH/RDP/database ports to known sources.", { authority: [AWS_SEC] }),
27
+ WAW020: auditRule("WAW020", "merge-worthy", "guidance", "IAM policy uses a wildcard Action", "Scope the policy to specific actions (least privilege).", { authority: [AWS_SEC] }),
28
+ WAW021: auditRule("WAW021", "merge-worthy", "guidance", "RDS storage not encrypted", "Enable StorageEncrypted for encryption at rest.", { authority: [AWS_SEC] }),
29
+ WAW022: auditRule("WAW022", "report-only", "guidance", "Lambda has no VpcConfig", "Consider a VpcConfig for network isolation if the function needs VPC resources.", { category: "best-practice" }),
30
+ WAW023: auditRule("WAW023", "report-only", "guidance", "CloudFront has no WAF web ACL", "Consider attaching a WAF web ACL.", { category: "best-practice" }),
31
+ WAW024: auditRule("WAW024", "report-only", "guidance", "ALB access logging disabled", "Enable access logging for audit trails.", { category: "best-practice" }),
32
+ WAW025: auditRule("WAW025", "merge-worthy", "guidance", "SNS topic not encrypted", "Set KmsMasterKeyId for encryption at rest.", { authority: [AWS_SEC] }),
33
+ WAW026: auditRule("WAW026", "merge-worthy", "guidance", "SQS queue not encrypted", "Enable SqsManagedSseEnabled or set KmsMasterKeyId.", { authority: [AWS_SEC] }),
34
+ WAW027: auditRule("WAW027", "report-only", "guidance", "DynamoDB point-in-time recovery disabled", "Enable PITR for recovery.", { category: "best-practice" }),
35
+ WAW028: auditRule("WAW028", "merge-worthy", "guidance", "EBS volume not encrypted", "Enable encryption at rest.", { authority: [AWS_SEC] }),
36
+ WAW029: auditRule("WAW029", "merge-worthy", "guidance", "Invalid DependsOn target", "Fix the dangling/self DependsOn reference.", { category: "correctness" }),
37
+ WAW030: auditRule("WAW030", "report-only", "guidance", "Missing DependsOn for a known ordering pattern", "Add the DependsOn the pattern requires.", { category: "best-practice" }),
38
+ WAW031: auditRule("WAW031", "report-only", "guidance", "EKS Addon missing ServiceAccountRoleArn", "Set ServiceAccountRoleArn (IRSA) for addons that need it.", { category: "best-practice" }),
39
+ WAW032: auditRule("WAW032", "merge-worthy", "guidance", "EFS transit encryption disabled on Fargate", "Enable transit encryption for the EFS volume.", { authority: [AWS_SEC] }),
40
+ WAW033: auditRule("WAW033", "merge-worthy", "guidance", "Solr heap exceeds Fargate task memory", "Lower SOLR_HEAP or raise task memory.", { category: "correctness" }),
41
+ WAW034: auditRule("WAW034", "report-only", "guidance", "Fargate Solr task under-provisioned", "Allocate >= 2048MB for the Solr task.", { category: "best-practice" }),
42
+ WAW035: auditRule("WAW035", "report-only", "guidance", "Solr container missing nofile ulimit", "Set a nofile ulimit >= 65535.", { category: "best-practice" }),
43
+ WAW036: auditRule("WAW036", "merge-worthy", "guidance", "Non-ASCII characters in resource properties", "Remove non-ASCII characters rejected at changeset time.", { category: "correctness" }),
44
+ WAW037: auditRule("WAW037", "merge-worthy", "guidance", "Null values in resource properties", "Fix the invalid AttrRef producing null property values.", { category: "correctness" }),
45
+ };
@@ -46,7 +46,7 @@ describe("awsCompletions", () => {
46
46
  }
47
47
  });
48
48
 
49
- test("limits results to 50", () => {
49
+ test("caps the unfiltered `new ` dump", () => {
50
50
  const ctx = makeCtx({
51
51
  linePrefix: "const x = new ",
52
52
  wordAtCursor: "",
@@ -55,7 +55,9 @@ describe("awsCompletions", () => {
55
55
  });
56
56
 
57
57
  const items = awsCompletions(ctx);
58
- expect(items.length).toBeLessThanOrEqual(50);
58
+ // Bare `new ` (no prefix) returns a bounded sample; a typed prefix returns
59
+ // every match so common resources aren't truncated (#600).
60
+ expect(items.length).toBeLessThanOrEqual(100);
59
61
  });
60
62
 
61
63
  test("returns empty for non-matching context", () => {
@@ -0,0 +1,67 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import {
3
+ flociRunCommand,
4
+ flociRmCommand,
5
+ flociExistsCommand,
6
+ flociHealthUrl,
7
+ flociEnv,
8
+ isFlociReady,
9
+ } from "./floci";
10
+
11
+ describe("flociRunCommand (#704)", () => {
12
+ test("minimal — defaults name/port/image, no docker socket", () => {
13
+ expect(flociRunCommand({})).toBe(
14
+ "docker run -d --rm --name chant-floci -p 4566:4566 floci/floci:latest",
15
+ );
16
+ });
17
+
18
+ test("custom name, port, and image", () => {
19
+ const cmd = flociRunCommand({ name: "floci-e2e", port: 4599, image: "floci/floci:1.5.30" });
20
+ expect(cmd).toContain("--name floci-e2e");
21
+ expect(cmd).toContain("-p 4599:4566");
22
+ expect(cmd.endsWith("floci/floci:1.5.30")).toBe(true);
23
+ });
24
+
25
+ test("dockerSocket mounts the socket before the image (for the ECR backing registry)", () => {
26
+ const cmd = flociRunCommand({ dockerSocket: true });
27
+ expect(cmd).toContain("-v /var/run/docker.sock:/var/run/docker.sock");
28
+ // socket flag precedes the trailing image arg
29
+ expect(cmd.indexOf("/var/run/docker.sock")).toBeLessThan(cmd.indexOf("floci/floci"));
30
+ });
31
+ });
32
+
33
+ describe("flociRmCommand / flociExistsCommand (#704)", () => {
34
+ test("rm force-removes the container", () => {
35
+ expect(flociRmCommand("chant-floci")).toBe("docker rm -f chant-floci");
36
+ });
37
+
38
+ test("exists checks a running container by name", () => {
39
+ expect(flociExistsCommand("chant-floci")).toBe("docker ps -q -f name=chant-floci");
40
+ });
41
+ });
42
+
43
+ describe("flociHealthUrl / flociEnv (#704)", () => {
44
+ test("health url targets the localstack-compatible endpoint on the host port", () => {
45
+ expect(flociHealthUrl(4599)).toBe("http://localhost:4599/_localstack/health");
46
+ });
47
+
48
+ test("env points the aws CLI at the endpoint with test creds", () => {
49
+ expect(flociEnv(4566, "us-east-1")).toEqual({
50
+ AWS_ENDPOINT_URL: "http://localhost:4566",
51
+ AWS_ACCESS_KEY_ID: "test",
52
+ AWS_SECRET_ACCESS_KEY: "test",
53
+ AWS_REGION: "us-east-1",
54
+ });
55
+ });
56
+ });
57
+
58
+ describe("isFlociReady (#704)", () => {
59
+ test("true when the health body names the required service", () => {
60
+ const body = '{"services": {"s3": "available", "cloudformation": "available"}}';
61
+ expect(isFlociReady(body, "cloudformation")).toBe(true);
62
+ });
63
+
64
+ test("false before the service appears", () => {
65
+ expect(isFlociReady('{"services": {"s3": "available"}}', "cloudformation")).toBe(false);
66
+ });
67
+ });
@@ -0,0 +1,149 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { safeHeartbeat, sleep } from "@intentius/chant/op";
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ const DEFAULT_NAME = "chant-floci";
8
+ const DEFAULT_PORT = 4566;
9
+ const DEFAULT_IMAGE = "floci/floci:latest";
10
+ const DEFAULT_REGION = "us-east-1";
11
+ const DEFAULT_READY_SERVICE = "cloudformation";
12
+
13
+ export interface FlociUpArgs {
14
+ /** Container name. Default: `chant-floci`. */
15
+ name?: string;
16
+ /** Host port mapped to the emulator's `:4566`. Default: `4566`. */
17
+ port?: number;
18
+ /** Floci image. Default: `floci/floci:latest`. */
19
+ image?: string;
20
+ /** Mount the docker socket — Floci needs it for the ECR backing registry. Default: `false`. */
21
+ dockerSocket?: boolean;
22
+ /** `AWS_REGION` exported for subsequent steps. Default: `us-east-1`. */
23
+ region?: string;
24
+ /** Service to wait for in `/_localstack/health`. Default: `cloudformation`. */
25
+ readyService?: string;
26
+ /** Readiness timeout in ms. Default: `60000`. */
27
+ timeoutMs?: number;
28
+ /** Health poll interval in ms. Default: `2000`. */
29
+ intervalMs?: number;
30
+ }
31
+
32
+ export interface FlociDownArgs {
33
+ /** Container name to remove. Default: `chant-floci`. */
34
+ name?: string;
35
+ }
36
+
37
+ /** `docker ps -q -f name=<name>` — non-empty stdout means the container is running. */
38
+ export function flociExistsCommand(name: string): string {
39
+ return `docker ps -q -f name=${name}`;
40
+ }
41
+
42
+ /** Build the `docker run` command that boots Floci. */
43
+ export function flociRunCommand(args: FlociUpArgs): string {
44
+ const name = args.name ?? DEFAULT_NAME;
45
+ const port = args.port ?? DEFAULT_PORT;
46
+ const image = args.image ?? DEFAULT_IMAGE;
47
+ const parts = ["docker", "run", "-d", "--rm", "--name", name, "-p", `${port}:4566`];
48
+ if (args.dockerSocket) parts.push("-v", "/var/run/docker.sock:/var/run/docker.sock");
49
+ parts.push(image);
50
+ return parts.join(" ");
51
+ }
52
+
53
+ /** Build the `docker rm -f` command. */
54
+ export function flociRmCommand(name: string): string {
55
+ return `docker rm -f ${name}`;
56
+ }
57
+
58
+ /** The Floci health endpoint URL for a host port. */
59
+ export function flociHealthUrl(port: number): string {
60
+ return `http://localhost:${port}/_localstack/health`;
61
+ }
62
+
63
+ /** The AWS env vars that point the `aws` CLI / SDK at a local Floci endpoint. */
64
+ export function flociEnv(port: number, region: string): Record<string, string> {
65
+ return {
66
+ AWS_ENDPOINT_URL: `http://localhost:${port}`,
67
+ AWS_ACCESS_KEY_ID: "test",
68
+ AWS_SECRET_ACCESS_KEY: "test",
69
+ AWS_REGION: region,
70
+ };
71
+ }
72
+
73
+ /** True once the health body reports the required service (e.g. `"cloudformation"`). */
74
+ export function isFlociReady(healthBody: string, service: string): boolean {
75
+ return healthBody.includes(`"${service}"`);
76
+ }
77
+
78
+ /**
79
+ * Boot a local Floci AWS emulator in Docker and point subsequent steps at it.
80
+ *
81
+ * Idempotent: reuses a running container of the same name. Waits for the health
82
+ * endpoint to report `readyService`, then sets `AWS_ENDPOINT_URL` + test creds in
83
+ * the process environment so a following `nativeApply`/`cfn-deploy` targets the
84
+ * emulator. Env injection assumes the in-process **local executor**; under a
85
+ * distributed Temporal worker, pass the endpoint explicitly instead.
86
+ *
87
+ * Uses longInfra profile — 20m timeout, heartbeat every poll (the image may pull).
88
+ */
89
+ export async function flociUp(args: FlociUpArgs, signal?: AbortSignal): Promise<{ endpoint: string }> {
90
+ const name = args.name ?? DEFAULT_NAME;
91
+ const port = args.port ?? DEFAULT_PORT;
92
+ const region = args.region ?? DEFAULT_REGION;
93
+ const service = args.readyService ?? DEFAULT_READY_SERVICE;
94
+ const timeoutMs = args.timeoutMs ?? 60_000;
95
+ const intervalMs = args.intervalMs ?? 2_000;
96
+
97
+ let running = false;
98
+ try {
99
+ const { stdout } = await execAsync(flociExistsCommand(name), { signal });
100
+ running = Boolean(stdout.trim());
101
+ } catch {
102
+ // `docker ps` failed — assume not running and try to start it.
103
+ }
104
+
105
+ if (running) {
106
+ console.log(`Floci container "${name}" already running — reusing`);
107
+ } else {
108
+ await execAsync(flociRunCommand({ ...args, name, port }), { signal });
109
+ }
110
+
111
+ const url = flociHealthUrl(port);
112
+ const deadline = Date.now() + timeoutMs;
113
+ let ready = false;
114
+ while (Date.now() < deadline) {
115
+ if (signal?.aborted) throw new Error("flociUp aborted");
116
+ safeHeartbeat({ step: "flociUp", container: name });
117
+ try {
118
+ const { stdout } = await execAsync(`curl -fs ${url}`, { signal });
119
+ if (isFlociReady(stdout, service)) {
120
+ ready = true;
121
+ break;
122
+ }
123
+ } catch {
124
+ // Not up yet (connection refused / non-2xx) — retry.
125
+ }
126
+ await sleep(intervalMs, signal);
127
+ }
128
+ if (!ready) {
129
+ throw new Error(`Floci "${name}" did not become ready within ${timeoutMs}ms`);
130
+ }
131
+
132
+ const env = flociEnv(port, region);
133
+ Object.assign(process.env, env);
134
+ console.log(`Floci ready on ${env.AWS_ENDPOINT_URL} (service: ${service})`);
135
+ return { endpoint: env.AWS_ENDPOINT_URL };
136
+ }
137
+
138
+ /**
139
+ * Stop and remove the local Floci emulator container. A no-op success when the
140
+ * container is already gone. Uses fastIdempotent profile — 5m timeout.
141
+ */
142
+ export async function flociDown(args: FlociDownArgs, signal?: AbortSignal): Promise<void> {
143
+ const name = args.name ?? DEFAULT_NAME;
144
+ try {
145
+ await execAsync(flociRmCommand(name), { signal });
146
+ } catch {
147
+ // Already removed (`--rm` on exit, or never started) — treat as success.
148
+ }
149
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AWS Op activities — resolved by the core activity registry when a project's
3
+ * `chant.config.ts` lists the `aws` lexicon. Contributes the local Floci AWS
4
+ * emulator lifecycle (`flociUp`/`flociDown`).
5
+ */
6
+ export {
7
+ flociUp,
8
+ flociDown,
9
+ flociRunCommand,
10
+ flociRmCommand,
11
+ flociExistsCommand,
12
+ flociHealthUrl,
13
+ flociEnv,
14
+ isFlociReady,
15
+ } from "./floci";
16
+ export type { FlociUpArgs, FlociDownArgs } from "./floci";
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { ownershipEntries } from "@intentius/chant/ownership";
3
+ import { AWS_TAG_OWNERSHIP_KEYS } from "./ownership";
4
+
5
+ describe("AWS_TAG_OWNERSHIP_KEYS", () => {
6
+ test("uses AWS colon-form tag keys", () => {
7
+ expect(AWS_TAG_OWNERSHIP_KEYS).toEqual({
8
+ managedBy: "chant:managed-by",
9
+ stack: "chant:stack",
10
+ env: "chant:env",
11
+ });
12
+ });
13
+
14
+ test("stamps the colon keys via the core ownershipEntries helper", () => {
15
+ const e = ownershipEntries(AWS_TAG_OWNERSHIP_KEYS, { stack: "billing", env: "prod" });
16
+ expect(e).toEqual({ "chant:managed-by": "chant", "chant:stack": "billing", "chant:env": "prod" });
17
+ });
18
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The aws lexicon's ownership tag convention. AWS tag keys allow `:`, so
3
+ * chant's markers use the `chant:<name>` form (distinct from the label-based
4
+ * convention core keeps for k8s/gcp). Core owns the generic stamp/detect logic
5
+ * (@intentius/chant/ownership); this is the aws-specific key naming it stamps.
6
+ */
7
+
8
+ import type { ChannelKeys } from "@intentius/chant/ownership";
9
+
10
+ /** AWS tag keys for chant's ownership markers. */
11
+ export const AWS_TAG_OWNERSHIP_KEYS: ChannelKeys = {
12
+ managedBy: "chant:managed-by",
13
+ stack: "chant:stack",
14
+ env: "chant:env",
15
+ };
package/src/plugin.ts CHANGED
@@ -8,6 +8,7 @@ import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
8
8
  import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
9
9
  import { discoverLintRules } from "@intentius/chant/lint/discover";
10
10
  import { postSynthChecks as postSynthCheckList } from "./lint/post-synth";
11
+ import { awsAuditCatalog } from "./lint/audit-catalog";
11
12
  import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
12
13
  import { readFileSync } from "fs";
13
14
  import { join, dirname } from "path";
@@ -28,6 +29,8 @@ import { awsHover } from "./lsp/hover";
28
29
  export const awsPlugin: LexiconPlugin = {
29
30
  name: "aws",
30
31
  serializer: awsSerializer,
32
+ // Audit rule metadata for this lexicon's WAW* checks (#687).
33
+ auditCatalog: () => awsAuditCatalog,
31
34
 
32
35
  lintRules(): LintRule[] {
33
36
  const rulesDir = join(dirname(fileURLToPath(import.meta.url)), "lint", "rules");
package/src/serializer.ts CHANGED
@@ -2,6 +2,7 @@ import type { Declarable, CoreParameter } from "@intentius/chant/declarable";
2
2
  import { isPropertyDeclarable } from "@intentius/chant/declarable";
3
3
  import type { Serializer, SerializerResult, SerializeContext } from "@intentius/chant/serializer";
4
4
  import { ownershipEntries, type OwnershipMarker } from "@intentius/chant/ownership";
5
+ import { AWS_TAG_OWNERSHIP_KEYS } from "./ownership";
5
6
  import type { LexiconOutput } from "@intentius/chant/lexicon-output";
6
7
  import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
7
8
  import { isChildProject, type ChildProjectInstance } from "@intentius/chant/child-project";
@@ -155,7 +156,7 @@ function serializeToTemplate(
155
156
  // on key collisions.
156
157
  const defaultTagEntries: TagEntry[] = [];
157
158
  if (ownership) {
158
- for (const [Key, Value] of Object.entries(ownershipEntries("aws-tag", ownership))) {
159
+ for (const [Key, Value] of Object.entries(ownershipEntries(AWS_TAG_OWNERSHIP_KEYS, ownership))) {
159
160
  defaultTagEntries.push({ Key, Value });
160
161
  }
161
162
  }