@intentius/chant-lexicon-gcp 0.0.22 → 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.
@@ -2,6 +2,9 @@
2
2
  * VpcNetwork composite — ComputeNetwork + ComputeSubnetwork + ComputeFirewall + ComputeRouterNAT.
3
3
  */
4
4
 
5
+ import { Composite, mergeDefaults } from "@intentius/chant";
6
+ import { VPCNetwork, Subnetwork, Firewall, Router, RouterNAT } from "../generated";
7
+
5
8
  export interface VpcSubnet {
6
9
  /** Subnet name suffix. */
7
10
  name: string;
@@ -32,14 +35,12 @@ export interface VpcNetworkProps {
32
35
  labels?: Record<string, string>;
33
36
  /** Namespace for all resources. */
34
37
  namespace?: string;
35
- }
36
-
37
- export interface VpcNetworkResult {
38
- network: Record<string, unknown>;
39
- subnets: Record<string, unknown>[];
40
- firewalls: Record<string, unknown>[];
41
- router?: Record<string, unknown>;
42
- routerNat?: Record<string, unknown>;
38
+ /** Per-member defaults for customizing individual resources. */
39
+ defaults?: {
40
+ network?: Partial<ConstructorParameters<typeof VPCNetwork>[0]>;
41
+ router?: Partial<ConstructorParameters<typeof Router>[0]>;
42
+ routerNat?: Partial<ConstructorParameters<typeof RouterNAT>[0]>;
43
+ };
43
44
  }
44
45
 
45
46
  /**
@@ -60,7 +61,7 @@ export interface VpcNetworkResult {
60
61
  * });
61
62
  * ```
62
63
  */
63
- export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
64
+ export const VpcNetwork = Composite<VpcNetworkProps>((props) => {
64
65
  const {
65
66
  name,
66
67
  autoCreateSubnetworks = false,
@@ -71,6 +72,7 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
71
72
  allowIapSsh = false,
72
73
  labels: extraLabels = {},
73
74
  namespace,
75
+ defaults: defs,
74
76
  } = props;
75
77
 
76
78
  const commonLabels: Record<string, string> = {
@@ -79,7 +81,7 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
79
81
  ...extraLabels,
80
82
  };
81
83
 
82
- const network: Record<string, unknown> = {
84
+ const network = new VPCNetwork(mergeDefaults({
83
85
  metadata: {
84
86
  name,
85
87
  ...(namespace && { namespace }),
@@ -87,58 +89,63 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
87
89
  },
88
90
  autoCreateSubnetworks,
89
91
  routingMode: "REGIONAL",
90
- };
92
+ } as Record<string, unknown>, defs?.network));
91
93
 
92
- const subnets: Record<string, unknown>[] = subnetDefs.map((sub) => ({
93
- metadata: {
94
- name: `${name}-${sub.name}`,
95
- ...(namespace && { namespace }),
96
- labels: { ...commonLabels, "app.kubernetes.io/component": "subnet" },
97
- },
98
- networkRef: { name },
99
- ipCidrRange: sub.ipCidrRange,
100
- region: sub.region,
101
- privateIpGoogleAccess: sub.privateIpGoogleAccess ?? true,
102
- }));
94
+ // Spread subnets into named members (subnet_<name>) for Composite validation.
95
+ const subnetEntries: Record<string, any> = {};
96
+ for (const sub of subnetDefs) {
97
+ subnetEntries[`subnet_${sub.name}`] = new Subnetwork({
98
+ metadata: {
99
+ name: `${name}-${sub.name}`,
100
+ ...(namespace && { namespace }),
101
+ labels: { ...commonLabels, "app.kubernetes.io/component": "subnet" },
102
+ },
103
+ networkRef: { name },
104
+ ipCidrRange: sub.ipCidrRange,
105
+ region: sub.region,
106
+ privateIpGoogleAccess: sub.privateIpGoogleAccess ?? true,
107
+ } as Record<string, unknown>);
108
+ }
103
109
 
104
- const firewalls: Record<string, unknown>[] = [];
110
+ // Spread firewalls into named members for Composite validation.
111
+ const firewallEntries: Record<string, any> = {};
105
112
 
106
113
  if (allowInternalTraffic) {
107
- firewalls.push({
114
+ firewallEntries.firewallAllowInternal = new Firewall({
108
115
  metadata: {
109
116
  name: `${name}-allow-internal`,
110
117
  ...(namespace && { namespace }),
111
118
  labels: { ...commonLabels, "app.kubernetes.io/component": "firewall" },
112
119
  },
113
120
  networkRef: { name },
114
- allowed: [
121
+ allow: [
115
122
  { protocol: "tcp", ports: ["0-65535"] },
116
123
  { protocol: "udp", ports: ["0-65535"] },
117
124
  { protocol: "icmp" },
118
125
  ],
119
126
  sourceRanges: subnetDefs.map((s) => s.ipCidrRange),
120
- });
127
+ } as Record<string, unknown>);
121
128
  }
122
129
 
123
130
  if (allowIapSsh) {
124
- firewalls.push({
131
+ firewallEntries.firewallAllowIapSsh = new Firewall({
125
132
  metadata: {
126
133
  name: `${name}-allow-iap-ssh`,
127
134
  ...(namespace && { namespace }),
128
135
  labels: { ...commonLabels, "app.kubernetes.io/component": "firewall" },
129
136
  },
130
137
  networkRef: { name },
131
- allowed: [{ protocol: "tcp", ports: ["22"] }],
138
+ allow: [{ protocol: "tcp", ports: ["22"] }],
132
139
  sourceRanges: ["35.235.240.0/20"], // IAP IP range
133
- });
140
+ } as Record<string, unknown>);
134
141
  }
135
142
 
136
- const result: VpcNetworkResult = { network, subnets, firewalls };
143
+ const result: Record<string, any> = { network, ...subnetEntries, ...firewallEntries };
137
144
 
138
145
  if (enableNat && natRegion) {
139
146
  const routerName = `${name}-router`;
140
147
 
141
- result.router = {
148
+ result.router = new Router(mergeDefaults({
142
149
  metadata: {
143
150
  name: routerName,
144
151
  ...(namespace && { namespace }),
@@ -146,9 +153,9 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
146
153
  },
147
154
  networkRef: { name },
148
155
  region: natRegion,
149
- };
156
+ } as Record<string, unknown>, defs?.router));
150
157
 
151
- result.routerNat = {
158
+ result.routerNat = new RouterNAT(mergeDefaults({
152
159
  metadata: {
153
160
  name: `${name}-nat`,
154
161
  ...(namespace && { namespace }),
@@ -158,8 +165,8 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
158
165
  region: natRegion,
159
166
  natIpAllocateOption: "AUTO_ONLY",
160
167
  sourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_IP_RANGES",
161
- };
168
+ } as Record<string, unknown>, defs?.routerNat));
162
169
  }
163
170
 
164
171
  return result;
165
- }
172
+ }, "VpcNetwork");
package/src/index.ts CHANGED
@@ -20,20 +20,20 @@ export * from "./generated/index";
20
20
 
21
21
  // Composites
22
22
  export {
23
- GkeCluster, CloudRunService, CloudSqlInstance, GcsBucket, VpcNetwork,
23
+ GkeCluster, CloudRunServiceComposite, CloudSqlInstance, GcsBucket, VpcNetwork,
24
24
  PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject,
25
25
  } from "./composites/index";
26
26
  export type {
27
- GkeClusterProps, GkeClusterResult,
28
- CloudRunServiceProps, CloudRunServiceResult,
29
- CloudSqlInstanceProps, CloudSqlInstanceResult,
30
- GcsBucketProps, GcsBucketResult,
31
- VpcNetworkProps, VpcNetworkResult, VpcSubnet,
32
- PubSubPipelineProps, PubSubPipelineResult,
33
- CloudFunctionWithTriggerProps, CloudFunctionWithTriggerResult,
34
- PrivateServiceProps, PrivateServiceResult,
35
- ManagedCertificateProps, ManagedCertificateResult,
36
- SecureProjectProps, SecureProjectResult,
27
+ GkeClusterProps,
28
+ CloudRunServiceProps,
29
+ CloudSqlInstanceProps,
30
+ GcsBucketProps,
31
+ VpcNetworkProps, VpcSubnet,
32
+ PubSubPipelineProps,
33
+ CloudFunctionWithTriggerProps,
34
+ PrivateServiceProps,
35
+ ManagedCertificateProps,
36
+ SecureProjectProps,
37
37
  } from "./composites/index";
38
38
 
39
39
  // IAM role constants
@@ -54,7 +54,8 @@ metadata:
54
54
  spec:
55
55
  location: US
56
56
  encryption:
57
- defaultKmsKeyName: projects/p/locations/l/keyRings/kr/cryptoKeys/k
57
+ kmsKeyRef:
58
+ external: projects/p/locations/l/keyRings/kr/cryptoKeys/k
58
59
  `;
59
60
  const diags = wgc101.check(makeCtx(yaml));
60
61
  const bucketDiags = diags.filter(d => d.entity === "my-bucket");
@@ -30,7 +30,8 @@ metadata:
30
30
  spec:
31
31
  location: US
32
32
  encryption:
33
- defaultKmsKeyName: projects/p/locations/l/keyRings/kr/cryptoKeys/k
33
+ kmsKeyRef:
34
+ external: projects/p/locations/l/keyRings/kr/cryptoKeys/k
34
35
  `;
35
36
  const diags = wgc101.check(makeCtx(yaml));
36
37
  const bucketDiags = diags.filter(d => d.entity === "my-bucket");
@@ -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
  });
