How chant compares
What chant is
Section titled “What chant is”chant is a compiler: TypeScript in, spec-native output out. You declare infrastructure as TypeScript objects, chant validates them against a lexicon’s semantic rules, and emits the target format, such as CloudFormation or GitLab CI.
The build path is pure synthesis: parsing, type-checking, linting, and emitting artifacts, with no API calls and no state. Deployment and drift are separate steps you choose: observational diff, cloud → code reconcile, or gated code → cloud apply, each built as an Op that reads the build output.
For the model behind the rows below — the three axes of state and where each tool sits — see Lifecycle Models.
Agents and automation increasingly deploy, monitor and remediate. chant does the deterministic, auditable part: turning typed declarations into spec-native output.
Nobody sells you a lifecycle
Section titled “Nobody sells you a lifecycle”Most infrastructure tools sell the compiler and the deployment lifecycle as one product. You could not get the compiler without the rest.
In chant, synthesis is pure and the output is native spec: standard CloudFormation, GitLab CI, or whatever the lexicon targets. If you stop using chant, you keep the output as is.
When you want deploy and drift steps, you write them yourself. Ops are declared as code in *.op.ts files and run on a local executor for one-shot work, or on a fountain steward when the work needs a machine of its own, a standing record, or a schedule.
Owning less
Section titled “Owning less”A pure synthesis core also owns less machinery. Because the build path makes no API calls, holds no state, and emits native spec, it needs no provider SDKs, state backend, credentials, or network at plan time. Turning source into output takes a type-checker, an evaluator over a static subset of TypeScript, and the lexicon packages for the formats you target.
A smaller build-time surface is a smaller thing to audit, and a smaller blast radius if something upstream is compromised: fewer packages execute during synthesis, and none of them reach the network or your cloud credentials. This follows from synthesis being side-effect-free; chant does not add it as a separate security feature. The same boundary is described value-by-value in Where Values Come From.
This applies to the build path only. The apply side (the CloudFormation CLI, a CI pipeline, an agent, an Op) carries its own dependencies and its own attack surface, which chant does not shrink.
Paradigms
Section titled “Paradigms”Configuration and infrastructure tools are grouped by several overlapping paradigms. Separating the terms shows where the tools differ.
- Infrastructure as Code (IaC) — infrastructure managed from machine-readable definitions rather than manual steps.
- Configuration as Code (CaC) — configuration authored in a programming language, versioned and reviewed like code.
- Configuration as Data (CaD) — configuration represented as declarative data, separated from the code that transforms it, and inspectable without running a program.
- Code as Data — the source is statically evaluable: it has no side effects and yields the same result without being executed.
A tool can satisfy more than one.
| IaC | Config as Code | Config as Data | Code as Data | |
|---|---|---|---|---|
| chant | ✓ | ✓ | ✓ | ✓ |
| ConfigHub | ✓ | — | ✓ | — |
| Formae | ✓ | ✓ | ✓ | ✓ |
| Terraform | ✓ | ✓ | — | — |
| CDK | ✓ | ✓ | — | — |
| Alchemy | ✓ | ✓ | — | — |
chant is a static subset of TypeScript (code as data) that compiles to native spec (configuration as data). Because the source is not executed, it satisfies the code and data paradigms at once.
The other rows sit on one side of that line. CDK executes its source, so the configuration exists as the output of a program run rather than as data available beforehand. Terraform authors configuration in a DSL (HCL) that is evaluated with functions and applied against state, so it is code rather than inert data. ConfigHub keeps configuration as data in a store, which places it in the data paradigms and not the code ones. Formae reaches all four the way chant does, through Pkl, a dedicated configuration language that evaluates to data. Formae and chant differ on the language and the runtime model: chant compiles a subset of a general-purpose language (TypeScript) and you invoke the lifecycle you want, where Formae uses a purpose-built language and an always-on agent that observes and reconciles.
Alchemy is the TypeScript-native tool on the executed side, and the one most worth naming explicitly: it is actively developed and its stated goal — infrastructure should feel like code, not configuration — is the same goal chant has. (Read against alchemy@2.0.0-beta.74, published under npm’s latest tag as of this writing; the project ships fast pre-1.0 betas, so treat specifics as dated rather than fixed.) Its resources are Effect programs — Layers composed together and run inside a Stack — and the desired-state graph exists only once that program runs to produce it, which lands Alchemy in the same cell as CDK: code and configuration-as-code, not data. The disagreement with chant is narrower than “code vs. config”: both agree infrastructure should feel like code; they disagree about whether that requires running it. Alchemy answers yes and gets composition, control flow, and Effect’s type safety by executing resources as async programs. chant answers no and gets the same code-feel — autocomplete, go-to-definition, refactoring, cross-file references — from a statically evaluable subset, without executing anything. typescript-as-data is chant’s answer to that question, not a rejection of Alchemy’s.
The paradigms say where each tool sits. Underneath, the mechanics differ.
| Authoring | Build model | Source of truth | Requires a store | Multi-cloud | |
|---|---|---|---|---|---|
| chant | TypeScript (static subset) | Compiled | Source + live system | No | Yes |
| AWS CDK | TypeScript, Python, and others | Executed | CloudFormation | No (AWS-managed) | No |
| Terraform | HCL | Executed | State file | Yes (state file) | Yes |
| ConfigHub | YAML data | Stored | The store | Yes (database) | Yes |
| Formae | Pkl | Compiled | Source | No | Yes |
| Alchemy | TypeScript (Effect-based) | Executed | State store | Yes | Yes |
Build model is how source becomes the applied result: compiled (evaluated to data with no side effects), executed (run as a program, which can reach the environment and vary by run), or stored (configuration is already data in a store, applied by a worker). chant and Formae are the compiled, storeless pair, and differ in the authoring language: a subset of a general-purpose language versus a purpose-built one. ConfigHub keeps the data in a database. CDK, Terraform, and Alchemy execute their source. Alchemy’s store is not a single mandatory backend — the default is a local .alchemy/state/ directory, with a Cloudflare Durable Object/D1-backed store available for team or CI use — but a store is always required.
Design decisions and their consequences
Section titled “Design decisions and their consequences”Each design choice below suits some workflows and not others.
| Decision | Terraform | CDK | chant |
|---|---|---|---|
| Language | HCL (custom DSL) | TypeScript, Python, Java, Go, C# (executed) | TypeScript (static subset) |
| Spec fidelity | Provider-mapped | Abstracted (L2/L3) | Spec-true |
| Output | State + API calls | CloudFormation | Native spec output |
| Lifecycle scope | Full | Full (via CloudFormation) | Deterministic core + chosen lifecycle (observe / reconcile / apply) |
| State model | Authoritative | Authoritative (via CloudFormation) | No authoritative state file; precise change set via live projection + cloud-side ownership |
| Reconciliation | code → cloud | code → cloud | none / code → cloud / cloud → code (chosen per environment) |
| Determinism | Mostly | No (code executes) | Yes (for lint-conforming source) |
| Walk-away cost | High (state, HCL) | Medium (CloudFormation remains) | Zero (output is native) |
| Semantic validation | None built-in | None built-in | Built-in (lexicon rules) |
| Drift detection | Built-in (plan / refresh) | Via CloudFormation drift detection | Built-in (lifecycle diff --live against any of 5 cloud lexicons + 4 artifact lexicons) |
Spec fidelity means whether the tool’s resource model matches the underlying spec 1:1. CDK’s L2 and L3 constructs deliberately abstract away from CloudFormation — that’s a feature for productivity and a cost for transparency. Terraform providers map closely but not identically to provider APIs. chant lexicons are generated directly from the target spec.
Walk-away cost is what happens if you stop using the tool. Terraform leaves you with a state file and tool-specific code. CDK leaves CloudFormation templates that continue to work. chant leaves native spec output — standard CloudFormation templates, GitLab CI files, whatever the lexicon targets. There’s nothing chant-specific in the output.
The execution model difference
Section titled “The execution model difference”The tools differ most in what happens when you “run” them:
Terraform: HCL is parsed, a dependency graph is built, the provider checks current state against desired state, and changes are applied. The state file is the source of truth.
CDK: Your TypeScript executes. Constructors run, methods are called, side effects happen. The result is captured as a CloudFormation template, which is then deployed. The execution can be nondeterministic — same code, different environment, potentially different output.
Alchemy: Your TypeScript also executes, by design. Resources are Effect programs — Layers composed together and run inside a Stack — and the desired-state graph exists only once that run has produced it. Planning, applying, and destroying are separate CLI verbs, so there’s a real preview step before anything changes; the graph the preview walks is still the output of a program run, not data that existed beforehand. Alchemy does this on purpose: running the code is what buys composition, control flow, and Effect’s type safety.
chant: by default, chant reduces your TypeScript directly from the AST to the resource objects it declares, executing none of it. Synthesis does nothing else. No API calls, no state, no deployment. Source outside that foldable subset falls back to being imported and run, per file; a separate evaluability lint pass (EVL) restricts source to literals, constants, and cross-resource references, so that executed result is equivalent to static data too. Same conforming source always produces the same output — see Folded vs Run.
This matters for:
- Determinism — chant’s output is a pure function of its input. No environment variables, no network calls, no runtime state.
- Auditability — every value in the output traces to a specific line in the source.
- Speed — no interpreter, no runtime, no provider calls. Synthesis is fast.
- IDE support — because the API surface is defined by types (not runtime behavior), autocompletion, inline errors, and go-to-definition work without executing anything.
- Agent-friendliness — agents can generate chant source files and know exactly what output they’ll produce. No surprises from execution-time behavior.
State: authoritative vs. observational
Section titled “State: authoritative vs. observational”The most consequential design difference after execution model is the lifecycle model.
Terraform treats state as authoritative — the state file IS the source of truth for what’s deployed. This enables precise plan/apply workflows: the tool knows exactly what exists, what changed, and what to do. The cost is operational burden: state must be locked during operations, stored in a backend with access controls, and protected from corruption. Sensitive data (database passwords, API keys) ends up in state because the tool needs it for diffing.
CDK inherits CloudFormation’s authoritative lifecycle model, which AWS manages as a service. Less operational burden, but the same architectural commitment: state is truth.
Alchemy looks the closest to chant’s model on the surface — state defaults to local files in .alchemy/state/, in-repo, inspectable, no separate service (a Cloudflare-backed store is available for team or CI use) — but the role is different. Alchemy’s reconcile step is documented as convergent: it reads live cloud state before deciding what to do, and can rediscover a resource by its deterministic physical name even without its prior state. Ownership is also read live — a provider returns Unowned(attrs) for a resource that carries someone else’s tags, and by default that fails the run (OwnedBySomeoneElse) unless the operator opts in with --adopt. That narrows the gap with chant’s live-projection model. What doesn’t narrow: the stored state, not a live query, is still what tells Alchemy which resources exist and are eligible to be deleted on a plan — the same role a Terraform state file plays, with a lighter operational cost. Same storage mechanism as a chant snapshot; a different role. chant’s snapshot is evidence that can be deleted between runs with no change in behavior. Alchemy’s state store drives the plan.
chant is designed with an observational lifecycle model — snapshots record what was observed at a point in time, not what must be true now. It does not give up the precise change set, though: chant computes a create/update/delete plan against the live system using cloud-side ownership markers rather than a hosted state file. What it drops is the authoritative file, and with it the costs:
- No locking — a stale snapshot is inconvenient, not dangerous
- No sensitive data — scrubbing is a design requirement, not a best-effort guideline
- No corruption risk — a bad snapshot doesn’t break deployments. Agents verify against live APIs regardless
- Minimal overhead — state is designed to live in git alongside source code
Trade-offs:
- Ownership is answered by a live marker on the resource, so the projection must query live APIs; a resource with no marker is
foreignand escalates to human review rather than being auto-deleted - Git-based snapshots give versioning, portability, and offline access, but no locking, no encryption at rest, and no access control separation from source code
What each tool optimizes for
Section titled “What each tool optimizes for”Terraform optimizes for multi-cloud lifecycle management with a unified workflow. One tool, one language, one lifecycle model across AWS, Azure, GCP, and hundreds of providers. The trade-off: HCL is a custom DSL with limited expressiveness, and the state file creates operational overhead.
CDK optimizes for AWS-native development with familiar languages and high-level abstractions. L2/L3 constructs encode AWS best practices and reduce boilerplate. The trade-off: abstractions obscure what’s actually deployed, and code execution means builds can be nondeterministic.
chant optimizes for typed, linted, deterministic synthesis that’s true to the underlying spec. Deployment is a separate step. The output is the platform’s native format, with no lock-in. The trade-off: the build path itself doesn’t apply, so you deploy through a chant Op or hand the artifact to your own tool.
Pipeline engines
Section titled “Pipeline engines”The tools above are compared on infrastructure. A pipeline engine asks a different question: where does the pipeline itself run? chant answers at build time. It compiles a typed pipeline to each forge’s own YAML. The forge runs that YAML with nothing extra installed, and its own tooling keeps working.
| Where the pipeline is defined | What runs in a CI job | Step isolation | Cache | |
|---|---|---|---|---|
| chant | TypeScript, compiled to the forge’s YAML | The forge’s runner and the commands chant wrote | A shell() step can run in a container pinned by digest | The forge’s |
| Dagger | Functions in modules (Go, Python, TypeScript and others) | The Dagger engine, in every job | Each operation runs in a container | Content-addressed, kept by the engine |
| Bazel | BUILD files in Starlark | Bazel, in every job | Sandboxed actions keyed by declared inputs | Local and remote, keyed by inputs |
Dagger moves the pipeline to run time: the forge’s job starts the engine, and the engine runs the graph. That buys the same pipeline locally and in CI and a cache that knows each step’s inputs. The cost is an engine in each job and a pipeline the forge sees only as one opaque step. Dagger also changed engines in 2026: v0.21 began replacing BuildKit’s solver with its own, and a new module format is still in development.
Bazel is the reference for a hermetic step: an action runs in a sandbox and its cache key covers every input it declares. The price is declaring every input, which chant’s steps don’t do today.
chant stays at build time on purpose. A generated pull-request pipeline’s jobs are typed values that each forge’s generator wraps, and chant ci run --local runs the same values without reading YAML. github-warden reads the job names chant generates and requires them as checks, which works because the jobs are the forge’s own. Where a step needs a pinned toolchain, give it an image (A shell step in a container). A Dagger runtime for Ops could plug into the opRuntime hook as a lexicon without changing this.
Trade-offs
Section titled “Trade-offs”chant’s design decisions have real consequences:
-
The build path doesn’t apply. A chant Op deploys the artifact, locally or on a steward, or your own tooling does, such as the CloudFormation CLI or a CI pipeline. The build stays pure so you choose that step.
-
Static evaluation means no runtime lookups at build time. You can’t call an API during synthesis to get a VPC ID or read a secret. Cross-resource references within a project work (via attribute references). For values that need to be resolved at deploy time, lexicons provide intrinsics. For values that need to be resolved before synthesis, agents or scripts can populate source files.
-
Lexicon ecosystem is young, but covering. Twenty-two lexicons ship today (AWS CloudFormation, Azure ARM, GCP Config Connector, Kubernetes, Helm, k3d, k3s, Docker, GitHub Actions, GitLab CI, Forgejo Actions, Fly Machines, Fountain, E2B, Render, Control Plane, Cedar, Terraform, OpenTelemetry Collector, Prometheus, Grafana, and SQL — Forgejo is a thin GitHub-Actions dialect, and SQL, ClickHouse first, is still in progress). The architecture supports any spec-based operational domain — see Lexicons for the current set and Lexicon Authoring for adding new ones.
-
The static subset of TypeScript is a constraint. No function calls, no control flow around resources, no dynamic property access. This is what makes synthesis deterministic, but it means some patterns that work in CDK don’t work in chant. See TypeScript as Data for exactly what’s supported.