You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
chant has no Ansible support and no record of considering it. grep -ril ansible across the tree returns nothing, and the only mention anywhere is #2080's preamble, which lists Ansible alongside "Terraform-only controls, live-cluster reads" as out of scope for the audit-rule backlog. That was a scoping note in a survey of 37 linters — ansible-lint is not among them and no Ansible rules were enumerated. This issue is a different question and should not be read as re-opening that one.
The question came up from a concrete want: chaining terraform, Ansible and helm in one Op run. The chaining half is already tracked at #2314 for terraform/helm/k8s. Whether Ansible can join it depends on what Ansible is to chant.
The case that it is a lexicon
There is a real machine-readable surface. Every Ansible module carries a DOCUMENTATION YAML block with typed options:, and ansible-doc --json dumps it. That satisfies what generatePipeline wants on paper: a fetchSchemas returning a map keyed by module name, a parseSchema producing typed options, a NamingStrategy over module names.
The case that it is not
A playbook is not a resource graph, it is an ordered program. Tasks run in sequence, register feeds later tasks, when branches, loop iterates, handlers fire at the end. chant's Declarable model is a graph resolved by reference, and the evaluator has no notion of task order.
Modelling tasks as resources would make declaration order semantically load-bearing in a system that deliberately resolves by dependency instead. That is smuggling control flow through a dependency resolver, and the failure would be quiet: a playbook whose emitted task order differs from the authored one still builds.
Ansible's ordered-steps-with-handlers shape is much closer to something chant already has a model for — an Op: phases, ordered steps, compensation on failure.
The precedent worth copying
The terraform lexicon is exactly this situation already resolved. It has no generated resource surface — one entity per HCL block, read from .tf files that already exist — and lexicons/terraform/src/serializer.ts:23 returns "", with the reason recorded in its own doc comment: emitting HCL "would put a second, generated copy of the estate beside the authored one." Its stated value is "what the entities let the rest of chant do with the root (post-synth checks, chant audit, the Op surface), not a rendered artifact."
Applied to Ansible that shape is:
read existing playbooks into entities for lint and chant audit
ship ansiblePlaybook / ansibleCheck Op activities, the way terraform ships terraformInit/terraformPlan/terraformApply
an AnsibleApplyOp composite alongside TerraformApplyOp
serializer is a stub; chant never writes a playbook
What this issue should decide
Wrapper or generated surface. If generated, say what a task's identity is and how order survives, because that is the part that does not obviously work.
Whether the Op activities need a lexicon at all. loadActivities resolves from @intentius/chant-lexicon-<name>/op/activities, so today an activity ships inside a lexicon — but core already has a generic shell activity, so running a playbook is possible now without any of this. Decide what a lexicon buys over shell.
Tier-1 reality check, in full. This is the part that decides whether the wrapper is worth it, so the requirements are listed rather than summarised.
The four lifecycle methods are all required.isLexiconPlugin() rejects a plugin missing any of generate, validate, coverage, package at load, not at first use. A wrapper still has to answer all four, and terraform shows what the answers look like when there is no upstream spec:
generate() — writes an empty registry (export {}). Not skipped: validate reads the registry and writeBundleSpec derives dist/meta.json from it. Its own comment calls an empty registry "the honest statement that this lexicon exports no generated resource classes."
coverage() — refuses by name: "Coverage analysis not applicable: this lexicon generates no types from an upstream spec".
validate() and package() — real, full implementations. No shortcut.
All 23 Tier-1 checks apply, and chant dev check-lexicon exits non-zero on any of them:
Plugin
exports a LexiconPlugin; serializer with name, rulePrefix, serialize
Rules
lintRules() >= 1; postSynthChecks() >= 1; every rule id under a declared prefix
LSP
registers completionProviderandhoverProvider
Docs
registers docs(); >= 1 .mdx page; every page reachable from the sidebar; every page names a Diataxis quadrant
Package
dist/manifest.json exists and declares a chantVersion; package.json routes exports["."].default at ./src/index.ts and deletes emitted JS in build
Examples
>= 1 in examples/; every shipped example builds and passes the lexicon's own post-synth checks
Intrinsics
every registered intrinsic exported; isTag matches authoring; foldsAsCall only on plain calls
MCP
tools and resources under one well-formed <lexicon>:<verb> namespace
Tests
plugin.test.ts and serializer.test.ts both exist
Coverage
coverageReport() leaves no upstream kind unaccounted (vacuous if the member is absent)
What the precedent actually cost. The terraform lexicon is the thin wrapper, and it ships 4 examples, 4 doc pages, 2 lint rules and 31 post-synth checks. "Thin" describes the absence of a generated resource surface, not the absence of work. An Ansible wrapper that clears Tier 1 is a comparable build, and the decision should be taken against that number rather than against the two-line serializer stub.
Two more from the authoring docs worth deciding here rather than discovering later. A lexicon that reads its own chant.config.ts namespace (ansible.playbooks, presumably, the way terraform.roots works) should declare a configSchema, or an unknown key inside it is silently ignored — chant dev check-lexicon warns when one is missing. And buildRoots() is the member terraform uses to turn config-declared roots into entities; an Ansible wrapper reading playbook paths from config wants the same seam.
Proof
A written decision on this issue, and either implementation issues filed against it or a recorded rejection with the reason. Refs #2314, #2080.
What
chant has no Ansible support and no record of considering it.
grep -ril ansibleacross the tree returns nothing, and the only mention anywhere is #2080's preamble, which lists Ansible alongside "Terraform-only controls, live-cluster reads" as out of scope for the audit-rule backlog. That was a scoping note in a survey of 37 linters —ansible-lintis not among them and no Ansible rules were enumerated. This issue is a different question and should not be read as re-opening that one.The question came up from a concrete want: chaining terraform, Ansible and helm in one Op run. The chaining half is already tracked at #2314 for terraform/helm/k8s. Whether Ansible can join it depends on what Ansible is to chant.
The case that it is a lexicon
There is a real machine-readable surface. Every Ansible module carries a
DOCUMENTATIONYAML block with typedoptions:, andansible-doc --jsondumps it. That satisfies whatgeneratePipelinewants on paper: afetchSchemasreturning a map keyed by module name, aparseSchemaproducing typed options, aNamingStrategyover module names.The case that it is not
A playbook is not a resource graph, it is an ordered program. Tasks run in sequence,
registerfeeds later tasks,whenbranches,loopiterates, handlers fire at the end. chant'sDeclarablemodel is a graph resolved by reference, and the evaluator has no notion of task order.Modelling tasks as resources would make declaration order semantically load-bearing in a system that deliberately resolves by dependency instead. That is smuggling control flow through a dependency resolver, and the failure would be quiet: a playbook whose emitted task order differs from the authored one still builds.
Ansible's ordered-steps-with-handlers shape is much closer to something chant already has a model for — an Op: phases, ordered steps, compensation on failure.
The precedent worth copying
The terraform lexicon is exactly this situation already resolved. It has no generated resource surface — one entity per HCL block, read from
.tffiles that already exist — andlexicons/terraform/src/serializer.ts:23returns"", with the reason recorded in its own doc comment: emitting HCL "would put a second, generated copy of the estate beside the authored one." Its stated value is "what the entities let the rest of chant do with the root (post-synth checks,chant audit, the Op surface), not a rendered artifact."Applied to Ansible that shape is:
chant auditansiblePlaybook/ansibleCheckOp activities, the way terraform shipsterraformInit/terraformPlan/terraformApplyAnsibleApplyOpcomposite alongsideTerraformApplyOpWhat this issue should decide
loadActivitiesresolves from@intentius/chant-lexicon-<name>/op/activities, so today an activity ships inside a lexicon — but core already has a genericshellactivity, so running a playbook is possible now without any of this. Decide what a lexicon buys overshell.ansible-lintoutside the audit backlog. If it is not, the lexicon's value shrinks to the Op activities, and (2) decides the whole thing.The four lifecycle methods are all required.
isLexiconPlugin()rejects a plugin missing any ofgenerate,validate,coverage,packageat load, not at first use. A wrapper still has to answer all four, and terraform shows what the answers look like when there is no upstream spec:generate()— writes an empty registry (export {}). Not skipped:validatereads the registry andwriteBundleSpecderivesdist/meta.jsonfrom it. Its own comment calls an empty registry "the honest statement that this lexicon exports no generated resource classes."coverage()— refuses by name:"Coverage analysis not applicable: this lexicon generates no types from an upstream spec".validate()andpackage()— real, full implementations. No shortcut.All 23 Tier-1 checks apply, and
chant dev check-lexiconexits non-zero on any of them:LexiconPlugin; serializer withname,rulePrefix,serializelintRules()>= 1;postSynthChecks()>= 1; every rule id under a declared prefixcompletionProviderandhoverProviderdocs(); >= 1.mdxpage; every page reachable from the sidebar; every page names a Diataxis quadrantdist/manifest.jsonexists and declares achantVersion;package.jsonroutesexports["."].defaultat./src/index.tsand deletes emitted JS in buildexamples/; every shipped example builds and passes the lexicon's own post-synth checksisTagmatches authoring;foldsAsCallonly on plain calls<lexicon>:<verb>namespaceplugin.test.tsandserializer.test.tsboth existcoverageReport()leaves no upstream kind unaccounted (vacuous if the member is absent)What the precedent actually cost. The terraform lexicon is the thin wrapper, and it ships 4 examples, 4 doc pages, 2 lint rules and 31 post-synth checks. "Thin" describes the absence of a generated resource surface, not the absence of work. An Ansible wrapper that clears Tier 1 is a comparable build, and the decision should be taken against that number rather than against the two-line serializer stub.
Two more from the authoring docs worth deciding here rather than discovering later. A lexicon that reads its own
chant.config.tsnamespace (ansible.playbooks, presumably, the wayterraform.rootsworks) should declare aconfigSchema, or an unknown key inside it is silently ignored —chant dev check-lexiconwarns when one is missing. AndbuildRoots()is the member terraform uses to turn config-declared roots into entities; an Ansible wrapper reading playbook paths from config wants the same seam.Proof
A written decision on this issue, and either implementation issues filed against it or a recorded rejection with the reason. Refs #2314, #2080.