Contributing a Lifecycle
chant’s core is a compiler: pure, deterministic, lint-gated TypeScript-to-spec compilation. Everything past synthesis — state, apply, drift, reconcile, retire — is the lifecycle, a first-class layer you build on that core rather than a fixed part of it. The shape of the project is:
Pluggable lexicons in. Pluggable lifecycles out. Pure synthesis in the middle.
Lexicons are the established pluggable axis. This page is about the other one: how you bring your own lifecycle. There are two doors.
Door 1 — Write a lifecycle as Ops
Section titled “Door 1 — Write a lifecycle as Ops”The first door ships today. A lifecycle is just orchestration over chant’s primitives, and you declare orchestration as Ops in *.op.ts files — named, phased workflows that run on the local executor or, when they need a machine of their own, on a fountain steward.
The built-in lifecycle composites are themselves nothing more than Ops you could have written:
WatchOp— observe: snapshot + live diff on a schedule.ReconcileOp—cloud → code: open PRs when live drifts from source.ApplyOp—code → cloud: apply through the target’s own delete path, owned-only on every target (what bounds each one).
To contribute a lifecycle this way, compose the primitives your workflow needs:
chant lifecycle plan <env>— the typed change set against live (CLI).chant import --from <env>— regenerate TypeScript from live state (Live Import).- The pre-built Op activities (
lifecycleSnapshot,lifecycleDiff,reconcilePr,nativeApply,compensateApply) plus your own. - Gate steps and
onFailurecompensation when the workflow must survive a crash or hold a human approval.
Package a reusable one as a composite that returns an Op carrying its own schedule, exactly as the built-ins do — see packages/core/src/op/composites/ for reference. That is a complete, shippable lifecycle.
Door 2 — Plug an existing lifecycle in
Section titled “Door 2 — Plug an existing lifecycle in”The second door is the direction, not a finished feature. The architecture is pluggable: the seams a foreign lifecycle would attach to already exist —
- Per-lexicon live access —
describeResources()(scrubbed observation) andexportResources()(full-fidelity export) give a lifecycle a typed view of live infrastructure without a hosted state file. - Ownership markers — provider-native tags/labels (ownership marking) let any lifecycle answer “is this mine?” from the resource itself.
- A runtime boundary —
chant run --on <lexicon>is a clean interface between declaring a workflow and choosing what hosts it.
The intended end state is that a vendor’s whole product becomes one plugin under chant’s pure synthesis — you don’t rip out Terraform, you plug it in as a lifecycle backend, and chant owns the socket.
Why two doors, not one
Section titled “Why two doors, not one”The two doors close the gap BYOL could otherwise open. “Bring your own lifecycle” must never read as “you’re on your own”: Door 1 gives you durable rails to build on today (chant makes it durable), and Door 2 means an existing investment isn’t thrown away — it’s plugged in. Own the socket, outlast the lifecycle war.
See also
Section titled “See also”lifecycle-reconcile-awsexample — a Door 1 lifecycle (ReconcileOp + ApplyOp) wired to a real stack- Lifecycle Models — the three axes and the observe -> reconcile -> authoritative dial
- Ops — declaring orchestration in
*.op.ts - Ops as teammates — when a lifecycle wants a hosted runtime
- Lexicon Authoring — the other pluggable axis