@intentius/chant-lexicon-cpln 0.53.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.
- package/README.md +85 -0
- package/dist/api.d.ts +77 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +17 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +6 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate-lexicon.d.ts +36 -0
- package/dist/codegen/generate-lexicon.d.ts.map +1 -0
- package/dist/codegen/generate-typescript.d.ts +11 -0
- package/dist/codegen/generate-typescript.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +15 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +30 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +17 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/cron-job.d.ts +60 -0
- package/dist/composites/cron-job.d.ts.map +1 -0
- package/dist/composites/gvc-environment.d.ts +47 -0
- package/dist/composites/gvc-environment.d.ts.map +1 -0
- package/dist/composites/index.d.ts +13 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/public-domain.d.ts +68 -0
- package/dist/composites/public-domain.d.ts.map +1 -0
- package/dist/composites/secret-access.d.ts +78 -0
- package/dist/composites/secret-access.d.ts.map +1 -0
- package/dist/composites/serverless-service.d.ts +78 -0
- package/dist/composites/serverless-service.d.ts.map +1 -0
- package/dist/composites/stateful-service.d.ts +79 -0
- package/dist/composites/stateful-service.d.ts.map +1 -0
- package/dist/coverage.d.ts +59 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +71 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +14 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/entity-props.d.ts +34 -0
- package/dist/entity-props.d.ts.map +1 -0
- package/dist/generated/index.d.ts +151 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/runtime.d.ts +5 -0
- package/dist/generated/runtime.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init-templates.d.ts +16 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +35 -0
- package/dist/kinds.d.ts +70 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/lint/audit-catalog.d.ts +19 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts +12 -0
- package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts +11 -0
- package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts +11 -0
- package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts +11 -0
- package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts +14 -0
- package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts +10 -0
- package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts +11 -0
- package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl022-container-ports.d.ts +10 -0
- package/dist/lint/post-synth/cpl022-container-ports.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl023-container-resources.d.ts +12 -0
- package/dist/lint/post-synth/cpl023-container-resources.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl024-probe-handler.d.ts +10 -0
- package/dist/lint/post-synth/cpl024-probe-handler.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts +11 -0
- package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts +11 -0
- package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts +13 -0
- package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl028-volume-set.d.ts +3 -0
- package/dist/lint/post-synth/cpl028-volume-set.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl029-link-targets.d.ts +3 -0
- package/dist/lint/post-synth/cpl029-link-targets.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl030-domain-routing.d.ts +3 -0
- package/dist/lint/post-synth/cpl030-domain-routing.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl040-pinned-image.d.ts +11 -0
- package/dist/lint/post-synth/cpl040-pinned-image.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl041-image-reference-form.d.ts +12 -0
- package/dist/lint/post-synth/cpl041-image-reference-form.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl042-gvc-placement.d.ts +10 -0
- package/dist/lint/post-synth/cpl042-gvc-placement.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl043-policy-scope.d.ts +16 -0
- package/dist/lint/post-synth/cpl043-policy-scope.d.ts.map +1 -0
- package/dist/lint/post-synth/helpers.d.ts +45 -0
- package/dist/lint/post-synth/helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/rules/cpl001-no-secret-literals.d.ts +3 -0
- package/dist/lint/rules/cpl001-no-secret-literals.d.ts.map +1 -0
- package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts +3 -0
- package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +6 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +7 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +6 -0
- package/dist/meta.json +2110 -0
- package/dist/okf/index.md +39 -0
- package/dist/okf/rules/CPL001.md +22 -0
- package/dist/okf/rules/CPL002.md +22 -0
- package/dist/okf/rules/CPL010.md +15 -0
- package/dist/okf/rules/CPL011.md +15 -0
- package/dist/okf/rules/CPL012.md +15 -0
- package/dist/okf/rules/CPL013.md +15 -0
- package/dist/okf/rules/CPL014.md +16 -0
- package/dist/okf/rules/CPL020.md +11 -0
- package/dist/okf/rules/CPL021.md +15 -0
- package/dist/okf/rules/CPL022.md +15 -0
- package/dist/okf/rules/CPL023.md +15 -0
- package/dist/okf/rules/CPL024.md +15 -0
- package/dist/okf/rules/CPL025.md +15 -0
- package/dist/okf/rules/CPL026.md +15 -0
- package/dist/okf/rules/CPL027.md +15 -0
- package/dist/okf/rules/CPL028.md +15 -0
- package/dist/okf/rules/CPL029.md +15 -0
- package/dist/okf/rules/CPL030.md +16 -0
- package/dist/okf/rules/CPL040.md +15 -0
- package/dist/okf/rules/CPL041.md +15 -0
- package/dist/okf/rules/CPL042.md +11 -0
- package/dist/okf/rules/CPL043.md +15 -0
- package/dist/okf/types/Domain.md +22 -0
- package/dist/okf/types/Gvc.md +21 -0
- package/dist/okf/types/Identity.md +29 -0
- package/dist/okf/types/IpSet.md +21 -0
- package/dist/okf/types/Policy.md +28 -0
- package/dist/okf/types/Secret.md +23 -0
- package/dist/okf/types/VolumeSet.md +22 -0
- package/dist/okf/types/Workload.md +38 -0
- package/dist/ownership.d.ts +23 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/reference-catalog.d.ts +24 -0
- package/dist/reference-catalog.d.ts.map +1 -0
- package/dist/rules/cpl001-no-secret-literals.ts +83 -0
- package/dist/rules/cpl002-prefer-resource-reference.ts +71 -0
- package/dist/rules/cpl010-unrestricted-outbound.ts +41 -0
- package/dist/rules/cpl011-internal-firewall-scope.ts +38 -0
- package/dist/rules/cpl012-no-literal-credential-env.ts +58 -0
- package/dist/rules/cpl013-qualified-identity-principal.ts +45 -0
- package/dist/rules/cpl014-qualified-secret-ref.ts +54 -0
- package/dist/rules/cpl020-serverless-single-port.ts +58 -0
- package/dist/rules/cpl021-cron-job-shape.ts +64 -0
- package/dist/rules/cpl022-container-ports.ts +64 -0
- package/dist/rules/cpl023-container-resources.ts +87 -0
- package/dist/rules/cpl024-probe-handler.ts +51 -0
- package/dist/rules/cpl025-autoscaling-shape.ts +100 -0
- package/dist/rules/cpl026-scale-to-zero.ts +67 -0
- package/dist/rules/cpl027-capacity-ai-conflict.ts +78 -0
- package/dist/rules/cpl028-volume-set.ts +183 -0
- package/dist/rules/cpl029-link-targets.ts +155 -0
- package/dist/rules/cpl030-domain-routing.ts +137 -0
- package/dist/rules/cpl040-pinned-image.ts +48 -0
- package/dist/rules/cpl041-image-reference-form.ts +60 -0
- package/dist/rules/cpl042-gvc-placement.ts +57 -0
- package/dist/rules/cpl043-policy-scope.ts +0 -0
- package/dist/rules/helpers.ts +114 -0
- package/dist/serializer.d.ts +69 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skills/chant-cpln-secrets.md +110 -0
- package/dist/skills/chant-cpln-workloads.md +78 -0
- package/dist/skills/chant-cpln.md +103 -0
- package/dist/spec/fetch.d.ts +43 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +79 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/spec/snapshot-cli.d.ts +18 -0
- package/dist/spec/snapshot-cli.d.ts.map +1 -0
- package/dist/types/index.d.ts +1649 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +26 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +74 -0
- package/src/api.ts +134 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +94 -0
- package/src/codegen/generate-cli.ts +34 -0
- package/src/codegen/generate-lexicon.ts +88 -0
- package/src/codegen/generate-typescript.ts +71 -0
- package/src/codegen/generate.ts +110 -0
- package/src/codegen/naming.ts +61 -0
- package/src/codegen/package.ts +35 -0
- package/src/composites/composites.test.ts +252 -0
- package/src/composites/cron-job.ts +124 -0
- package/src/composites/gvc-environment.ts +104 -0
- package/src/composites/index.ts +17 -0
- package/src/composites/public-domain.ts +144 -0
- package/src/composites/secret-access.ts +136 -0
- package/src/composites/serverless-service.ts +143 -0
- package/src/composites/stateful-service.ts +179 -0
- package/src/coverage.ts +149 -0
- package/src/describe-resources.test.ts +252 -0
- package/src/describe-resources.ts +310 -0
- package/src/detect.ts +58 -0
- package/src/entity-props.ts +98 -0
- package/src/generated/index.d.ts +1649 -0
- package/src/generated/index.ts +156 -0
- package/src/generated/lexicon-cpln.json +2110 -0
- package/src/generated/runtime.ts +4 -0
- package/src/index.ts +31 -0
- package/src/init-templates.ts +131 -0
- package/src/kinds.ts +173 -0
- package/src/lint/audit-catalog.ts +230 -0
- package/src/lint/post-synth/cpl010-unrestricted-outbound.ts +41 -0
- package/src/lint/post-synth/cpl011-internal-firewall-scope.ts +38 -0
- package/src/lint/post-synth/cpl012-no-literal-credential-env.ts +58 -0
- package/src/lint/post-synth/cpl013-qualified-identity-principal.ts +45 -0
- package/src/lint/post-synth/cpl014-qualified-secret-ref.ts +54 -0
- package/src/lint/post-synth/cpl020-serverless-single-port.ts +58 -0
- package/src/lint/post-synth/cpl021-cron-job-shape.ts +64 -0
- package/src/lint/post-synth/cpl022-container-ports.ts +64 -0
- package/src/lint/post-synth/cpl023-container-resources.ts +87 -0
- package/src/lint/post-synth/cpl024-probe-handler.ts +51 -0
- package/src/lint/post-synth/cpl025-autoscaling-shape.ts +100 -0
- package/src/lint/post-synth/cpl026-scale-to-zero.ts +67 -0
- package/src/lint/post-synth/cpl027-capacity-ai-conflict.ts +78 -0
- package/src/lint/post-synth/cpl028-volume-set.ts +183 -0
- package/src/lint/post-synth/cpl029-link-targets.ts +155 -0
- package/src/lint/post-synth/cpl030-domain-routing.ts +137 -0
- package/src/lint/post-synth/cpl040-pinned-image.ts +48 -0
- package/src/lint/post-synth/cpl041-image-reference-form.ts +60 -0
- package/src/lint/post-synth/cpl042-gvc-placement.ts +57 -0
- package/src/lint/post-synth/cpl043-policy-scope.ts +0 -0
- package/src/lint/post-synth/helpers.ts +114 -0
- package/src/lint/post-synth/index.ts +45 -0
- package/src/lint/post-synth/post-synth.test.ts +737 -0
- package/src/lint/rules/cpl001-no-secret-literals.ts +83 -0
- package/src/lint/rules/cpl002-prefer-resource-reference.ts +71 -0
- package/src/lint/rules/index.ts +9 -0
- package/src/lint/rules/rules.test.ts +108 -0
- package/src/lsp/completions.test.ts +44 -0
- package/src/lsp/completions.ts +27 -0
- package/src/lsp/hover.test.ts +53 -0
- package/src/lsp/hover.ts +130 -0
- package/src/ownership.ts +28 -0
- package/src/package-cli.ts +25 -0
- package/src/plugin.test.ts +131 -0
- package/src/plugin.ts +187 -0
- package/src/reference-catalog.ts +110 -0
- package/src/serializer.test.ts +233 -0
- package/src/serializer.ts +253 -0
- package/src/skills/chant-cpln-secrets.md +110 -0
- package/src/skills/chant-cpln-workloads.md +78 -0
- package/src/skills/chant-cpln.md +103 -0
- package/src/spec/cpln-openapi.snapshot.json +10651 -0
- package/src/spec/fetch.ts +77 -0
- package/src/spec/parse.ts +451 -0
- package/src/spec/snapshot-cli.ts +71 -0
- package/src/validate-cli.ts +8 -0
- package/src/validate.ts +102 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cpln serializer — emits the multi-document YAML that `cpln apply --file`
|
|
3
|
+
* consumes.
|
|
4
|
+
*
|
|
5
|
+
* Each declared resource becomes one document:
|
|
6
|
+
*
|
|
7
|
+
* kind: workload
|
|
8
|
+
* name: my-app
|
|
9
|
+
* gvc: my-gvc
|
|
10
|
+
* tags:
|
|
11
|
+
* chant.intentius.io/managed-by: chant
|
|
12
|
+
* spec:
|
|
13
|
+
* containers:
|
|
14
|
+
* - name: main
|
|
15
|
+
* image: nginx:1.27
|
|
16
|
+
*
|
|
17
|
+
* Three things this file is careful about.
|
|
18
|
+
*
|
|
19
|
+
* **Only authoring surface is emitted.** Control Plane's own guidance is to
|
|
20
|
+
* export with `-o yaml-slim` rather than `-o yaml` before re-applying, because
|
|
21
|
+
* the server-side fields (`status`, `id`, `created`, `lastModified`, `links`)
|
|
22
|
+
* break `cpln apply`. Those are attributes in this lexicon rather than
|
|
23
|
+
* properties, so they never reach a document in the first place — the shape a
|
|
24
|
+
* user can declare is already the slim shape.
|
|
25
|
+
*
|
|
26
|
+
* **References become links, not names.** Control Plane addresses resources by
|
|
27
|
+
* link (`//gvc/prod/identity/api`, `//secret/db-password`), and the GVC-scoped
|
|
28
|
+
* forms have to carry the GVC. Passing a declared resource where a link is
|
|
29
|
+
* expected resolves to the right link for its kind, which is the difference
|
|
30
|
+
* between a reference that survives a rename and a hand-spelled string that
|
|
31
|
+
* does not. The identity form matters especially: the bare `//identity/NAME`
|
|
32
|
+
* that reads perfectly plausibly is *silently ignored* by Control Plane's
|
|
33
|
+
* policy engine, and is documented as a common silent failure.
|
|
34
|
+
*
|
|
35
|
+
* **Document order is stable and dependency-first.** `cpln apply` resolves
|
|
36
|
+
* ordering itself for a multi-doc file, so this is not needed for correctness —
|
|
37
|
+
* it is so the emitted file diffs cleanly between builds and reads in the order
|
|
38
|
+
* a person would write it.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
42
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
43
|
+
import type { Serializer, SerializeContext } from "@intentius/chant/serializer";
|
|
44
|
+
import type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
45
|
+
import { ownershipEntries } from "@intentius/chant/ownership";
|
|
46
|
+
import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
|
|
47
|
+
import { emitYAML } from "@intentius/chant/yaml";
|
|
48
|
+
import { KINDS, kindByTypeName, type CplnKind } from "./kinds";
|
|
49
|
+
import { CPLN_TAG_OWNERSHIP_KEYS } from "./ownership";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Document order: a GVC before anything inside it, and the org-scoped assets a
|
|
53
|
+
* workload references before the workload. Matches the order the API would
|
|
54
|
+
* accept the documents in one at a time.
|
|
55
|
+
*/
|
|
56
|
+
const KIND_ORDER: string[] = ["gvc", "secret", "ipset", "policy", "identity", "volumeset", "workload", "domain"];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Manifest key order. `kind` and `name` first because that is how every
|
|
60
|
+
* Control Plane example reads; `spec` last because it is the long one.
|
|
61
|
+
*/
|
|
62
|
+
const KEY_ORDER: string[] = ["kind", "name", "description", "gvc", "tags"];
|
|
63
|
+
|
|
64
|
+
/** Properties consumed to build the envelope rather than copied into the body. */
|
|
65
|
+
const ENVELOPE_KEYS = new Set(["name", "description", "gvc", "tags"]);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `satisfies` rather than a type annotation: `Serializer.serialize` returns
|
|
69
|
+
* `string | SerializerResult`, and cpln always emits a single YAML string.
|
|
70
|
+
* Annotating widens the export to the union and every caller — the tests
|
|
71
|
+
* included — has to narrow it back with a cast that asserts something the
|
|
72
|
+
* implementation already guarantees. `satisfies` checks the shape against the
|
|
73
|
+
* interface and keeps the narrower return type.
|
|
74
|
+
*/
|
|
75
|
+
export const cplnSerializer = {
|
|
76
|
+
name: "cpln",
|
|
77
|
+
rulePrefix: "CPL",
|
|
78
|
+
|
|
79
|
+
serialize(entities: Map<string, Declarable>, _outputs?: LexiconOutput[], context?: SerializeContext): string {
|
|
80
|
+
// The walker wants Declarable → name; link resolution wants the inverse.
|
|
81
|
+
const entityNames = new Map<Declarable, string>();
|
|
82
|
+
for (const [name, entity] of entities) entityNames.set(entity, name);
|
|
83
|
+
|
|
84
|
+
const visitor = cplnVisitor(entities);
|
|
85
|
+
|
|
86
|
+
const documents: Array<{ order: number; name: string; text: string }> = [];
|
|
87
|
+
|
|
88
|
+
for (const [entityName, entity] of entities) {
|
|
89
|
+
const kind = kindByTypeName(entity.entityType);
|
|
90
|
+
// Not ours. A build can mix lexicons, and each serializer sees only the
|
|
91
|
+
// entities routed to it, but skipping defensively costs nothing.
|
|
92
|
+
if (!kind) continue;
|
|
93
|
+
|
|
94
|
+
const props = readProps(entity);
|
|
95
|
+
const manifest: Record<string, unknown> = { kind: kind.kind };
|
|
96
|
+
|
|
97
|
+
// `name` falls back to the entity's own name, so a resource declared
|
|
98
|
+
// without one still round-trips rather than emitting `name: undefined`.
|
|
99
|
+
manifest.name = props.name ?? entityName;
|
|
100
|
+
|
|
101
|
+
if (props.description !== undefined) manifest.description = walkValue(props.description, entityNames, visitor);
|
|
102
|
+
if (kind.gvcScoped) manifest.gvc = walkValue(props.gvc, entityNames, visitor);
|
|
103
|
+
|
|
104
|
+
const tags = buildTags(props.tags, entityNames, visitor, context);
|
|
105
|
+
if (tags) manifest.tags = tags;
|
|
106
|
+
|
|
107
|
+
for (const [key, value] of Object.entries(props)) {
|
|
108
|
+
if (ENVELOPE_KEYS.has(key) || value === undefined) continue;
|
|
109
|
+
manifest[key] = walkValue(value, entityNames, visitor);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
documents.push({
|
|
113
|
+
order: KIND_ORDER.indexOf(kind.kind),
|
|
114
|
+
name: String(manifest.name),
|
|
115
|
+
text: emitManifest(manifest),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
documents.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
|
|
120
|
+
|
|
121
|
+
return documents.map((d) => d.text).join("\n---\n");
|
|
122
|
+
},
|
|
123
|
+
} satisfies Serializer;
|
|
124
|
+
|
|
125
|
+
// ── Tags and ownership ─────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Merge the declared tags with chant's ownership marker.
|
|
129
|
+
*
|
|
130
|
+
* The marker is stamped even when the resource declares no tags of its own —
|
|
131
|
+
* that is the whole point of it, since an unmarked resource is invisible to
|
|
132
|
+
* the owned-resource prune that lets `chant delete` be precise without a state
|
|
133
|
+
* file.
|
|
134
|
+
*/
|
|
135
|
+
function buildTags(
|
|
136
|
+
declared: unknown,
|
|
137
|
+
entityNames: Map<Declarable, string>,
|
|
138
|
+
visitor: SerializerVisitor,
|
|
139
|
+
context?: SerializeContext,
|
|
140
|
+
): Record<string, unknown> | undefined {
|
|
141
|
+
const walked = declared === undefined ? undefined : walkValue(declared, entityNames, visitor);
|
|
142
|
+
const tags: Record<string, unknown> =
|
|
143
|
+
walked && typeof walked === "object" && !Array.isArray(walked) ? { ...(walked as Record<string, unknown>) } : {};
|
|
144
|
+
|
|
145
|
+
if (context?.ownership) {
|
|
146
|
+
Object.assign(tags, ownershipEntries(CPLN_TAG_OWNERSHIP_KEYS, context.ownership));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return Object.keys(tags).length > 0 ? tags : undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── References ─────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Build the Control Plane link for a declared resource.
|
|
156
|
+
*
|
|
157
|
+
* GVC-scoped kinds nest under their GVC. For identities in particular the
|
|
158
|
+
* nested form is not cosmetic: a policy binding written against the bare
|
|
159
|
+
* `//identity/NAME` is accepted and then ignored.
|
|
160
|
+
*/
|
|
161
|
+
export function cplnLink(kind: CplnKind, name: string, gvc?: unknown): string {
|
|
162
|
+
if (kind.kind === "gvc") return `//gvc/${name}`;
|
|
163
|
+
if (kind.gvcScoped) {
|
|
164
|
+
if (typeof gvc !== "string" || gvc.length === 0) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Cannot build a link to ${kind.kind} "${name}": its \`gvc\` is not a plain string at build time. ` +
|
|
167
|
+
`GVC-scoped links must be \`//gvc/<gvc>/${kind.kind}/<name>\`, and the bare \`//${kind.kind}/<name>\` ` +
|
|
168
|
+
`form is silently ignored by Control Plane.`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return `//gvc/${gvc}/${kind.kind}/${name}`;
|
|
172
|
+
}
|
|
173
|
+
return `//${kind.kind}/${name}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Visitor for the generic serializer walker.
|
|
178
|
+
*
|
|
179
|
+
* Property declarables unwrap to plain objects; resource references resolve to
|
|
180
|
+
* Control Plane links; attribute references have nowhere to go.
|
|
181
|
+
*/
|
|
182
|
+
function cplnVisitor(entities: Map<string, Declarable>): SerializerVisitor {
|
|
183
|
+
const linkFor = (logicalName: string): string => {
|
|
184
|
+
const target = entities.get(logicalName);
|
|
185
|
+
const kind = target && kindByTypeName(target.entityType);
|
|
186
|
+
if (!target || !kind) {
|
|
187
|
+
// A reference to something this lexicon does not model. The logical name
|
|
188
|
+
// is the most useful thing left to emit, and a post-synth check reports
|
|
189
|
+
// links that do not resolve.
|
|
190
|
+
return logicalName;
|
|
191
|
+
}
|
|
192
|
+
const props = readProps(target);
|
|
193
|
+
const name = typeof props.name === "string" ? props.name : logicalName;
|
|
194
|
+
return cplnLink(kind, name, props.gvc);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
attrRef: (name, attribute) => {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Cannot serialize a reference to ${name}.${attribute}: Control Plane manifests have no ` +
|
|
201
|
+
`template-time reference language, so an attribute known only after apply cannot be embedded in one. ` +
|
|
202
|
+
`Reference the resource itself — it resolves to a link — or read the attribute after apply.`,
|
|
203
|
+
);
|
|
204
|
+
},
|
|
205
|
+
resourceRef: linkFor,
|
|
206
|
+
propertyDeclarable: (entity, walk) => {
|
|
207
|
+
const props = isResourceDeclarable(entity) ? entity.props : undefined;
|
|
208
|
+
if (!props || typeof props !== "object") return undefined;
|
|
209
|
+
const result: Record<string, unknown> = {};
|
|
210
|
+
for (const [key, value] of Object.entries(props)) {
|
|
211
|
+
if (value !== undefined) result[key] = walk(value);
|
|
212
|
+
}
|
|
213
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Emission ───────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
function readProps(entity: Declarable): Record<string, unknown> {
|
|
221
|
+
const props = isResourceDeclarable(entity) ? entity.props : undefined;
|
|
222
|
+
return props && typeof props === "object" ? (props as Record<string, unknown>) : {};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Emit one manifest document, ordered keys first and the rest alphabetically. */
|
|
226
|
+
function emitManifest(manifest: Record<string, unknown>): string {
|
|
227
|
+
const lines: string[] = [];
|
|
228
|
+
|
|
229
|
+
for (const key of KEY_ORDER) {
|
|
230
|
+
if (manifest[key] !== undefined) lines.push(emitKeyValue(key, manifest[key]));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const rest = Object.keys(manifest)
|
|
234
|
+
.filter((k) => !KEY_ORDER.includes(k))
|
|
235
|
+
.sort();
|
|
236
|
+
for (const key of rest) {
|
|
237
|
+
if (manifest[key] !== undefined) lines.push(emitKeyValue(key, manifest[key]));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return `${lines.join("\n")}\n`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Scalars get `key: value`; blocks get the value indented beneath the key. */
|
|
244
|
+
function emitKeyValue(key: string, value: unknown): string {
|
|
245
|
+
const yaml = emitYAML(value, 1);
|
|
246
|
+
return yaml.startsWith("\n") ? `${key}:${yaml}` : `${key}: ${yaml}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Kind order used for document sorting, exported for the serializer tests. */
|
|
250
|
+
export const DOCUMENT_KIND_ORDER: readonly string[] = KIND_ORDER;
|
|
251
|
+
|
|
252
|
+
/** Every modelled kind, re-exported so consumers need not reach into `kinds`. */
|
|
253
|
+
export const SERIALIZED_KINDS = KINDS;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-cpln-secrets
|
|
3
|
+
description: Wire Control Plane secrets, identities and policies so a workload can actually read them — the three-step path and its silent failures
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Control Plane Secrets, Identities and Policies
|
|
8
|
+
|
|
9
|
+
Control Plane's own documentation calls a partial version of this its **number one support issue**. The reason is worth stating up front: every way of getting it wrong fails *silently at runtime*. The API accepts the broken form, the workload starts, and the failure surfaces later as an application error.
|
|
10
|
+
|
|
11
|
+
## The three steps
|
|
12
|
+
|
|
13
|
+
All three are required. Missing any one produces no apply-time error.
|
|
14
|
+
|
|
15
|
+
**1. The workload has an identity.**
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
export const identity = new Identity({ name: "web-identity", gvc: "prod" });
|
|
19
|
+
|
|
20
|
+
export const web = new Workload({
|
|
21
|
+
name: "web",
|
|
22
|
+
gvc: "prod",
|
|
23
|
+
spec: { identityLink: identity, /* ... */ },
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**2. A policy grants that identity `reveal` on the secret.**
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
export const policy = new Policy({
|
|
31
|
+
name: "web-secrets",
|
|
32
|
+
targetKind: "secret",
|
|
33
|
+
targetLinks: ["//secret/db-password"],
|
|
34
|
+
bindings: [{
|
|
35
|
+
permissions: ["reveal"],
|
|
36
|
+
principalLinks: ["//gvc/prod/identity/web-identity"],
|
|
37
|
+
}],
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**3. The reference is field-qualified.**
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
env: [{ name: "DATABASE_PASSWORD", value: "cpln://secret/db-password.payload" }]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`SecretAccess` does steps 1 and 2, and `secretRef()` does step 3:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { SecretAccess, secretRef } from "@intentius/chant-lexicon-cpln";
|
|
51
|
+
|
|
52
|
+
export const { identity, policy } = SecretAccess({
|
|
53
|
+
name: "web-identity",
|
|
54
|
+
gvc: "prod",
|
|
55
|
+
secrets: ["db-password"],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// → "cpln://secret/db-password.payload"
|
|
59
|
+
secretRef("db-password", "payload");
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## The two silent failures
|
|
63
|
+
|
|
64
|
+
**The principal link must be GVC-qualified.** `//identity/NAME` is accepted and ignored. Only `//gvc/GVC/identity/NAME` grants anything. (CPL013)
|
|
65
|
+
|
|
66
|
+
**The secret reference must name a field.** `cpln://secret/db` resolves to nothing:
|
|
67
|
+
|
|
68
|
+
| Type | Field |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `opaque` | `.payload` |
|
|
71
|
+
| `dictionary` | `.KEY` — one env var per key, or volume-mount the whole thing as a directory |
|
|
72
|
+
| `userpass` | `.username`, `.password` |
|
|
73
|
+
| `tls` | `.cert`, `.key` |
|
|
74
|
+
| `keypair` | `.publicKey`, `.privateKey` |
|
|
75
|
+
| `aws` | `.accessKey`, `.secretKey`, `.roleArn` |
|
|
76
|
+
| `gcp` | unqualified — conventionally a volume-mounted JSON file |
|
|
77
|
+
|
|
78
|
+
(CPL014)
|
|
79
|
+
|
|
80
|
+
## Identities
|
|
81
|
+
|
|
82
|
+
- **GVC-scoped and not shareable.** An identity cannot be used from another GVC — declare one per GVC with the same spec (CPL029).
|
|
83
|
+
- A workload has **at most one**.
|
|
84
|
+
- One cloud account per provider: one AWS + one GCP + one Azure, not two AWS.
|
|
85
|
+
- Provider sections have XOR rules: AWS `roleName` ⊻ `policyRefs`, GCP `serviceAccount` ⊻ `bindings`. Network resources `IPs` ⊻ `FQDN`.
|
|
86
|
+
- **Do not assign one unless the workload needs it** — secret access, credential-free cloud access, or private network access. An empty identity assignment complicates audit traces for no benefit.
|
|
87
|
+
|
|
88
|
+
## Policies
|
|
89
|
+
|
|
90
|
+
- `targetKind` is singular and lowercase.
|
|
91
|
+
- Pick exactly one scope: `target: "all"`, `targetLinks`, or `targetQuery`.
|
|
92
|
+
- `ipset`, `mk8s` and `workloadreplica` are **not** valid targets — they are governed through their parent.
|
|
93
|
+
- Max 50 bindings per policy, 200 principal links per binding. Permissions must be sorted alphabetically and unique.
|
|
94
|
+
- Never set `origin` — the system sets it, and a declared value reads as drift on every plan. `builtin` policies cannot be modified at all.
|
|
95
|
+
|
|
96
|
+
Principal forms: `//user/EMAIL`, `//group/NAME`, `//serviceaccount/NAME`, `//gvc/GVC/identity/NAME`.
|
|
97
|
+
|
|
98
|
+
(CPL043)
|
|
99
|
+
|
|
100
|
+
## Secret values in source
|
|
101
|
+
|
|
102
|
+
There are 12 secret types. `cpln secret create` does not exist — the CLI has a `create-<type>` variant for each.
|
|
103
|
+
|
|
104
|
+
Never put credential material in a chant declaration. CPL001 fires on recognisable credential shapes — private keys, cloud access keys, JWTs, database URLs with inline passwords — at author time, where the finding has a file and a line and the credential has not yet reached git history. CPL012 catches the rest from the model: a credential-named env var set to any literal.
|
|
105
|
+
|
|
106
|
+
Read the value from the environment at build time, or set it out of band with `cpln secret edit` and leave chant managing only the secret's existence and its type.
|
|
107
|
+
|
|
108
|
+
## Pull secrets are GVC-level
|
|
109
|
+
|
|
110
|
+
`spec.pullSecretLinks` on the GVC, not on the workload. Only `docker`, `ecr` and `gcp` types are valid as pull secrets. Images from your own org's registry need none.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-cpln-workloads
|
|
3
|
+
description: Choose a Control Plane workload type and configure autoscaling, Capacity AI, resources and probes correctly
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Control Plane Workloads
|
|
8
|
+
|
|
9
|
+
## Pick the type first — it is immutable
|
|
10
|
+
|
|
11
|
+
Changing `type` means delete and recreate. Almost every other constraint follows from it.
|
|
12
|
+
|
|
13
|
+
| | Serverless | Standard | Stateful | Cron |
|
|
14
|
+
|---|:---:|:---:|:---:|:---:|
|
|
15
|
+
| Ports | **exactly 1 HTTP** | 0 or more | 0 or more | **none** |
|
|
16
|
+
| Scale to zero | `rps` / `concurrency` | KEDA only | KEDA only | no |
|
|
17
|
+
| Persistent volumes | no | no | **yes** | no |
|
|
18
|
+
| Multi-metric autoscaling | no | yes | yes | n/a |
|
|
19
|
+
| `spec.job` | forbidden | forbidden | forbidden | **required** |
|
|
20
|
+
| `timeoutSeconds` max | 600 | 3600 | 3600 | n/a |
|
|
21
|
+
|
|
22
|
+
- **Serverless** must expose exactly one HTTP port. Zero is the common mistake and the confusing one: it deploys, reports healthy, and serves nothing (CPL020).
|
|
23
|
+
- **Cron** must not expose ports and must set `spec.job.schedule`. Probes, autoscaling, `timeoutSeconds` and `debug` are all accepted and ignored, so setting them is a silent no-op (CPL021).
|
|
24
|
+
- **Stateful** is the only type that mounts `ext4`/`xfs` volume sets, and the only one that supports `replicaDirect` load balancing or `workloadLink` domain routing.
|
|
25
|
+
|
|
26
|
+
Max 8 containers per workload. Workload names are ≤ 49 characters and cannot end in `-headless`. Container names cannot start with `cpln-` or `debugger-`.
|
|
27
|
+
|
|
28
|
+
## Resources: the ratio is the surprise
|
|
29
|
+
|
|
30
|
+
Defaults are `cpu: 50m`, `memory: 128Mi`.
|
|
31
|
+
|
|
32
|
+
- CPU ≥ 25 millicores, memory ≥ 32 MiB.
|
|
33
|
+
- **`memory(MiB) / cpu(millicores)` ≤ 8.** So `2Gi` needs at least 256m of CPU. A memory-heavy, CPU-light workload is rejected — which surprises people coming from Kubernetes, where the two are independent. The `cpln/relaxMemoryToCpuRatio` tag raises the ceiling to 32.
|
|
34
|
+
|
|
35
|
+
CPL023 checks all three, using the defaults when a value is omitted, so an unset field is checked as what it will actually become.
|
|
36
|
+
|
|
37
|
+
## Autoscaling
|
|
38
|
+
|
|
39
|
+
`metric` and `multi` are alternatives, not layers. `target` belongs to the single-metric form and is capped at 100 for `cpu`/`memory` (it is a utilization percentage).
|
|
40
|
+
|
|
41
|
+
Scale to zero is the one worth being careful with. `minScale: 0` is accepted on any type and only *takes effect* for serverless under `rps`/`concurrency`, or standard/stateful under KEDA. Everywhere else the workload holds at one replica and the cost saving never arrives, with nothing reported (CPL026).
|
|
42
|
+
|
|
43
|
+
KEDA has to be enabled on the GVC before a workload in it can use it.
|
|
44
|
+
|
|
45
|
+
## Capacity AI is on by default
|
|
46
|
+
|
|
47
|
+
For serverless, standard and cron. It resizes CPU and memory from observed usage, and it is **mutually exclusive** with:
|
|
48
|
+
|
|
49
|
+
- CPU-utilization autoscaling — dynamic CPU moves the baseline the metric scales against.
|
|
50
|
+
- Multi-metric autoscaling — needs stable baselines.
|
|
51
|
+
- GPUs — GPU allocation is fixed.
|
|
52
|
+
|
|
53
|
+
These conflicts are usually reached by *adding* CPU scaling or a GPU to a workload that never opted into Capacity AI, so CPL027 says which of the two it is. Turn it off with `spec.defaultOptions.capacityAI: false`.
|
|
54
|
+
|
|
55
|
+
## Probes
|
|
56
|
+
|
|
57
|
+
Exactly one of `exec`, `grpc`, `tcpSocket`, `httpGet` per probe. A probe with only timing fields set looks configured and checks nothing (CPL024).
|
|
58
|
+
|
|
59
|
+
Defaults differ by type: readiness is TCP-on-port for serverless and **disabled** for standard, stateful and cron. And `spec.containers[].port` must match the port the process actually binds, or health checks fail.
|
|
60
|
+
|
|
61
|
+
## Firewalls start closed
|
|
62
|
+
|
|
63
|
+
Both directions of the external firewall are disabled by default, and the internal one is `none`.
|
|
64
|
+
|
|
65
|
+
- Inbound: add CIDRs (`0.0.0.0/0` for the internet).
|
|
66
|
+
- Outbound: CIDRs, or hostnames with a wildcard prefix (`*.amazonaws.com`). Hostname rules allow ports 80/443/445 only unless `outboundAllowPort` says otherwise.
|
|
67
|
+
- CIDR rules take precedence over hostname rules; blocked rules take precedence over allowed ones.
|
|
68
|
+
|
|
69
|
+
CPL010 flags outbound `0.0.0.0/0` — it is rarely needed and is the egress path for anything that gets a foothold. CPL011 flags internal `same-org`, which crosses the boundary the GVC exists to draw.
|
|
70
|
+
|
|
71
|
+
Internal traffic between workloads in a GVC is automatically mTLS-encrypted at `WORKLOAD.GVC.cpln.local:PORT`; there is nothing to configure.
|
|
72
|
+
|
|
73
|
+
## Images
|
|
74
|
+
|
|
75
|
+
- Never prefix a public image with `docker.io/` — `nginx:1.27`, not `docker.io/library/nginx:1.27` (CPL041).
|
|
76
|
+
- Your own org's images are `//image/NAME:TAG` in a workload spec. `<org>.registry.cpln.io` is for `docker login`/`push` only.
|
|
77
|
+
- Images must be `linux/amd64` — the wrong platform is an `exec format error` at runtime, not at apply.
|
|
78
|
+
- Pin a tag or digest. A scale-from-zero cold start re-pulls, so `:latest` means two replicas of one deploy can be different builds (CPL040).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-cpln
|
|
3
|
+
description: Declare Control Plane (cpln) infrastructure from chant — the kinds, GVC scoping, links, and what the serializer emits
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Control Plane with chant
|
|
8
|
+
|
|
9
|
+
## The shape
|
|
10
|
+
|
|
11
|
+
Control Plane's hierarchy is two levels, and it decides almost everything else:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Org (immutable, cannot be deleted)
|
|
15
|
+
├── org-scoped: Secret, Policy, Domain, IpSet
|
|
16
|
+
└── GVC — the placement and networking boundary
|
|
17
|
+
├── Workload
|
|
18
|
+
├── Identity
|
|
19
|
+
└── VolumeSet
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This lexicon models those eight kinds. `Gvc`, `Secret`, `Policy`, `Domain` and `IpSet` are org-scoped; `Workload`, `Identity` and `VolumeSet` take a required `gvc`.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Gvc, Workload } from "@intentius/chant-lexicon-cpln";
|
|
26
|
+
|
|
27
|
+
export const gvc = new Gvc({
|
|
28
|
+
name: "prod",
|
|
29
|
+
spec: {
|
|
30
|
+
staticPlacement: {
|
|
31
|
+
locationLinks: ["/org/acme/location/aws-us-east-1"],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const web = new Workload({
|
|
37
|
+
name: "web",
|
|
38
|
+
gvc: "prod",
|
|
39
|
+
spec: {
|
|
40
|
+
type: "serverless",
|
|
41
|
+
containers: [{ name: "main", image: "nginx:1.27", ports: [{ number: 8080, protocol: "http" }] }],
|
|
42
|
+
firewallConfig: { external: { inboundAllowCIDR: ["0.0.0.0/0"] } },
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`chant build` emits multi-document YAML for `cpln apply --file`. The `gvc` property becomes the manifest's own `gvc:` key, so the file is self-contained and does not depend on a `--gvc` flag.
|
|
48
|
+
|
|
49
|
+
## Pass resources, not link strings
|
|
50
|
+
|
|
51
|
+
Control Plane addresses resources by link. Pass the declared resource where a link is expected and the serializer emits the right one for its kind:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
export const identity = new Identity({ name: "web-identity", gvc: "prod" });
|
|
55
|
+
|
|
56
|
+
export const web = new Workload({
|
|
57
|
+
name: "web",
|
|
58
|
+
gvc: "prod",
|
|
59
|
+
spec: { identityLink: identity, /* → //gvc/prod/identity/web-identity */ ... },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This matters most for identities. The bare `//identity/NAME` form reads perfectly naturally, is accepted by the API, and is **silently ignored** — the policy applies cleanly and grants nothing. Only `//gvc/GVC/identity/NAME` works. Passing the resource is how you stop having to remember that (CPL013 catches it if you write the string by hand).
|
|
64
|
+
|
|
65
|
+
Link forms, when you do need to write one:
|
|
66
|
+
|
|
67
|
+
| Kind | Link |
|
|
68
|
+
|---|---|
|
|
69
|
+
| GVC | `//gvc/NAME` |
|
|
70
|
+
| Workload / Identity / VolumeSet | `//gvc/GVC/<kind>/NAME` |
|
|
71
|
+
| Secret / Policy / Domain / IpSet | `//<kind>/NAME` |
|
|
72
|
+
| Location | `/org/ORG/location/<provider>-<region>` |
|
|
73
|
+
| Own org's image | `//image/NAME:TAG` |
|
|
74
|
+
|
|
75
|
+
`cpln://secret/NAME.FIELD` and `cpln://volumeset/NAME` are different things — runtime resolution URIs the container reads, not links between resources.
|
|
76
|
+
|
|
77
|
+
## Composites
|
|
78
|
+
|
|
79
|
+
Five, each encoding rules that are easy to violate by omission:
|
|
80
|
+
|
|
81
|
+
- `GvcEnvironment` — GVC with locations and pull secrets. Pull secrets are GVC-level, not per workload.
|
|
82
|
+
- `ServerlessService` — one HTTP port, explicit firewall, autoscaling defaults.
|
|
83
|
+
- `CronJob` — schedule, no ports, no knobs cron ignores.
|
|
84
|
+
- `StatefulService` — workload + volume set, mounted, with the capacity floor checked.
|
|
85
|
+
- `SecretAccess` — identity + policy, GVC-qualified.
|
|
86
|
+
|
|
87
|
+
## Live state
|
|
88
|
+
|
|
89
|
+
Every kind carries a free-form `tags` map, and chant stamps its ownership marker there — `chant.intentius.io/managed-by: chant` plus stack and env. That is what makes `chant plan` precise without a state file: a resource carrying the marker is this stack's, one without it is never auto-deleted.
|
|
90
|
+
|
|
91
|
+
Reading live state needs `CPLN_ORG` and `CPLN_TOKEN` (a service account key, or a JWT). Use the env var, not `--token` — the flag leaks into process listings and logs.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
CPLN_ORG=acme CPLN_TOKEN=$(cat key) chant plan
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Things that are immutable
|
|
98
|
+
|
|
99
|
+
Worth settling before the first apply, because the fix afterwards is delete-and-recreate:
|
|
100
|
+
|
|
101
|
+
- Workload `type` and `name`.
|
|
102
|
+
- VolumeSet `fileSystemType` and `performanceClass` — recreating means data loss.
|
|
103
|
+
- The org itself, which cannot be deleted at all without Control Plane support.
|