What's New
A hand-curated rollup of capabilities shipped recently. If you learned chant before this lands, this page is the catch-up. Each entry links to the closing issue or PR for the full context.
For the day-to-day reference docs see Lifecycle Models, chant lifecycle, Live Import, Reconciling Lifecycle, Drift Detection, and Implementing Observation.
The fountain lexicon follows Managoat’s removals
Section titled “The fountain lexicon follows Managoat’s removals”Managoat removed the team and persistent sandboxes, with the team’s schedules and webhooks, and the fountain lexicon is now generated from Managoat v0.29.0, whose spec has none of them. managoat/managoat#2793 tracks the change across both projects. A steward is an acp Agent. chant run <op> --on fountain sends its runs to one long-lived conversation resumed by channel_id, and that conversation’s sandbox keeps its disk across park and wake. Its cadences fire from a clock outside Managoat. Teammate, Schedule and Webhook are deprecated stubs that fail the build with FTN025, which names the replacement, and a profile’s team is now steward. Coming from Teammate, Schedule and Webhook maps each removed construct to what replaced it.
Postgres schemas in the sql lexicon
Section titled “Postgres schemas in the sql lexicon”The sql lexicon has a second dialect, Postgres, at @intentius/chant-lexicon-sql/postgres. Each schema object is written as its own CREATE statement in a tagged template, with COMMENT ON statements after it, and nextval(${seq}) keeps a sequence a reference. The types come from one pinned postgres release per supported major, 14 to 18. sql.postgresMajor picks the major a project targets, and four lint rules and eighteen post-synth checks hold a schema to it.
chant sql diff and chant sql plan classify every change by the lock it takes and cite the Postgres 18 page for each rule. postgresApply, the postgres target of ApplyOp, groups its statements into transactions by that class and runs CONCURRENTLY builds outside them, each statement under a lock timeout. A column rename or a type change across kinds is refused in place and runs as PostgresMigrationOp, which fills the new column in batches with a receipt committed in each batch’s transaction and waits at a gate before the switch and again before the old column is dropped. Import reads canonical text from the catalog’s printers and leaves out what an ORM, an extension or a managed provider owns. The overview explains why the two dialects share a way of working and little else; like the ClickHouse dialect, it runs from a checkout until the package is published.
| # | Capability |
|---|---|
| #3277, #3278 | The spikes: types from pg_catalog at one pin per major, and a hand-written Postgres DDL parser |
| #3279 | A dialect-neutral core, the tags and entities, import and observation, and the lock classifier |
| #3280 | postgresApply: transactions, CONCURRENTLY, timeouts, the comment ownership marker, prune, and chant emulator up for Postgres |
| #3281 | PostgresMigrationOp, the expand-and-contract migration of a column |
| #3282 | Managed providers as configuration, and SQLPG004 |
| #3283 | Completions, hover and MCP tools for Postgres templates |
| #3284 | The Postgres declaring, plan and migration skills, and three init templates |
| #3285 | Post-synth checks SQLPG101 to SQLPG118 |
| #3286 | The SoftDeleteTable, AuditLogTable, JoinTable, TenantTable and RefreshedView composites, and five examples |
| #3297 | A lexicon can declare several upstream pins; chant dev pinned-upgrade reports each Postgres major |
ClickHouse schemas in the sql lexicon
Section titled “ClickHouse schemas in the sql lexicon”@intentius/chant-lexicon-sql declares database schema in TypeScript, one dialect at a time, and ClickHouse is the first, at @intentius/chant-lexicon-sql/clickhouse. Every schema object is written as its own CREATE statement inside a tagged template named for its kind. chant build parses each template while it folds the file, so an interpolated table or column stays a reference: references order the build and give each view column-level lineage. The ClickHouse types are generated from the system.* tables of a pinned clickhouse-server release, and three lint rules and twenty post-synth checks hold a schema to what that server accepts.
chant import --from <env> writes a running server’s schema as declarations. chant sql diff and chant sql plan classify every change as metadata only, a background rewrite or a rebuild, each with the ClickHouse ALTER restriction behind it. clickhouseApply, the clickhouse target of ApplyOp, makes the first two kinds of change and refuses a rebuild, which runs instead as ClickHouseRebuildOp: a gated Op that fills a new table partition by partition with a receipt for each, verifies it, and swaps it in. chant’s ownership marker is a trailer on each object’s comment. The lexicon is spec-true per dialect rather than database-agnostic, and its overview explains why. The package is not on npm yet.
| # | Capability |
|---|---|
| #3195, #3196 | The spikes: types read from a pinned server’s catalog, and a lossless ClickHouse DDL parser behind the tags |
| #3197 | Tables and views as tagged templates, import from a live server, observation, and the classified plan |
| #3208 | clickhouseApply, the comment ownership marker, and chant emulator up --lexicon sql |
| #3210 | Completions and hover inside the templates, and MCP tools and resources |
| #3211 | Skills, init templates and template detection |
| #3213 | Post-synth checks SQLCH102 to SQLCH120 |
| #3198 | ClickHouseRebuildOp: dual write, a backfill with receipts, verification, a plan-bound swap gate, and retention of the old table |
| #3248 | The ReplacingTable, EventsTable, RollupView, CdcMirror and ShardedTable composites |
A component gate approval is bound to its environment and plan
Section titled “A component gate approval is bound to its environment and plan”A component gate approval used to be recorded under the component and the gate name only. An approval given for a deploy to staging could pass the same gate on a later deploy to prod, and so could a promote or a rollback. Each approval now records the environment and the plan digest it was given for, and chant run --components, chant components promote, chant components rollback and chant components redeploy accept only an approval for their own. The plan covers the composition as the run executes it, the environment and, for a promote, rollback or redeploy, the digest being deployed. chant approve --env <env> picks which environment’s pending fact to approve.
This is a listed level-0 exception. Approvals recorded before it are refused, and the refusal prints the chant approve command that replaces them. v0.80.0 warned whenever a component gate passed on such an approval.
| # | Capability |
|---|---|
| #2574 | Component gate approvals bound to environment and plan digest, and chant approve --env |
Digests chant computes are real SHA-256
Section titled “Digests chant computes are real SHA-256”contentDigest labelled its output sha256: but computed a 32-bit string hash repeated to 64 hex characters. It now returns "sha256:" + sha256(utf8(content)), so every manifestDigest and every digest of a synthesized template or BOM changes once for the same content. Image digests from docker build and other tools were already real and are unchanged. chant 0.80.0 warned about this a release ahead, as the level-0 exceptions require.
Old values in a release ledger or in persisted build manifests are never rewritten. They read back flagged legacy-digest, still resolve by their old digest, and are accepted through the 0.89.x releases. Digests made before real SHA-256 has the details.
Everything else core hashes now goes through the same contentDigest over canonicalJson. Two values can move once as a result, because the old code sorted object keys by locale rather than by code unit. A plan digest can change for a plan whose keys mix upper and lower case, or digits and punctuation. An Op gate approved for such a plan before the upgrade stops and asks for chant approve again. Nothing is applied on a stale approval. The props hashes in a lifecycle snapshot can change the same way, so the first chant lifecycle diff <env> against a snapshot taken before the upgrade may list a few resources as changed.
| # | Capability |
|---|---|
| #2514 | Real SHA-256 in contentDigest, manifest and SBOM digests, with old ledger entries flagged legacy-digest |
Level-0 output is pinned by golden tests
Section titled “Level-0 output is pinned by golden tests”Workspaces are coming in levels, and a project with no chant.workspace.json stays at level 0, where nothing changes. On every pull request, golden tests now run the five level-0 commands over copies of five example projects and compare each output with a committed file. A git fixture pins the ledger paths and ownership markers next to them. The few changes allowed to alter level-0 output are listed, with their issues and warning releases, on Level-0 Exceptions. None has shipped a warning yet.
| # | Capability |
|---|---|
| #2526 | CLI goldens for level 0, the ledger and marker fixture, and a check that no workspace module loads |
Ops run on the local runtime and on a fountain steward
Section titled “Ops run on the local runtime and on a fountain steward”Temporal is gone. An Op no longer compiles to workflow code for an engine you have to run: chant run <op> executes the phases in this process, and chant run <op> --on fountain hands the same declaration to a steward, one teammate on a persistent sandbox whose thread becomes that environment’s operational history. The temporal lexicon, the --temporal flag and the generated worker are all removed. The bridge that caught --temporal ahead of the parser and exited 2 with a line pointing at --on fountain shipped in 0.58.0 only, and #2204 took it back out for 0.59.0, so the flag now fails as an unknown flag like any other.
What used to need a workflow engine now lives in three places nothing has to host. A gate is a fact on the gate ledger: a run that reaches an unapproved one records that it is waiting and exits 3, chant approve <op> <gate> writes the resolution, and the next run reads both and walks through. Nothing is suspended, so nothing has to survive being suspended. The field naming a gate is gate, the same word chant approve takes on the command line, renamed from signalName by #2202 once 0.58.0 had shipped with the old spelling; the old key is still read and warns once at build, and it goes away in 0.60.0. A cadence is schedule on the Op itself, plain data that the github generator, chant operator and a fountain Steward each render their own way. And a machine is a steward, declared alongside the ops it runs. See Ops as teammates for where each half belongs, and Choosing Your Deployment Model for which to reach for.
| # | Capability |
|---|---|
| #2114 | The runtime seam — chant run --on <lexicon>, gate-as-fact with exit 3 and chant approve, the base activities and ACTIVITY_PROFILES in core, labels and the run ledger, op.schedule and the Op composites, and the OPS lint rules |
| #2115 | The fountain lexicon as the hosted runtime — Teammate / Schedule / Webhook, fountain.profiles, chant acp, the opRuntime provider, observation, and the Steward composite |
| #2116 | Temporal removed — the lexicon, every reference in core, the examples, the docs, the CI, and the published package deprecated in favour of @intentius/chant-lexicon-fountain |
Op pipelines behave the same on GitHub, Forgejo and GitLab
Section titled “Op pipelines behave the same on GitHub, Forgejo and GitLab”One chant project generates the Op pipelines for all three forges, and since 0.60.0 each forge has received every job with the same triggers and the same names. What a job could do still varied by generator rather than by what the forge actually supports. That gap is now closed, and a first real execution of the generated pipelines closed a second one nobody had seen.
ScheduledOpSpec now takes variables, so a per-Op secret lands on that Op’s own job instead of the whole file. A project with no OIDC no longer puts the apply key on every pull-request job.
Posting now works off github.com too. gh api resolves a relative path against /api/v3 for any host but github.com, and Forgejo serves /api/v1, so every sticky-comment call built from a bare path answered 404 there. Calls are now built from GITHUB_API_URL, which GitHub Actions and Forgejo Actions both already set correctly. The issue finding mode gained a GitLab path and a sticky GitHub one, so a nightly Op edits the issue it owns rather than opening a new one each run.
The generated GitLab file and stage are named ops rather than scheduled-ops, which is a breaking rename of the file a consuming project includes.
Four failures came from running the pipelines rather than reading them. A gated job could not start under container:, whose default shell is sh, because the generated script opens with set -o pipefail. TerraformAdoptOp had no Init phase, so its Ledger step found no provider schema on any fresh checkout. A CI clone has no committer identity, so the gate’s own commit failed, and the run reported no error at all. A CI clone also fetches only the pipeline ref, so an absent chant/lifecycle read as a ledger where nothing had been approved. A retried job then recorded a second pending fact, and chant approve force-wrote the branch over the record it should have appended to.
| # | Capability |
|---|---|
| #2290 | Per-Op variables on the Op’s own job, across all three generators |
| #2291 | comment finding mode on Forgejo, over a full-URL gh api call |
| #2292 | A GitLab issues path for reconcilePr’s issue mode |
| #2293 | The GitLab stage and file are named ops (breaking) |
| #2297 | The GitHub issue path edits the issue it owns instead of opening one per run |
| #2299 | The gated step declares shell: bash, so it starts under container: |
| #2301 | A committer identity for the ledger commit, and an Op failure that names its cause |
| #2302 | TerraformAdoptOp gained the Init phase its Ledger step depends on |
| #2303 | The ledger branch is fetched before it is read or appended to |
| #2305 | PrPlanReport’s sticky comment targets the resolved API base |
A gate approves a plan, not the next run
Section titled “A gate approves a plan, not the next run”Approving a gated apply used to authorise the next run of that Op. The resolution on the gate ledger named the op and the gate, plus who approved it and when, and nothing about what had been planned; each chant run re-plans in its own Plan phase. INTENTIUS/choudoufu#1026 measured it against a real root. After an approval, a renamed resource and a re-run, the apply proceeded without a word.
Both halves of the loop now carry a plan identity. A TerraformApplyOp’s Plan step publishes planDigest, a hash of its terraform show -json change set, and ApplyOp’s publishes one over the lifecycleDiff change set. The gate step references it, the run records it on the pending fact, and chant approve copies it onto the resolution. A later run whose fresh plan digests differently stops, naming the digest that was approved alongside the digest that was planned, with the chant approve line that closes the gap. Nothing is applied. This is a wider window than the exit-3 refusal a live apply <planfile> already made: that one compares the Apply step’s fresh plan against the file it was handed inside one run, and this compares a later run’s plan against what a human actually read.
The digest covers the change set and only the change set: addresses, actions, and the values that would be written. It deliberately ignores when the plan was taken, which run took it, and the plan file’s bytes, so re-planning an unchanged root is the same plan rather than a new one to re-approve.
Migration. A resolution written before this carries no digest, and a digest-less resolution does not satisfy a plan-bound gate. The safe reading was the one to take: such a record proves somebody approved something, and nothing about what, and accepting it once would apply exactly the unreviewed change the check exists to catch, on the estates that have been running longest. Any gate standing open across the upgrade needs one more chant approve after its next run, and the refusal says so by name rather than leaving a reader to guess. chant approve with nothing pending now refuses too, since there is no plan on the ledger for it to approve; run the Op first, or pass --plan <digest>.
| # | Capability |
|---|---|
| #2300 | The resolution record carries a plan digest, and a run whose plan has moved refuses by name |
Exploitability-aware vulnerability gating
Section titled “Exploitability-aware vulnerability gating”vuln-gate can now gate on whether a CVE is actually being exploited, not just how bad it would be: KEV catalog membership (CISA’s list of vulnerabilities exploited in the wild) and the EPSS score (likelihood of exploitation in the next 30 days). Four new vulnPolicy fields — failOnKev, failEpssAtOrAbove, warnEpssAtOrAbove, exploitabilityFixableOnly — work project-wide from chant.config.ts or per-step. All default to off, so upgrading blocks nothing new; the one change every gate sees is that VulnGateFailedError now names the rule that fired per finding (in CISA KEV since 2024-03-11, known ransomware use instead of a bare CVE list). VEX suppression still outranks everything, KEV included. grype reports the data; trivy (v0.73) reports none of it, in which case the gate behaves exactly as before. See Exploitability gating.
| # | Capability |
|---|---|
| #1461 | The epic — exploitability as an escalation layer over the severity gate, defaults off |
| #1462–#1464 | VulnFinding carries EPSS/KEV fields; grype/trivy parsers keep what the scanner reports; the data survives scan → gate on both routes |
| #1465 | The gate evaluates the predicates — KEV/EPSS blocks OR’d with severity, most-specific reason reported, rule named in the error |
| #1466 | chant.config.ts plumbing — the fields flow through vulnPolicy with zero thresholds and explicit false overrides intact |
A Kubernetes runtime surface
Section titled “A Kubernetes runtime surface”The k8s lexicon had chant’s strongest authoring surface and its weakest runtime one: describeResources() shelled kubectl get once per declared entity, serially, through a hardcoded twenty-entry map that warn-skipped every CRD. lifecycle diff --live, lifecycle plan and behold’s overlay all inherited that cap. It now reads through a typed API client in its own package — coverage from the same codegen pass that emits the declarable classes (180-odd types, CRDs included) confirmed against the cluster’s own API discovery, concurrent reads, and failures carrying the API server’s code and reason instead of a parsed stderr line. A run that calls kubectlApply or waitForReady no longer needs a kubectl binary.
Writes went the same way. Every Kubernetes apply — including ApplyOp({ target: "kubectl" }), which used to shell kubectl apply -f — is now a server-side apply as the field manager chant:<stack>, so the API server records per field which of your tools wrote it. When another manager already owns a field the apply would set, chant names the owner and the contested paths and stops; it never forces a conflict on your behalf. See The Kubernetes API client.
| # | Capability |
|---|---|
| #1073 | The epic — what a Kubernetes runtime surface is for (managedFields, ownerReferences, events/conditions) and what it deliberately is not (no applying operator, no kubectl clone) |
| #1074 | The typed API client — kubectl shell-outs retired from observation, live export, kubectlApply and waitForReady; exec credential plugins allowlisted, cached and recorded; the client kept off the build path structurally |
| #1075 | Server-side apply — the field manager chant:<stack> derived from ownership.stack, a conflict that names the competing manager and the contested paths, opt-in force-conflicts, and the ownership-scoped prune moved off kubectl apply --prune |
Provisioned infrastructure diagrams
Section titled “Provisioned infrastructure diagrams”The newest thread. chant graph gained a live data source: chant graph --live --env <name> graphs the actually-provisioned infrastructure, not the declared source — nodes from each lexicon’s observation, edges reconstructed from the live references between resources (a subnet’s VPC, an ALB’s target group), and containment (VPC ⊃ subnet ⊃ resource) as boundary structure. --overlay classifies each resource against declared source — managed / foreign / pending — so the diagram shows drift. The companion painter, pinhole, renders the result as an AWS-style architecture diagram. AWS is enriched from the deployed CloudFormation template, so a managed AWS stack draws its real topology. Scope is managed-only (owned resources); a full-account scan is the natural next phase. See chant graph --live and Implementing Observation for the lexicon-author hooks.
| # | Capability |
|---|---|
| #777 | chant graph --live — project observed resources into IR nodes (physicalId + ownership) |
| #778 | Live edge reconstruction — a per-lexicon referenceCatalog (identities + reference/containment rules) + AWS catalog |
| #779 | Live containment grouping (byContainer) — VPC / subnet boundaries for the renderer |
| #784 | enrichLiveAttrs — real AWS edges by resolving the deployed CloudFormation template’s {Ref}/{Fn::GetAtt} to node ids |
| #780 | --overlay — declared-vs-provisioned drift (managed / foreign / pending) on the diagram |
| pinhole#74, pinhole#75 | pinhole architecture render (nested boundary boxes) + AWS icon pack |
Components & supply-chain attestations
Section titled “Components & supply-chain attestations”An earlier thread. The components release model — a deploy composed from a bounded set of capabilities, run by one generic orchestrator — gained supply-chain attestations: SBOM, SLSA provenance, and keyless signing attached to the artifact digest, with a verify gate that halts a deploy before apply. chant run --components runs a component’s deploy composition on the local executor by default. The dir/zip/jar SBOM path is hermetic — no external tool. See Supply-Chain Attestations.
| # | Capability |
|---|---|
| #606, #608 | SBOM generation + attach for build-producing components |
| #613, #615 | Native SPDX/CycloneDX BOM writer — hermetic lockfile SBOM (no external tool) + IaC config-BOM |
| #614, #619 | Per-artifact reproducibility/provenance + component BOM aggregation |
| #610, #620 | Real SbomGenerator (syft/buildx), ReferrerLookup (oras), publish-time referrer attach |
| #622, #624 | Keyless cosign signing + SLSA provenance + verify gate |
| #623, #625 | Supply-Chain Attestations — first-timer docs page |
| #628, #631 | vuln-gate correctness hardening |
| #629, #632 | Wire chant.config.ts sbom/signing/vulnPolicy into run --components |
| #630, #633 | Runnable examples/supply-chain + hermetic SBOM default |
| #634, #635 | Default vuln-gate/scan-vulnerabilities to a real auto-detecting scanner (grype → trivy) |
| #637, #638 | Apply chant.config defaults on the Op codegen path |
Pipeline context for agents
Section titled “Pipeline context for agents”chant now exposes what chant build already computes about a pipeline — its jobs and run order, what it pulls in from outside, its security findings, where each job came from — as a set of read-only MCP tools. They give an agent a before-it-runs view of a pipeline change, complementary to the after-it-ran view live tooling provides. Every tool builds from source and never touches a live instance, reads run history, or writes anything — that boundary is what keeps chant a context producer, not a copy of the live tooling. See Agent Integration for the full tool reference.
| # | Capability |
|---|---|
| #328, #331 | GitLab read-only context tools — gitlab:checks (WGL findings), gitlab:pipeline (stages/jobs/order), gitlab:references (includes/components/images + pinned?), gitlab:affected (needs chain), gitlab:pipeline-yaml |
| #333 | The parallel github:* set for Actions workflows — github:checks (GHA findings), github:workflow, github:references (actions/images + SHA-pinned?), github:affected, github:workflow-yaml |
| #334 | gitlab:source + gitlab:owns, on a new entity-level build-provenance side channel in core — trace a job back to its declaring file and composite; report whether a job is declared by chant here (declared-in-source, since pipeline jobs are not taggable cloud resources) |
| #335 | gitlab:compare — migrate a GitHub workflow to GitLab and report which security properties survive the edge (translated / approximated / needs-review / lost), reusing the security-fate model |
| #329, #332 | chant lifecycle plan --report gitlab-mr + the MrPlanReport composite — publish the plan as the GitLab merge-request plan widget (artifacts:reports:terraform); counts only (create/update/delete) |
| #336 | A Docker runtime E2E (just gitlab-runtime-e2e) that actually executes a chant-generated .gitlab-ci.yml via gitlab-ci-local — proves GitLab CI accepts and runs the output, not just that it is well-formed |
Forgejo lexicon (Codeberg / Forgejo / Gitea)
Section titled “Forgejo lexicon (Codeberg / Forgejo / Gitea)”A new lexicon for Forgejo Actions — the CI behind Codeberg, self-hosted Forgejo, and Gitea — built as a thin dialect of the github lexicon. Forgejo runs GitHub-Actions-compatible YAML, so you author exactly as for GitHub (same entities and composites, imported from @intentius/chant-lexicon-forgejo) and the dialect applies on build. The differentiated value is the github->forgejo compare: what the move silently costs.
| # | Capability |
|---|---|
| #339 | Serializer dialect — emits .forgejo/workflows/ (or .gitea/workflows/); drops keys the Forgejo runner ignores (permissions, continue-on-error) with build warnings; maps runner labels (ubuntu-latest → docker, override via forgejo.runnerLabels) |
| #340 | uses: action-ref resolver — Forgejo has no Marketplace, so common actions/* rewrite under forgejo.actionsRoot (default https://code.forgejo.org), docker/* pin to GitHub URLs, and unmapped refs are reported |
| #341 | chant migrate --to forgejo + the forgejo:compare MCP tool — a per-property security-fate report (translated / approximated / needs-review / lost) of what survives the move |
| #342 | Parity surface — the full forgejo:* read-only context tools, WFJ010/011 lint checks, init templates, a chant-forgejo skill, and a reference page |
CI/CD pipeline security audit
Section titled “CI/CD pipeline security audit”A workflow/pipeline security pass across the github and gitlab lexicons, split along chant’s architectural boundary: deterministic checks run in the pure build, live-resolution checks run in the operational layer. Both lexicons were abstracted to the same core problems — pin & vet external references, least-privilege tokens, trust boundaries against untrusted input, secret containment, unsound expressions, artifact/cache hygiene — so the GitHub -> GitLab migration can map problem -> problem and carry the security invariant across the edge.
| # | Capability |
|---|---|
| #296 | Epic: GitHub workflow security audit — GHA029–058 post-synth checks generalize SHA-pinning to every uses:/image:, flag over-broad token scopes, taint untrusted input into run:, contain secrets, reject unsound/obfuscated expressions, and enforce artifact/cache hygiene |
| #305 | Epic: GitLab pipeline security audit — WGL029–048, the same problems on GitLab primitives (includes/components, CI_JOB_TOKEN/id_tokens, masked/protected variables, rules:) |
| #292, #303 | WorkflowAuditOp / PipelineAuditOp — the live, always-fresh half: resolve pinned refs against moving upstreams (stale SHAs, impostor refs, advisories, archived projects), one-shot on the local runtime or on the Op’s own cadence, reporting via report / issue / pull-request (or merge-request) |
| #294 | Dependabot composite + resource — models .github/dependabot.yml so it’s emitted and lintable, with safe defaults (cooldown window, external code execution denied); checks GHA057/058 |
| #306 | Security-aware migration — a security-fate classification (Translated / Approximated / NeedsReview / Lost) on the migration provenance model, new MIG-* security rules, and a --validate security backend that runs the GitLab checks against migrated output |
| #293 | Fixed a duplicate lint ID — the post-synth “missing job-level permissions” check moved to GHA013, freeing GHA020 for the secret-detection rule |
Live import & the lifecycle dial
Section titled “Live import & the lifecycle dial”The follow-on to the observation thread: chant turned its single observational drift workflow into a full family — observe -> reconcile -> authoritative, chosen per environment. It gained live import (regenerate TypeScript from running cloud/cluster state) and projection + cloud-side ownership (a precise create/update/delete change set computed against live), without hosting an authoritative state file. The snapshot is never load-bearing — the projection reads ownership from the live resource only. The primitives need no runtime at all, and an Op only asks for a machine of its own when it has to run where nobody is watching.
| # | Capability |
|---|---|
| #113 | exportResources() on the LexiconPlugin contract — full-fidelity import IR from a live API, branded distinct from the scrubbed observation types so a secret-bearing export can never reach the lifecycle code paths |
| #114 | chant import --from <env> — live import driver with --type / --name / --owned / --verbatim selectors and a secrets warning |
| #115, #116, #117 | Live export for AWS (CloudFormation get-template), Kubernetes (kubectl get -o json, stripped to declared shape), and GCP Config Connector |
| #159 | Live export for Azure (az group export) — makes Azure a full live-import peer alongside AWS, GCP, and Kubernetes; --owned filtering via the chant-managed-by tag |
| #118 | chant lifecycle plan — typed ChangeSet (create / update / delete / adopt / noop) from the live diff; strictly read-only |
| #119 | Ownership marker contract + serializer stamping — provider-native marker (AWS/Azure tags, K8s/GCP labels) carrying stack identity, stamped at synthesis. Opt-in via ownership config; walk-away cost stays zero |
| #120 | owned filter on describe/export — query the marker live; marker present + undeclared → delete candidate, absent → foreign (adopt only); degrades to detect-only where no marker channel exists |
| #121 | Ownership-gated delete in the change set — delete only for owned orphans, read from the live marker, never from the snapshot |
| #122 | reconcilePr Op activity — regenerate drifted/orphaned entities and open a reviewable PR (modes: pull-request / issue / report); never commits to the main branch |
| #123 | ReconcileOp composite — the cloud → code workflow (snapshot → plan → reconcile); one-shot on the local runtime, continuous on the Op’s own cadence |
| #124 | ApplyOp composite — the code → cloud workflow via native apply (Kubernetes server-side apply / CloudFormation deploy / per-resource ARM PUT); deletes ride the target’s own delete path and are owned-only on all three (what bounds each one) |
| #125 | Approval gates + compensation for destructive apply — an approval recorded before the delete, and saga-style onFailure rollback |
| #126–#131 | Docs: new Lifecycle Models (three-axis model + dial) and Durable Workflows concept pages; Live Import and Reconciling Lifecycle guides; Implementing Live Export authoring guide; governance/comparison revised to retire the “no plan” concession; cross-page tone audit |
Observation & drift detection
Section titled “Observation & drift detection”The biggest single thread. chant lifecycle diff went from a digest-vs-digest fingerprint check to a real live-vs-declared-vs-snapshot diff that catches out-of-band cloud mutations across nine lexicons.
| # | Capability |
|---|---|
| #26 | chant lifecycle diff <env> --live — three-way diff (declared / observed-now / observed-then), six resource categories (missing / orphan / disappeared / newly observed / drifted / unchanged) with attribute-level deltas |
| #39 | describeResources() contract gains entity-prop pass-through — lexicons now receive declared props (e.g. K8s metadata.name/namespace) and can map cloud-side identifiers back to chant entity names |
| #42 | Four describeResources() implementations — AWS, Azure, GCP (Config Connector), Kubernetes |
| #51 | listArtifacts() plugin contract — context-keyed observation for lexicons whose chant entities describe authoring primitives rather than 1:1 cloud resources |
| #52 | Helm listArtifacts() — Helm releases via helm list -A -o json |
| #53 | Docker listArtifacts() — containers / images / networks via three independent NDJSON queries |
| #56 | GitHub / GitLab — runtime observation documented as N/A; both describe git-tracked authoring primitives where drift is git diff |
| #30 | chant/lifecycle orphan branch concurrency — pushes use --force-with-lease; concurrent snapshots fail fast instead of silently overwriting |
| #31 | WatchOp composite — periodic lifecycle observation from an Op (Snapshot + Diff phases) carrying a cron |
Op runtime
Section titled “Op runtime”The Op composite gained two ergonomic upgrades that were previously hand-coded boilerplate in every example.
| # | Capability |
|---|---|
| #28 | An Op’s labels are emitted for it — OpName by default, plus whatever the Op declares. No more hand-rolled boilerplate; chant run list and chant operator log --env filter on them out of the box |
| #41 | outcomeAttribute on activity steps — capture an activity’s return value (e.g. lifecycleDiff’s drifted boolean) onto the run record. Used by WatchOp to surface Drift = "true"/"false" per run |
| #29, #40 | Test coverage for cli/handlers/lifecycle.ts (then state.ts), run.ts, graph.ts, and runOp — refactor surface is now safe |
| #162 | Compile-smoke for generated Op output — type-checks the emitted workflow.ts/worker.ts/activities.ts against the live activity signatures, so an activity-signature change that the serializer doesn’t track fails at compile time instead of drifting silently |
Cross-lexicon migration
Section titled “Cross-lexicon migration”A typed-compiler port of the upstream gitlab-org/ci-cd/github-actions-to-gitlab-ci Agent Skill (MIT). Translates .github/workflows/*.yml into .gitlab-ci.yml (or chant TypeScript) with per-key provenance recorded as SARIF, a curated mapping registry for the top 33 marketplace actions, and optional composite-pattern recognition for Node-shaped pipelines. The gitlab plugin exposes the same translation via a gitlab:migrate MCP tool and a chant-gitlab-migrate skill.
| # | Capability |
|---|---|
| #85 | GitHub → GitLab IR transformer with per-key provenance side channel; Kahn topo-sort stage inference; 30+ rule IDs for translation events |
| #86 | chant migrate CLI — top-level command dispatching through a new optional migrationSource(from) hook on LexiconPlugin |
| #87 | ActionMapping[] registry — Tier 1 (14 essentials): checkout, setup-, cache, artifacts, docker/ |
| #88 | ActionMapping Tier 2 (14 common) + Tier 3 (5 niche) — covers all 33 actions from the upstream skill’s reference table |
| #98 | Fixtures lifted from the upstream skill’s syntax-mapping + marketplace-actions before/after pairs; 16 fixture-driven tests asserting canonical-YAML and shape-of-report equality |
| #89 | --emit ts mode — produces typed chant TypeScript the user owns and rebuilds with chant build |
| #90 | SARIF v2.1.0 report (--report <path>) + --strict exit-code escalation on needs-review diagnostics |
| #91 | --validate shells out to glci (offline) or glab ci lint (fallback); skips gracefully when neither is on PATH |
| #92 | --use-composites IR-level recogniser — collapses 2-job Node pipelines into a single NodePipeline({...}) call |
| #93 | lexicons/gitlab/scripts/migrate-samples.sh — local roundtrip harness over real public GitHub workflows; 70% pass-rate threshold |
| #95 | chant-gitlab-migrate skill + new chant init --skill <name> filter to install a single skill |
| #96 | gitlab:migrate MCP tool registered on the gitlab plugin’s MCP server |
| #94 | Migration docs page with inspiration / lineage section crediting the upstream MIT-licensed skill |
| #97 | Umbrella — full work landed on PR #99 (draft) |
Build pipeline & lexicon fixes
Section titled “Build pipeline & lexicon fixes”| # | Capability |
|---|---|
| #21 | Lexicon root index.ts re-exports — three composites that examples imported through deep paths now exported from the package root; silent build failures resolved |
| #23, #25 | Azure schema parser — upstream renamed NetworkInterfaceIPConfiguration / SubnetPropertiesFormat / SecurityRule to Common*-prefixed names; bare property aliases now apply correctly, CI green |
| #38 | chant build writes output paths whose parent directory doesn’t exist (mkdir-p) — previously failed with ENOENT on first build of a project with nested output dirs |
Doc rollup
Section titled “Doc rollup”After all the runtime work shipped, the docs site got a sweep to surface it:
| # | Capability |
|---|---|
| #63 | Accuracy fixes — stale claims, broken links, rollup misses across index.mdx, comparison, philosophy, installation, quick-start, cli/overview |
| #64 | Watching Lifecycle — first-class user-guide page for WatchOp (was previously a subsection of the Ops guide) |
| #65 | Implementing Observation — lexicon-author walkthrough of describeResources() and listArtifacts() with patterns from every shipping implementation |
| #66 | Drift Detection — concept page for the observational lifecycle model, ten diff categories, and when drift detection earns its keep |
| #67 | This page + the lexicon observation coverage matrix |
Earlier — the Op core (#5–#11)
Section titled “Earlier — the Op core (#5–#11)”These shipped before the observation thread but underpin everything above. Listed for completeness: