chant lifecycle
Synopsis
Section titled “Synopsis”chant lifecycle snapshot <env> [lexicon] [--src <dir>]chant lifecycle show <env>chant lifecycle diff <env> [--live] [--src <dir>]chant lifecycle diff <env> --between <refA> <refB> [lexicon] [--json]chant lifecycle plan <env> [lexicon] [--json] [--owned] [--report gitlab-mr] [--src <dir>]chant lifecycle affected --base <ref> [--head <ref>] [--include-dependents] [--json]chant lifecycle log [env]Description
Section titled “Description”chant lifecycle captures point-in-time snapshots of deployed infrastructure by querying the cloud provider API and saving the result to a git orphan branch. Snapshots enable drift detection — comparing what’s currently built against what was last deployed, and (with --live) against what’s actually in the cloud right now.
The environment name (e.g. dev, staging, prod) must be declared in chant.config.ts under environments.
snapshot, diff, and plan build your declarations to compare against live state. By default they build the project root (.). For a mixed-layout project — chant infrastructure in src/ alongside application code that has import side effects (starts a server, opens a connection) — set sourceDir in chant.config.ts so the lifecycle build only synthesizes the infra:
export default { lexicons: ["k8s"], sourceDir: "src" };The --src <dir> flag overrides sourceDir for a single invocation. Use the same source root for snapshot and diff so their build digests stay comparable.
For the conceptual model behind these commands — observational vs. authoritative state, resources vs. artifacts, when drift detection earns its keep — see Drift Detection.
Subcommands
Section titled “Subcommands”lifecycle snapshot <env>
Section titled “lifecycle snapshot <env>”Query the provider API for the current deployed state, then save a snapshot to the chant/lifecycle orphan branch.
chant lifecycle snapshot devchant lifecycle snapshot prod aws # snapshot only the aws lexiconlifecycle show <env>
Section titled “lifecycle show <env>”Print the latest saved snapshot for an environment.
chant lifecycle show devlifecycle diff <env>
Section titled “lifecycle diff <env>”Two modes, depending on whether --live is set.
Default (digest mode). Build the current project, fingerprint the resource declarations, and compare against the digest stored in the last snapshot. Fast, offline, and useful as a fast-feedback check inside CI — but only catches changes you’ve made in source. Ignores the cloud entirely.
chant lifecycle diff devReports added, removed, changed, and unchanged resources at the declaration level.
--live (drift mode). Query the cloud provider API right now via each lexicon’s describeResources() plugin method, then compare the live result against both the previous snapshot and the current build. This is the path that actually catches external mutations (someone scaled a node pool by hand, deleted a bucket, etc.).
chant lifecycle diff prod --liveOutput is grouped into six categories per lexicon:
| Category | Meaning |
|---|---|
| missing | Declared in source, but not present in the cloud right now |
| orphan | Present in the cloud, but not declared in source |
| disappeared | In the previous snapshot, but gone now |
| newly observed | Declared and observed now, but not in any prior snapshot |
| drifted | Observed in both snapshots; status, physical ID, or attributes changed |
| unchanged | Observed in both snapshots; metadata identical |
Drifted entries include attribute-level deltas (status, physicalId, lastUpdated, and each attributes.* key).
--between <refA> <refB> — two saved snapshots
Section titled “--between <refA> <refB> — two saved snapshots”Every snapshot is a commit on the chant/lifecycle orphan branch (list them with lifecycle log). --between diffs two of them against each other — a purely historical, read-only comparison (no build, no cloud query). It answers “what changed in the cloud between Tuesday and Thursday” independent of source.
chant lifecycle log prod # find the two commitschant lifecycle diff prod --between <shaA> <shaB>chant lifecycle diff prod --between <shaA> <shaB> aws --jsonPer lexicon (all declared, or the one you name), it reports a two-way delta: added (in B, not A), removed (in A, not B), changed (in both, metadata differs, with the attribute paths), and unchanged. --json emits { environment, refA, refB, lexicons }. This feeds the deployment-lanes frame-pair diff.
lifecycle plan <env>
Section titled “lifecycle plan <env>”Promote the live diff to a typed, read-only change set. Where lifecycle diff --live reports six observation categories, lifecycle plan classifies each resource into one action you can act on:
chant lifecycle plan prodchant lifecycle plan prod --json # emit the ChangeSet as JSON| Action | Meaning |
|---|---|
| create | Declared in source, absent from the cloud |
| update | Declared and live, but the live config drifted |
| delete | A chant-owned resource that is live but no longer declared |
| adopt | Live but undeclared, ownership not established — a candidate to pull back into source, never an auto-delete |
| noop | Declared and live with no drift, or already reconciled |
create and update are precise from declared-vs-live. delete is only ever proposed for an undeclared resource whose live ownership marker (ownership marking) confirms it is chant’s; a foreign orphan is adopt, and an orphan with no marker data is adopt, never delete. Pass --owned to restrict the query to chant-owned resources.
The classification reads ownership from the live marker, never from the snapshot, so the snapshot never becomes load-bearing: the delete decision is identical whether or not a snapshot exists. The moment a mutation trusted the snapshot, the snapshot would have become an authoritative state file under a different name — which chant deliberately avoids.
lifecycle plan is strictly read-only: it builds, queries, and classifies. It never mutates the cloud and never deploys — chant build stays pure.
Crosswalk: observation categories → plan actions
Section titled “Crosswalk: observation categories → plan actions”The six diff --live categories and the five plan actions describe the same signal on different axes. Observation categories key on three sets — declared in source, observed now, in the last snapshot. The plan collapses the snapshot axis (it’s evidence, never authority) and adds an ownership axis. The mapping:
Observation category (diff --live) | Plan action (plan) | Why |
|---|---|---|
| missing | create | Declared, absent from the cloud |
| drifted | update (when declared) | Declared and live, attributes changed since the snapshot |
| orphan | adopt, or delete when the live ownership marker confirms it’s chant’s | The one decision the marker makes — never the snapshot |
| newly observed | noop | Declared and live; no snapshot baseline to drift against yet |
| unchanged | noop (when declared) | Declared and live, identical |
| disappeared | create if still declared, else noop | A snapshot-history signal; the plan reads live + source, not snapshot age |
The “when declared” qualifiers are where the axes diverge: an undeclared resource that also drifted or is unchanged is still an orphan to the plan — the snapshot history doesn’t change that it’s live-but-undeclared, so ownership decides between adopt and delete.
Reporting to a GitLab merge request
Section titled “Reporting to a GitLab merge request”--report gitlab-mr emits the plan as the JSON GitLab reads for its merge-request plan widget:
chant lifecycle plan prod --report gitlab-mr > tfplan.json# {"create":3,"update":1,"delete":0}Declare the file as artifacts:reports:terraform and GitLab renders “3 to add, 1 to change, 0 to delete” on the MR. The widget format is generic — any tool that emits this JSON gets it — so the plan maps onto it directly. Only the mutating actions count: adopt and noop are excluded, since the widget has no column for live-but-undeclared or no-change.
The widget label always reads “Terraform” — that is GitLab’s fixed string, not a claim chant makes. The GitLab lexicon ships an MrPlanReport composite that wires the job for you.
Resources vs artifacts
Section titled “Resources vs artifacts”A single lexicon may report both resources (entity-keyed, via describeResources()) and artifacts (context-keyed, via listArtifacts()); lifecycle diff --live shows them in separate sections. Artifacts use a four-category two-way diff (no “declared” axis):
| Category | Meaning |
|---|---|
| artifacts added | Observed now, not in last snapshot |
| artifacts removed | In last snapshot, gone now |
| artifacts changed | In both; metadata changed |
| artifacts unchanged | In both; metadata identical |
For the conceptual distinction between resources and artifacts, see Drift Detection — Resources and artifacts.
Coverage today
Section titled “Coverage today”See the Runtime observation coverage matrix on the Lexicons overview page for the canonical version with query-mechanism notes. Summary:
| Lexicon | describeResources() | listArtifacts() |
|---|---|---|
| AWS | ✅ | |
| Temporal | ✅ | |
| K8s | ✅ | |
| GCP (via Config Connector) | ✅ | |
| Azure | ✅ | |
| Helm | ✅ | |
| Docker | ✅ | |
| GitHub / GitLab | N/A — see lexicon READMEs |
Lexicons that implement neither method are warn-skipped — --live doesn’t fail the whole command for them.
The Temporal lexicon resolves its connection via the chant config: lifecycle snapshot <env> (or --live against <env>) looks up temporal.profiles.<env> in your chant.config.ts and falls back to temporal.defaultProfile. The same profile model is used by chant run. With #39’s entity-prop pass-through, namespaces/schedules/search-attributes are mapped back to chant entity names when the declared props.name (etc.) match the server-side identifier.
The K8s lexicon shells out to kubectl get <kind> <name> [-n <namespace>] -o json per declared entity. The cluster context comes from the user’s kubeconfig — there’s no separate profile model. Twenty common resource types (Deployment, Service, ConfigMap, Secret, Namespace, Pod, PVC, ServiceAccount, Job, CronJob, Ingress, NetworkPolicy, RBAC roles + bindings, ReplicaSet, StatefulSet, DaemonSet) are covered out of the box; CRDs and uncommon types are warn-skipped.
The GCP lexicon uses the same kubectl shell-out path against Config Connector CRDs. The entity type’s GVK derivation matches the serializer (GCP::Storage::Bucket → storagebucket.storage.cnrm.cloud.google.com), so any of the ~300 chant-supported GCP resources work without per-type configuration. Ready=True on the resource’s status is mapped to READY; otherwise the condition’s reason is surfaced.
The Azure lexicon shells out to az resource show --resource-group <env> --name <name> --resource-type <type> -o json per declared entity. The <env> argument is treated as the Azure resource group name. properties.provisioningState becomes the status. Top-level resources only — nested ARM types (e.g. Microsoft.Storage/storageAccounts/blobServices) are warn-skipped since az resource show doesn’t accept compound type names directly.
Concurrent snapshots
Section titled “Concurrent snapshots”Snapshots are pushed to the orphan chant/lifecycle branch with git push --force-with-lease. If two operators (or a CI job + a human) take a snapshot for the same remote at the same time, the second push is rejected rather than silently overwriting the first. You’ll see:
error: Another snapshot completed for chant/lifecycle after this run started (env: prod).hint: Pull and retry: `git fetch origin chant/lifecycle:chant/lifecycle` && `chant lifecycle snapshot prod`.Pull the remote ref and re-run — the first snapshot is preserved, the second proceeds against the updated baseline.
lifecycle affected
Section titled “lifecycle affected”In a multi-stack project, answer “which stacks does this change affect?” without planning everything. Read-only — it returns the set; fanning lifecycle plan / an ApplyOp over it is an Op you compose.
chant lifecycle affected --base origin/mainchant lifecycle affected --base origin/main --include-dependents --json“Affected” is two distinct things, because chant serializes cross-stack references symbolically:
- Directly changed — the stack’s built artifact differs between base and head. Caught by artifact diff over deterministic builds, so a comment-only or refactor-with-no-output-change edit is correctly excluded.
- Operationally affected (dependents) — a stack whose own artifact is unchanged but which consumes an upstream export whose value changed. Its bytes don’t move, yet it may need re-apply. Caught by walking the cross-stack graph (
chant graph --stacks) from the directly-changed set, with--include-dependents.
A stack whose inputs come from outside synthesis (deploy-time parameters) cannot be judged from a source diff — it is reported as indeterminate (“external-input — cannot confirm from source”), not silently included or excluded.
Builds are obtained lazily: head is the in-place build of the working tree (or --head <ref>); base is built from --base <ref> in a single throwaway worktree (auto-removed), or skipped entirely if you supply pre-built sources via the library. Because build is deterministic, a cached or supplied base is as trustworthy as a rebuild.
--json emits { changed, dependents, indeterminate }. The same logic is exported as affectedStacks() for programmatic use.
Composing it. chant returns the set; you drive the fan-out. For example, plan only the affected stacks in CI:
chant lifecycle affected --base "$BASE_SHA" --include-dependents --json \ | jq -r '.changed + .dependents | unique[]' \ | while read stack; do chant lifecycle plan "$ENV" "$stack"; doneThe same shape works from an Op you write — affected is the primitive; the fan-out (plan, gate, apply) is yours to compose. chant never blindly applies the set.
| Flag | Meaning |
|---|---|
--base <ref> | Base git ref to diff against (required). |
--head <ref> | Head ref (default: the working tree). |
--include-dependents | Add downstream consumers of changed stacks. |
--json | Emit the result as JSON. |
lifecycle log [env]
Section titled “lifecycle log [env]”List the history of snapshots. Omit env to show all environments.
chant lifecycle log # all environmentschant lifecycle log dev # just devExit Codes
Section titled “Exit Codes”| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (invalid environment, API failure, no snapshot found) |
See Also
Section titled “See Also”chant build— build current output for diffing- Configuration — declare
environmentsinchant.config.ts - Watching Lifecycle — wire
lifecycle snapshot+lifecycle diff --liveto aTemporalSchedulevia theWatchOpcomposite - Implementing Observation — for lexicon authors, how to plug into
--liveviadescribeResources()/listArtifacts()