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 |
| escape hatch | shell | core | typed, lint-flagged |
| 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 |
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.
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.