Skip to content

Lexicons

A lexicon is a collection of types and semantic lint rules for an operational area. The core system is lexicon-agnostic — it discovers files, collects exports, resolves dependencies, and runs semantic lint rules. Each lexicon plugs into this pipeline with its own types and rules.

LexiconPackageResourcesReference
AWS CloudFormation@intentius/chant-lexicon-aws1500+Reference
Azure ARM@intentius/chant-lexicon-azure200+Reference
GCP Config Connector@intentius/chant-lexicon-gcp150+Reference
Kubernetes@intentius/chant-lexicon-k8s50+ resources, 35+ propertiesReference
Helm@intentius/chant-lexicon-helm10+ resourcesReference
GitHub Actions@intentius/chant-lexicon-github3 resources, 14 compositesReference
GitLab CI/CD@intentius/chant-lexicon-gitlab3 resources, 16 propertiesReference — also ships GitHub Actions migration
Forgejo Actions (Codeberg / Gitea)@intentius/chant-lexicon-forgejothin github dialect (reuses github entities)Reference — ships GitHub Actions migration + forgejo:compare
Docker@intentius/chant-lexicon-docker6 resources (Compose + Dockerfile)Reference
Fly Machines@intentius/chant-lexicon-fly6 resources (Machines API)Reference — ships the mudflaps emulator for offline round-trips
Fountain@intentius/chant-lexicon-fountain6 resources (Environment, Vault, Agent, Teammate, Schedule, Webhook)Reference — sandboxed agent workloads and the steward that operates them; conversations are ops, not resources
Render@intentius/chant-lexicon-render14 resources (Public API)Reference — services, datastores, env groups, projects; applied straight to the Public API
Control Plane@intentius/chant-lexicon-cpln8 resources, 142 property typesReference — GVCs, workloads, identities and secrets
Cedar@intentius/chant-lexicon-cedarPolicy and Schema, plus entity and action types generated from your schemaReference — emits .cedar and Cedar JSON; observes and imports against Amazon Verified Permissions
k3d@intentius/chant-lexicon-k3d1 resource (Cluster) + config property types, pinned to k3d v5.9.0Reference — emits the k3d.io/v1alpha5 config k3d cluster create --config consumes; k3dUp/k3dDown Op activities
k3s@intentius/chant-lexicon-k3s3 resources (Server, Agent, Registries)Reference — emits config.yaml and registries.yaml for /etc/rancher/k3s/; k3sInstall/k3sUninstall Op activities
Terraform@intentius/chant-lexicon-terraformone entity per HCL block, no generated resource surfaceReference — reads an existing root module’s .tf files as-is; terraformInit/terraformPlan/terraformApply/terraformShow Op activities and the TerraformApplyOp composite

Lexicons opt into the chant lifecycle diff <env> --live pipeline by implementing one of two plugin methods: describeResources() (entity-keyed — for 1:1 cloud resources) or listArtifacts() (context-keyed — for runtime concepts created by tooling outside chant’s entity model). See Drift Detection — Resources and artifacts for the conceptual distinction, and Implementing Observation for the author-side walkthrough.

A third, optional method goes one level deeper. observeResourcesDeep() reads the full live property tree per declared entity, which is what turns drift detection from “this resource changed status” into “this property changed, from this to that” — including properties source never declared, the class CloudFormation’s own drift detection cannot see. See Deep observation.

