Skip to content

Lexicons

llms.txt lists every page for an agent

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-fountain3 resources (Environment, Vault, Agent)Reference — sandboxed agent workloads and the steward that operates them; conversations are ops, not resources
E2B@intentius/chant-lexicon-e2b1 resource (Template)Reference — chant run <op> --on e2b runs an Op on a steward sandbox on E2B, or any API that speaks E2B’s, that sleeps paused between runs; Template declares the image the steward is made from
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
OpenTelemetry Collector@intentius/chant-lexicon-otel32 components (6 receivers, 11 processors, 6 exporters, 6 connectors, 3 extensions), Pipeline, Service, a GenAI preset; the NodeAgent, RedMetrics, GenAiPipeline, OtlpCollector and TailSamplingTier compositesReference — emits collector YAML; defineComponent adds components chant doesn’t ship, with a pinned schema
Grafana@intentius/chant-lexicon-grafanaDashboard, Datasource, 26 panels, 13 typed query classes (PromQL, TraceQL, LogQL, Elasticsearch, CloudWatch, Azure Monitor, Cloud Monitoring, BigQuery, Pyroscope, OpenSearch, PostgreSQL, MySQL, MSSQL) and typed datasource settings, 9 variable kinds; the RedDashboard, SloDashboard and AgentDashboard compositesReference — emits dashboard JSON and provisioning files; panel options and queries generated from Grafana’s schemas at a pinned version; the composites build dashboards from an otel spanmetrics connector, a prometheus Slo and the GenAI preset
Prometheus@intentius/chant-lexicon-prometheusRuleGroup, plus Route, Receiver, InhibitRule, TimeInterval and AlertmanagerSettings; the Slo, GenAiRules, RedAlerts, Watchdog and AlertRouting compositesReference — emits a rule file and alertmanager.yml; PromQL parsed at build time; the same RuleGroup renders into a k8s PrometheusRule; an Slo builds SLI recording rules and multiwindow burn-rate alerts; GenAiRules builds rate, latency, token and cost rules from the otel GenAI preset’s metrics
SQL@intentius/chant-lexicon-sqlTwo dialects. ClickHouse: databases, tables, views and materialized views; engines, column types, codecs and settings from a pinned server’s system.* tables; every change classified as metadata only, a background rewrite or a rebuild, applied by clickhouseApply (the clickhouse ApplyOp target) with rebuilds run as ClickHouseRebuildOp; five composites. Postgres: schemas, tables, indexes, views, materialized views, sequences, enum types, domains and extensions; types from one pinned server per major, 14 to 18; every change classified by the lock it takes, applied by postgresApply (the postgres target) in transactions with CONCURRENTLY outside them, with a column rename or type change run as PostgresMigrationOp; managed providers as configuration; five composites. Both: SQL-shaped tagged templates with references, dependency order and column lineage, and import from a live serverReference — one lexicon with a subpath per database dialect (/clickhouse, /postgres); spec-true per dialect rather than database-agnostic
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.

