- Forthcoming
- Standard Library
Standard Library Expansion
A Netsukefile that needs a field out of JSON, a layered configuration, a build matrix, or a version check reaches for
shell() today. RFC 0006 proposes absorbing those operations into the template language as pure, bounded,
capability-classified helpers, so the generated graph stays byte-for-byte reproducible and netsuke help targets
can answer more questions without executing anything.
Status on 19 October 2026
- Not in v0.1.0-beta4. No helper from the expansion ships. All 49 tasks in roadmap phase 6 are unticked. The shipped standard library is documented separately.
- Specified. RFC 0006 (status Proposed, created 26 August 2026) was merged to
mainas a specification. Its release target: after v0.1.0 final, and “no part of this RFC may land in the v0.1.0 hardening release”. It names v0.1.x and v0.2.x as targets. - Being split. Open PR #697 divides the accepted set among focused child RFCs 0013 to 0020, of which only RFC 0013, structured data interchange, is written; it adds a test suite asserting that every accepted helper has exactly one owner.
- Adjacent work in flight. Open PR #702 (roadmap 3.14.8) implements
env(name, default=...)and acompactfilter.shell_escapeandshell_join, on which RFC 0006'sshell_quotedepends, are not on that branch yet.
Why expand it
Fifty-seven new helpers — forty-one filters and sixteen tests — across ten capability groups, surveyed from ansible-core and filtered hard. The RFC's own case for the volume: “the alternative is not a smaller standard library; it is the same operations expressed as shell pipelines that Netsuke can neither audit nor reproduce.”
Measured by subprocesses removed
The roadmap states the value plainly: it is “measured by the manifest operations that no longer require a subprocess, not by name parity with Ansible”.
Stolen one at a time
Ansible's generic lookup(...) dispatcher is rejected; useful capabilities come across as typed, named helpers. One name per capability: no alias families, no win_* variants where a dialect argument will do.
Mostly pure
Fifty-two of the fifty-seven are pure. Four read the filesystem and one reads the environment. None reads the clock, the network, or a subprocess, so most of the new surface is also available to netsuke help targets.
The helper contract
RFC 0006 §6 is normative: “A child issue that does not satisfy every clause below for every helper it adds is not complete.” Roadmap step 6.1 builds the shared machinery before any family lands.
| Clause | What it requires |
|---|---|
| 6.1 Purity classes | Exactly one label per helper: pure, or clock-, environment-, filesystem-, network-, or subprocess-observing. Only pure helpers are available in manifest queries. |
| 6.2 Query availability | Every non-pure helper registers in the query environment as an always-failing stub with a localized diagnostic — “they must never simply be absent”. A test exercises each registration. |
| 6.3 Determinism | “A Netsukefile compiled twice from the same inputs must produce the same generated Ninja byte for byte.” No hash-table ordering, LF everywhere, dialect-driven separators, C locale, no unseeded randomness. |
| 6.4 Capability boundary | Filesystem through the injected cap_std handle, environment through an injected reader. “A path outside the capability boundary is an error, never a false result.” |
| 6.5 Platform contract | Unix, Windows, and unsupported-platform behaviour documented. Lexical helpers take dialect: host, posix, or windows. A helper that cannot honour its contract fails with a typed diagnostic naming the platform and the capability. |
| 6.6 Types and errors | Enumerated accepted kinds, no silent coercion, strict undefined, none as a value, duplicates rejected by default, checked overflow, and unknown option values that list the valid set. |
| 6.7 Canonical equality | Values are equal when their RFC 8785 canonical JSON is byte-identical. Deduplication keeps first appearance. |
| 6.8 Resource bounds | Rejects unreasonable expansion before allocating: 8 MiB input and depth 128 for parsers, 100,000 YAML alias nodes, 100,000 tuples for product and combinations, 10,000 for permutations, 1 MiB compiled patterns. |
| 6.9 Diagnostics | A domain error enum per group; Fluent keys under stdlib.<module>.<condition>; machine codes of the form netsuke::jinja::<module>::<reason>. |
| 6.10 Naming | “Netsuke registers exactly one name per capability.” |
| 6.11 Docs and tests | A guide entry with its purity label, an executed tested-example, unit, property, and integration tests, a query-disposition test, and an inventory row. |
Where host probing comes up — the filesystem predicates and expandvars of roadmap 6.7 — the separate system facts preview (RFC 0029) covers what a manifest can learn about its host without probing at all.
Helper families
Shallow end first. Each panel gives signatures exactly as the RFC heads them, and each family links its roadmap step. Every one is proposed.
Read structured data without a subprocess
Roadmap 6.2Duplicate keys are rejected with the key and byte offset: “last-key-wins is a silent data-loss trapdoor in a build manifest”. to_yaml quotes yes, no, on, and off so the Norway problem cannot reach a generated file. Specified in detail by child RFC 0013.
text | from_json
text | from_yaml_all
value | to_yaml(indent=2, sort_keys=false, explicit_start=false)
Make configuration layering expressible
Roadmap 6.3combine keeps first-appearance key order and offers replace, keep, append, and prepend list policies. The RFC states outright that recursive merging is not associative, with a worked counterexample.
mapping | combine(*others, recursive=false, list_merge='replace')
sequence | items2dict(key_name='key', value_name='value', duplicates='error')
Build matrices with stable ordering
Roadmap 6.4product is “for build matrices over target triples, feature sets, and optimization levels”. Cardinality is checked before allocation, and the roadmap test deliberately varies hash state to catch any helper reimplemented over a hash set.
a | product(*others, repeat=1)
{{ records | selectattr('tags', 'contains', 'rust') | list }}
Text and version conditions
Roadmap 6.5One regular-expression dialect, netsuke-regex-v1, over the Rust regex crate. Version comparison is strict SemVer with a required operator: “1.82 is not a Semantic Version and is rejected”. Whether to tolerate a v prefix is an open question.
value | regex_replace(pattern, replacement, ignorecase=false, multiline=false, count=0, mandatory_count=0)
{% if compiler_version is version('1.82.0', '>=') %}
Path text for a platform other than the host
Roadmap 6.6Python lets ['/safe/root', '/etc/passwd'] | path_join yield /etc/passwd; “that is a trapdoor in a build manifest and Netsuke refuses it”. A Unix host must parse a Windows path exactly as a Windows host would.
parts | path_join(dialect='host')
path | relpath(start, dialect='host')
Probe host state through query-gated seams
Roadmap 6.7The four filesystem-observing and one environment-observing helpers of the set. All run through injected seams and are refused under help targets; mount may ship Unix-only, with an explicit error on Windows rather than a silent false.
path is link_exists
text | expandvars(dialect='host', missing='error')
File text and stable identities
Roadmap 6.8to_uuid is UUIDv5 under a frozen Netsuke namespace. A block comment whose text contains the closing marker is an error, so comment text cannot escape into the generated file as live syntax. human_readable and human_to_bytes are locale-independent.
text | to_uuid(namespace=<Netsuke namespace>)
text | comment(style='hash', prefix=none)
Timestamps without the clock
Roadmap 6.9Parsing and formatting are pure and never read the clock. A closed specifier set, the C locale, and no zone database: %Z is refused and IANA zone names are rejected. Identical manifests produce identical text on machines with different locales.
text | to_datetime(format='%Y-%m-%d %H:%M:%S', timezone='UTC')
timestamp | strftime(format)
Deliberately deferred. Seeded shuffle and random, log/pow/root,
and type_debug wait on roadmap 6.10,
which adopts a candidate only with a named consumer. Unseeded randomness stays rejected permanently.
Proportionate quality gates
Roadmap step 6.11 sits outside RFC 0006's Ansible-derived set but follows the same contract. It exposes the paths changed between two commits as typed data, and a pure filter to test them, so a manifest can leave an expensive gate out of the graph when nothing it checks has changed. The design is Proposed; ADR-015 governs the bounded Git adapter.
The design document's motivating example (proposed)
targets:
- name: lint-rust
when: >-
git_changed_files('origin/main...HEAD')
| matches_glob('**/*.rs', 'Cargo.toml', 'Cargo.lock')
command: cargo clippy --workspace --all-targets --all-features -- -D warnings
git_changed_files(range)
Accepts exactly A..B and A...B, rejecting malformed and option-like endpoints before Git starts. Runs fixed argument vectors with --no-lazy-fetch, parses NUL-delimited output, and returns sorted, de-duplicated paths. Impure: it flips the render's impurity flag before Git runs and is a named failing stub under help targets.
matches_glob(*patterns)
Pure and variadic: true when any path matches any pattern, with semantics aligned to glob(). At most 64 patterns and 65,536 pattern bytes, checked before anything is compiled. Every path is validated even after an early match.
Scope is narrow on purpose: no working-tree inspection, no untracked files, and no per-linter file selection. The helper decides whether a target enters the graph; it does not change Ninja's incremental model. The roadmap's success test: the documented example omits its Rust target for a Python-only changeset and includes it for every Rust path class.
From the shallow end
Every new helper is additive. A manifest that uses none of them compiles as it does today, and the existing
basename and dirname gain dialect as an optional argument rather than changing
behaviour. The route in, in order:
- Today. The shipped standard library — path filters,
glob,which,command_available, and collection filters — covers most manifests. - First step. Replace one
shell('jq ...')withfrom_json, or one--versionscrape with theversiontest. Both are pure, sohelp targetscan evaluate them. - Next. Layer configuration with
combineand expand matrices withproduct, trusting the ordering to be stable. - Cross-platform. Compose paths with an explicit
dialect, and read the host's own conventions from the host facts of RFC 0029 once they land. - Deep end. Probe the host through the 6.7 predicates, and gate expensive checks on a Git changeset.
Confidence
All planned. The phase's success criterion is written as tests, not prose.
Byte-identical output, tested end to end
A representative matrix manifest must compile twice to byte-identical Ninja. The layering step's example must do the same in an isolated workspace.
Property tests where laws exist
Round trips for encoders and serializers, ordering and idempotence for set algebra, composition laws for combine, path_join, and normpath.
Query disposition cannot be forgotten
Roadmap 6.1.6's contract test makes it impossible to add a non-pure helper without deciding how it behaves under help targets.
Documentation that runs
Every guide example is executed by the existing documentation-example tests, and the maintained inventory is test-enforced so it cannot drift. PR #697's coverage suite proves each accepted helper has exactly one owning child RFC.
Accessibility and localization
- Every message is a Fluent key. Netsuke ships 35 locales, and the new diagnostics join that set.
- Codes survive translation. Each error carries a stable
netsuke::jinja::<module>::<reason>code, the shapenetsuke::jinja::which::not_foundalready uses. #702 puts the code in the Fluent text so the diagnostic stays greppable in every locale. - A gap, named. The query-restriction message is built with
format!and is not localized today. Roadmap 6.1.5 fixes it. - Output that ignores the reader's locale. Pinning the C locale and ASCII digits is a determinism rule, and it also means a build log reads the same wherever it is read.
- Where the design is silent. The RFC says nothing about screen-reader-specific rendering of helper diagnostics beyond what the shared reporter already provides.