package/src/plugin.ts CHANGED
@@ -6,14 +6,16 @@
6
6
  */
7
7
 
8
8
  import { createRequire } from "module";
9
- import type { LexiconPlugin, SkillDefinition, InitTemplateSet, ResourceMetadata } from "@intentius/chant/lexicon";
9
+ import type { LexiconPlugin, InitTemplateSet, ResourceMetadata } from "@intentius/chant/lexicon";
10
10
  const require = createRequire(import.meta.url);
11
11
  import type { LintRule } from "@intentius/chant/lint/rule";
12
- import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
13
12
  import type { TemplateParser } from "@intentius/chant/import/parser";
14
13
  import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
15
14
  import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
16
- import type { McpToolContribution, McpResourceContribution } from "@intentius/chant/mcp/types";
15
+ import { discoverPostSynthChecks } from "@intentius/chant/lint/discover";
16
+ import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
17
+ import { join, dirname } from "path";
18
+ import { fileURLToPath } from "url";
17
19
  import { gcpSerializer } from "./serializer";
18
20
 
19
21
  export const gcpPlugin: LexiconPlugin = {
@@ -27,37 +29,9 @@ export const gcpPlugin: LexiconPlugin = {
27
29
  return [hardcodedProjectRule, hardcodedRegionRule, publicIamRule];
28
30
  },
29
31
 
30
- postSynthChecks(): PostSynthCheck[] {
31
- const { wgc101 } = require("./lint/post-synth/wgc101");
32
- const { wgc102 } = require("./lint/post-synth/wgc102");
33
- const { wgc103 } = require("./lint/post-synth/wgc103");
34
- const { wgc104 } = require("./lint/post-synth/wgc104");
35
- const { wgc105 } = require("./lint/post-synth/wgc105");
36
- const { wgc106 } = require("./lint/post-synth/wgc106");
37
- const { wgc107 } = require("./lint/post-synth/wgc107");
38
- const { wgc108 } = require("./lint/post-synth/wgc108");
39
- const { wgc109 } = require("./lint/post-synth/wgc109");
40
- const { wgc110 } = require("./lint/post-synth/wgc110");
41
- const { wgc201 } = require("./lint/post-synth/wgc201");
42
- const { wgc202 } = require("./lint/post-synth/wgc202");
43
- const { wgc203 } = require("./lint/post-synth/wgc203");
44
- const { wgc204 } = require("./lint/post-synth/wgc204");
45
- const { wgc301 } = require("./lint/post-synth/wgc301");
46
- const { wgc302 } = require("./lint/post-synth/wgc302");
47
- const { wgc303 } = require("./lint/post-synth/wgc303");
48
- const { wgc111 } = require("./lint/post-synth/wgc111");
49
- const { wgc112 } = require("./lint/post-synth/wgc112");
50
- const { wgc113 } = require("./lint/post-synth/wgc113");
51
- const { wgc401 } = require("./lint/post-synth/wgc401");
52
- const { wgc402 } = require("./lint/post-synth/wgc402");
53
- const { wgc403 } = require("./lint/post-synth/wgc403");
54
- return [
55
- wgc101, wgc102, wgc103, wgc104, wgc105, wgc106, wgc107, wgc108, wgc109, wgc110,
56
- wgc111, wgc112, wgc113,
57
- wgc201, wgc202, wgc203, wgc204,
58
- wgc301, wgc302, wgc303,
59
- wgc401, wgc402, wgc403,
60
- ];
32
+ postSynthChecks() {
33
+ const postSynthDir = join(dirname(fileURLToPath(import.meta.url)), "lint", "post-synth");
34
+ return discoverPostSynthChecks(postSynthDir, import.meta.url);
61
35
  },
62
36
 
63
37
  intrinsics() {
@@ -353,50 +327,13 @@ export const bucketReader = new IAMPolicyMember({
353
327
  return resources;
354
328
  },
355
329
 
356
- mcpTools(): McpToolContribution[] {
357
- return [
358
- {
359
- name: "diff",
360
- description: "Compare current build output against previous output for GCP Config Connector manifests",
361
- inputSchema: {
362
- type: "object" as const,
363
- properties: {
364
- path: {
365
- type: "string",
366
- description: "Path to the infrastructure project directory",
367
- },
368
- },
369
- required: ["path"],
370
- },
371
- async handler(params: Record<string, unknown>): Promise<unknown> {
372
- const { diffCommand } = await import("@intentius/chant/cli/commands/diff");
373
- const result = await diffCommand({
374
- path: (params.path as string) ?? ".",
375
- serializers: [gcpSerializer],
376
- });
377
- return result;
378
- },
379
- },
380
- ];
330
+ mcpTools() {
331
+ return [createDiffTool(gcpSerializer, "Compare current build output against previous output for GCP Config Connector manifests")];
381
332
  },
382
333
 
383
- mcpResources(): McpResourceContribution[] {
334
+ mcpResources() {
384
335
  return [
385
- {
386
- uri: "resource-catalog",
387
- name: "GCP Config Connector Resource Catalog",
388
- description: "JSON list of all supported GCP Config Connector resource types",
389
- mimeType: "application/json",
390
- async handler(): Promise<string> {
391
- const lexicon = require("./generated/lexicon-gcp.json") as Record<string, { resourceType: string; kind: string }>;
392
- const entries = Object.entries(lexicon).map(([className, entry]) => ({
393
- className,
394
- resourceType: entry.resourceType,
395
- kind: entry.kind,
396
- }));
397
- return JSON.stringify(entries);
398
- },
399
- },
336
+ createCatalogResource(import.meta.url, "GCP Config Connector Resource Catalog", "JSON list of all supported GCP Config Connector resource types", "lexicon-gcp.json"),
400
337
  {
401
338
  uri: "examples/basic-bucket",
402
339
  name: "Basic GCS Bucket Example",
@@ -422,111 +359,86 @@ export const bucket = new StorageBucket({
422
359
  ];
423
360
  },
424
361
 
425
- skills(): SkillDefinition[] {
426
- const { readFileSync } = require("fs");
427
- const { join, dirname } = require("path");
428
- const { fileURLToPath } = require("url");
429
- const dir = dirname(fileURLToPath(import.meta.url));
430
-
431
- const skills: SkillDefinition[] = [];
432
-
433
- const skillFiles = [
434
- {
435
- file: "chant-gcp.md",
436
- name: "chant-gcp",
437
- description: "GCP Config Connector lifecycle — build, lint, apply, and troubleshoot from a chant project",
438
- triggers: [
439
- { type: "file-pattern" as const, value: "*.gcp.ts" },
440
- { type: "context" as const, value: "gcp" },
441
- ],
442
- parameters: [
443
- {
444
- name: "resourceType",
445
- type: "string",
446
- description: "GCP Config Connector resource type to work with",
447
- },
448
- ],
449
- examples: [
450
- {
451
- title: "Create a Storage Bucket",
452
- output: "new StorageBucket({ location: \"US\", storageClass: \"STANDARD\" })",
453
- },
454
- {
455
- title: "Create a GKE Cluster",
456
- output: "new GKECluster({ location: GCP.Region, releaseChannel: { channel: \"REGULAR\" } })",
457
- },
458
- ],
459
- },
460
- {
461
- file: "chant-gcp-security.md",
462
- name: "chant-gcp-security",
463
- description: "GCP security best practices for infrastructure",
464
- triggers: [
465
- { type: "context" as const, value: "gcp security" },
466
- { type: "context" as const, value: "gcp iam" },
467
- ],
468
- parameters: [],
469
- examples: [
470
- {
471
- title: "Secure Storage Bucket",
472
- input: "Create a storage bucket with encryption and uniform access",
473
- output: "import { GcsBucket } from \"@intentius/chant-lexicon-gcp\";\n\nconst { bucket } = GcsBucket({ name: \"my-bucket\", kmsKeyName: \"...\" });",
474
- },
475
- ],
476
- },
477
- {
478
- file: "chant-gcp-patterns.md",
479
- name: "chant-gcp-patterns",
480
- description: "Advanced GCP Config Connector patterns",
481
- triggers: [
482
- { type: "context" as const, value: "gcp patterns" },
483
- { type: "context" as const, value: "gcp composites" },
484
- ],
485
- parameters: [],
486
- examples: [
487
- {
488
- title: "VPC with Subnets",
489
- input: "Create a VPC network with private subnets",
490
- output: "import { VpcNetwork } from \"@intentius/chant-lexicon-gcp\";\n\nconst { network, subnets } = VpcNetwork({ name: \"my-vpc\", subnets: [...] });",
491
- },
492
- ],
493
- },
494
- {
495
- file: "chant-gke.md",
496
- name: "chant-gke",
497
- description: "GKE end-to-end workflow — bootstrap cluster, deploy Config Connector resources, deploy K8s workloads",
498
- triggers: [
499
- { type: "context" as const, value: "gke" },
500
- { type: "context" as const, value: "gcp kubernetes" },
501
- { type: "context" as const, value: "config connector" },
502
- ],
503
- parameters: [],
504
- examples: [
505
- {
506
- title: "Deploy GKE microservice",
507
- input: "Deploy a GKE project end-to-end",
508
- output: "npm run bootstrap && npm run deploy",
509
- },
510
- ],
511
- },
512
- ];
513
-
514
- for (const skill of skillFiles) {
515
- try {
516
- const content = readFileSync(join(dir, "skills", skill.file), "utf-8");
517
- skills.push({
518
- name: skill.name,
519
- description: skill.description,
520
- content,
521
- triggers: skill.triggers,
522
- parameters: skill.parameters,
523
- examples: skill.examples,
524
- });
525
- } catch { /* skip missing skills */ }
526
- }
527
-
528
- return skills;
529
- },
362
+ skills: createSkillsLoader(import.meta.url, [
363
+ {
364
+ file: "chant-gcp.md",
365
+ name: "chant-gcp",
366
+ description: "Build, validate, and deploy GCP Config Connector manifests from a chant project",
367
+ triggers: [
368
+ { type: "file-pattern" as const, value: "*.gcp.ts" },
369
+ { type: "context" as const, value: "gcp" },
370
+ ],
371
+ parameters: [
372
+ {
373
+ name: "resourceType",
374
+ type: "string",
375
+ description: "GCP Config Connector resource type to work with",
376
+ },
377
+ ],
378
+ examples: [
379
+ {
380
+ title: "Create a Storage Bucket",
381
+ output: "new StorageBucket({ location: \"US\", storageClass: \"STANDARD\" })",
382
+ },
383
+ {
384
+ title: "Create a GKE Cluster",
385
+ output: "new GKECluster({ location: GCP.Region, releaseChannel: { channel: \"REGULAR\" } })",
386
+ },
387
+ ],
388
+ },
389
+ {
390
+ file: "chant-gcp-security.md",
391
+ name: "chant-gcp-security",
392
+ description: "GCP security best practices for infrastructure",
393
+ triggers: [
394
+ { type: "context" as const, value: "gcp security" },
395
+ { type: "context" as const, value: "gcp iam" },
396
+ ],
397
+ parameters: [],
398
+ examples: [
399
+ {
400
+ title: "Secure Storage Bucket",
401
+ input: "Create a storage bucket with encryption and uniform access",
402
+ output: "import { GcsBucket } from \"@intentius/chant-lexicon-gcp\";\n\nconst { bucket } = GcsBucket({ name: \"my-bucket\", kmsKeyName: \"...\" });",
403
+ },
404
+ ],
405
+ },
406
+ {
407
+ file: "chant-gcp-patterns.md",
408
+ name: "chant-gcp-patterns",
409
+ description: "Advanced GCP Config Connector patterns",
410
+ triggers: [
411
+ { type: "context" as const, value: "gcp patterns" },
412
+ { type: "context" as const, value: "gcp composites" },
413
+ ],
414
+ parameters: [],
415
+ examples: [
416
+ {
417
+ title: "VPC with Subnets",
418
+ input: "Create a VPC network with private subnets",
419
+ output: "import { VpcNetwork } from \"@intentius/chant-lexicon-gcp\";\n\nconst { network, subnets } = VpcNetwork({ name: \"my-vpc\", subnets: [...] });",
420
+ },
421
+ ],
422
+ },
423
+ {
424
+ file: "chant-gcp-gke.md",
425
+ name: "chant-gcp-gke",
426
+ description: "End-to-end GKE workflow bridging GCP infrastructure and Kubernetes workloads",
427
+ triggers: [
428
+ { type: "context" as const, value: "gke" },
429
+ { type: "context" as const, value: "gcp kubernetes" },
430
+ { type: "context" as const, value: "config connector" },
431
+ ],
432
+ parameters: [],
433
+ examples: [
434
+ {
435
+ title: "Deploy GKE microservice",
436
+ input: "Deploy a GKE project end-to-end",
437
+ output: "npm run bootstrap && npm run deploy",
438
+ },
439
+ ],
440
+ },
441
+ ]),
530
442
 
531
443
  async generate(options?: { verbose?: boolean }): Promise<void> {
532
444
  const { generate, writeGeneratedFiles } = await import("./codegen/generate");
@@ -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