diff --git a/docs/contents.md b/docs/contents.md index b8e4d493b..67099f59d 100644 --- a/docs/contents.md +++ b/docs/contents.md @@ -28,6 +28,9 @@ operator, user, and contributor references are easier to find. - [roadmap-composition.md](roadmap-composition.md): Roadmap continuation for local includes, versioned bundles, explicit external acquisition, and composition-specific execution-context integration. +- [roadmap-progressive-enhancement.md](roadmap-progressive-enhancement.md): + Phases 20 to 25 for shallow-end compatibility, optional typed inputs, + contention classes, states and probes, owned cleanup, and maturity policies. - [archive/roadmap-completed-foundations.md](archive/roadmap-completed-foundations.md): Archived completed roadmap foundations with relevance assessments and traceability notes. @@ -77,6 +80,16 @@ operator, user, and contributor references are easier to find. fuzzing](rfcs/0008-code-health.md): Proposed workflow-policy validation, gate self-consistency, health-signal ownership, and scheduled coverage-guided fuzzing. +- [RFC 0021: Managed states and functional probes][rfc-0021]: Optional + preparation contracts, default built-in checks, and bounded external probes. +- [RFC 0022: Optional typed task inputs][rfc-0022]: Gradual input annotation, + shared bundle-parameter validation, and explicit source provenance. +- [RFC 0023: Artefact ownership and scoped cleanup][rfc-0023]: Exact output + ownership, bounded previews, and capability-scoped deletion. +- [RFC 0024: Named contention classes][rfc-0024]: Optional per-edge limits + lowered to Ninja pools, with explicit invocation-only scope. +- [RFC 0025: Progressive enhancement and maturity policies][rfc-0025]: + Shallow-end compatibility and opt-in, scoped, trust-aware enforcement. [rfc-0009]: rfcs/0009-structured-command-working-directories.md [rfc-0012]: rfcs/0012-netsukefile-property-testing.md @@ -84,6 +97,11 @@ operator, user, and contributor references are easier to find. [rfc-0002]: rfcs/0002-repository-relative-includes.md [rfc-0004]: rfcs/0004-digest-pinned-external-bundles.md [rfc-0011]: rfcs/0011-allow-listed-structured-command-shells.md +[rfc-0021]: rfcs/0021-managed-states-and-probes.md +[rfc-0022]: rfcs/0022-typed-task-inputs.md +[rfc-0023]: rfcs/0023-artefact-ownership-and-scoped-cleanup.md +[rfc-0024]: rfcs/0024-named-contention-classes.md +[rfc-0025]: rfcs/0025-progressive-enhancement-and-maturity-policies.md ## Decision records diff --git a/docs/rfcs/0021-managed-states-and-probes.md b/docs/rfcs/0021-managed-states-and-probes.md new file mode 100644 index 000000000..c97906cff --- /dev/null +++ b/docs/rfcs/0021-managed-states-and-probes.md @@ -0,0 +1,325 @@ +# RFC 0021: Managed states and functional probes + +## Preamble + +- **RFC number:** 0021 +- **Status:** Proposed +- **Created:** 2026-09-19 +- **Scope:** Optional preparation contracts, not a new build scheduler +- **Implementation:** [Progressive-enhancement roadmap, phase 23][roadmap] + +## 1. Summary + +Add optional named states for things that must be prepared before a command +runs. A state describes a condition, the evidence needed to check it, and an +optional preparation recipe. Built-in probes serve the normal case; external +functional checks support project-specific conditions without a provider plugin +or a Nagios installation. + +A plain `command: uv sync` remains valid. Adding a state is worthwhile only +when an author needs an explicit precondition, validated reuse, or a shared +preparation contract. No context, typed input, ownership declaration, maturity +policy, or bundle is compulsory. [RFC 0025][maturity] owns this shallow-end +compatibility requirement. + +## 2. Problem and existing boundaries + +A directory timestamp cannot establish that a virtual environment contains the +requested interpreter and packages. A successful previous installation cannot +establish that an external service still works. Conversely, a test that +requires an already-installed extension must not silently build it. + +[RFC 0001][commands] owns structured execution; its working-directory, +temporary resource, and shell amendments remain authoritative. This RFC adds +execution units to that runner, not a template-time subprocess facility. Cargo, +uv, and other ecosystem tools retain dependency resolution and incremental +compilation. + +## 3. Progressive authoring + +The following proposed fragment uses a built-in presence probe. It promises +only that `build` is a directory, not that its contents form a valid build: + +```yaml +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 +``` + +`kind` selects a documented built-in probe by default. There is no mandatory +`probe: builtin` boilerplate. Initial kinds are `directory`, `file`, and +`python-venv`. The first two check the declared object type without following +symlinks. They make no content-freshness claim. `python-venv` checks the actual +interpreter, its environment prefix, and any declared interpreter constraint; +it must not advertise package-set verification that it does not implement. + +A richer proposed fragment keeps a functional check separate from preparation: + +```yaml +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 +``` + +Here 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-owned runtime code, not an example of shipped tooling. +Its declared condition must cover any package expectations on which consumers +rely. A lockfile digest records preparation inputs, not current installation +integrity. + +`kind: custom` supports conditions without a suitable built-in kind. It +requires an explicit external probe and cannot acquire stronger verification +claims than that probe supplies. A custom state may omit `path` when it +observes a service; that does not authorize network access or remote mutation. + +## 4. State definition and identity + +State names use the existing declaration-name and namespace rules. Unknown +fields, duplicate names, unresolved references, and unsupported kinds fail +before execution. Definitions contain `kind`, kind-specific configuration, an +optional `path`, `identity`, optional `prepare`, optional `probe`, and optional +`accept_degraded`. + +`identity.files` contains exact capability-scoped paths, not shell patterns. +Missing required files are errors; optional inputs need a future explicit +contract. The normalized definition, probe implementation/version, preparation +plan, relevant declared input values, declared file contents, and resolved +execution settings contribute to identity. Built-ins add their documented +interpreter/platform facts. Identity excludes irrelevant typed inputs. + +Files used by an external probe, including project scripts, must participate in +identity. Bundled runtime files follow RFC 0003's resource inventory and digest +rules. Ambient tools cannot be described as pinned solely because their names +match. Diagnostics distinguish a declared command from a verified executable +identity and disclose unmodelled ambient dependencies. + +A persistent record proves only that a particular preparation completed and +passed verification. It is never a reusable live probe result. Validate mutable +state on every state operation; do not cache success by a directory timestamp, +unqualified success stamp, or default time-to-live. Removing or corrupting a +record means no trusted preparation evidence, not success. + +Without an `identity` declaration, readiness depends on current probes and no +prior preparation record is required. Declaring `identity` adds matching +preparation evidence as a readiness condition. Post-preparation verification +checks the newly prepared candidate identity plus fresh probes, not the stale +record it is about to replace; publication still follows successful checks. + +For identities that require preparation evidence, a mismatch establishes +`not_ready` only after the resource can be inspected successfully. Permission +failure, unsupported inspection, and unreadable identity inputs remain errors or +`unknown`, not repair triggers. A custom functional condition without such +identity inputs can become ready through verification alone. + +## 5. Operations and outcomes + +The proposed command union adds exactly three single-key operations: + +- `require_state: NAME` verifies readiness and never prepares anything. +- `ensure_state: NAME` verifies, prepares once when definitely not ready and + preparation is declared and authorized, then verifies again. +- `prepare_state: NAME` explicitly runs preparation and subsequent verification, + delegating any incremental work to the selected ecosystem tool. + +An operation holds its state lease as described in section 8. Preparation must +finish successfully and post-verification must establish readiness before +Netsuke records success. There is no automatic repair loop or fallback runner. +Failure stops subsequent commands in the action. An absent preparation recipe +produces an actionable unmet-precondition result, not an inferred installer. + +The result algebra is `ready`, `not_ready`, `degraded`, and `unknown`. +`degraded` fails a required readiness condition by default and never triggers +repair. A state may explicitly set `accept_degraded: true` only within operator +policy; the warning remains observable. `unknown` never authorizes automatic +preparation or satisfies a required condition. An explicit `prepare_state` is a +separate authorized mutation request, not a consequence of a failed probe. +Cancellation remains cancellation. + +Combining built-in and external checks requires every condition to pass. +Inspection errors take precedence over a possible repair decision: an unknown +check prevents repair even when another check reports not-ready. A preparation +identity mismatch and a functional failure retain distinct reason codes. + +## 6. External probe protocol + +The optional `nagios` protocol uses the conventional plugin exit statuses[^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 1: Nagios-style statuses and their Netsuke interpretation. + +Other exits, signals, spawn failure, timeout, malformed output encoding, and +output-budget exhaustion produce `unknown` with a distinct execution reason. A +process's exit status is authoritative; a success-looking message cannot +convert a failing exit into readiness. The first stdout line supplies a human +summary. Remaining stdout and stderr are bounded diagnostic data. A `|` suffix +may be retained as uninterpreted performance data, but it never controls state, +identity, scheduling, or authorization. No metric parser or Nagios daemon is +required. + +Proposed defaults are a ten-second wall-clock deadline and 64 KiB combined +stdout/stderr. Optional `probe.timeout_seconds` and `probe.max_output_bytes` +are positive integers; trusted operator limits cap requested bounds. Drain both +streams concurrently, enforce limits during collection, and terminate and reap +the owned process tree on timeout, cancellation, or excess output. No shell +redirection or unbounded capture is necessary. A probe cannot change +command-local runtime bindings in the consuming action. + +Only ordinary structured invocation fields needed for a functional check are +accepted inside `probe.external`: `invoke`, `env`, `cwd`, and allowed `shell` +selection. Reject pipelines, stream files, runtime capture, nested state +operations, and cleanup there. Explicit named-shell probes remain possible +under RFC 0011; direct invocation is the default. Probe return codes are not +Netsuke's public CLI exit codes: the existing structured-result mapping owns +that translation. + +Probes should be observational and idempotent, but executing arbitrary code +does not prove either property. The runner must not claim a read-only or +network sandbox that it does not supply. An operator can forbid external probes +or restrict their executable identities. Project configuration and bundle +content cannot weaken those restrictions. Lack of authorization is an error, +not a reason to bypass the probe. Do not run probes or preparation merely to +render help, inspect context, check a manifest, generate a graph, or preview +work. + +## 7. Execution settings and security + +Use the ordinary inherited environment, exact per-command overlays, trusted +shell selection, and capability-scoped working-directory rules. A new named +context system is not a prerequisite. If named contexts later become available, +resolve them through the same command plan rather than a state-private resolver. + +Probe and preparation commands may intentionally use different tools. Record +both effective settings and diagnose accidental environment-root mismatches; do +not replace an unavailable requested interpreter with an ambient one. External +code has the same trust implications as a build recipe. State annotations do +not make an untrusted checkout safe to execute. + +Redact probe arguments and output through the shared diagnostic policy, bound +messages, and escape terminal control sequences. Do not export raw probe output +as metric labels. Built-ins must use the injected environment and filesystem +seams, not process-global mutation or a separate configuration reader. + +## 8. Scheduling, mutation, and durable records + +Keep state operations inside ordinary Ninja-scheduled action edges. They are +not graph-discovery operations, and their results cannot change manifest-time +conditions or add undeclared dependencies. The action-plan codec must represent +them explicitly and reject unknown versions during replay. + +State evidence is not itself a Ninja output. A state-using action must either +be an always-run action or explicitly opt into always-run execution; reject an +incremental file target with state operations until a separate +runtime-validation contract can guarantee that its checks actually run. This +avoids silently skipping a readiness check because an unrelated output is up to +date. + +For managed mutable paths, acquire a workspace-scoped advisory lease for every +state referenced by an action before its first command, in canonical resource +order, and retain the leases through the last consumer in that action. These +are integrity locks for cooperating invocations, not a replacement scheduler. +Bound acquisition and never acquire a lease while recursively invoking Netsuke. +Reject different state identities claiming the same mutable path within one +selected build closure; isolated paths are the first-version remedy. + +A standalone preparation action does not hold a lease for its dependants. +Consumers therefore need their own `require_state` or `ensure_state` operation. +Shared preparation may be skipped after fresh validation, but readiness cannot +be memoized across another action's mutation. Unmanaged commands and other +programs do not participate; the guarantee must say so explicitly. + +Named contention classes in [RFC 0024][contention] can reduce contention before +action dispatch. They do not replace state leases across invocations. Shared +state identity and filesystem alias handling must use existing capability +anchors; ambiguous aliases or unsupported locking filesystems fail rather than +pretend to serialize access. + +Store records in a versioned, bounded runtime namespace separate from dyndep +sidecars. Publish records atomically only after verification. Interrupted +preparation leaves no success record; partial resources remain unverified and +may need explicit remediation. Cleanup invalidates records under the same +lease. Do not automatically remove an environment after failure or run +undeclared teardown commands. Artefact ownership is a separate optional +contract. + +## 9. Verification and acceptance + +Unit and behavioural tests must cover all four results, all three operations, +post-preparation failure, identity changes, damaged records, denied probes, and +absence of a preparation recipe. Property tests must establish that unknown +results never authorize repair and only verified success creates a record. + +External-probe fixtures must cover 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. Assert bounded collection and complete process reaping. + +End-to-end tests must delete a package/interpreter component after successful +preparation, change identity inputs, contend on the same resource from separate +processes, and interrupt between preparation and record publication. A normal +hello-world build must start zero probes and create no state records. + +The Cuprum canary must preserve its restricted extension-test selection and +verify that `require_state` reports a missing extension without running +Maturin. Measure setup reduction without counting a stale probe result as a +cache hit. + +## 10. Alternatives, migration, and outstanding decisions + +Timestamp-only stamps are insufficient for live readiness. A mandatory Nix-like +store would require a different workflow and is out of scope. External checks +alone would force every project to reinvent common checks; built-ins alone +would force plugin development for ordinary functional conditions. + +Adoption is per action. Existing commands and environment management remain +supported, and no package installer becomes a prerequisite for unrelated work. +Allocate the manifest and persisted-plan versions during acceptance alongside +RFC 0001; examples here are proposed fragments, not current-release promises. + +Before implementation, ratify the precise `python-venv` inspection contract, +portable lease implementation, runtime-record retention limits, and operator +probe-policy fields. A future uv-specific package-integrity probe should reuse +uv's supported interfaces rather than implement another resolver. None of these +choices may make bare command recipes depend on state machinery. + +## 11. Recommendation + +Deliver built-in probes and explicit operation semantics first, then external +functional checks with the same result algebra and process boundary. Keep +readiness, preparation evidence, and artefact ownership distinct. + +[roadmap]: ../roadmap-progressive-enhancement.md#23-verified-preparation-without-mandatory-environments +[maturity]: 0025-progressive-enhancement-and-maturity-policies.md +[commands]: 0001-structured-command-blocks.md +[contention]: 0024-named-contention-classes.md +[^1]: [Nagios plugin development guidelines](https://nagios-plugins.org/doc/guidelines.html), + plugin return codes. diff --git a/docs/rfcs/0022-typed-task-inputs.md b/docs/rfcs/0022-typed-task-inputs.md new file mode 100644 index 000000000..fd3de28f6 --- /dev/null +++ b/docs/rfcs/0022-typed-task-inputs.md @@ -0,0 +1,235 @@ +# RFC 0022: Optional typed task inputs + +## Preamble + +- **RFC number:** 0022 +- **Status:** Proposed +- **Created:** 2026-09-19 +- **Scope:** Public task configuration with gradual adoption +- **Implementation:** [Progressive-enhancement roadmap, phase 21][roadmap] + +## 1. Summary + +Add an optional `inputs` mapping for validated, discoverable task +configuration. Ordinary `vars` remain valid and keep their current semantics. +An author can promote one externally meaningful value without annotating every +variable, rewriting every command, adopting bundles, or declaring an execution +context. + +Use the same type vocabulary, value validation, and redaction contract as +[RFC 0003's bundle parameters][bundles]. Do not create a rival parameter +system. The [maturity-policy RFC][maturity] permits explicit organizations or +projects to require selected contracts; it does not turn annotations into a +default gate. + +## 2. Problem and current boundaries + +Raw flag strings combine user intent, argument transport, and tool-specific +syntax. The distinction matters when compilation workers and test workers use +different switches. Validation should reject an invalid worker count before any +command starts, while argv handling should remain RFC 0001's responsibility. + +The current manifest supports variables. RFC 0003 already proposes typed bundle +parameters, and roadmap phase 5 owns profile and context integration with +OrthoConfig. This RFC supplies task-input semantics and their integration, not +another configuration loader, profile store, or command metadata generator. + +## 3. The shallow end and one-value promotion + +This existing-style fragment remains legitimate without annotation: + +```yaml +vars: + workers: 2 + +actions: + - name: test + command: "pytest -n {{ workers }}" +``` + +The following proposed fragment adds a contract for that one input: + +```yaml +inputs: + workers: + type: integer + minimum: 1 + default: 2 + description: Number of pytest workers + expose: non-secret + +actions: + - name: test + command: + invoke: pytest -n {{ inputs.workers }} +``` + +Changing `workers` to `inputs.workers` makes the promotion explicit. There is +no implicit alias, mutation of `vars`, or requirement to annotate unrelated +values. Structured invocation is recommended for dynamic arguments, but +adopting an input does not silently convert a legacy shell string into direct +execution. + +## 4. Definition and value model + +The initial `type` vocabulary is exactly `string`, `bool`, `integer`, `path`, +`sequence`, `sequence`, and `mapping`. A string +with `choices` represents an enum. An argument list uses `sequence`; +there is no competing `argv` or `enum` type in the initial grammar. + +A definition contains required `type`, optional `default`, `description`, and +`expose`, plus applicable constraints. No default means required. `minimum` and +`maximum` constrain integers; `choices` contains a nonempty, duplicate-free +list of values of the declared scalar type. Reject inverted ranges, mismatched +constraints, unknown fields, and defaults that violate their own contract. + +Definitions and defaults are literal data, like bundle descriptors, not Jinja +programs. Computed internal values belong in ordinary `vars` after input +resolution. No executable, clock, filesystem, or network default expressions +are necessary. Required but unused root inputs still fail definition +resolution; authoring an optional facility must not introduce an unrelated +required input. + +Use the existing supported integer range and reject out-of-range values. A +Boolean is not an integer; `1.0` is not silently accepted as `1`. Strings +remain strings, including empty strings. Null is not an omitted optional value +unless a future shared nullable-type contract explicitly introduces it. + +`path` is a typed path value, not a filesystem capability or permission grant. +Validation checks encoding and lexical shape without creating files. A consumer +that opens, executes, or deletes the path applies its own workspace and +operator capability constraints at use time. Collections are homogeneous, +bounded, and validated element by element; sequence order is significant. + +`inputs` is immutable during manifest expansion. Introducing this reserved +namespace must preserve manifests that do not opt into the new schema. When an +opted-in manifest already defines a conflicting `vars.inputs`, emit a targeted +migration diagnostic rather than overwrite it silently. + +## 5. Sources, precedence, and provenance + +Introduce one proposed CLI option, `--input NAME=VALUE`, on commands that +compile a manifest. Register it through the canonical command metadata before +public examples or implementation. Names select declared root inputs only. +Qualified private bundle parameters remain inaccessible; the root passes +selected values through the existing `with` boundary. + +CLI parsing is type-directed, not shell splitting or arbitrary YAML evaluation: + +- Strings and paths retain the text after the first `=` exactly. +- Integers use the documented signed decimal grammar without floats. +- Booleans accept exactly `true` and `false`. +- Sequences and mappings accept JSON of the declared shape, with bounded size + and explicit duplicate-key rejection. + +Reject unknown names and duplicate CLI occurrences. Do not interpret an input +as an executable shell fragment. Direct argv interpolation must preserve +spaces, metacharacters, empty elements, and sequence boundaries. The shell +still owns interpretation inside an explicitly selected shell recipe. + +Precedence, highest first, is explicit CLI input, explicitly selected profile +input, resolved configuration input, then manifest default. Existing +OrthoConfig rules determine precedence within configuration sources; this RFC +does not reorder system, user, project, and explicitly selected configuration +files. The profile integration task must reconcile its own overlay order with +this contract before implementation. No automatically inferred environment +variables supply task inputs in the first version. + +Validate the effective value once, retaining the winning source and bounded +shadowed-source provenance. Duplicate declarations and malformed source data +remain errors even when a higher-precedence value exists. Configuration syntax +cannot create undeclared inputs or weaken their constraints. Operator ceilings +apply after selection and may reject a request; they are not defaults that a +project can override. + +Profiles bind input values; they do not replace input definitions. Inspection +must identify the effective profile, source, and validation contract without +running a build or acquiring tool environments. Persistent generated plans fix +their resolved values: replay never re-reads a different ambient profile. + +## 6. Composition and identity + +Includes follow RFC 0002's duplicate and provenance rules. The initial root +`inputs` mapping supplies the root interface. Included fragments can reference +it within their established scope; they cannot silently overwrite definitions. +A bundle receives explicit values through `with` and exposes them internally as +`bundle.params`, not the importer's whole `inputs` object. + +Extract or reuse one feature-owned normalized parameter contract for root +inputs and bundle parameters. Share parsing, validation, scalar normalization, +constraint diagnostics, and redaction. Bundle selection, locks, private +exports, and namespace resolution remain composition's responsibility. Root +input support must not wait for external acquisition or require any bundle. + +Resolved values used by an action or state contribute to its existing +fingerprint. Preserve collection order and canonicalize mapping-key order. Do +not replace action hashing or invent a separate build cache. Unused inputs must +not alter a resolved command's argv; conservative graph-level invalidation may +remain until dependency tracking can narrow it safely. + +Raw argument-list escape hatches remain legal. Tool adapters that claim control +over workers, interpreter selection, or environment location must reject +conflicting owned switches in those lists. Typed data alone does not guarantee +that a tool-specific flag bag respects the declared policy. + +## 7. Diagnostics, redaction, and limits + +Follow RFC 0003: values are redacted by default. Only the exact declaration +`expose: non-secret` permits ordinary inspection of a value, subject to +stronger operator policy. Show the name, expected type, constraint, source +location, and remedy without echoing a rejected secret-looking value. Human +output, JSON, verbose output, snapshots, and telemetry share that boundary. + +Redaction is not secret storage. This RFC does not introduce a secret type or +promise confidentiality for values deliberately passed as process arguments or +persisted in action plans. Future secret support must address process listings, +plan storage, and low-entropy digest leakage rather than merely hide UI text. + +Use the existing evaluation budgets for definition count, collection depth, +element count, input byte size, and diagnostics. Apply bounds before expensive +allocation or evaluation. A maturity setting cannot disable type correctness, +resource limits, or capability checks for an opted-in declaration. + +## 8. Compatibility, tests, and rollout + +Allocate the manifest version with the shared schema acceptance work. Older +readers must reject new syntax with a useful version remedy rather than ignore +it. Existing untyped manifests keep their parse, execution, and diagnostic +behaviour under the default policy. No release should force a broad migration +merely because this optional vocabulary exists. + +Test the same value corpus through bundle and task-input validation. Include +false-as-integer, overflow, whitespace, empty strings, duplicate keys, invalid +constraints, required values, unknown names, hostile argv elements, Windows +paths, and collection-order preservation. Property-test source precedence, +normalization stability, and redaction of rejected values. + +Integration tests must cover profile selection, generated-plan replay, +namespace privacy, environment-independent defaults, and values passed across +an include/bundle boundary. Check one-variable promotion in a real command and +retain the unmodified hello-world fixture. Compare actual child argv, not just +rendered YAML. Add examples and metadata through the existing documentation and +schema pipelines, without a parallel input-help renderer. + +## 9. Alternatives and outstanding decisions + +Mandatory annotations would obstruct onboarding. Continuing with only raw flag +strings would leave validation and discovery to each recipe. A second schema +language for task inputs would duplicate RFC 0003 and make composition harder. +Implicit promotion of every variable would unexpectedly create a public API. + +Before implementation, ratify the CLI option's placement in canonical metadata, +profile overlay details, and the exact existing integer and path-normalization +contracts to share. Whether richer dependent defaults or custom validators are +needed remains deferred until canaries demonstrate demand. Arbitrary validation +code is not part of the initial input schema. + +## 10. Recommendation + +Deliver one optional root interface with the existing bundle type vocabulary, +explicit promotion, deterministic source selection, and default redaction. Keep +internal variables untyped unless their author chooses otherwise. + +[roadmap]: ../roadmap-progressive-enhancement.md#21-typed-inputs-with-one-parameter-contract +[bundles]: 0003-versioned-local-bundles.md#6-parameter-model +[maturity]: 0025-progressive-enhancement-and-maturity-policies.md diff --git a/docs/rfcs/0023-artefact-ownership-and-scoped-cleanup.md b/docs/rfcs/0023-artefact-ownership-and-scoped-cleanup.md new file mode 100644 index 000000000..872ad6cf4 --- /dev/null +++ b/docs/rfcs/0023-artefact-ownership-and-scoped-cleanup.md @@ -0,0 +1,259 @@ +# RFC 0023: Artefact ownership and scoped cleanup + +## Preamble + +- **RFC number:** 0023 +- **Status:** Proposed +- **Created:** 2026-09-19 +- **Scope:** Optional owned-output declarations and bounded deletion +- **Implementation:** [Progressive-enhancement roadmap, phase 24][roadmap] + +## 1. Summary + +Add optional named artefacts and one standardized scoped-cleanup operation. An +author who declares disposable output ownership gains bounded enumeration, a +useful preview, explicit confirmation, and capability-scoped deletion. Plain +commands, existing targets, and existing `clean` behaviour remain supported. + +Ownership is an assertion by the manifest author, not proof that a directory +contains no valuable files. The implementation must make the scope inspectable +and enforce its boundaries without claiming to sandbox arbitrary recipes. +[RFC 0025][maturity] makes this an opt-in improvement rather than an onboarding +prerequisite. + +## 2. Problem and existing boundaries + +Repositories repeat cleanup lists and broad deletion commands. The lists drift +away from producers, and shell fragments obscure path, symlink, and error +semantics. A benchmark report, shared tool cache, temporary directory, and +prepared environment also need different retention decisions. + +Netsuke already has target outputs and delegates ordinary output cleaning to +Ninja. Its runtime owns dyndep sidecars under a separate bounded-retention +contract. RFC 0010 owns command-local temporary directories. This RFC must not +replace those mechanisms or infer ownership from arbitrary command text. + +## 3. Progressive authoring + +The following proposed fragment adds one explicit disposable directory and a +cleanup action without a state, typed input, context, or package provider: + +```yaml +artefacts: + build-output: + path: build + kind: directory + +actions: + - name: clean-build + command: + clean_owned: [build-output] +``` + +`path` names exactly one workspace-relative object. `kind` is `file` by +default; recursive ownership requires explicit `directory`. A directory +declaration claims the entire subtree, including existing and subsequently +created children. The preview must say this plainly. It is unsuitable for a +directory shared with source files or another owner's mutable state. + +A producer can identify a report without changing whether the action runs: + +```yaml +artefacts: + benchmark-report: + path: dist/benchmarks/microbenchmarks.json + role: report + create_parent: true + +actions: + - name: benchmark + produces: [benchmark-report] + command: + invoke: python benchmarks/run.py --output dist/benchmarks/microbenchmarks.json +``` + +The command is illustrative. `produces` records ownership and expected outputs; +it does not make a benchmark incremental or authorize replay of an old result. +Directory creation occurs only when that producer executes, through the same +path boundary used for cleanup. + +## 4. Declaration, ownership, and output contracts + +An artefact contains required `path`, optional `kind`, optional `role`, and +optional `create_parent`. Initial roles are `generated`, `report`, `cache`, and +`environment`, with `generated` the default. A role describes intent, not an +automatic deletion or caching rule. No role disappears merely because another +action failed. Exact paths, rather than arbitrary glob expressions, keep the +first implementation bounded and reviewable. + +Use existing namespace, duplicate, and source-provenance rules. Each normalized +path has one owning declaration. Reject duplicate or overlapping ownership +roots, including case-equivalent names on the relevant filesystem. Parent +creation is not an ownership claim over the parent or its other contents. A +directory and a separately declared child are overlapping owners in this first +version; declare one or use disjoint exact files. + +A `produces` reference identifies one producer. Reject multiple producers for +the same artefact unless an existing explicit target contract already supplies +one shared producer. An explicit artefact may refer to an existing target +output only when both identify that same producer and compatible object type. +It augments metadata; it must not create a second Ninja producer edge. + +Verify declared required outputs after successful production. Missing outputs +are producer failures, not success. Failure may leave partial owned outputs; +record the failure but retain those outputs for inspection and explicit +cleanup. Do not automatically remove them or reuse them as successful build +evidence. + +Declarations without a producer remain useful for externally generated, +explicitly disposable paths. Inspection must distinguish declared ownership +from observed successful production. Neither status attests that the author +chose a safe directory. No producer receipt is required merely to remove a +declared legacy build directory, but declaration, bounded preview, and +authorization are. + +This does not introduce remote artefact delivery, a content-addressed store, or +a provenance attestation system. Roadmap phase 5 retains its delivery boundary. + +## 5. Scoped-cleanup operation and public command integration + +Add `clean_owned: [NAME, ...]` as an explicit execution unit in the structured +command union. The list is nonempty, order-insensitive after name resolution, +and duplicate references normalize to one selection. Unknown references fail. +The operation may name artefacts only; it never accepts raw shell paths. + +Extend the existing `clean` command with repeatable `--artefact NAME` +selection. Register the extension in canonical CLI metadata before implementing +it. Without that option, preserve existing Ninja-output cleanup. With explicit +artefact selection, clean only the selected ownership roots; do not implicitly +add every Ninja output, environment, cache, or runtime directory. + +These proposed commands demonstrate preview and explicit noninteractive consent: + +```bash +netsuke clean --artefact build-output --dry-run +netsuke clean --artefact build-output --force --no-input +``` + +Both command and recipe forms use the same planner, validator, deleter, and +structured results. Use the existing mutation metadata for `--dry-run`, +`--force`, and `--no-input`; do not invent a cleanup-specific confirmation +framework. An interactive run can request confirmation after displaying scope. +A noninteractive destructive run without explicit consent fails before deletion. +`--force` skips confirmation, not validation, ownership conflicts, or bounds. + +A dry-run is a read-only plan, not a reusable authorization token. Execution +resolves and validates scope again, and must not consume a stale user-supplied +list as trusted filesystem authority. The preview lists normalized roots, +recursive ownership, existing objects, missing objects, retained siblings, and +any rejected scope. It must not truncate away selected objects and then claim +to show a complete destructive plan: exceeding bounds fails the plan. + +## 6. Filesystem safety contract + +Anchor paths at the effective workspace capability, not the caller's ambient +working directory or the location of an included fragment. Reject absolute +paths, empty paths, the workspace root, parent traversal, incompatible +encodings, and platform-specific escape forms before touching the filesystem. + +Protect the effective manifest, loaded configuration, local runtime scripts +known to the compiled plan, declared source paths, VCS metadata such as `.git`, +and Netsuke's reserved runtime directories. A directory claim containing a +protected object is invalid. Without source-control metadata Netsuke cannot +identify every source file; the contract must disclose this limitation rather +than infer arbitrary source ownership. + +Never follow a symlink or Windows reparse point during recursive traversal. A +selected final symlink may be unlinked as an object within the declared root, +but never traversed to its destination. Revalidate its identity and parent +capability at deletion. Reject unsupported mount, junction, or filesystem cases +rather than fall back to lexical `starts_with` checks or ambient `rm -rf`. + +Use handle-relative traversal and deletion with platform-specific identity +checks. A path checked before enumeration is not automatically safe at +deletion. Detect replacement of selected roots and parents, and stop affected +work. No claim of race freedom is acceptable until adversarial replacement +tests pass on each supported platform; unavailable guarantees must produce +explicit refusal. Hard-linked file removal unlinks the selected directory +entry, never truncates the shared underlying file. + +Enforce operator-capped entry, depth, byte, and elapsed-time limits. Perform +the bounded admission pass before the first deletion; exceeding a bound there +causes zero deletions. Recheck while deleting because the tree may change. +Delete files before directories in a stable order. Already-missing objects are +successful no-ops. Permission, replacement, or interruption errors may follow +partial progress; report exact removed, retained, and failed objects. Cleanup +is not an atomic transaction and must never report rollback that it did not +perform. + +## 7. Interaction with states, concurrency, and replay + +A separately declared environment artefact may name the same normalized path as +a [managed state][states]; the resource registry identifies that association. +Ownership remains optional for state use. When an environment is selected for +cleanup, acquire its integrity lease and invalidate its readiness records +before any deletion, including failure paths. Probe success from before cleanup +cannot establish subsequent readiness. + +Reject a selected build closure that both cleans and produces or consumes the +same declared resource. A Ninja pool would serialize access but would not +establish the user's intended order. Separate invocations are the initial +remedy. Coordinated state leases protect cooperating invocations; unrelated +external programs remain outside the guarantee. + +Pure artefact cleanup also needs an exclusive lease over each selected root, +ordered canonically. Producers participating in ownership use the same lease. +It must share the state-resource identity boundary rather than introduce a +second incompatible lock system. The lightweight artefact-only path cannot +require authoring a state declaration. + +Persist ownership definitions and provenance in the versioned action plan, not +captured directory listings. Replay obtains fresh capabilities and enumerates +again. Existing dyndep retention and command-private temporary cleanup retain +their original owners; `clean_owned` cannot target their reserved locations. A +later explicit `cargo clean` recipe still has Cargo's own semantics and is not +covered by Netsuke's scoped-deletion guarantee. + +## 8. Acceptance and migration + +Keep the existing hello-world and legacy `clean` fixtures unchanged. Add a +one-directory example whose complete ownership declaration needs only `path` and +`kind`. Test explicit file production, parent creation, stale reports, missing +outputs, and unchanged always-run benchmark behaviour. + +Property tests must cover canonical-path uniqueness, scope normalization, +protected roots, and duplicate selection. End-to-end tests must exercise +symlinks, reparse points, root replacement, hard links, permission errors, case +collisions, interrupted partial deletion, entry-budget overflow, and missing +paths. External sentinel files must remain unchanged. Assert zero filesystem +writes in dry-run, including no preparation or probe execution. + +Migrate Cuprum incrementally: explicitly declare disjoint output roots first; +keep unmatched wildcard cleanup as an ordinary command until a separate safe +file-selection contract exists. Do not add unrestricted glob deletion merely to +reproduce a long shell command in the initial release. Deleting legacy output +trees remains an explicit author decision shown in preview. + +## 9. Alternatives and outstanding decisions + +A mandatory out-of-tree store would change onboarding and project layout. +Inferring ownership from redirections or tool names would be unreliable. +Wrapping `rm -rf` would provide neither platform consistency nor a capability +boundary. Requiring an artefact declaration for every existing target would +make an optional benefit contagious. + +Before implementation, ratify the supported-platform deletion primitives, +resource-lease identity, ownership/source conflict rules, and concrete operator +limits. Atomic trash-and-rename, adoption receipts, wildcard collections, role +selectors, and remote delivery remain separate possible extensions, not +prerequisites for a useful exact-path cleanup operation. + +## 10. Recommendation + +Start with exact files and explicitly owned directory trees, one bounded +cleanup implementation, and transparent scope. Preserve ordinary recipes and +existing cleaning while giving annotated outputs stronger, testable guarantees. + +[roadmap]: ../roadmap-progressive-enhancement.md#24-owned-artefacts-and-bounded-cleanup +[maturity]: 0025-progressive-enhancement-and-maturity-policies.md +[states]: 0021-managed-states-and-probes.md diff --git a/docs/rfcs/0024-named-contention-classes.md b/docs/rfcs/0024-named-contention-classes.md new file mode 100644 index 000000000..5a2e14cda --- /dev/null +++ b/docs/rfcs/0024-named-contention-classes.md @@ -0,0 +1,196 @@ +# RFC 0024: Named contention classes + +## Preamble + +- **RFC number:** 0024 +- **Status:** Proposed +- **Created:** 2026-09-19 +- **Scope:** Optional concurrency limits lowered to Ninja pools +- **Implementation:** [Progressive-enhancement roadmap, phase 22][roadmap] + +## 1. Summary + +Allow an action to name a contention class when it should share a bounded +number of concurrent execution slots with other actions. Lower that declaration +to Ninja pools. Keep Ninja as the scheduler, preserve dependencies as ordering +and data requirements, and leave unannotated actions unchanged. + +A simple build needs no resource model. One class and one scalar annotation +must suffice to prevent two cooperating native-build actions from overlapping. +No toolchain, state, context, ownership declaration, or typed input is required. + +## 2. Problem and existing capabilities + +Repositories use serial aggregates and independent worker flags to manage +shared caches and expensive native builds. Ordering requirements, contention, +and subprocess parallelism are different concepts. Serializing an aggregate can +unnecessarily block independent work while failing to constrain unrelated +actions that use the same resource. + +The current intermediate representation (IR) already has `Action.pool`. This +RFC supplies the declaration, resolution, bounds, provenance, and backend +contract around that existing concept, not a second resource scheduler. Ninja +pools limit concurrent edges and remain subject to Ninja's global job +limit.[^1] They do not limit the number of threads spawned by one edge. + +## 3. Progressive authoring + +This proposed fragment serializes two native-build commands while leaving other +actions eligible for normal scheduling: + +```yaml +contention_classes: + native-build: + capacity: 1 + +actions: + - name: rust-test + contention: native-build + command: cargo test + - name: extension + contention: native-build + command: maturin develop +``` + +The absence of `contention` retains the ordinary backend scheduling policy. +Plain legacy commands can opt in: structured-command adoption is not required +for a pool annotation. Capacity is a positive integer; there is no required +worker-count calculation or platform-detection preamble. + +## 4. Definition and resolution + +`contention_classes` maps names to definitions containing `capacity`. An +executable action or target may set one scalar `contention` reference. Literal +capacities work independently; typed input expressions may be added through +[RFC 0022][inputs] without making that feature a prerequisite. + +Reject zero, negative, fractional, Boolean, unbounded, and out-of-range +capacities. Unknown fields, duplicate declarations, missing references, and +reserved internal names fail validation. Apply an operator maximum as a hard +ceiling and report the effective capacity and its provenance; malformed values +never become a default or silently coerce to integers. + +An action can belong to at most one class in the initial release. A list is an +error, not an implicit request for several locks. An explicit rule annotation +can supply a default when an action has none; conflicting explicit references +must fail rather than depend on include order. The implementation must settle +this interaction with the actual recipe inheritance contract before enabling +rule-level annotations. Initial delivery may support action/target annotations +only, with an explicit unsupported-field diagnostic on rules. + +Dependency-only aggregates cannot hold a slot because they execute no command. +Reject aggregate annotations with a remedy naming the executable children; do +not infer recursive inheritance across their dependency closure. An annotation +on a consumer neither constrains its prerequisites nor changes their order. + +## 5. Scheduling and lowering + +Resolve public names to stable internal class identities before backend +emission. Emit one Ninja pool per used class and attach its identity to the +corresponding executable edges. Unused classes emit no pool or runtime work. +The complete multi-command action holds one edge slot until it finishes or +fails; releasing slots between commands would weaken the declared contract. + +A class changes eligibility for concurrent dispatch, not the dependency graph's +meaning. It does not add dependencies, deduplicate actions, guarantee fairness, +or determine completion order among eligible actions. Preserve the existing +serial-dependency dyndep contract rather than replace it with pool-based order. + +Do not implement weighted CPU or memory tokens, multiple independent resource +acquisition, distributed locks, or a second ready queue. An action requiring +several related resources can use one explicitly named conservative class. +Unrelated classes cannot express overlapping exclusion sets in this version; +combining their names into a new class would not enforce the original limits. + +Ninja's special `console` pool cannot simultaneously be combined with a custom +pool on the same edge. Reject a request that requires both guarantees. Do not +silently discard either console behaviour or contention limits. + +Serialize resolved class definitions and references in generated plans and +include scheduling metadata in the existing graph/plan identity. Reject unknown +persisted variants or missing pool definitions before replay. Backends without +pool support must report the unsupported guarantee rather than ignore it. No +API may pass an unchecked public class name straight into Ninja source. + +## 6. Scope, worker budgets, and state integration + +The concurrency guarantee applies to one Ninja invocation. Two independent +Netsuke invocations do not share a pool. A depth-one class also does not stop +an external Cargo process from using the same directory. Document the guarantee +in human and structured inspection, not only in this RFC. + +Internal workers remain separate. A class with capacity one may still launch +many compiler or test threads. Tool adapters can consume typed worker inputs, +but the pool is neither a CPU quota nor a memory bound. Operator job ceilings +and the backend's jobserver behaviour remain authoritative. + +[Managed states][states] use integrity leases when cooperating invocations +mutate or consume the same managed path. A contention class can avoid +dispatching unnecessary competitors but does not replace those leases. State +operations inside an action retain its slot; they must not recursively start +Netsuke and wait for the same class. Bounded lock acquisition must not hide a +scheduling deadlock or provide an unbounded retry loop. + +Resource exclusivity is not semantic ordering. Cleaning and consuming a path in +the same selected closure remains an ownership conflict even with capacity one. + +## 7. Namespaces, inspection, and defaults + +Use RFC 0002's provenance and duplicate rules for includes. Bundle-private +classes remain private under RFC 0003. Sharing a class across bundles requires +an explicit importer binding to a declared root class; identical local names +must not accidentally serialize unrelated bundles. Local manifest delivery must +not wait for this optional bundle-binding integration. + +Inspection identifies each action's resolved class, requested and effective +capacity, source declaration, and invocation-only scope. JSON uses the existing +versioned metadata envelope. Diagnostics use bounded identifiers and source +spans; metric labels must not contain arbitrary class names or paths. + +The default manifest gains no pool, mandatory annotation, or new warning. +[Maturity policies][maturity] may require a class for explicitly selected +actions, but may not infer such a requirement from a command containing the word +`cargo` or silently alter capacity. + +## 8. Acceptance and compatibility + +Compare unannotated graph and Ninja output against existing fixtures. Add a +small legacy-command example before any larger structured-runner canary. Test +unknown classes, capacity bounds, conflicting definitions, console conflicts, +aggregates, namespace privacy, and persisted-plan compatibility. + +Use controlled child processes and a shared test journal to measure live +concurrency. Under global `-j` greater than one, assert that a capacity-one +class never overlaps, a capacity-two class never has three active edges, and an +unrelated action can proceed while that class is occupied. Check the entire +command-sequence interval rather than just process launch timestamps. + +Exercise failure, cancellation, and early-stop behaviour without timing-only +sleep assertions. Property-test stable naming, declaration-order independence, +and reference resolution. A separate two-invocation test must demonstrate the +boundary: pools alone do not claim process-wide mutual exclusion. + +## 9. Alternatives and outstanding decisions + +Serial dependencies encode order, not reusable contention policy. A lock +command inside every recipe repeats infrastructure and hides it from +scheduling. A generalized multi-resource scheduler would duplicate Ninja and +create a much larger correctness burden. Mandatory resource budgets would +undermine the shallow end without necessarily controlling subprocesses. + +Ratify the operator ceiling field, class-name lowering, console interaction, +and any rule-default semantics before implementing the public grammar. Reserve +multi-resource allocation and cross-host scheduling for separate evidence-led +proposals. Ordinary native compilation limits must not depend on those features. + +## 10. Recommendation + +Expose a deliberately small public contract over Ninja pools, with one optional +class per executable edge and explicit scope. Keep dependencies, internal +worker counts, and cross-invocation integrity locks separate. + +[roadmap]: ../roadmap-progressive-enhancement.md#22-named-contention-without-a-second-scheduler +[inputs]: 0022-typed-task-inputs.md +[states]: 0021-managed-states-and-probes.md +[maturity]: 0025-progressive-enhancement-and-maturity-policies.md +[^1]: [Ninja manual: pools](https://ninja-build.org/manual.html#ref_pool). diff --git a/docs/rfcs/0025-progressive-enhancement-and-maturity-policies.md b/docs/rfcs/0025-progressive-enhancement-and-maturity-policies.md new file mode 100644 index 000000000..1681d71a5 --- /dev/null +++ b/docs/rfcs/0025-progressive-enhancement-and-maturity-policies.md @@ -0,0 +1,264 @@ +# RFC 0025: Progressive enhancement and maturity policies + +## Preamble + +- **RFC number:** 0025 +- **Status:** Proposed +- **Created:** 2026-09-19 +- **Scope:** Shallow-end compatibility and opt-in contract enforcement +- **Implementation:** [Progressive-enhancement roadmap, phases 20 and + 25][roadmap] + +## 1. Summary + +Make progressive enhancement a compatibility requirement, not introductory +marketing. Netsuke must remain useful as a small command runner and build +compiler. States, typed inputs, named execution settings, owned artefacts, and +contention declarations add local guarantees only when explicitly adopted. + +Add optional maturity policies for projects and operators that want to require +particular declarations. Policy adoption must resemble enabling selected type +checker rules: ordinary unannotated manifests remain legitimate, and stricter +coverage is not a new installation prerequisite. No maturity score, automatic +promotion, or mandatory project tier is necessary. + +## 2. The shallow-end contract + +The current quickstart's first manifest remains a release acceptance fixture: + +```yaml +netsuke_version: "1.0.0" + +targets: + - name: hello.txt + command: "echo 'Hello from Netsuke!' > hello.txt" + +defaults: + - hello.txt +``` + +The manifest requires Netsuke and Ninja, not a typed input, context registry, +state provider, ownership model, package manager, policy file, or network +connection. The current list-of-mappings target shape and manifest version must +not be replaced with an invented onboarding dialect. + +These requirements apply to every RFC in this proposal set: + +- A plain command retains its existing ambient environment, working-directory, + shell, interpolation, and scheduling semantics. Simplicity must not silently + change a legacy shell string into direct argv execution. +- Opting one action into a feature does not require annotating its neighbours or + aggregates. An unannotated aggregate may depend on an annotated action. +- Unused state or context declarations never cause runtime probes, installation, + network access, or resource acquisition. Existing template-time capability + behaviour remains governed by its own policy; these additions introduce no + new inspection-time execution. +- Ordinary variables remain supported. No heuristic promotes `uv sync` to a + state, a redirection to an ownership claim, or a Cargo command to a pool. +- The straightforward command escape hatch remains available under the default + policy. A selected stricter policy may reject it with a local explanation, + but a plugin must not be the only way to execute an unusual tool. +- New opt-in schemas must validate correctly. Optional adoption is not + permission to ignore malformed typed inputs, unsafe paths, or unknown state + operations. + +The first page of onboarding must teach only the existing core. Subsequent +examples introduce one feature when it solves a visible problem, show the +simpler alternative, and state the added guarantee and its limits. Basic usage +is a supported destination, not a temporary migration stage. + +## 3. Relationship to existing plans + +RFC 0001 owns structured execution, RFCs 0002 to 0004 own composition, and +roadmap phase 5 owns profiles, inspection, and their OrthoConfig integration. +Issue #592's semantic linter owns reusable manifest analysis. This RFC adds +maturity-rule selection and trust-aware enforcement over that analysis; it must +not establish a competing linter, parser, configuration loader, or JSON +envelope. + +[RFC 0021][states] supplies preparation contracts, [RFC 0022][inputs] supplies +input contracts, [RFC 0023][artefacts] supplies ownership, and +[RFC 0024][contention] supplies pool-backed contention. Named execution +contexts remain a compatible extension point, not a sixth prerequisite hidden +in these five RFCs. The maturity schema may add a context rule only after that +separate surface has an accepted definition and an implementation. + +Do not repurpose RFC 0008's repository health tiers or RFC 0005's release +admission policy as user-manifest maturity. Their purposes, trust sources, and +failure meanings differ. + +## 4. Proposed policy shape + +A proposed root policy can require one useful guarantee at a time: + +```yaml +maturity: + rules: + - id: structured-commands + severity: warn + select: [publish] + - id: owned-cleanup + severity: error + select: [clean-build] +``` + +The default is an empty rule list. There is no default warning about missing +annotations. Every rule contains `id`, `severity`, and an explicit `select` +list of action/target identities. Initial severities are `off`, `warn`, and +`error`. Exact names keep the first scope contract small; bounded patterns can +follow only with a reviewed match and namespace contract. + +Unknown rules, duplicate rule/subject combinations, unknown selectors, and +unsupported severities fail validation. A policy cannot claim enforcement of a +feature that the installed version does not understand. Emit a +version/capability remedy rather than silently skip the rule. + +Rule-specific `subjects` lists refine what the rule checks, where required. +Selectors name the graph nodes to inspect, not arbitrary paths or command-text +regular expressions. Evaluation uses resolved declarations and provenance. + +## 5. Initial rule contracts + +The initial rules have deliberately narrow, checkable meanings: + +| Rule | Required evidence on selected nodes | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `structured-commands` | Every resolved executable recipe unit is structured; legacy shell strings fail coverage. Explicit structured shell selection is not a claim of direct-argv safety. | +| `typed-inputs` | Each explicitly named configuration subject in `subjects` resolves to a typed input contract, not only an untyped variable. Internal variables are not automatically public inputs. | +| `owned-cleanup` | A selected cleanup action contains explicit `clean_owned` operations or nonexecuting aggregation only; arbitrary executable deletion recipes cannot satisfy the declaration contract. | +| `verified-states` | Each state named in `subjects` has a `require_state` or `ensure_state` operation before its first non-state command unit; a probe in a different action is insufficient. | +| `contention-declared` | Each selected executable edge resolves an explicit valid contention class. Dependency-only aggregates are not executable subjects. | + +Table 1: Initial declaration-coverage rules, not whole-program safety proofs. + +`typed-inputs` and `verified-states` require nonempty explicit `subjects`. The +first implementation must not guess which variables are external knobs or which +arbitrary command consumes an environment. For `verified-states`, the policy's +selected action/subject pair asserts that consumption relationship; analysis +requires the readiness operations before the first non-state command unit, +rather than guessing a consumption point inside a shell command. This is a +conservative opt-in recipe-order contract, not inferred whole-program dataflow. + +The ordinary schema enforces correctness regardless of rule severity. For +example, an invalid path in `clean_owned` remains an error even when +`owned-cleanup` is off. A structured command using a shell is still subject to +existing shell policy. Passing these rules does not establish hermeticity, +reproducibility, correct cleanup ownership, or safe untrusted-code execution. + +## 6. Scope and non-contagion + +Resolve policy against the selected build closure, retaining distinct +definition and invocation provenance. A rule selecting `publish` applies when +that node will execute; it does not make an unrelated `hello.txt` build fail +coverage. Global syntax and reference errors remain errors even outside the +closure. `netsuke check` can inspect the whole manifest and report each +explicit scope. + +Aggregate selection must not implicitly make every dependency strict. If a +future closure selector is added, its spelling must be explicit and inspection +must enumerate its bounded expansion. Rule references within a selected recipe +remain part of that recipe and cannot hide legacy execution from its coverage +check. Imported private nodes retain qualified provenance without becoming +publicly selectable through an accidental export. + +A root may impose policy on imported public actions. An imported fragment or +bundle cannot relax its importer. Bundle-local strengthening applies only to +that instance, never unrelated root targets. Unknown private selectors fail; +implicit namespace wildcarding is not permitted. + +## 7. Trust-aware composition and profiles + +Use the existing configuration provenance and trusted operator boundary. An +automatically discovered project file, imported bundle, or explicitly chosen +`--config` file is not thereby trusted to weaken operator policy. + +For overlapping rule/subject scopes, combine severity monotonically: +`off < warn < error`. Project and bundle declarations can strengthen but cannot +lower an operator floor. Expand and normalize scopes before combining them so +renaming a selector or splitting a rule cannot hide an overlap. Constraints +specific to a rule also combine without widening allowed behaviour. + +Profiles may select reviewed policy sets using the existing profile machinery. +Record the policy source and effective rule set; merely choosing a development +profile must not disable an operator error. Do not introduce a public +`--ignore-policy` escape hatch. An author may remove a project-owned optional +rule when no stronger authority requires it, but cannot bypass the runtime +capability boundary. + +The initial release needs no universal `strict` preset. A future preset must +have a versioned, enumerable rule set and cannot gain new blocking rules on an +unrelated software upgrade. Report-only adoption precedes error enforcement. +Narrow exceptions require an independently reviewed future contract; do not +ship a blanket suppression file that silently makes strict mode meaningless. + +## 8. Evaluation and diagnostics + +Run declaration coverage through the semantic linter's typed inventory. +Separate pure policy evaluation from manifest loading, capability observation, +and runner effects. Evaluate applicable errors before starting any selected +user action. Inspection and dry-run do not execute state probes to satisfy a +maturity rule; they check declaration evidence only. + +A warning reports a gap without changing a successful command's exit status. An +error uses the existing validation/policy failure class and stops execution. +Human and JSON output include rule ID, severity, selected subject, definition +span, policy-source span, and one local remedy. Reuse Fluent localization, +structured-result envelopes, and redaction metadata; do not leak input values +or probe output in diagnostics or metric labels. + +`context --json` describes supported rules and effective settings through the +existing metadata surface. `check --json` reports findings. Neither depends on +a new `explain` command, whose separate roadmap evaluation remains unresolved. +Supported basic manifests must have no new maturity messages under default +settings, including verbose warnings that imply untyped usage is deprecated. + +## 9. Acceptance, learning loop, and rollout + +Retain the exact quickstart fixture and a small three-command project. Snapshot +parsed declarations, generated commands, default diagnostics, and selected +execution behaviour before and after every feature. Compare observed child +arguments and filesystem effects, not just apparent YAML similarity. + +Add one-feature-only examples: a typed worker input without a context, a state +without typed inputs, one cleanup root without a state, and one pool on a +legacy command. Combine annotated and unannotated actions under an ordinary +aggregate. Assert that unrelated invocation starts no probes and creates no +state records. + +Property-test severity monotonicity, scope composition, order independence, +namespace resolution, and inability to weaken operator constraints. End-to-end +tests must cover profiles, imported policies, unknown rule versions, warning +versus error exits, selected versus whole-manifest checks, and generated-plan +replay under the applicable trusted policy. + +Document and measure onboarding separately from the Cuprum migration. The +quickstart may not gain required declarations. Each enhancement must +demonstrate its local benefit and explicitly identify any remaining shell +helper; moving boilerplate to an unreviewed imaginary bundle does not count as +simplification. Do not claim a usability improvement from line count alone. + +## 10. Alternatives and outstanding decisions + +Mandatory maturity levels would make advanced features contagious. +Automatically promoting projects by size or feature count would alter semantics +unexpectedly. A global strict mode with an evolving implicit rule list would +make upgrades break otherwise unchanged manifests. Separate validators per +feature would duplicate source handling and reporting. + +Ratify policy-source placement within the shared configuration contract, +selected-closure inspection metadata, and the semantic linter's reusable +inventory boundary before implementation. Rule-specific exceptions, named +presets, pattern selectors, and context-coverage rules remain deferred. + +## 11. Recommendation + +Ratify the shallow-end compatibility contract before expanding the language. +Deliver a small, opt-in, scope-explicit rule mechanism over the shared linter, +with stronger policies controlled by the appropriate authority rather than +imposed on every Netsuke user. + +[roadmap]: ../roadmap-progressive-enhancement.md +[states]: 0021-managed-states-and-probes.md +[inputs]: 0022-typed-task-inputs.md +[artefacts]: 0023-artefact-ownership-and-scoped-cleanup.md +[contention]: 0024-named-contention-classes.md diff --git a/docs/roadmap-progressive-enhancement.md b/docs/roadmap-progressive-enhancement.md new file mode 100644 index 000000000..9d9582a1b --- /dev/null +++ b/docs/roadmap-progressive-enhancement.md @@ -0,0 +1,444 @@ +# Netsuke progressive-enhancement roadmap + +This document continues the [active roadmap](roadmap.md) and the +[composition roadmap](roadmap-composition.md) with phases 20 to 25. It tracks +proposed work, not implemented features. Existing task identifiers and +completion states remain unchanged. Dependencies, rather than phase-number +order, determine delivery sequencing. + +The product hypothesis is that Netsuke can remove repeated orchestration +machinery while retaining its shallow end. The unchanged quickstart must remain +useful. Each feature must work independently with ordinary commands before a +combined Cuprum migration serves as its integration canary. External bundles, +new named execution contexts, and a universal strict mode are not prerequisites. + +## Contract ownership and integration boundaries + +- [RFC 0021](rfcs/0021-managed-states-and-probes.md) owns states, built-in and + external probes, preparation evidence, and operation semantics. +- [RFC 0022](rfcs/0022-typed-task-inputs.md) owns optional root inputs and + shares parameter validation with RFC 0003, rather than duplicating it. +- [RFC 0023](rfcs/0023-artefact-ownership-and-scoped-cleanup.md) owns declared + artefact scope and standardized cleanup. +- [RFC 0024](rfcs/0024-named-contention-classes.md) owns public contention + declarations lowered to Ninja pools. +- [RFC 0025](rfcs/0025-progressive-enhancement-and-maturity-policies.md) owns + shallow-end compatibility and opt-in policy composition. + +RFC 0001 and phases 12 to 14 retain ownership of command parsing, argv, +execution, process cleanup, capability-scoped paths, and persisted action +plans. Phase 11 retains trusted shell selection. Phases 16 to 19 retain include +and bundle composition. Phase 5 and OrthoConfig retain generic profile, schema, +metadata, redaction, and result machinery. The semantic linter tracked by issue +`#592` retains the reusable manifest-analysis boundary. No new task may +duplicate those implementations merely to avoid an explicit integration +dependency. + +New public grammar is proposed, not shipped: RFC 0022 proposes +`--input NAME=VALUE` on manifest-compiling commands; RFC 0023 proposes +`clean --artefact NAME`. Register both with the canonical vocabulary and +metadata before delivery. Use existing `check`, `context --json`, `--dry-run`, +`--force`, and `--no-input` contracts. Do not introduce an unreviewed `explain` +command or new exit-code system. + +Every implementation task includes relevant unit and behavioural tests. Use +Proptest for normalization and algebraic invariants, bounded Kani harnesses for +pure transition logic where useful, and subprocess end-to-end tests for +process, filesystem, locking, and backend boundaries. Reuse installed or cached +tooling; this roadmap does not require new source-built proof tools for +ordinary gates. + +## 20. Preserve the shallow end before adding contracts + +Hypothesis: optional semantic annotations can add guarantees without increasing +the prerequisites or required vocabulary of a first Netsuke build. + +### 20.1. Establish compatibility and schema admission fixtures + +Outcome: a release can demonstrate unchanged basic behaviour rather than merely +assert it. The fixtures expose whether later syntax has become contagious. + +- [ ] 20.1.1. Ratify the progressive-enhancement contracts and version gates. + - [ ] Review RFCs 0021 to 0025, resolve their outstanding schema decisions, + and record accepted decisions through the normal ADR process. + - [ ] Coordinate manifest and persisted-plan version allocation with 12.1.1, + 16.1.1, and 17.1.1 without requiring bundle implementation first. + - [ ] Record operation-union ownership, feature-specific capability reporting, + and rejection of unsupported syntax. See RFC 0025 sections 2 and 3. +- [ ] 20.1.2. Add unchanged-basic-workflow acceptance fixtures. Requires 20.1.1. + - [ ] Preserve the exact quickstart manifest, scalar shell recipes, ordinary + variables, and the list-of-mappings action/target structure. + - [ ] Test actual child commands, default diagnostics, files, and absence of + state records, probe execution, installation, and new network activity. + - [ ] Add a mixed annotated/unannotated aggregate fixture. Success: enabling + one feature does not require declarations on unrelated nodes. + +### 20.2. Make the common boundaries independently consumable + +Outcome: features can reuse schema and reporting facilities without forcing all +other advanced features into their minimum implementation. + +- [ ] 20.2.1. Publish a feature and metadata integration contract. Requires + 20.1.1; coordinate with phase 5's context/profile work. + - [ ] Specify optional feature reporting and unknown-version diagnostics in + the existing JSON envelope and command metadata source of truth. + - [ ] Allocate shared typed-parameter validation to one owner with 17.1.3; + keep bundle resolution and root input sourcing separate. + - [ ] Define the common capability-scoped resource identity and lease seam + used by states and owned producers/cleanup, including its reuse limits. +- [ ] 20.2.2. Add progressive documentation acceptance checks. Requires 20.1.2 + and 20.2.1. + - [ ] Keep the first-page example unchanged and stage one-feature-only + examples outside the mandatory onboarding path. + - [ ] Check examples against accepted schemas when those schemas land; + distinguish proposed fragments from runnable examples until then. + - [ ] Record the before/after onboarding vocabulary and execution footprint. + Reject required advanced declarations without an explicit compatibility + decision, not an unnoticed documentation rewrite. + +- [ ] 20.2.3. Implement optional capability-scoped resource leases. Requires + 20.2.1. + - [ ] Supply one bounded, canonically ordered lease implementation for + cooperating producers, state consumers, and scoped cleanup. + - [ ] Test cross-process contention, cancellation, path aliases, missing + resource roots, and unsupported filesystems; create no runtime lease records + for features that an invocation does not use. + - [ ] Keep the seam independent of state declarations and Ninja scheduling. + Feature tasks integrate it rather than creating competing lock systems. + +## 21. Typed inputs with one parameter contract + +Hypothesis: optional typed public inputs make task configuration predictable +without annotating internal variables or creating a second profile system. + +### 21.1. Validate values before executing a manifest + +Outcome: root inputs and bundle parameters accept and reject the same values, +with errors local to the responsible declaration or source. + +- [ ] 21.1.1. Implement the normalized parameter contract. Requires 20.2.1. + - [ ] Reuse or extract RFC 0003 section 6's types, constraints, default + validation, exposure metadata, and bounded diagnostics. + - [ ] Test Boolean/integer distinction, overflow, collection bounds, duplicate + keys, choices, empty values, and path capability non-authority. + - [ ] Share a conformance corpus with bundle work without requiring bundle + loading. See RFC 0022 sections 4 and 7. +- [ ] 21.1.2. Add optional root input declarations and immutable resolution. + Requires 21.1.1 and 20.1.2. + - [ ] Parse `inputs`, preserve ordinary `vars`, detect namespace collisions, + and validate before Jinja expansion. + - [ ] Support one-value promotion without implicit aliases or executable + defaults. Retain declaration and reference spans. + - [ ] Property-test normalization stability and run the unchanged-basic + fixture. See RFC 0022 sections 3, 4, and 8. + +### 21.2. Bind explicit callers and profiles to the same interface + +Outcome: a user can identify which source supplied a value without learning a +new configuration stack. Precedence cases decide whether the contract is clear. + +- [ ] 21.2.1. Wire type-directed CLI and configuration input sources. Requires + 21.1.2 and 20.2.1. + - [ ] Register `--input NAME=VALUE` in canonical metadata; reject unknown and + duplicate names, shell splitting, and malformed typed JSON collections. + - [ ] Integrate existing configuration/profile provenance and ratify overlay + order with phase 5; apply operator constraints after source selection. + - [ ] Test every source precedence pair and default redaction in errors, + verbose output, and JSON. See RFC 0022 sections 5 and 7. +- [ ] 21.2.2. Preserve resolved inputs through graph and plan generation. + Requires 21.2.1 and 12.3.1 for structured-plan integration. + - [ ] Add used values to existing fingerprints and preserve argv splicing + through RFC 0001; do not create a second hashing or cache system. + - [ ] Freeze values for persisted-plan replay and test a later changed ambient + profile cannot replace them. + - [ ] Test include/bundle boundaries when 16.3.3 and 17.4.3 are available; + keep this composition matrix separate from local input delivery. + +### 21.3. Demonstrate useful annotation without broad migration + +Outcome: the worker-count canary validates one public knob and leaves all +unrelated task configuration unchanged. + +- [ ] 21.3.1. Publish and execute the typed-worker canary. Requires 21.2.2. + - [ ] Observe real Cargo/nextest or fixture-equivalent argv for distinct build + and test workers, including argument-list conflict rejection. + - [ ] Add a compact guide example and effective-value provenance output. + - [ ] Run one-input-only and mixed untyped/typed fixtures. Success: no + context, state, or bundle is required to use a validated worker count. + +## 22. Named contention without a second scheduler + +Hypothesis: one optional contention annotation can control shared build +pressure without confusing dependency order or subprocess worker limits. + +### 22.1. Resolve one class per executable edge + +Outcome: the public model is small enough to lower directly to existing backend +pool machinery, with no hidden overlapping-resource scheduler. + +- [ ] 22.1.1. Add bounded contention declarations and reference validation. + Requires 20.1.1 and 20.2.1. + - [ ] Implement positive integer capacities, one scalar action/target class, + operator ceilings, reserved names, and source-local errors. + - [ ] Reject aggregate annotations, multiple classes, and unsupported + rule-default forms. See RFC 0024 sections 3 and 4. + - [ ] Add a legacy-command fixture; typed input support is optional and + integrates only after 21.1.2. +- [ ] 22.1.2. Lower resolved classes into Ninja pool definitions. Requires + 22.1.1. + - [ ] Reuse `Action.pool`, emit stable bounded names, and preserve dependency + and dyndep semantics. Reject console/custom-pool conflicts. + - [ ] Include complete action sequences and persisted scheduling metadata; + integrate with 12.3.1 rather than reimplementing the codec. + - [ ] Property-test declaration-order independence and unannotated-output + compatibility. See RFC 0024 section 5. + +### 22.2. Verify concurrency and communicate its limits + +Outcome: measured edge concurrency, rather than syntax snapshots alone, proves +the guarantee and exposes its invocation-only boundary. + +- [ ] 22.2.1. Add controlled-process concurrency tests. Requires 22.1.2. + - [ ] Assert capacities one and two under larger global `-j`, and show an + unrelated action can run while a class is occupied. + - [ ] Exercise multi-command edges, failure, cancellation, and console + rejection without relying solely on sleeps. + - [ ] Demonstrate that separate Ninja invocations do not share the limit. See + RFC 0024 sections 6 and 8. +- [ ] 22.2.2. Publish class inspection and the native-build canary. Requires + 22.2.1 and 20.2.2. + - [ ] Show requested/effective capacity, source, and invocation-only scope. + - [ ] Document that slots are not CPU/memory quotas or internal worker counts. + - [ ] Add explicit shared-class bundle binding only after 17.4.3; local class + support must not depend on external acquisition. + +## 23. Verified preparation without mandatory environments + +Hypothesis: built-in readiness checks plus optional external functional probes +remove preparation glue while preserving explicit preconditions and tool choice. + +### 23.1. Define readiness separately from execution and repair + +Outcome: a pure transition model establishes when preparation is permitted +before process execution or durable records complicate the implementation. + +- [ ] 23.1.1. Implement state definitions and the readiness algebra. Requires + 20.1.1 and 20.2.1. + - [ ] Model kinds, declared identity inputs, optional preparation, all four + outcomes, and the three operations in RFC 0021 sections 3 to 5. + - [ ] Property-test that unknown never authorizes repair, degraded requires + explicit acceptance, and ensure performs at most one preparation attempt. + - [ ] Reject unsupported incremental-target state checks instead of letting + Ninja skip readiness verification. See RFC 0021 section 8. +- [ ] 23.1.2. Implement default built-in probes through existing seams. Requires + 23.1.1 and 12.2.3 where interpreter execution is needed. + - [ ] Deliver directory, file, and precisely specified Python-environment + probes without requiring third-party plugins. + - [ ] Distinguish object presence, interpreter validity, package verification, + and preparation identity; never overstate a probe's evidence. + - [ ] Test missing, corrupted, unreadable, replaced, and incompatible objects + through injected environment/filesystem adapters. + +### 23.2. Execute leased state operations with durable evidence + +Outcome: cooperating invocations can validate and prepare mutable state without +stale success records or a second scheduling loop. + +- [ ] 23.2.1. Integrate state units into the structured runner and plan codec. + Requires 23.1.2, 12.3.1, and the phase 12 process-lifecycle contract. + - [ ] Execute require, ensure, and prepare with fail-fast consumer ordering; + never probe during check, graph generation, help, or dry-run. + - [ ] Preserve resolved argv, environment, cwd, provenance, and typed result + mapping. Do not add state-private command execution. + - [ ] Reject unsupported replay versions. See RFC 0021 sections 5 and 7. +- [ ] 23.2.2. Implement bounded integrity leases and atomic state records. + Requires 23.2.1 and 20.2.3. + - [ ] Hold canonically ordered resource leases through each action's state + consumers; reject conflicting identities for one mutable path. + - [ ] Publish success only after post-verification and invalidate observations + across mutation. Bound retention, acquisition, and interrupted recovery. + - [ ] Test separate processes, damaged records, replacement races, and + interruption before publication. See RFC 0021 sections 4 and 8. + +### 23.3. Admit external functional checks without implicit repair + +Outcome: a repository-owned executable can supply a bounded readiness check +without requiring a provider implementation or a Nagios service. + +- [ ] 23.3.1. Implement the optional Nagios-style protocol adapter. Requires + 23.2.1. + - [ ] Map exits 0 to 3 exactly as RFC 0021 section 6 specifies and preserve + distinct spawn, signal, timeout, protocol, and output-limit reasons. + - [ ] Combine built-in and external evidence without allowing stdout to forge + identity or override a failing result. + - [ ] Test all codes, partial output, empty summaries, performance suffixes, + bad encoding, and contradictory success text. +- [ ] 23.3.2. Enforce probe budgets, authority, and process cleanup. Requires + 23.3.1 and the shared bounded process-tree termination facility. + - [ ] Apply operator-capped deadlines and combined output limits during + concurrent collection; kill and reap hung descendants on cancellation. + - [ ] Reject unauthorized external code and unsupported command fields; + preserve allowed-shell policy and redact diagnostic data. + - [ ] Use hostile fixture probes to prove bounded behaviour. An observational + convention must not be described as an operating-system sandbox. + +### 23.4. Validate state value in a real migration + +Outcome: the canary removes redundant setup while retaining intentionally +strict preconditions, and default builds pay no state-management cost. + +- [ ] 23.4.1. Add the Cuprum preparation and extension-guard canaries. Requires + 23.2.2 and 23.3.2. + - [ ] Verify an environment, remove a required component, and demonstrate + fresh detection rather than a stale timestamp or probe cache hit. + - [ ] Preserve the exact restricted extension suite and prove require-only + execution never invokes Maturin to repair a missing extension. + - [ ] Test ordinary commands beside stateful actions and assert zero probes + for the unchanged hello-world selection. +- [ ] 23.4.2. Publish state diagnostics, guarantees, and the probe author guide. + Requires 23.4.1 and 20.2.2. + - [ ] Document smallest built-in use, custom probes, result meanings, trust, + lease scope, failure recovery, and the lack of automatic teardown. + - [ ] Add metadata and localization through existing surfaces. + - [ ] Integrate optional typed inputs, named contexts, and bundle runtime + resources only through their accepted owners, not mandatory prerequisites. + +## 24. Owned artefacts and bounded cleanup + +Hypothesis: explicit disposable ownership can make cleanup predictable without +requiring a new output-store layout or annotating every existing target. + +### 24.1. Establish ownership before permitting deletion + +Outcome: one path has one explicit owner, and report registration does not +silently create caching or overlapping output producers. + +- [ ] 24.1.1. Implement exact-path artefact definitions and producer references. + Requires 20.1.1 and 20.2.1. + - [ ] Add file/directory kind, descriptive roles, optional parent creation, + and `produces` metadata. See RFC 0023 sections 3 and 4. + - [ ] Reject overlapping/case-equivalent ownership and duplicate producers; + reconcile explicit declarations with existing target outputs. + - [ ] Test missing outputs, retained partial outputs, and always-run reports. +- [ ] 24.1.2. Implement the bounded read-only cleanup planner. Requires 24.1.1. + - [ ] Resolve explicit names to workspace capabilities, protect source and + runtime paths, and enumerate complete bounded scope without deletion. + - [ ] Make directory-subtree ownership and pre-existing contents visible; + reject invalid, over-budget, or incomplete previews. + - [ ] Property-test path normalization and scope uniqueness. See RFC 0023 + sections 5 and 6. + +### 24.2. Delete only validated scope through one implementation + +Outcome: recipe cleanup and explicit CLI cleanup share confirmation, +confinement, partial-failure reporting, and fresh execution-time validation. + +- [ ] 24.2.1. Implement capability-scoped deletion and ownership leases. + Requires 24.1.2 and 20.2.3. + - [ ] Use supported handle-relative deletion, no-follow traversal, identity + rechecks, and canonical lease ordering shared with owned producers. + - [ ] Test symlinks, reparse points, hard links, replacement, permissions, + interruption, and external sentinels on supported hosts. + - [ ] Report partial progress honestly and refuse unsupported guarantees. Do + not substitute lexical containment or shell deletion. +- [ ] 24.2.2. Wire the operation and explicit `clean --artefact` selection. + Requires 24.2.1, 12.3.1, and existing mutation metadata integration. + - [ ] Add `clean_owned`, canonical CLI metadata, `--dry-run`, `--force`, and + `--no-input` handling through the shared planner/deleter. + - [ ] Preserve ordinary `clean` behaviour when explicit artefact selection is + absent; never implicitly include caches or environments. + - [ ] Test zero writes in preview, refusal without consent, fresh replay + validation, and bounds that `--force` cannot bypass. + +### 24.3. Integrate lifecycle invalidation and staged migration + +Outcome: cleanup cannot leave trusted readiness evidence for an environment it +has removed, and ordinary workflows retain their independent cleanup choices. + +- [ ] 24.3.1. Integrate state invalidation and selected-closure conflicts. + Requires 24.2.2 and 23.2.2. + - [ ] Invalidate state records before deleting their owned environments under + the same resource lease, including partial-failure paths. + - [ ] Reject simultaneous cleanup and declared production/consumption of the + same resource; a pool must not be treated as semantic ordering. + - [ ] Keep dyndep and command-private temporary cleanup under their existing + owners. See RFC 0023 section 7. +- [ ] 24.3.2. Publish exact-path cleanup examples and a Cuprum canary. Requires + 24.2.2 and 20.2.2; state examples also require 24.3.1. + - [ ] Migrate disjoint output roots without demanding an artefact for every + target, and retain unsupported wildcard cleanup as an explicit recipe. + - [ ] Document ownership assertions, recursive scope, retained caches, preview + limitations, and external-command boundaries. + - [ ] Demonstrate that the one-directory feature works without state syntax. + +## 25. Opt-in maturity policies without compulsory strictness + +Hypothesis: explicit scoped enforcement can support stricter projects without +changing default onboarding, pretending to prove safety, or duplicating linting. + +### 25.1. Reuse semantic analysis for narrowly defined coverage rules + +Outcome: one typed inventory supports both the semantic linter and policy +findings, with no command-text guessing or competing parser. + +- [ ] 25.1.1. Define the reusable policy inventory boundary with issue #592. + Requires 20.2.1 and the semantic linter's accepted inventory contract. + - [ ] Retain resolved recipe units, references, ownership, and provenance; + distinguish policy coverage from schema/runtime correctness. + - [ ] Implement pure rule/subject selection without executing probes or user + actions. See RFC 0025 sections 3 to 6. + - [ ] Add fixtures proving an unrelated selected target does not inherit + another action's coverage requirements. +- [ ] 25.1.2. Implement the initial rules as independently gated checks. + Requires 25.1.1 and each rule's relevant delivered feature. + - [ ] Add structured commands after 12.3.3, typed subjects after 21.2.1, + contention after 22.1.2, states after 23.2.1, and cleanup after 24.2.2. + - [ ] Reject unknown/unsupported rule IDs rather than claim incomplete + enforcement. No rule waits for an unrelated feature. + - [ ] Test exact coverage semantics and remedies from RFC 0025 section 5; do + not certify hermeticity from declaration presence. + +### 25.2. Compose enforcement without weakening trusted constraints + +Outcome: profile and import layering cannot make a project-owned warning +replace an operator error or hide a rule through scope rewriting. + +- [ ] 25.2.1. Add opt-in policy declarations and trust-aware severity merging. + Requires 25.1.1 and the existing configuration provenance boundary. + - [ ] Keep default rules empty; combine overlapping scopes monotonically and + retain the strongest applicable authority. + - [ ] Integrate profiles through phase 5's owner, with no automatic promotion, + blanket bypass, or new configuration loader. + - [ ] Property-test overlap, order independence, scope normalization, and + inability to weaken operator policy. See RFC 0025 section 7. +- [ ] 25.2.2. Wire preflight and existing human/JSON reporting. Requires 25.1.2 + and 25.2.1. + - [ ] Evaluate applicable errors before execution, keep warnings nonblocking, + and preserve ordinary structural errors regardless of coverage severity. + - [ ] Use `check`, `context --json`, shared exit classes, localization, and + redaction; introduce no separate `explain` command. + - [ ] Test selected-closure versus whole-manifest checks and persisted plans + under applicable trusted policy. See RFC 0025 section 8. + +### 25.3. Prove progressive enhancement end to end + +Outcome: small projects and the Monster Makefile can both use Netsuke without +sharing the same configuration burden. + +- [ ] 25.3.1. Run the progressive and strict-policy acceptance matrix. Requires + 25.2.2, 21.3.1, 22.2.2, 23.4.2, and 24.3.2. + - [ ] Run unchanged hello-world, one-feature-only cases, mixed aggregates, + report-only adoption, and explicit scoped errors. + - [ ] Add imported-policy coverage after 17.4.3 without blocking local + adoption; reject unknown private selectors and weakening imports. + - [ ] Assert zero default maturity warnings, no unrelated probes, unchanged + legacy command semantics, and no mandatory external tools or bundles. +- [ ] 25.3.2. Publish release guidance from measured canary results. Requires + 25.3.1 and 20.2.2. + - [ ] Describe gradual adoption and reversal, exact guarantees, operator + authority, and the limits of declaration-only checks. + - [ ] Compare repository-maintained machinery across manifests, helpers, and + real bundles; do not hide complexity behind nonexistent packages. + - [ ] Keep all first-page prerequisites and required declarations unchanged. + Reconsider any feature that cannot demonstrate local benefit before broad + promotion; no universal strict preset is required for release.