LexicondescribeResources()observeResourcesDeep()listArtifacts()OwnershipNotes
AWSTagsCloudFormation DescribeStackResources over the applier’s own read transport (chant #1206). Deep read: the Cloud Control resource model for S3 buckets and IAM roles/managed policies, plus a bulk aws ec2 describe-security-groups (still a CLI spawn) mapped onto the CloudFormation model for security groups — four kinds today, the remainder tracked in chant #1015/#1269. --owned available on the deep read (the payloads carry tags) and on live export, not on describe
AzureTagsThin and deep reads both GET the full ARM resource JSON over the applier’s ARM transport (chant #1212 — no az CLI); resource-group is the env name. The deep read flattens ARM’s properties.* onto the shape chant’s generated classes declare; top-level ARM types only — a nested compound type reports unsupported-kind. --owned available on live export
GCP (Config Connector)LabelsDirect REST GETs via the applier’s per-kind mappers (chant #1209 — no Config Connector cluster needed to observe). The deep read reshapes the REST body into the declared CNRM shape (metadata/spec) and prunes noise by a static per-kind table — a REST payload carries no field ownership to prune by. A kind with no mapper reports unsupported-kind
KubernetesLabelsTyped API client (@intentius/chant-k8s-client) — concurrent reads, any kind the cluster’s discovery reports including CRDs, typed errors; cluster resolved from k8s.profiles.<env>.context when bound (chant #1100) — a mismatched ambient context refuses loudly rather than reading the wrong cluster. Deep pruning is derived from metadata.managedFields: a field a foreign manager owns and source never declared is controller noise; a foreign write to a field chant declares stays visible, and the drift report names the owning manager
Fly MachinesMetadataMachines API (flaps) reads, or the mudflaps emulator offline. Two-tier ownership: machines carry managed-by: chant in config.metadata; volumes, IPs and certificates have no per-resource marker, so ownership is inferred at the app boundary and says so
FountainMetadataREST reads against the fountain API, one list per declared kind, over all six: environments, vaults, agents, teammates, schedules and webhooks (chant #2128). Two-tier ownership: --owned reads the managed-by: chant metadata marker, which environments, vaults and agents carry; a teammate and a schedule have no metadata of their own and inherit the verdict from the agent behind them; a webhook inherits nothing and reports unknown, so it is never proposed for deletion. Identity follows the serializer: a teammate keys on its roster name, a schedule on its teammate and its name together, a webhook on its url. The list endpoints render the whole record, so the deep read (chant #1217) needs no second GET per resource, and fountain’s JSON views name their fields the way the request schema does — the payload passes through and noise is a static per-kind table, since a REST payload carries no field ownership to prune by. Every server-resolved id is reported as the name the vocabulary source writes the reference in — an agent’s environment_id as the environment’s name, a schedule’s agent_id as its teammate, a teammate’s launch bindings as its environment and vault. A teammate is the one kind projected rather than passed through, because GET /api/team renders a roster row (presence, unread state, the last turn) rather than a record. Secrets are listed keys-only and collapsed whole on both sides: presence classifies, a key or a value never reaches a diff row
RenderEnv varsPublic API reads, one name-scoped list per declared entity plus a workspace scan for orphans. Two-tier ownership: services and env groups carry CHANT_MANAGED_BY=chant in their env vars (Render has no tags), and disks/custom domains inherit their service’s verdict; datastores, projects, environments, credentials and webhooks have no marker channel and report unknown, so they are never proposed for deletion
Control PlaneTagsREST reads against the Control Plane Core API. The GVC-scoped kinds have an org-wide rollup (/org/{org}/workload alongside the per-GVC form), so the read is one request per kind rather than one per resource. tags is the ownership channel and every read path returns it, so --owned resolves a real verdict on the thin read — which is why describeResources is declared on the channel
Cedar (AVP)AVP tagsReads the declared policy set against a live Amazon Verified Permissions policy store. The tri-state carries more weight here than on most substrates — a declared policy reported absent becomes a create, and creating a permit that already exists duplicates a grant — so every path that did not look says so. Design record in lexicons/cedar/src/avp/OWNERSHIP.md
k3dLabelsTwo commands (chant #1412): k3d cluster list -o json for presence and running state, and docker inspect on the cluster’s first server node for the ownership marker, since k3d’s list output carries runtimeLabels only on the node
k3sLabelsThe #1412 k3d answer applied to the Server/Agent shape (chant #1603). Reuses the same k8s.profiles.<env>.context binding helm and the k8s lexicon read (chant #1488), so the kubectl context is resolved rather than ambient
HelmRelease identity / LabelsPer-release reads over the helm CLI (chant #1246): each declared Helm::Chart resolves to its release, and helm get manifest + helm get hooks — both channels, since hooks are excluded from the manifest — become per-resource rows. Rows are owned via release identity and classify as runtime children of their chart, so an unpinned release is not drift. The deep read (chant #1247) delegates to the k8s lexicon’s typed client and reuses its deepNormalizationHooks, so managed-fields ownership and Secret masking apply unchanged. helm list -A -o json still reports every release as an artifact — declared or not
TerraformState file, or the marker tags on a choudoufu rootterraform show -json over each configured root’s current state, matched against the <root>/<address> keys buildRoots() produced (chant #2087). On a stock root ownership is state membership, not a marker: that root is on the trusted-state-file row of lifecycle models’s third axis, so an address the state carries is owned and everything else is unknown (a declared module block, which state has resources under but no row for, lands there). Only resource and data blocks have rows at all; a terraform/provider/variable/output block reports unsupported-kind. A root under binary: "choudoufu" that declares an estate is read by choudoufu live-plan -json instead and sits on the live-marker row like every other lexicon (chant #2104): its tofu-estate/tofu-address tags are the answer, a bound instance is owned, an unowned resource at a declared identity is foreign, an adoptable match is unknown carrying the two tag values that would claim it, and live-ls -estate -json enumerates the owned orphans for observeAmbient/teardownOwned. A root whose read fails reports every entity it declares read-failed, or no-credentials where the failure names credentials, naming the root
Dockerdocker ps, docker image ls, docker network ls (NDJSON)
GitHub ActionsN/A — workflow definitions are git-tracked; drift is git diff (rationale)
GitLab CI/CDN/A — same rationale (README)
Forgejo ActionsN/A — workflow definitions are git-tracked; drift is git diff (same as github)

Lexicons that implement neither are warn-skipped — --live doesn’t fail the whole command for them.

The Ownership column shows the marker channel a lexicon queries for the --owned filter (ownership marking). means the --owned query isn’t available for that lexicon — it has no durable marker channel. Where a lexicon is asked for owned resources but can’t read a marker on a given path — e.g. AWS and Azure describeResources, which the filter degrades to detect-only — it stamps an explicit unknown verdict and returns everything rather than silently filtering. The --owned filter is honored on live export, where the full config (with tags) is available.

Every observing lexicon reports per declared entity in three states, not two (the observation contract): observed present, observed absent, or not observed with a reason. Coverage gaps in the table above — Azure’s nested ARM types, a read denied by RBAC — surface as unobserved entries in lifecycle diff --live and lifecycle plan, never as absences, so an uncovered resource is reported as unknown instead of being proposed for creation.

The GitLab lexicon ships a chant migrate source for from: github — translates .github/workflows/*.yml into .gitlab-ci.yml (or typed chant TypeScript), with provenance recorded as SARIF and a curated mapping table for the top 33 marketplace actions. See GitLab -> Migration for the full surface, or the chant migrate CLI reference.

The Forgejo lexicon implements the same migrationSource("github") hook — a thin edge, since Forgejo runs GitHub-Actions-compatible YAML. Its value is the forgejo:compare safety view: a per-property security-fate report of what the move costs (permissions/continue-on-error -> lost, unresolved uses: -> needs-review).

Future lexicons can opt into the same machinery by implementing migrationSource(from: string) on their plugin.

TypeScript definition files that describe available resource classes, their properties, and intrinsic functions. These types power editor autocompletion, inline documentation, and compile-time checks.

Lint rules specific to the target ecosystem. These integrate with chant lint to catch provider-specific mistakes like invalid resource configurations or naming convention violations.

Each lexicon implements the Serializer interface to convert evaluated resources into the target platform’s output format.

interface Serializer {
name: string;
rulePrefix: string;
serialize(entities: Map<string, Declarable>, outputs?: LexiconOutput[]): string | SerializerResult;
}

A bundle is the distributable form of a lexicon — a dist/ directory containing everything chant needs at build time:

ArtifactDescription
manifest.jsonLexicon metadata (name, version, intrinsics, pseudo-parameters)
meta.jsonResource registry mapping type names to definitions
types/index.d.tsTypeScript declarations for all resource and property types
integrity.jsonPer-artifact xxhash64 checksums
rules/Lint rule implementations
skills/AI assistant skill definitions

When you install a lexicon package (e.g. @intentius/chant-lexicon-aws), the bundle is what ships inside it. Lexicon authors produce bundles with chant dev publish.

chant is provider-agnostic by design. The core handles parsing, module resolution, evaluation, and serialization — but knows nothing about specific infrastructure platforms.

Lexicons bridge this gap:

  • The core stays lean — no bundled provider SDKs or type catalogs
  • Providers update independently — new resource types ship as lexicon updates
  • Custom targets are first-class — organizations can author internal lexicons
  • Projects declare exactly what they need — explicit, reproducible dependencies