Contributing a Lifecycle
Show the whole prompt
Read https://intentius.io/chant/contributing/lifecycles/.
Write the lifecycle I describe as an Op composite in `*.op.ts`, built from the lifecycle activities the page lists. Give it gate steps and `onFailure` compensation where it holds an approval, and write a test for it.
Run `npx chant build`, `npx chant lint` and the tests. Run the Op against a local emulator with `chant run <op> --no-record` and show me the output. Then open a pull request.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Optional: hand this page to your coding agent
Read https://intentius.io/chant/contributing/lifecycles/.Write the lifecycle I describe as an Op composite in `*.op.ts`, built from the lifecycle activities the page lists. Give it gate steps and `onFailure` compensation where it holds an approval, and write a test for it.Run `npx chant build`, `npx chant lint` and the tests. Run the Op against a local emulator with `chant run <op> --no-record` and show me the output. Then open a pull request.Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.chant’s core is a compiler: pure, deterministic, lint-gated TypeScript-to-spec compilation. Everything after synthesis, such as apply and drift, is the lifecycle. You build it on the core and can swap it out. 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 rails to build on today (chant runs the lifecycle you compose), and Door 2 means an existing investment isn’t thrown away — it’s plugged in.
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