A fourth, predictBehaviour(), answers a different question from the other three. Rather than reporting what a substrate holds, it reports how the declared estate would behave at a traffic level the caller names, as cost per hour and headroom per entity plus an error-rate expectation and a resilience verdict. Every figure names the engine that produced it, that engine’s version and stated tolerance, and whether it was modeled from list prices or validated against a bill. Core implements it, and each lexicon contributes behaviourKinds: rows saying what its own entity types are worth to an engine (chant #2382). The overlay therefore works on whatever lexicons a project already uses rather than requiring one more. See the behaviour contract.

LexicondescribeResources()observeResourcesDeep()listArtifacts()predictBehaviour()OwnershipNotes
AWS✅✅TagsCloudFormation 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
Azure✅✅TagsThin 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
Kubernetes✅✅LabelsTyped 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 Machines✅MetadataMachines 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
Fountain✅✅MetadataREST reads against the Managoat API, one list per declared kind: environments, vaults and agents (chant #2128). --owned reads the managed-by: chant metadata marker all three carry. Identity follows the serializer: each kind keys on its name. The list endpoints render the whole record, so the deep read (chant #1217) needs no second GET per resource, and Managoat’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. Secrets are listed keys-only and collapsed whole on both sides: presence classifies, a key or a value never reaches a diff row
Render✅Env 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 Plane✅TagsREST 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
k3d✅LabelsTwo 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
k3s✅LabelsThe #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
Helm✅✅✅Release 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
Terraform✅✅State 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
Docker✅—docker ps, docker image ls, docker network ls (NDJSON)
GitHub Actions—N/A — workflow definitions are git-tracked; drift is git diff (rationale)
GitLab CI/CD—N/A — same rationale (README)
Forgejo Actions—N/A — workflow definitions are git-tracked; drift is git diff (same as github)
OpenTelemetry Collector—N/A — a collector config file is an input to a collector the platform lexicons deploy; there is nothing live to read
Grafana✅✅Labels / provider annotationDashboards over /apis/dashboard.grafana.app (v1 on 13.x, v1beta1 on 12.x, discovered per server), falling back to /api/dashboards/uid on Grafana 11; datasources over /api/datasources/uid (chant #2946). The target is grafana.profiles.<env> in chant.config.ts, else GRAFANA_URL. The deep read puts the stored dashboard back into the vocabulary it was declared in by running it through the importer, so a drift path is panels[0].panels[1].title, not a JSON pointer into Grafana’s model; noise (build-assigned ids, grid positions and refIds, the built-in annotation, empty options) is a static table in the hooks. A panel, row, query or variable is compared inside its dashboard and gets no row of its own. Ownership: owned from chant’s managed-by label, or from the grafana.app/managerId annotation naming one of the project’s dashboard providers; datasources and Grafana 11 dashboards say unknown. A dashboard Grafana stores as v2 is read at v2 (v2beta1 on 12.x) and converted to the classic model before the diff (chant #2947)
SQL (ClickHouse)✅✅Comment trailerOne read of system.databases and system.tables per run over the server’s HTTP interface, bound by sql.profiles.<env> (else CLICKHOUSE_URL). chant import --from reads SHOW CREATE per object and writes the server’s canonical form as database/table/view declarations. chant’s ownership marker is a trailer on each object’s comment, [chant managed-by=chant stack=<stack> env=<env>], appended after the declared comment and taken off again wherever a definition is read, so describeResources answers owned or foreign and --owned exports the marked objects. The deep read parses each object’s SHOW CREATE into the declaration’s own shape and writes a field the server only reformatted the way the declaration wrote it, so the server’s rewriting is not drift; classifyDisruption maps a change onto the classifier’s ClickHouse ALTER rules
SQL (Postgres)✅✅Comment trailerCatalog reads over node-postgres (loaded only for a Postgres project), bound by sql.profiles.<env> with a postgres:// URL (else POSTGRES_URL), each session with an empty search_path. chant import --from assembles each object’s statement from the catalog’s printers (pg_get_constraintdef, pg_get_indexdef, pg_get_viewdef and the rest) and writes the server’s canonical form; an ORM’s revision table, an extension’s objects and a managed provider’s schemas are read as foreign and left out. The ownership marker is the same comment trailer, set with COMMENT ON. The deep read parses each printed statement with the declaration’s own tag, so normalization compares both through the same rules; classifyDisruption maps a change onto the lock classifier’s rules and answers unknown where both definitions are needed
Prometheus✅— (ruler namespace)Rule groups over a Mimir, Cortex or Loki ruler’s config API, by the namespace prometheus.profiles.<env>.ruler gives each group, with health from /api/v1/rules; on a plain Prometheus, /api/v1/rules alone. Alertmanager entities against the loaded config, from /api/v2/status or Mimir’s and Cortex’s /api/v1/alerts (chant #3371). chant import --from writes the groups and the config as source. A rule group carries no marker, so every verdict is unknown; what bounds chant on a ruler is the declared namespaces, which are all an import reads. prometheus.yml entities report unsupported-kind

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 hold that platform knowledge:

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

chant