@intentius/chant-lexicon-gcp 0.0.15

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 (122) hide show
  1. package/dist/integrity.json +36 -0
  2. package/dist/manifest.json +12 -0
  3. package/dist/meta.json +10919 -0
  4. package/dist/rules/gcp-helpers.ts +117 -0
  5. package/dist/rules/hardcoded-project.ts +58 -0
  6. package/dist/rules/hardcoded-region.ts +56 -0
  7. package/dist/rules/public-iam.ts +43 -0
  8. package/dist/rules/wgc101.ts +56 -0
  9. package/dist/rules/wgc102.ts +35 -0
  10. package/dist/rules/wgc103.ts +45 -0
  11. package/dist/rules/wgc104.ts +42 -0
  12. package/dist/rules/wgc105.ts +46 -0
  13. package/dist/rules/wgc106.ts +36 -0
  14. package/dist/rules/wgc107.ts +39 -0
  15. package/dist/rules/wgc108.ts +41 -0
  16. package/dist/rules/wgc109.ts +39 -0
  17. package/dist/rules/wgc110.ts +38 -0
  18. package/dist/rules/wgc111.ts +54 -0
  19. package/dist/rules/wgc112.ts +56 -0
  20. package/dist/rules/wgc113.ts +42 -0
  21. package/dist/rules/wgc201.ts +36 -0
  22. package/dist/rules/wgc202.ts +39 -0
  23. package/dist/rules/wgc203.ts +44 -0
  24. package/dist/rules/wgc204.ts +39 -0
  25. package/dist/rules/wgc301.ts +34 -0
  26. package/dist/rules/wgc302.ts +34 -0
  27. package/dist/rules/wgc303.ts +37 -0
  28. package/dist/skills/chant-gcp-patterns.md +367 -0
  29. package/dist/skills/chant-gcp-security.md +276 -0
  30. package/dist/skills/chant-gcp.md +108 -0
  31. package/dist/types/index.d.ts +26529 -0
  32. package/package.json +35 -0
  33. package/src/actions/index.ts +52 -0
  34. package/src/codegen/docs-cli.ts +7 -0
  35. package/src/codegen/docs.ts +820 -0
  36. package/src/codegen/generate-cli.ts +24 -0
  37. package/src/codegen/generate.ts +252 -0
  38. package/src/codegen/naming.test.ts +49 -0
  39. package/src/codegen/naming.ts +132 -0
  40. package/src/codegen/package.ts +66 -0
  41. package/src/composites/cloud-function.ts +117 -0
  42. package/src/composites/cloud-run-service.ts +124 -0
  43. package/src/composites/cloud-sql-instance.ts +126 -0
  44. package/src/composites/composites.test.ts +432 -0
  45. package/src/composites/gcs-bucket.ts +111 -0
  46. package/src/composites/gke-cluster.ts +125 -0
  47. package/src/composites/index.ts +20 -0
  48. package/src/composites/managed-certificate.ts +79 -0
  49. package/src/composites/private-service.ts +95 -0
  50. package/src/composites/pubsub-pipeline.ts +102 -0
  51. package/src/composites/secure-project.ts +128 -0
  52. package/src/composites/vpc-network.ts +165 -0
  53. package/src/coverage.test.ts +27 -0
  54. package/src/coverage.ts +51 -0
  55. package/src/default-labels.test.ts +111 -0
  56. package/src/default-labels.ts +93 -0
  57. package/src/generated/index.d.ts +26529 -0
  58. package/src/generated/index.ts +1723 -0
  59. package/src/generated/lexicon-gcp.json +10919 -0
  60. package/src/generated/runtime.ts +4 -0
  61. package/src/import/generator.test.ts +125 -0
  62. package/src/import/generator.ts +82 -0
  63. package/src/import/parser.test.ts +167 -0
  64. package/src/import/parser.ts +80 -0
  65. package/src/import/roundtrip.test.ts +66 -0
  66. package/src/index.ts +54 -0
  67. package/src/lint/post-synth/gcp-helpers.ts +117 -0
  68. package/src/lint/post-synth/index.ts +20 -0
  69. package/src/lint/post-synth/post-synth.test.ts +693 -0
  70. package/src/lint/post-synth/wgc101.ts +56 -0
  71. package/src/lint/post-synth/wgc102.ts +35 -0
  72. package/src/lint/post-synth/wgc103.ts +45 -0
  73. package/src/lint/post-synth/wgc104.ts +42 -0
  74. package/src/lint/post-synth/wgc105.ts +46 -0
  75. package/src/lint/post-synth/wgc106.ts +36 -0
  76. package/src/lint/post-synth/wgc107.ts +39 -0
  77. package/src/lint/post-synth/wgc108.ts +41 -0
  78. package/src/lint/post-synth/wgc109.ts +39 -0
  79. package/src/lint/post-synth/wgc110.ts +38 -0
  80. package/src/lint/post-synth/wgc111.ts +54 -0
  81. package/src/lint/post-synth/wgc112.ts +56 -0
  82. package/src/lint/post-synth/wgc113.ts +42 -0
  83. package/src/lint/post-synth/wgc201.ts +36 -0
  84. package/src/lint/post-synth/wgc202.ts +39 -0
  85. package/src/lint/post-synth/wgc203.ts +44 -0
  86. package/src/lint/post-synth/wgc204.ts +39 -0
  87. package/src/lint/post-synth/wgc301.ts +34 -0
  88. package/src/lint/post-synth/wgc302.ts +34 -0
  89. package/src/lint/post-synth/wgc303.ts +37 -0
  90. package/src/lint/rules/hardcoded-project.ts +58 -0
  91. package/src/lint/rules/hardcoded-region.ts +56 -0
  92. package/src/lint/rules/index.ts +3 -0
  93. package/src/lint/rules/public-iam.ts +43 -0
  94. package/src/lint/rules/rules.test.ts +63 -0
  95. package/src/lsp/completions.test.ts +67 -0
  96. package/src/lsp/completions.ts +17 -0
  97. package/src/lsp/hover.test.ts +66 -0
  98. package/src/lsp/hover.ts +54 -0
  99. package/src/package-cli.ts +24 -0
  100. package/src/plugin.test.ts +250 -0
  101. package/src/plugin.ts +405 -0
  102. package/src/pseudo.test.ts +40 -0
  103. package/src/pseudo.ts +19 -0
  104. package/src/serializer.test.ts +250 -0
  105. package/src/serializer.ts +232 -0
  106. package/src/skills/chant-gcp-patterns.md +367 -0
  107. package/src/skills/chant-gcp-security.md +276 -0
  108. package/src/skills/chant-gcp.md +108 -0
  109. package/src/spec/fetch.test.ts +16 -0
  110. package/src/spec/fetch.ts +121 -0
  111. package/src/spec/parse.test.ts +163 -0
  112. package/src/spec/parse.ts +432 -0
  113. package/src/testdata/compute-instance.yaml +93 -0
  114. package/src/testdata/iam-policy-member.yaml +66 -0
  115. package/src/testdata/manifests/compute-instance.yaml +18 -0
  116. package/src/testdata/manifests/full-app.yaml +34 -0
  117. package/src/testdata/manifests/storage-bucket.yaml +12 -0
  118. package/src/testdata/storage-bucket.yaml +100 -0
  119. package/src/validate-cli.ts +13 -0
  120. package/src/validate.test.ts +38 -0
  121. package/src/validate.ts +30 -0
  122. package/src/variables.ts +15 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * WGC110: KMS CryptoKey missing rotation period
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc110: PostSynthCheck = {
9
+ id: "WGC110",
10
+ description: "KMS CryptoKey without rotation period configured",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "KMSCryptoKey") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ if (!spec.rotationPeriod) {
25
+ diagnostics.push({
26
+ checkId: "WGC110",
27
+ severity: "warning",
28
+ message: `KMSCryptoKey "${getResourceName(manifest)}" has no rotation period configured — consider setting rotationPeriod for key hygiene`,
29
+ entity: getResourceName(manifest),
30
+ lexicon: "gcp",
31
+ });
32
+ }
33
+ }
34
+ }
35
+
36
+ return diagnostics;
37
+ },
38
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * WGC111: Dangling resource reference
3
+ *
4
+ * Checks that every `resourceRef.name` in a Config Connector resource's spec
5
+ * references a `metadata.name` that exists in the output.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName, findResourceRefs } from "./gcp-helpers";
10
+
11
+ export const wgc111: PostSynthCheck = {
12
+ id: "WGC111",
13
+ description: "Resource reference points to a name not defined in the output",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ const diagnostics: PostSynthDiagnostic[] = [];
17
+
18
+ for (const [_lexicon, output] of ctx.outputs) {
19
+ if (typeof output !== "string") continue;
20
+
21
+ const manifests = parseGcpManifests(output);
22
+
23
+ // Collect all defined resource names
24
+ const definedNames = new Set<string>();
25
+ for (const manifest of manifests) {
26
+ if (isConfigConnectorResource(manifest)) {
27
+ const name = manifest.metadata?.name;
28
+ if (name) definedNames.add(name);
29
+ }
30
+ }
31
+
32
+ // Check each resource's refs
33
+ for (const manifest of manifests) {
34
+ if (!isConfigConnectorResource(manifest)) continue;
35
+ const resourceName = getResourceName(manifest);
36
+ const refs = findResourceRefs(manifest.spec);
37
+
38
+ for (const refName of refs) {
39
+ if (!definedNames.has(refName)) {
40
+ diagnostics.push({
41
+ checkId: "WGC111",
42
+ severity: "warning",
43
+ message: `Resource "${resourceName}" references "${refName}" via resourceRef, but no resource with that metadata.name exists in the output`,
44
+ entity: resourceName,
45
+ lexicon: "gcp",
46
+ });
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ return diagnostics;
53
+ },
54
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * WGC112: Missing or invalid apiVersion
3
+ *
4
+ * Every Config Connector resource must have an apiVersion matching
5
+ * `<service>.cnrm.cloud.google.com/v<version>`.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseGcpManifests, getResourceName } from "./gcp-helpers";
10
+
11
+ const VALID_API_VERSION = /^[a-z]+\.cnrm\.cloud\.google\.com\/v\d+(?:alpha\d+|beta\d+)?$/;
12
+
13
+ export const wgc112: PostSynthCheck = {
14
+ id: "WGC112",
15
+ description: "Config Connector resource has missing or invalid apiVersion",
16
+
17
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
18
+ const diagnostics: PostSynthDiagnostic[] = [];
19
+
20
+ for (const [_lexicon, output] of ctx.outputs) {
21
+ if (typeof output !== "string") continue;
22
+
23
+ const manifests = parseGcpManifests(output);
24
+
25
+ for (const manifest of manifests) {
26
+ // Only check resources that look like Config Connector (have a kind)
27
+ if (!manifest.kind) continue;
28
+
29
+ const resourceName = getResourceName(manifest);
30
+
31
+ if (!manifest.apiVersion) {
32
+ diagnostics.push({
33
+ checkId: "WGC112",
34
+ severity: "error",
35
+ message: `Resource "${resourceName}" (kind: ${manifest.kind}) is missing apiVersion`,
36
+ entity: resourceName,
37
+ lexicon: "gcp",
38
+ });
39
+ continue;
40
+ }
41
+
42
+ if (!VALID_API_VERSION.test(manifest.apiVersion)) {
43
+ diagnostics.push({
44
+ checkId: "WGC112",
45
+ severity: "error",
46
+ message: `Resource "${resourceName}" has invalid apiVersion "${manifest.apiVersion}" — expected format: <service>.cnrm.cloud.google.com/v<version>`,
47
+ entity: resourceName,
48
+ lexicon: "gcp",
49
+ });
50
+ }
51
+ }
52
+ }
53
+
54
+ return diagnostics;
55
+ },
56
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * WGC113: Alpha API version warning
3
+ *
4
+ * Warns when a Config Connector resource uses an alpha API version
5
+ * (e.g. v1alpha1). Alpha APIs are unstable — prefer v1beta1 or v1.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName } from "./gcp-helpers";
10
+
11
+ export const wgc113: PostSynthCheck = {
12
+ id: "WGC113",
13
+ description: "Config Connector resource uses an alpha API version",
14
+
15
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ const diagnostics: PostSynthDiagnostic[] = [];
17
+
18
+ for (const [_lexicon, output] of ctx.outputs) {
19
+ if (typeof output !== "string") continue;
20
+
21
+ const manifests = parseGcpManifests(output);
22
+
23
+ for (const manifest of manifests) {
24
+ if (!isConfigConnectorResource(manifest)) continue;
25
+
26
+ const apiVersion = manifest.apiVersion!;
27
+ if (/alpha\d+/.test(apiVersion)) {
28
+ const resourceName = getResourceName(manifest);
29
+ diagnostics.push({
30
+ checkId: "WGC113",
31
+ severity: "warning",
32
+ message: `Resource "${resourceName}" uses alpha API version "${apiVersion}" — alpha APIs are unstable, prefer v1beta1 or v1`,
33
+ entity: resourceName,
34
+ lexicon: "gcp",
35
+ });
36
+ }
37
+ }
38
+ }
39
+
40
+ return diagnostics;
41
+ },
42
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * WGC201: Missing managed-by label
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc201: PostSynthCheck = {
9
+ id: "WGC201",
10
+ description: "Config Connector resource without app.kubernetes.io/managed-by label",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (!isConfigConnectorResource(manifest)) continue;
20
+
21
+ const labels = manifest.metadata?.labels;
22
+ if (!labels?.["app.kubernetes.io/managed-by"]) {
23
+ diagnostics.push({
24
+ checkId: "WGC201",
25
+ severity: "info",
26
+ message: `Resource "${getResourceName(manifest)}" has no app.kubernetes.io/managed-by label — consider using defaultLabels() for consistent labeling`,
27
+ entity: getResourceName(manifest),
28
+ lexicon: "gcp",
29
+ });
30
+ }
31
+ }
32
+ }
33
+
34
+ return diagnostics;
35
+ },
36
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * WGC202: GKE cluster without workload identity
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc202: PostSynthCheck = {
9
+ id: "WGC202",
10
+ description: "ContainerCluster without workload identity configuration",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "ContainerCluster") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ const workloadIdentity = spec.workloadIdentityConfig as Record<string, unknown> | undefined;
25
+ if (!workloadIdentity?.workloadPool) {
26
+ diagnostics.push({
27
+ checkId: "WGC202",
28
+ severity: "warning",
29
+ message: `ContainerCluster "${getResourceName(manifest)}" does not have workload identity configured — recommended for secure pod-to-GCP authentication`,
30
+ entity: getResourceName(manifest),
31
+ lexicon: "gcp",
32
+ });
33
+ }
34
+ }
35
+ }
36
+
37
+ return diagnostics;
38
+ },
39
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * WGC203: GKE node pool with cloud-platform OAuth scope
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc203: PostSynthCheck = {
9
+ id: "WGC203",
10
+ description: "ContainerNodePool using overly broad cloud-platform OAuth scope",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "ContainerNodePool") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ const nodeConfig = spec.nodeConfig as Record<string, unknown> | undefined;
25
+ const oauthScopes = nodeConfig?.oauthScopes as string[] | undefined;
26
+
27
+ if (Array.isArray(oauthScopes) && oauthScopes.some(s =>
28
+ (typeof s === "string" && s.includes("cloud-platform")) ||
29
+ (typeof s === "object" && s !== null && JSON.stringify(s).includes("cloud-platform")),
30
+ )) {
31
+ diagnostics.push({
32
+ checkId: "WGC203",
33
+ severity: "warning",
34
+ message: `ContainerNodePool "${getResourceName(manifest)}" uses cloud-platform OAuth scope — this grants overly broad access; prefer workload identity with fine-grained IAM`,
35
+ entity: getResourceName(manifest),
36
+ lexicon: "gcp",
37
+ });
38
+ }
39
+ }
40
+ }
41
+
42
+ return diagnostics;
43
+ },
44
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * WGC204: ComputeInstance without shielded VM config
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc204: PostSynthCheck = {
9
+ id: "WGC204",
10
+ description: "ComputeInstance without shielded VM configuration",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "ComputeInstance") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ const shieldedConfig = spec.shieldedInstanceConfig as Record<string, unknown> | undefined;
25
+ if (!shieldedConfig) {
26
+ diagnostics.push({
27
+ checkId: "WGC204",
28
+ severity: "info",
29
+ message: `ComputeInstance "${getResourceName(manifest)}" has no shielded VM configuration — consider enabling secureboot, vtpm, and integrity monitoring`,
30
+ entity: getResourceName(manifest),
31
+ lexicon: "gcp",
32
+ });
33
+ }
34
+ }
35
+ }
36
+
37
+ return diagnostics;
38
+ },
39
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * WGC301: No audit logging config (IAMAuditConfig) in output
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests } from "./gcp-helpers";
7
+
8
+ export const wgc301: PostSynthCheck = {
9
+ id: "WGC301",
10
+ description: "No IAMAuditConfig resource found in output — audit logging may not be configured",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ const manifests = parseGcpManifests(output);
19
+ if (manifests.length === 0) continue;
20
+
21
+ const hasAuditConfig = manifests.some(m => m.kind === "IAMAuditConfig");
22
+ if (!hasAuditConfig) {
23
+ diagnostics.push({
24
+ checkId: "WGC301",
25
+ severity: "info",
26
+ message: "No IAMAuditConfig resource found — consider adding audit logging for compliance",
27
+ lexicon: "gcp",
28
+ });
29
+ }
30
+ }
31
+
32
+ return diagnostics;
33
+ },
34
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * WGC302: Service API not explicitly enabled
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests } from "./gcp-helpers";
7
+
8
+ export const wgc302: PostSynthCheck = {
9
+ id: "WGC302",
10
+ description: "No Service resource found — GCP APIs may not be explicitly enabled",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ const manifests = parseGcpManifests(output);
19
+ if (manifests.length === 0) continue;
20
+
21
+ const hasService = manifests.some(m => m.kind === "Service" && m.apiVersion?.includes("serviceusage.cnrm"));
22
+ if (!hasService) {
23
+ diagnostics.push({
24
+ checkId: "WGC302",
25
+ severity: "info",
26
+ message: "No Service (serviceusage) resource found — consider explicitly enabling required GCP APIs",
27
+ lexicon: "gcp",
28
+ });
29
+ }
30
+ }
31
+
32
+ return diagnostics;
33
+ },
34
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * WGC303: Missing VPC Service Controls perimeter
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests } from "./gcp-helpers";
7
+
8
+ export const wgc303: PostSynthCheck = {
9
+ id: "WGC303",
10
+ description: "No AccessContextManager ServicePerimeter found — VPC Service Controls not configured",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ const manifests = parseGcpManifests(output);
19
+ if (manifests.length === 0) continue;
20
+
21
+ const hasPerimeter = manifests.some(m =>
22
+ m.kind === "AccessContextManagerServicePerimeter" ||
23
+ (m.apiVersion?.includes("accesscontextmanager") && m.kind?.includes("ServicePerimeter")),
24
+ );
25
+ if (!hasPerimeter) {
26
+ diagnostics.push({
27
+ checkId: "WGC303",
28
+ severity: "info",
29
+ message: "No VPC Service Controls perimeter found — consider adding for data exfiltration protection",
30
+ lexicon: "gcp",
31
+ });
32
+ }
33
+ }
34
+
35
+ return diagnostics;
36
+ },
37
+ };