Roadmap
What exists now, what is being built, and what is only proposed — each with the evidence behind it and the criterion that makes it done.
Before you read the board
How to read a status
-
Shipped
Merged, tested, and released: in 0.1.0, or in a 0.2.0 beta.
-
In progress
An open pull request exists. Plans count; merged does not.
-
Proposed
Written down, with an exit criterion, and not started.
A phase number is not a status: the source roadmap has finished and unfinished steps side by side in several phases. Shipped cards say which PyPI release first carried the work.
The board
Shipped
27 items-
Catalogue, typed commands, and scoped policy
Program values, sh.make builders, CuprumContext scopes, and before and after hooks.
Done when Unknown programs are refused at construction; forbidden ones before spawn.
Shipped Released in 0.1.0 Last checked 2026-10-19Evidence: Catalogue, typed commands, and scoped policy -
Pipelines, fail-fast, and telemetry adapters
Composition with |, per-stage results, structured events, and logging, metrics, and tracing adapters.
Done when The first failing non-final stage terminates the rest and is reported as failure_index.
Shipped Released in 0.1.0 Last checked 2026-10-19Evidence: Pipelines, fail-fast, and telemetry adapters -
GitHub Actions presentation sink
An opt-in sink that folds a run into a log group and turns a failure into one annotation, or the same framing from RunOutputOptions(group=True, annotate_failure=True).
Done when Capture, exit status, and the result are byte-for-byte unchanged by the sink.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Evidence: GitHub Actions presentation sink -
Concurrent command execution
run_concurrent with bounded concurrency, collect-all or fail-fast, and results in submission order.
Done when Hooks fire per command and failures map back to submission positions.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Evidence: Concurrent command execution -
Line-level output and the idle heartbeat
SafeCmd.lines(), on_line callbacks, and an opt-in keepalive for children that fall silent.
Done when The heartbeat never terminates a process, extends a timeout, or enters captured output.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Evidence: Line-level output and the idle heartbeat -
Execution timeouts
Per-call, per-context, and scoped timeouts, raising TimeoutExpired with partial output.
Done when Explicit beats ExecutionContext beats scope; the child is always reaped.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Evidence: Execution timeouts -
Core builders and one-line catalogues
Validated git, rsync, and tar builders, plus ProgramCatalogue.from_programs() and from_project().
Done when A standalone script declares its programs in one call.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Evidence: Core builders and one-line catalogues -
Optional Rust stream pump
A native inter-stage pump with Python fallback, backend selection, parity tests, and a benchmark ratchet, in one stable-ABI wheel per platform for CPython 3.12 and later. Technical preview.
Done when Identical bytes on both backends; no native wheel is required to use Cuprum.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Evidence: Optional Rust stream pump -
Audited native boundaries
The pump descriptor race closed, lifecycle modelled with Loom, and unsafe Rust confined to audited crates.
Done when Verus, Kani, and Miri evidence archived for the applicable boundaries.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Evidence: Audited native boundaries -
Catalogue-backed scoped contexts
scoped(catalogue=…) establishes a scope directly from a catalogue rather than restating its allowlist.
Done when Merged with tests and users’ guide coverage.
Shipped Released in 0.2.0b1 Last checked 2026-10-19Pull request #463 -
Explicit environment replacement and unset
EnvMode selects an inherited, overlaid, or replacement child environment, and UNSET removes an inherited variable, without touching os.environ.
Done when The effective mode reaches every ExecEvent and labels the execution and failure counters.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: Explicit environment replacement and unset -
Scoped executable bindings
bind_executable() runs a catalogued program from a pinned path or a resolver while the allowlist, metadata, and telemetry keep the logical name.
Done when Resolution happens strictly after allowlist enforcement, and the result reports resolved_path.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: Scoped executable bindings -
sh.make resolves the scoped catalogue
Inside scoped(catalogue=…), sh.make() resolves the program from that catalogue; an explicit catalogue= still wins, and the default applies only outside any catalogue scope.
Done when A program the active catalogue does not list is refused when the builder is made, not at run time.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: sh.make resolves the scoped catalogue -
A typed argument contract for builders
Builders accept exactly ArgValue — str, int, float, bool, and paths — and reject bytes, lists, and other objects. A keyword that names an execution option, such as cwd= or timeout=, raises TypeError pointing at run_sync.
Done when A static checker and the run-time check reject the same call; no value reaches the child as its repr.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: A typed argument contract for builders -
An owned handle for a running command
SafeCmd.start() returns a RunningCommand with wait(), signal(), terminate(), and kill(), settled once however the child ends; async with tears a live child down.
Done when terminate() and kill() are safe to repeat after the child has gone, signal() on an exited child raises SignalAfterExitError, a stopped child still reports its output, and no child is left unreaped.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: An owned handle for a running command -
Byte-exact result mode
run_bytes() and run_bytes_sync() on commands and pipelines return the child’s own bytes through BytesCommandResult and BytesPipelineResult, with every measurement intact.
Done when run() never returns bytes, run_bytes() never returns text, and a timeout still carries partial output.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: Byte-exact result mode -
A typed package
cuprum/py.typed ships in every wheel and source distribution, so type checkers read Cuprum’s annotations instead of Any.
Done when A downstream type check resolves every exported name.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: A typed package -
One terminal outcome per execution
A settled event closes every observed execution exactly once with a bounded terminal_outcome; the metrics adapter counts failures and durations from it.
Done when Spawn failures and cancellations settle without an invented exit code.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: One terminal outcome per execution -
Control observation
observe_control() reports what signal(), terminate(), and kill() did to a child, with refusals as first-class outcomes and a bounded metrics hook.
Done when Emission is fail-open, and a PID never becomes a metric label.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: Control observation -
Opt-in recovery from a closed echo destination
BrokenPipePolicy.BEST_EFFORT disables echo for the affected stream alone when a sink raises BrokenPipeError; capture, line observation, and reaping continue.
Done when STRICT stays the default, and the fallback is reported once through relay_fallbacks and the echo channel.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: Opt-in recovery from a closed echo destination -
Opt-in process-group ownership
ProcessGroupPolicy.OWN_GROUP on the ExecutionContext makes the child a session and group leader on POSIX, so teardown reclaims descendants that hold a pipe open.
Done when INHERIT stays the default; only the group the spawn created is signalled; Windows refuses rather than pretending.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Evidence: Opt-in process-group ownership -
Opt-in redaction for telemetry
A RedactionPolicy of literals and patterns masks configured values in log records, spans, and Actions titles, without changing what runs or what is captured.
Done when Inert by default, fails closed with RedactionError, and leaves bounded metric labels unchanged.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Pull request #625 -
Streaming stdin and standard-stream redirection
StdinStream feeds a child’s stdin from an async producer one chunk at a time, and StdioTarget sends stdout or stderr to a file, a descriptor, or the parent rather than a pipe.
Done when More input than fits in memory can be fed, and a redirected stream never passes through the parent.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Pull request #511 -
Cheaper per-line event emission
A stream’s invariant event fields are computed once per observed stream rather than per line, with payloads unchanged.
Done when Dataclass construction fell to 29.9% of the consume subtree against a 30% criterion, with the thin margin recorded.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Pull request #433 -
A typed error boundary for the Rust streams
A RustStreamError enum at the PyO3 boundary, so both backends fail the same way.
Done when Error-mapping coverage at the boundary; proptest parity is the next step (6.1.2).
Shipped Released in 0.2.0b2 Last checked 2026-10-19Pull request #432 -
Safe native I/O on Windows
The native path is constrained to handles that synchronous I/O can use; a raw overlapped handle is rejected as windows_raw_handle_rejected rather than used.
Done when Merged with Windows coverage; pipeline hops there keep using the Python pump and stay correct.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Pull request #458 -
Wider Miri coverage
Miri runs across more of the Rust stream boundary.
Done when Merged, with the new evidence archived.
Shipped Released in 0.2.0b2 Last checked 2026-10-19Pull request #468
In progress
3 items-
Bounded output delivery off the execution loop
An opt-in worker and byte-bounded queue so a slow sink cannot stall the run’s event loop; saturation drops presentation output and says so, while capture stays complete.
Done when Synchronous delivery is unchanged by default; drops and worker failures are categorized and countable.
In progress Last checked 2026-10-19Pull request #622 -
Proptest parity at the stream boundary
A plan to drive the compiled consume boundary with generated UTF-8 and malformed bytes against the Python consumer, with committed replay evidence.
Done when Identical results on both sides for every generated payload, with shrinking and replay recorded.
In progress Last checked 2026-10-19Pull request #619 -
Cheaper per-hook dispatch
A plan to remove the per-hook awaitable check from per-line event dispatch while keeping payloads, hook order, and failure semantics.
Done when Judged on its own profile criterion, since it shifts the 5.2.1 share by succeeding.
In progress Last checked 2026-10-19Pull request #574
Proposed
10 items-
Test doubles without a process
An execution backend seam above the spawn, so a caller’s policy module can be tested for what it asked to run, with a recording double, and without a PATH stub.
Done when A substituted run emits the same events and hooks, creates no process, and never weakens the catalogue or allowlist checks.
Proposed Last checked 2026-10-19Evidence: Test doubles without a process -
A scaffold for project builders
A template module and checklist for writing project-specific builders.
Done when The template and checklist are in the users’ guide.
Proposed Last checked 2026-10-19Evidence: A scaffold for project builders -
Opt-in attribute-style commands
sh.register("ls", LS)-style sugar with allowlist enforcement and static-analysis support.
Done when Registered attributes stay type-checked; nothing is discovered from PATH.
Proposed Last checked 2026-10-19Evidence: Opt-in attribute-style commands -
Policy toggles and recommended defaults
Switches for narrowing, unsafe-namespace warnings, and default echo, with safe defaults. Executable bindings are deliberately not part of this item.
Done when Documented defaults and a migration note in the release notes.
Proposed Last checked 2026-10-19Evidence: Policy toggles and recommended defaults -
Rust capture for capture-only streams
Route fd-backed, UTF-8, capture-only streams through the Rust consume helper.
Done when At least 20% lower wall time than the tuned Python baseline, or a recorded negative result.
Proposed Last checked 2026-10-19Evidence: Rust capture for capture-only streams -
Raw-sink echo and tee acceleration
A native helper for qualifying sinks, profiled before it is built.
Done when A bounded, measured opportunity before any implementation.
Proposed Last checked 2026-10-19Evidence: Raw-sink echo and tee acceleration -
A native wheel for Windows on Arm
A cp312-abi3 win_arm64 wheel, planned for a later 0.2.0 beta.
Done when The release builds the wheel, and CI installs it and sees is_rust_available() return True.
Proposed Last checked 2026-10-19Evidence: A native wheel for Windows on Arm -
Free-threaded stable-ABI wheels
An abi3t wheel (PEP 803) so free-threaded CPython 3.15 gains the Rust pump, after auditing both layers for GIL-free safety.
Done when Free-threaded CI passes without re-enabling the GIL, and installers pick the right wheel on every interpreter.
Proposed Last checked 2026-10-19Evidence: Free-threaded stable-ABI wheels -
Decide on native orchestration
Prototype a synchronous native subprocess path and decide whether pipeline orchestration goes native.
Done when A recorded decision with measurements.
Proposed Last checked 2026-10-19Evidence: Decide on native orchestration -
Setwork: generated typed adapters
A companion compiler that turns command descriptions into typed functions returning SafeCmd.
Done when A publishable first corpus; see the Setwork preview.
Proposed Last checked 2026-10-19Read the preview
Public works
Follow the work, or join it.
Every card links to its evidence. The source roadmap has the detail behind each step, and issues are the place to argue for a route that is not on the map yet.