Forthcoming capability
Proposed · no release named Proposed RFC

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-venv inspection 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

Nagios-style statuses and their Netsuke interpretation, RFC 0021 table 1
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.”

require_state

Verifies readiness and never prepares anything.

ensure_state

Verifies; prepares once when the state is definitely not ready and preparation is declared and authorized; then verifies again.

prepare_state

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 sync remains 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

  1. A directory or file state with ensure_state: presence, nothing more.
  2. A python-venv state with identity and prepare, so preparation is recorded against its inputs.
  3. require_state on guards that must never build what they check.
  4. An external functional check under the nagios protocol, then kind: custom for conditions no built-in covers.
  5. The opt-in verified-states maturity 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-states findings, 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.