Capabilities
A capability is a typed leaf behavior, registered once and named after an operation. Components compose capabilities; the orchestrator dispatches to them by kind. This is the unit of reuse — smaller than a pipeline, smaller than a “kind” of component.
interface Capability<In, Out> { kind: string; // "publish-image", "cfn-deploy", ... run(ctx: DeployContext, input: In): Promise<Out>; rollback?(ctx: DeployContext, input: In): Promise<void>; // paired compensation}Typed In/Out let compositions wire one step’s output into the next and let lint check the wiring before anything runs.
The families
Section titled “The families”The set is a plugin registry, not a closed enum. Verbs group into families. The Plugin column says who contributes each: core ships the cloud-agnostic verbs in its always-loaded starter set; a cloud lexicon contributes its own leaves through the same capability-plugin seam, loaded when a project’s chant.config.ts lists that lexicon (lexicons: ["aws"]). So a component’s cfn-deploy step resolves only when the aws lexicon is active — core itself has no AWS in it. See the cloud boundary.
| Family | Verbs | Plugin | Notes |
|---|---|---|---|
| build | docker-build, zip-package, jvm-build | core | source → archive, keyed by artifact type |
| sbom | generate-sbom | core | artifact-type-keyed software SBOM (native SPDX/CycloneDX, hermetic lockfile backend) into the build archive — see Build Archive |
| supply chain | sign, attest-provenance, verify, scan-vulnerabilities, vuln-gate | core | keyless cosign signing/attestation + a deploy-time verify gate and vulnerability policy gate |
| wait / verify (agnostic) | wait-cluster-healthy, wait-endpoint, health-gate | core | bolt-port cluster probe + HTTP readiness/health gates |
| secrets | ensure-secret | core | generated-once materialization (#1365): read-then-write — present means done, never mints over an existing value, never rotates implicitly; a presence / declared key-set / metadata mismatch stops the apply naming key names only; needs-opt-out rollback (COMP003); store adapters come from provider lexicons |
| escape hatch | shell | core | typed, lint-flagged |
| apply (Cloudflare Workers) | wrangler-deploy, wrangler-versions-promote, r2-sync | core | wraps the wrangler/rclone CLIs directly — the Workers plane is deliberately ceded to wrangler (no cloudflare lexicon owns these; #1293); r2-sync mirrors s3-sync’s input shape |
| sbom (config) | extract-config-bom | aws | IaC config-BOM (declared resources / nested stacks / external refs) from the synthesized CloudFormation template into the build archive |
| publish | publish-image, load-image-on-host, publish-asset / publish-artifact | aws | deploy-time, promote by identity; an interface with backends |
| apply | cfn-deploy, ecs-update-service, lambda-deploy, s3-sync, cdn-invalidate, run-migration | aws | cfn-deploy carries safety options (changeset preview, onReplace, stageGsi) |
| job submission | emr-start-job-run, emr-submit-step | aws | point a running compute service at an artifact; reusable for Glue / Batch / Step Functions |
| host / code delivery | code-deploy, copy-to-host, remote-exec | aws | code-deploy = AWS CodeDeploy; the others are SSM host verbs (SSH transport pending) |
| wait / verify (cloud) | wait-for-stack, wait-steady-state, wait-job | aws | CloudFormation / ECS / EMR terminal-state polls |
| safety / rollback | snapshot-before, rollback-previous | aws | DynamoDB/RDS/EBS snapshot + restore |
| apply | kubectl-apply, kustomize-apply | k8s | server-side apply as chant:<stack> with the marker-scoped prune; kustomize renders in front of the same pipeline |
| gitops | argo-app, flux-reconcile | k8s | apply the controller’s CR through the same stack-labelled pipeline, then wait for convergence — Healthy+Synced for Argo, Ready (terminal on wedge reasons) for Flux |
A genuinely new apply family (job submission was one) is written once and then reused across every component of that shape. A new component is a declaration only. This is why the family count grows sub-linearly against components — see the cloud boundary for how each family’s leaf maps per cloud.
Stickiness lives in the capability
Section titled “Stickiness lives in the capability”Some applies are sticky. A DynamoDB GSI updates one at a time; a key-schema change forces replacement; RDS and OpenSearch changes can trigger blue/green or replacement that loses data. That knowledge belongs inside the capability, configured by declarative options, not scripted per component:
phase("Apply", [ cfnDeploy({ stack: "ddb", template: "archive:ddb.template.json", onReplace: "block", stageGsi: true }), // sticky knowledge, captured once]);Every sticky resource reuses that one implementation. The alternative — each component scripting its own workaround — is exactly the sprawl the model exists to remove. Capabilities are allowed to be smart (preflight, guard, stage), not thin shells.
Cloudflare’s Worker versions are the same discipline paying off with a whole new cloud attached: wrangler-deploy returns the version id it just published, and wrangler-versions-promote moves live traffic to any version by id. Cloudflare’s own gradual-deployment rollback is exactly “promote to a prior version” — no bespoke undo to write — so wrangler-deploy declares a rollback that re-promotes to whatever version was live before it ran, landing on the same RollbackPolicy: "native" (../orchestration/#rollback-comes-free) every other native-rollback verb uses. r2-sync has no such native undo (an overwritten/deleted object is gone), so it carries needs-opt-out — the same disposition s3-sync does.
The discipline
Section titled “The discipline”The pattern holds only if variance stays behind the capability interface. Three guardrails, enforced by lint:
- Verbs, not nouns. A capability describes an operation (
cfn-deploy), never a component (deploy-search-service). A capability used by exactly one component is a smell. - No orchestrator special-casing. The driver takes only the contract. A
shellescape hatch is allowed but flagged, and must declare a reason. - No composition copy-paste. Identical compositions repeated across components are a declaration-sprawl signal — reach for a preset.
Presets — reuse without a closed set
Section titled “Presets — reuse without a closed set”Capabilities give own-destiny, but the common shapes should not be retyped. Presets are named compositions, shipped like the ApplyOp composite:
EcsFargateComponent({ service: "search", image: "search", healthPath: "/healthz" });A component uses a preset, or starts from one and drops to raw capabilities where it is special. Both produce the same contract and run through the same orchestrator, so presets remove copy-paste without creating a closed strategy set.
Read next
Section titled “Read next”- Composition & Wiring — arranging capabilities into a deploy.
- Cloud-Agnostic Boundary — which capabilities port across clouds and which are per-cloud leaves.