- Forthcoming
- States and Probes
States and Probes
A directory timestamp cannot prove that a virtual environment holds the right interpreter, and last week's successful install cannot prove that a service still answers. RFC 0021 proposes optional named states: a condition, the evidence that checks it, and an optional recipe that prepares it — verified every time, prepared at most once, and never repaired behind anyone's back.
Status on 19 October 2026
- Not in v0.1.0-beta4. Nothing on this page is implemented. The released compiler does not accept
states,ensure_state, or any other syntax shown here. - Proposed. RFC 0021 has status Proposed, created 19 September 2026. It landed in commit 54837e9, which its own message calls documentation only: no runtime implementation, schema changes, or accepted ADRs.
- Planned as phase 23. The progressive-enhancement roadmap splits the work into eight tasks; none is ticked. The roadmap says it “tracks proposed work, not implemented features”.
- No release named. The RFC calls its examples “proposed fragments, not current-release promises”, and leaves the
python-venvinspection contract, the portable lease implementation, record retention limits, and operator probe-policy fields to be ratified before implementation.
Why states
Today a manifest expresses preparation as an ordinary command — command: uv sync — and Ninja decides
freshness by timestamp. That is fine until the question stops being “is this file newer?” and becomes “is
this environment usable?”. States are, informally, the sensors of the system; Netsuke's own vocabulary is
states, probes, and external functional checks, and the rest of this page uses it.
Timestamps are not evidence
“A directory timestamp cannot establish that a virtual environment contains the requested interpreter and packages.” A state checks the thing itself, on every use.
Guards must not build
“A test that requires an already-installed extension must not silently build it.” A requirement that quietly repairs itself tests the repair, not the precondition.
Not a new scheduler
State operations are execution units on RFC 0001's structured runner, not a template-time subprocess facility. Cargo, uv, and friends keep dependency resolution and incremental compilation.
Declaring a state
A state definition carries kind, kind-specific configuration, an optional path, identity,
and optional prepare, probe, and accept_degraded. kind selects a documented
built-in probe; there is no probe: builtin boilerplate. The smallest useful state checks that a directory exists:
Proposed syntax, RFC 0021 §3 (verbatim)
states:
build-directory:
kind: directory
path: build
prepare:
invoke: python -c "from pathlib import Path; Path('build').mkdir(exist_ok=True)"
actions:
- name: prepare-directory
command:
ensure_state: build-directory
It promises only that build is a directory, “not that its contents form a valid build”. Unknown fields, duplicate names, unresolved references, and unsupported kinds fail before execution.
| Kind | What the built-in probe claims |
|---|---|
| directory, file | The declared object has that type, checked without following symlinks. No content-freshness claim. |
| python-venv | The actual interpreter, its environment prefix, and any declared interpreter constraint. It “must not advertise package-set verification that it does not implement.” |
| custom | Nothing of its own. Requires an explicit external probe and cannot claim more than that probe supplies. May omit path to observe a service, which does not authorize network access or remote mutation. |
Probes and external functional checks
Built-in probes serve the ordinary case. When a project needs a condition no built-in knows about, it adds an external
functional check: a repository-owned executable that reports through the optional nagios protocol. No Nagios
installation, daemon, or metric parser is involved — only the plugin exit-status convention.
Proposed syntax, RFC 0021 §3 (verbatim)
states:
dev-env:
kind: python-venv
path: .venv
identity:
files: [pyproject.toml, uv.lock, tools/check_dev_env.py]
prepare:
invoke: uv sync --locked --group dev
probe:
external:
invoke: python tools/check_dev_env.py
protocol: nagios
actions:
- name: test
command:
- ensure_state: dev-env
- invoke: uv run --no-sync pytest
The built-in environment checks still run. The external check adds a condition; “it cannot bypass the built-in checks or forge preparation identity.” The script is project code, not shipped tooling, and a lockfile digest records what preparation was given, not whether the installation is still intact.
Exit codes
| Exit | Probe result | Default state-operation behaviour |
|---|---|---|
| 0 | ready | Continue only if every other condition passes. |
| 1 | degraded | Stop without repair; explicit acceptance may continue. |
| 2 | not_ready | An ensure may prepare once; a requirement never prepares. |
| 3 | unknown | Stop without repair. |
Table from RFC 0021 §6. The statuses follow the Nagios plugin guidelines.
The exit status is authoritative
Any other exit, a signal, a spawn failure, a timeout, malformed output encoding, or an exhausted output budget is unknown, with a distinct reason. “A success-looking message cannot convert a failing exit into readiness.” The first stdout line is a human summary; a | performance suffix may be kept but never controls anything.
Bounded, and honest about it
Proposed defaults: a ten-second deadline and 64 KiB of combined output, adjustable with probe.timeout_seconds and probe.max_output_bytes under operator caps. Only invoke, env, cwd, and an allowed shell are accepted in probe.external. The runner “must not claim a read-only or network sandbox that it does not supply.”
Require, ensure, prepare
The proposal adds exactly three single-key operations to the structured command union. Readiness is defined separately from repair, and there is “no automatic repair loop or fallback runner.”
Verifies readiness and never prepares anything.
Verifies; prepares once when the state is definitely not ready and preparation is declared and authorized; then verifies again.
Explicitly runs preparation and then verification, leaving incremental work to the ecosystem tool. A separate authorized request, not a consequence of a failed probe.
Adapted from RFC 0021 §5 and §9; the built-extension definition is omitted
actions:
- name: extension-tests
command:
- require_state: built-extension
- invoke: uv run --no-sync pytest tests/extension
This is the guard the RFC's Cuprum canary wants: require_state reports a missing extension without running
Maturin to build it. Failure stops the rest of the action, and a state with no preparation recipe yields “an actionable
unmet-precondition result, not an inferred installer.”
The result algebra. ready, not_ready, degraded, and unknown.
degraded fails a requirement by default and never triggers repair; accept_degraded: true may admit it
within operator policy, and the warning stays visible. unknown never authorizes preparation, and an inspection error
outranks a repair decision: one unknown check blocks repair even when another says not-ready.
Evidence and leases
Identity
identity.files lists exact paths, not shell patterns. Identity covers the normalized definition, the probe
implementation and version, the preparation plan, relevant declared input values, the declared files' contents, and the
resolved execution settings; it excludes irrelevant typed inputs.
Files an external probe uses must participate. Diagnostics distinguish a declared command from a verified executable and
disclose unmodelled ambient dependencies.
Records
A persistent record “proves only that a particular preparation completed and passed verification. It is never a reusable live probe result.” Mutable state is validated on every operation: no timestamp stamps, no unqualified success stamps, no default time-to-live. A removed or corrupted record means no trusted evidence, not success. Records are published atomically, only after verification, so interrupted preparation leaves none.
Leases
Before its first command, an action acquires a workspace-scoped advisory lease for every state it references, in canonical order, and holds them through its last consumer. They are integrity locks for cooperating invocations; unmanaged commands and other programs do not take part, and the guarantee must say so.
Scheduling
State operations stay inside ordinary Ninja-scheduled action edges. State evidence is not a Ninja output, so a state-using action must be always-run; an incremental file target with state operations is rejected rather than risk skipping a readiness check because an unrelated output is up to date.
Cleanup and teardown
No automatic removal after failure and no undeclared teardown. Artefact ownership is a separate optional contract; when an owned environment is cleaned, its records are invalidated under the same lease before anything is deleted.
Roadmap: 23.2, leased state operations with durable evidence, building on the shared lease seam in 20.2.
From the shallow end
The guarantee belongs to RFC 0025.
Unused state declarations never cause runtime probes, installation, network access, or resource acquisition. No heuristic
promotes uv sync to a state. The quickstart manifest stays a release-acceptance fixture, and basic usage is
“a supported destination, not a temporary migration stage.” RFC 0021 defers to it by name.
What stays optional
- A plain
command: uv syncremains valid. A state earns its place only when an author needs an explicit precondition, validated reuse, or a shared preparation contract. - Adoption is per action. No context, typed input, ownership declaration, maturity policy, or bundle is compulsory.
- Help,
check, graph generation, context inspection, and dry-run never run a probe or a preparation.
Quickstart manifest, as RFC 0025 §2 fixes it
netsuke_version: "1.0.0"
targets:
- name: hello.txt
command: "echo 'Hello from Netsuke!' > hello.txt"
defaults:
- hello.txt
A normal hello-world build must start zero probes and create no state records.
The route in, one step at a time
- A
directoryorfilestate withensure_state: presence, nothing more. - A
python-venvstate withidentityandprepare, so preparation is recorded against its inputs. require_stateon guards that must never build what they check.- An external functional check under the
nagiosprotocol, thenkind: customfor conditions no built-in covers. - The opt-in
verified-statesmaturity rule, for a project that wants selected actions checked for a state operation before their first command.
The RFC's delivery order matches: “built-in probes and explicit operation semantics first, then external functional checks”. See roadmap 23.1 and 23.3.
Confidence
Every item below is a planned acceptance requirement from RFC 0021 §9 and roadmap phase 23. None exists yet. The roadmap asks for Proptest over algebraic invariants, bounded Kani harnesses for pure transition logic where useful, and subprocess end-to-end tests for process, filesystem, and locking boundaries.
Properties
Unknown never authorizes repair; degraded requires explicit acceptance; ensure performs at most one preparation attempt; only verified success creates a record.
Hostile probes
Every exit code, empty and multiline output, performance suffixes, non-UTF-8 data, terminal escapes, huge simultaneous output, hung descendants, cancellation, and “a failure message containing apparent success text”. Collection must stay bounded and every process reaped.
End to end
Delete a package or interpreter component after successful preparation; change identity inputs; contend on one resource from separate processes; interrupt between preparation and record publication.
A real migration
The Cuprum canary (roadmap 23.4) must show fresh detection of a removed component, never a stale probe counted as a cache hit, and zero probes for the unchanged hello-world selection.
The limits are stated as plainly as the guarantees. “State annotations do not make an untrusted checkout safe to execute.” An observational convention “must not be described as an operating-system sandbox.”
Accessibility and localization
Every human-readable message Netsuke emits is localized by default through its Fluent catalogues: state results,
unmet preconditions, and preparation failures arrive in the reader's locale, while state names, result values such as
not_ready, and reason codes stay untranslated. RFC 0021 does not discuss how state results appear in Netsuke's
accessible output mode, and this page does not invent a
promise. What the design does say bears on anyone reading probe output through a screen reader or a log:
- Probe arguments and output are redacted through the shared diagnostic policy, messages are bounded, and terminal control sequences are escaped. Raw probe output never becomes a metric label.
- The first stdout line of an external probe is a human summary; the rest is bounded diagnostic data. A preparation-identity mismatch and a functional failure keep distinct reason codes.
- Roadmap task 23.4.2 publishes state diagnostics and a probe author guide, and adds “metadata and localization through existing surfaces” — the Fluent catalogues Netsuke already ships.
- RFC 0025 §8 tells maturity diagnostics, including
verified-statesfindings, to “Reuse Fluent localization” and never to leak probe output.
An external probe’s summary line is the probe author’s own text, so Netsuke shows it as written, inside its own localized framing.