@intentius/chant-lexicon-gcp 0.0.18 → 0.0.24

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 (68) hide show
  1. package/dist/integrity.json +12 -8
  2. package/dist/manifest.json +1 -1
  3. package/dist/meta.json +18141 -0
  4. package/dist/rules/schema-registry.ts +91 -0
  5. package/dist/rules/wgc101.ts +1 -1
  6. package/dist/rules/wgc401.ts +59 -0
  7. package/dist/rules/wgc402.ts +54 -0
  8. package/dist/rules/wgc403.ts +84 -0
  9. package/dist/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
  10. package/dist/skills/chant-gcp-patterns.md +3 -2
  11. package/dist/skills/chant-gcp-security.md +3 -2
  12. package/dist/skills/chant-gcp.md +363 -28
  13. package/package.json +20 -2
  14. package/src/codegen/docs.test.ts +16 -0
  15. package/src/codegen/generate.test.ts +18 -0
  16. package/src/codegen/generate.ts +11 -0
  17. package/src/codegen/package.test.ts +16 -0
  18. package/src/composites/cloud-function.ts +23 -15
  19. package/src/composites/cloud-run-service.ts +20 -13
  20. package/src/composites/cloud-sql-instance.ts +18 -14
  21. package/src/composites/composites.test.ts +94 -62
  22. package/src/composites/gcs-bucket.ts +13 -9
  23. package/src/composites/gke-cluster.ts +91 -16
  24. package/src/composites/index.ts +11 -11
  25. package/src/composites/managed-certificate.ts +19 -15
  26. package/src/composites/private-service.ts +23 -15
  27. package/src/composites/pubsub-pipeline.ts +30 -18
  28. package/src/composites/secure-project.ts +42 -27
  29. package/src/composites/vpc-network.ts +42 -35
  30. package/src/generated/lexicon-gcp.json +18141 -0
  31. package/src/import/import-fixtures.test.ts +98 -0
  32. package/src/index.ts +11 -11
  33. package/src/lint/post-synth/gcp-helpers.test.ts +166 -0
  34. package/src/lint/post-synth/post-synth.test.ts +132 -1
  35. package/src/lint/post-synth/schema-registry.ts +91 -0
  36. package/src/lint/post-synth/wgc101.test.ts +40 -0
  37. package/src/lint/post-synth/wgc101.ts +1 -1
  38. package/src/lint/post-synth/wgc102.test.ts +38 -0
  39. package/src/lint/post-synth/wgc103.test.ts +38 -0
  40. package/src/lint/post-synth/wgc104.test.ts +37 -0
  41. package/src/lint/post-synth/wgc105.test.ts +46 -0
  42. package/src/lint/post-synth/wgc106.test.ts +38 -0
  43. package/src/lint/post-synth/wgc107.test.ts +38 -0
  44. package/src/lint/post-synth/wgc108.test.ts +42 -0
  45. package/src/lint/post-synth/wgc109.test.ts +46 -0
  46. package/src/lint/post-synth/wgc110.test.ts +37 -0
  47. package/src/lint/post-synth/wgc111.test.ts +46 -0
  48. package/src/lint/post-synth/wgc112.test.ts +48 -0
  49. package/src/lint/post-synth/wgc113.test.ts +36 -0
  50. package/src/lint/post-synth/wgc201.test.ts +38 -0
  51. package/src/lint/post-synth/wgc202.test.ts +38 -0
  52. package/src/lint/post-synth/wgc203.test.ts +45 -0
  53. package/src/lint/post-synth/wgc204.test.ts +42 -0
  54. package/src/lint/post-synth/wgc301.test.ts +39 -0
  55. package/src/lint/post-synth/wgc302.test.ts +36 -0
  56. package/src/lint/post-synth/wgc303.test.ts +37 -0
  57. package/src/lint/post-synth/wgc401.test.ts +46 -0
  58. package/src/lint/post-synth/wgc401.ts +59 -0
  59. package/src/lint/post-synth/wgc402.test.ts +40 -0
  60. package/src/lint/post-synth/wgc402.ts +54 -0
  61. package/src/lint/post-synth/wgc403.test.ts +59 -0
  62. package/src/lint/post-synth/wgc403.ts +84 -0
  63. package/src/plugin.test.ts +4 -1
  64. package/src/plugin.ts +258 -177
  65. package/src/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
  66. package/src/skills/chant-gcp-patterns.md +3 -2
  67. package/src/skills/chant-gcp-security.md +3 -2
  68. package/src/skills/chant-gcp.md +363 -28
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Schema registry for GCP Config Connector resources.
3
+ *
4
+ * Loads lexicon-gcp.json and builds a lookup from CRD kind to field schema,
5
+ * used by post-synth rules to validate YAML against known CRD schemas.
6
+ */
7
+
8
+ import { createRequire } from "module";
9
+ const require = createRequire(import.meta.url);
10
+
11
+ export interface FieldSchema {
12
+ type: string;
13
+ required: boolean;
14
+ enum?: string[];
15
+ ref?: boolean;
16
+ }
17
+
18
+ export interface ResourceSchema {
19
+ fields: Record<string, FieldSchema>;
20
+ required: string[];
21
+ }
22
+
23
+ interface LexiconEntry {
24
+ kind: "resource" | "property";
25
+ gvkKind?: string;
26
+ schema?: ResourceSchema;
27
+ }
28
+
29
+ let cachedRegistry: Map<string, ResourceSchema> | null = null;
30
+
31
+ /**
32
+ * Get the schema registry: Map<gvkKind, ResourceSchema>.
33
+ */
34
+ export function getSchemaRegistry(): Map<string, ResourceSchema> {
35
+ if (cachedRegistry) return cachedRegistry;
36
+
37
+ cachedRegistry = new Map();
38
+ try {
39
+ const lexicon = require("../../generated/lexicon-gcp.json") as Record<string, LexiconEntry>;
40
+ for (const entry of Object.values(lexicon)) {
41
+ if (entry.kind === "resource" && entry.gvkKind && entry.schema) {
42
+ cachedRegistry.set(entry.gvkKind, entry.schema);
43
+ }
44
+ }
45
+ } catch {
46
+ // Lexicon JSON not yet generated — return empty registry
47
+ }
48
+
49
+ return cachedRegistry;
50
+ }
51
+
52
+ /**
53
+ * Levenshtein distance between two strings.
54
+ */
55
+ export function levenshtein(a: string, b: string): number {
56
+ const m = a.length;
57
+ const n = b.length;
58
+ const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
59
+
60
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
61
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
62
+
63
+ for (let i = 1; i <= m; i++) {
64
+ for (let j = 1; j <= n; j++) {
65
+ dp[i][j] = a[i - 1] === b[j - 1]
66
+ ? dp[i - 1][j - 1]
67
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
68
+ }
69
+ }
70
+
71
+ return dp[m][n];
72
+ }
73
+
74
+ /**
75
+ * Find the closest field name by Levenshtein distance.
76
+ * Returns the suggestion if distance ≤ 3, otherwise undefined.
77
+ */
78
+ export function suggestField(unknown: string, knownFields: string[]): string | undefined {
79
+ let best: string | undefined;
80
+ let bestDist = 4; // threshold
81
+
82
+ for (const field of knownFields) {
83
+ const dist = levenshtein(unknown.toLowerCase(), field.toLowerCase());
84
+ if (dist < bestDist) {
85
+ bestDist = dist;
86
+ best = field;
87
+ }
88
+ }
89
+
90
+ return best;
91
+ }
@@ -30,7 +30,7 @@ export const wgc101: PostSynthCheck = {
30
30
  diagnostics.push({
31
31
  checkId: "WGC101",
32
32
  severity: "warning",
33
- message: `StorageBucket "${name}" has no encryption configuration — consider adding spec.encryption.defaultKmsKeyName`,
33
+ message: `StorageBucket "${name}" has no encryption configuration — consider adding spec.encryption.kmsKeyRef`,
34
34
  entity: name,
35
35
  lexicon: "gcp",
36
36
  });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WGC401: Unknown spec field
3
+ *
4
+ * Flags fields in a Config Connector resource spec that are not defined
5
+ * in the CRD schema. Includes "did you mean?" suggestion via Levenshtein.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
10
+ import { getSchemaRegistry, suggestField } from "./schema-registry";
11
+
12
+ export const wgc401: PostSynthCheck = {
13
+ id: "WGC401",
14
+ description: "Config Connector resource spec contains unknown field",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ const diagnostics: PostSynthDiagnostic[] = [];
18
+ const registry = getSchemaRegistry();
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
+ if (!isConfigConnectorResource(manifest)) continue;
27
+
28
+ const kind = manifest.kind;
29
+ if (!kind) continue;
30
+
31
+ const schema = registry.get(kind);
32
+ if (!schema) continue; // No schema available for this kind
33
+
34
+ const spec = getSpec(manifest);
35
+ if (!spec) continue;
36
+
37
+ const knownFields = Object.keys(schema.fields);
38
+ const resourceName = getResourceName(manifest);
39
+
40
+ for (const field of Object.keys(spec)) {
41
+ if (field in schema.fields) continue;
42
+
43
+ const suggestion = suggestField(field, knownFields);
44
+ const hint = suggestion ? ` — did you mean "${suggestion}"?` : "";
45
+
46
+ diagnostics.push({
47
+ checkId: "WGC401",
48
+ severity: "error",
49
+ message: `Resource "${resourceName}" (${kind}): unknown spec field "${field}"${hint}`,
50
+ entity: resourceName,
51
+ lexicon: "gcp",
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ return diagnostics;
58
+ },
59
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * WGC402: Missing required spec field
3
+ *
4
+ * Flags Config Connector resources that are missing required fields
5
+ * according to their CRD schema.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
10
+ import { getSchemaRegistry } from "./schema-registry";
11
+
12
+ export const wgc402: PostSynthCheck = {
13
+ id: "WGC402",
14
+ description: "Config Connector resource is missing a required spec field",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ const diagnostics: PostSynthDiagnostic[] = [];
18
+ const registry = getSchemaRegistry();
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
+ if (!isConfigConnectorResource(manifest)) continue;
27
+
28
+ const kind = manifest.kind;
29
+ if (!kind) continue;
30
+
31
+ const schema = registry.get(kind);
32
+ if (!schema) continue;
33
+
34
+ const spec = getSpec(manifest);
35
+ const specKeys = new Set(spec ? Object.keys(spec) : []);
36
+ const resourceName = getResourceName(manifest);
37
+
38
+ for (const requiredField of schema.required) {
39
+ if (!specKeys.has(requiredField)) {
40
+ diagnostics.push({
41
+ checkId: "WGC402",
42
+ severity: "error",
43
+ message: `Resource "${resourceName}" (${kind}): missing required field "${requiredField}"`,
44
+ entity: resourceName,
45
+ lexicon: "gcp",
46
+ });
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ return diagnostics;
53
+ },
54
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * WGC403: Type/structure mismatch in spec field
3
+ *
4
+ * Flags spec fields where the value type doesn't match the CRD schema:
5
+ * - String where number expected (e.g. availableMemoryMb: "512")
6
+ * - Scalar string where resourceRef object expected (e.g. topicRef: "name")
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
11
+ import { getSchemaRegistry } from "./schema-registry";
12
+
13
+ export const wgc403: PostSynthCheck = {
14
+ id: "WGC403",
15
+ description: "Config Connector resource spec field has wrong type or structure",
16
+
17
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
18
+ const diagnostics: PostSynthDiagnostic[] = [];
19
+ const registry = getSchemaRegistry();
20
+
21
+ for (const [_lexicon, output] of ctx.outputs) {
22
+ if (typeof output !== "string") continue;
23
+
24
+ const manifests = parseGcpManifests(output);
25
+
26
+ for (const manifest of manifests) {
27
+ if (!isConfigConnectorResource(manifest)) continue;
28
+
29
+ const kind = manifest.kind;
30
+ if (!kind) continue;
31
+
32
+ const schema = registry.get(kind);
33
+ if (!schema) continue;
34
+
35
+ const spec = getSpec(manifest);
36
+ if (!spec) continue;
37
+
38
+ const resourceName = getResourceName(manifest);
39
+
40
+ for (const [field, value] of Object.entries(spec)) {
41
+ const fieldSchema = schema.fields[field];
42
+ if (!fieldSchema) continue; // Unknown fields handled by WGC401
43
+
44
+ // Check: resourceRef field expects an object, got a scalar
45
+ if (fieldSchema.ref && typeof value === "string") {
46
+ diagnostics.push({
47
+ checkId: "WGC403",
48
+ severity: "error",
49
+ message: `Resource "${resourceName}" (${kind}): field "${field}" expects a resourceRef object (e.g. { name, kind }), got string "${value}"`,
50
+ entity: resourceName,
51
+ lexicon: "gcp",
52
+ });
53
+ continue;
54
+ }
55
+
56
+ // Check: number field got a string that looks numeric
57
+ if (fieldSchema.type === "number" && typeof value === "string") {
58
+ diagnostics.push({
59
+ checkId: "WGC403",
60
+ severity: "error",
61
+ message: `Resource "${resourceName}" (${kind}): field "${field}" expects a number, got string "${value}"`,
62
+ entity: resourceName,
63
+ lexicon: "gcp",
64
+ });
65
+ continue;
66
+ }
67
+
68
+ // Check: boolean field got a string
69
+ if (fieldSchema.type === "boolean" && typeof value === "string") {
70
+ diagnostics.push({
71
+ checkId: "WGC403",
72
+ severity: "error",
73
+ message: `Resource "${resourceName}" (${kind}): field "${field}" expects a boolean, got string "${value}"`,
74
+ entity: resourceName,
75
+ lexicon: "gcp",
76
+ });
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ return diagnostics;
83
+ },
84
+ };
@@ -1,5 +1,5 @@
1
1
  ---
2
- skill: chant-gke
2
+ skill: chant-gcp-gke
3
3
  description: End-to-end GKE workflow bridging GCP infrastructure and Kubernetes workloads
4
4
  user-invocable: true
5
5
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
- source: chant-lexicon
3
- lexicon: gcp
2
+ skill: chant-gcp-patterns
3
+ description: Advanced GCP Config Connector patterns
4
+ user-invocable: true
4
5
  ---
5
6
 
6
7
  # Advanced GCP Config Connector Patterns with Chant
@@ -1,6 +1,7 @@
1
1
  ---
2
- source: chant-lexicon
3
- lexicon: gcp
2
+ skill: chant-gcp-security
3
+ description: GCP security best practices for infrastructure
4
+ user-invocable: true
4
5
  ---
5
6
 
6
7
  # GCP Security Best Practices for Chant