Use this section when you already know the workflow and need exact command or schema details.
This is the multi-page printable view of this section. Click here to print.
Reference
1 - CLI
ainfra init [--name NAME] [--template TEMPLATE]
[--environment ENVIRONMENT] [--format text|json]
ainfra validate [<path-or-template>] [--input INPUT]
[--format text|json]
ainfra doctor [--input INPUT | --environment ENV] [--format text|json]
ainfra plan [<template> --input INPUT | --environment ENV]
[--destroy] [--format text|json]
ainfra apply [<template> --input INPUT | --environment ENV]
--approve PLAN_ID
ainfra up --environment ENV --approve PLAN_ID --known-hosts FILE
[--format json|yaml]
ainfra destroy [<template> --input INPUT | --environment ENV]
--approve-destroy PLAN_ID
ainfra down --environment ENV --approve-destroy PLAN_ID
ainfra outputs [<template> | --environment ENV] --run PLAN_ID
[--format json|yaml]
ainfra configure [<template> | --environment ENV] --run PLAN_ID
--known-hosts FILE [--check]
ainfra status --environment ENV [--format text|json]
ainfra legacy inspect [--root PYTHON_REPOSITORY] [--format text|json]
ainfra inventory --output OUTPUT --destination DESTINATION
init
Initializes the current directory without contacting a provider or invoking
OpenTofu or Ansible. It creates ainfra.yaml, ainfra.lock, and one example
under environments/, then adds .ainfra/ to .gitignore.
Initialization preflights every primary file and refuses to overwrite any of them. There is intentionally no force flag. The lock pins the selected built-in template’s name, version, source, and content digest.
validate
Validates an InfrastructureTemplate, TemplateInput, or
InfrastructureOutput document. It does not resolve credentials or mutate
infrastructure. With no target, it discovers the nearest ancestor
ainfra.yaml and validates the project, lockfile, environment references,
template digest, contracts, and policy.
doctor
Reports local dependency and configuration readiness without mutation.
Providing --input adds checks that depend on the target environment’s
backend mode and capabilities. --environment selects project mode and checks
the configured input plus compatible OpenTofu and Ansible versions. The two
selectors are mutually exclusive.
plan
Validates the template and input, invokes OpenTofu planning, applies policy
checks, and emits a reviewable plan ID. The built-in template is materialized
into an isolated per-plan workspace. --destroy creates the corresponding
destruction review without bypassing the exact-plan gate.
Project mode uses --environment ENV and discovers the nearest ancestor
ainfra.yaml. Explicit compatibility mode remains
TEMPLATE --input INPUT; mixing selectors fails before any subprocess.
apply
Applies only the plan identified by --approve, from its retained isolated
workspace. A stale, absent, changed, or mismatched identifier fails closed
before an infrastructure process starts.
Project plans use the ainfra.plan/v1alpha2 protocol and bind the canonical
project root plus the exact bytes of ainfra.yaml and ainfra.lock. Legacy
explicit records remain v1alpha1; records cannot cross-authorize between
the two modes.
destroy
Destroys only through the exact destroy plan identified by
--approve-destroy. The implementation applies that reviewed plan rather than
running an unreviewed direct destroy command.
up
Completes a previously reviewed project apply plan. It does not create or select a plan. After rechecking local readiness and the exact approval ID, it composes apply, standardized output collection, Ansible configuration, and Ansible check-mode verification. Successfully completed stages are resumed without repeating their mutations; interrupted or failed stages require manual recovery.
The required --known-hosts file remains independent host-identity evidence.
The resulting validated infrastructure output is emitted as JSON or YAML.
down
Applies only a previously reviewed project destroy plan. It never creates a
destroy plan, reuses an apply approval, selects the latest plan implicitly, or
invokes an unreviewed direct destroy command. After applying the exact plan,
it runs a separate tofu state list verification and records destroyed only
when no resources remain in the reviewed backend state.
outputs
Collects OpenTofu output for one exact successfully applied run, rejects
sensitive or ownership-inconsistent values, and emits the validated,
sanitized InfrastructureOutput. It never substitutes raw state or raw engine
output for that contract.
configure
Builds a private-address inventory from one run’s validated output and invokes
the retained Ansible playbook. --known-hosts must name an independently
verified, non-group/world-writable host-key file. After a successful
configuration, --check records a distinct check-mode verification stage; it
never records configuration as applied.
inventory
Transforms a validated standardized output into an Ansible inventory at an explicit destination.
legacy inspect
Inspects Python-prototype .ainfra/runs/ evidence without invoking OpenTofu,
Ansible, credential providers, or provider APIs and without writing files. It
reports sanitized record identities, exact plan-byte integrity, and corrupt
entry counts.
Legacy lifecycle state is always unknown. The command never imports a plan,
synthesizes project bindings or lifecycle events, or recommends applying or
destroying with an old ID. Machine output uses
ainfra.legacy-inspection/v1alpha1 and always requires a new project-bound
plan before normal project lifecycle can begin.
status
Reads only validated project files and durable local run evidence. It never invokes OpenTofu, Ansible, credential providers, DNS, or provider APIs.
The report classifies the latest project-bound run as planned, applied,
output-collected, configured, verified, destroy-planned, destroyed,
destroy-applied, partial, stale, corrupt, legacy, or none. Started
operations without a matching success or failure event are partial; they are
never assumed safe to retry. Machine output uses ainfra.status/v1alpha1 and
includes sanitized checks and the next safe command or manual-recovery route.
2 - Contracts
All schemas use JSON Schema draft 2020-12 and reject unknown fields.
| Contract | Source | Purpose |
|---|---|---|
InfrastructureTemplate/v1alpha1 | schemas/template-manifest.v1alpha1.json | Template engines, paths, capabilities, and invariants |
TemplateInput/v1alpha1 | schemas/template-input.v1alpha1.json | Non-secret operator intent and credential references |
InfrastructureOutput/v1alpha1 | schemas/template-output.v1alpha1.json | Stable, non-secret handoff to downstream systems |
ainfra.plan/v1alpha1 | schemas/plan-record.v1alpha1.json | Exact reviewed-plan binding for lifecycle authorization |
ainfra.plan/v1alpha2 | schemas/plan-record.v1alpha2.json | Project-bound lifecycle authorization |
ainfra.run/v1alpha2 | schemas/run-record.v1alpha2.json | Immutable run identity |
ainfra.run-event/v1alpha1 | schemas/run-event.v1alpha1.json | Append-only lifecycle evidence |
ainfra.status/v1alpha1 | schemas/status.v1alpha1.json | Sanitized local status output |
ainfra.legacy-inspection/v1alpha1 | schemas/legacy-inspection.v1alpha1.json | Read-only legacy evidence report |
Compatibility
The apiVersion is locked through the v1 series. An incompatible v2 requires
a full migration rather than silent coercion.
Validation behavior
- Unsupported
apiVersionandkindvalues fail. - Unknown properties fail.
- Secret-shaped standard outputs fail.
- Undeclared capabilities fail.
- Template paths cannot escape the template directory.
- Secret references describe a source but do not contain the secret.
Run:
ainfra validate DOCUMENT
Positive and negative fixtures live under tests/fixtures/contracts/.
Reviewed-plan records
A reviewed-plan record binds the intended operation to the selected template and version, environment identity, canonical input path and bytes, template tree, remote backend configuration, and generated OpenTofu plan bytes. Apply and destroy must reject any changed binding before starting an infrastructure process.
Explicit compatibility mode writes ainfra.plan/v1alpha1 and reads normal
records created by the Python implementation. Project mode writes
ainfra.plan/v1alpha2, adding the canonical project root and exact project
configuration and lockfile paths and digests. The two modes cannot
cross-authorize.
Approval identifiers are exactly 20 lowercase hexadecimal characters. Plan
files must resolve beneath their corresponding
.ainfra/runs/<PLAN_ID>/ directory.
Run records and events
run.json repeats only non-secret immutable plan identity. Lifecycle evidence
is append-only and ordered by a contiguous sequence number. Configuration,
check-mode convergence verification, and post-destroy zero-resource
verification are distinct stages; timestamps are informational and never
override event order. Failed events contain only a stable AINFRA-E* code and
a sanitized recovery category.
The older ainfra.run/v1alpha1 apply/destroy marker remains legacy success
evidence. It is not treated as a durable event history and is never upgraded
silently.
3 - Templates
ainfra embeds its built-in templates in the release binary. Their reviewed
source remains under templates/; each directory contains an
ainfra-template.yaml manifest and a template-specific README.
| Template | Source | Purpose | Status |
|---|---|---|---|
hetzner-kubernetes-baseline | templates/hetzner-kubernetes-baseline | Private-networked, hardened Debian 13 hosts prepared for a later Kubernetes installation | Initial validated template |
The binary does not currently use an external registry or download templates at runtime. Clone the repository only when you want to inspect or adapt source.
Inspect locally
find templates -mindepth 2 -maxdepth 2 -name ainfra-template.yaml -print
ainfra validate hetzner-kubernetes-baseline \
--input templates/hetzner-kubernetes-baseline/inputs/example.input.yaml
The identifier supplied to the CLI is the directory name, not a filesystem path:
ainfra plan hetzner-kubernetes-baseline \
--input /path/to/environment.input.yaml
For selection and extension guidance, see the template strategy and template authoring guide.
4 - Hetzner Kubernetes-ready baseline
The baseline provisions one control-plane-capable Debian 13 host and optional workers on a narrow private network. It does not install Kubernetes.
OpenTofu creates:
- a project-owned private network and subnet;
- a narrowly scoped firewall;
- registrations for operator-supplied SSH public keys;
- Debian 13 servers;
- public IPv4 or IPv6 attachments only when explicitly enabled.
Ansible configures:
- secure SSH daemon policy;
- nftables host firewall policy;
- unattended security updates;
- persistent journal and audit configuration;
- Kubernetes-ready operating-system prerequisites.
Networking
Public IPv4 and IPv6 are independent and disabled in the standard input. Disabling IPv4 means no billable public IPv4 resource or attachment may exist. The generated inventory prefers private management addresses.
Image
The complete supported image set contains one value:
debian-13— the official Hetzner Debian 13 image, updated within its major release and validated by this template.
Host verification
Before the first Ansible connection, verify each SSH host-key fingerprint
through a trusted console or another out-of-band channel. Add that verified key
to known_hosts.
The approved disposable live sequence performs:
- cloud-init schema validation;
- OpenTofu plan and apply;
- deterministic SSH host identity verification;
- Ansible check mode;
- Ansible apply;
- a second apply with zero changes;
- destroy;
- provider and state checks confirming zero resources.
Source layout
templates/hetzner-kubernetes-baseline/
├── ainfra-template.yaml
├── inputs/
├── tofu/
├── cloud-init/
└── ansible/
5 - Acceptance matrix
| Requirement | Evidence |
|---|---|
| Versioned strict contracts | Schema and positive/negative fixture tests |
| Visible wrapper commands | CLI, runner, lifecycle, and adapter tests |
| No template hardcoding | Contract-driven discovery and fixtures |
| Pinned Hetzner template | Provider lock, image enum, validation |
| Secure SSH and keys | Schema, policy, plan, and host tests |
| Safe state | Backend capability validation |
| Secret-free output | Strict schema, redaction, Gitleaks |
| Local validation gates | scripts/validate-all, scripts/test-all |
| Standalone Rust product | No Python package; installed-shell no-tool test |
| Disposable lifecycle | Approved live apply, idempotence, and teardown |
| Clear portfolio boundaries | README and architecture review |
| No unsafe donor behavior | Negative policy and plan tests |
| No GitHub workflows | Repository-policy test |
Every release criterion maps to an automated local check or documented manual verification with sanitized evidence. A claim without one of those forms of evidence remains unverified.