Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,9 @@ working on the Rust portions of the project:
RUSTFLAGS="-D warnings" cargo test --all-targets --all-features
```

running the Rust test suite with warnings denied. Use `make fmt`
running the Rust test suite with warnings denied. It then runs Cargo
doctests separately with all features, because Nextest does not execute
doctests. Use `make fmt`
(`cargo +nightly-2026-05-28 fmt --all`) to apply formatting fixes reported
by the formatter check alongside the Python and Markdown formatters.
- The project toolchain stays pinned to Rust `1.85.0` and declares `rustfmt`,
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ RUSTFMT_CARGO ?= $(CARGO) +$(RUSTFMT_TOOLCHAIN)
WHITAKER ?= whitaker
BUILD_JOBS ?=
RUST_FLAGS ?= -D warnings
RUSTDOC_FLAGS ?= -D warnings
RUSTDOC_FLAGS ?= --cfg docsrs -D warnings
DOCTEST_RUSTDOC_FLAGS = $(RUSTDOC_FLAGS) $(if $(DEV_FAST_HOST_IS_LINUX),-Zunstable-options --display-doctest-warnings --doctest-build-arg=-D --doctest-build-arg=warnings)
CARGO_FLAGS ?= --all-targets --all-features
CLIPPY_FLAGS ?= $(CARGO_FLAGS) -- $(RUST_FLAGS)
DOC_FLAGS ?= --jobs 1
Expand All @@ -70,6 +71,7 @@ DOC_FLAGS ?= --jobs 1
# billed for, and never above it.
TEST_JOBS ?= 1
TEST_FLAGS ?= $(CARGO_FLAGS) --jobs $(TEST_JOBS)
DOCTEST_FLAGS ?= --workspace --doc --all-features $(BUILD_JOBS)
TEST_RUSTFLAGS ?= $(RUST_FLAGS) -C codegen-units=1
WHITAKER_CARGO_FLAGS ?= $(CARGO_FLAGS) --jobs 1
WHITAKER_RUSTFLAGS ?= $(RUST_FLAGS) -C codegen-units=1
Expand Down Expand Up @@ -104,6 +106,7 @@ PYTEST_TARGETS ?= cuprum/unittests/test_*.py \
tests/test_ci_*.py \
tests/test_native_sdist.py \
scripts/tests/test_boundary_*.py \
scripts/tests/test_rust_lint_baseline_contract.py \
tests/behaviour/test_[a-h]*.py \
tests/behaviour/test_[i-r]*.py \
tests/behaviour/test_[s-z]*.py
Expand Down Expand Up @@ -361,6 +364,7 @@ test-rust: $(RUST_DEBUG_PREREQUISITE) ## Run the Rust suite
echo "cargo-nextest not found; falling back to cargo test." >&2; \
cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(RUST_DEBUG_CARGO) test $(TEST_FLAGS) $(BUILD_JOBS); \
fi
cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTDOCFLAGS="$(DOCTEST_RUSTDOC_FLAGS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(RUST_DEBUG_CARGO) test $(DOCTEST_FLAGS)

msrv-check: ## Verify every Rust target compiles on the published MSRV
cd $(RUST_DIR) && $(MSRV_CARGO_COMMAND) check --workspace --all-targets --all-features
Expand Down
30 changes: 29 additions & 1 deletion cuprum/unittests/test_dev_fast_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ def _routing_lines(output: str) -> list[str]:
]


def _assert_required_tokens(route: str, tokens: tuple[str, ...], subject: str) -> None:
"""Require every token that makes a routed command's contract binding."""
missing = tuple(token for token in tokens if token not in route)
assert not missing, f"{subject} omitted required routing tokens: {missing}"


def _assert_fragment_free(route: str) -> None:
"""Reject any selected Linux development acceleration from a protected route."""
assert "--config" not in route, f"protected route selected Cargo config: {route}"
Expand Down Expand Up @@ -198,7 +204,7 @@ def test_linux_debug_routes_select_the_fragment_and_injected_cargo() -> None:
direct_cargo = [
line for line in routing.splitlines() if "probe-cargo --config" in line
]
assert sum(line.count("probe-cargo --config") for line in direct_cargo) == 7, (
assert sum(line.count("probe-cargo --config") for line in direct_cargo) == 8, (
"each standard and explicit debug Cargo invocation must select one fragment"
)
assert all(
Expand All @@ -212,6 +218,28 @@ def test_linux_debug_routes_select_the_fragment_and_injected_cargo() -> None:
)


def test_rust_doctests_are_a_separate_debug_routed_gate() -> None:
"""Keep doctests covered when nextest owns ordinary Rust tests."""
output = _dry_run("test-rust", variables={"DEV_FAST_HOST_IS_LINUX": "yes"})
doctest = next(
line
for line in _routing_lines(output)
if "test --workspace --doc --all-features" in line
)
_assert_required_tokens(
doctest,
(
"probe-cargo --config ../tools/dev-fast/config.toml",
'RUSTFLAGS="-D warnings',
'RUSTDOCFLAGS="--cfg docsrs -D warnings -Zunstable-options',
"--display-doctest-warnings",
"--doctest-build-arg=-D --doctest-build-arg=warnings",
"-Clink-arg=-fuse-ld=mold",
),
"the Linux doctest route",
)


@pytest.mark.parametrize(
"flags",
[
Expand Down
108 changes: 108 additions & 0 deletions cuprum/unittests/test_doctest_warning_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Prove the Linux doctest route fails when a doctest emits a warning."""

from __future__ import annotations

import os
import shutil
import subprocess # ruff: ignore[suspicious-subprocess-import] - this test executes a fixed pinned Cargo command.
import sys
import typing as typ

import pytest

if typ.TYPE_CHECKING:
from pathlib import Path

DEV_FAST_TOOLCHAIN = "nightly-2026-08-23"
DOCTEST_RUSTDOC_FLAGS = (
"--cfg docsrs -D warnings -Zunstable-options --display-doctest-warnings "
"--doctest-build-arg=-D --doctest-build-arg=warnings"
)
WARNING_DOCTEST = """//! Warning-bearing doctest fixture.

/// ```
/// #[deprecated(note = "doctest warning contract")]
/// fn legacy() {}
/// legacy();
/// ```
pub struct WarningFixture;
"""


def _run_doctests(
crate: Path, cargo: str, rustdoc_flags: str | None
) -> subprocess.CompletedProcess[str]:
"""Run the pinned nightly with the standard backend and stated Rustdoc flags."""
environment: dict[str, str] = dict(os.environ)
environment.update({
"CARGO_TARGET_DIR": str(crate / "target"),
"RUSTFLAGS": "-D warnings",
"RUSTUP_TOOLCHAIN": DEV_FAST_TOOLCHAIN,
})
if rustdoc_flags is not None:
environment["RUSTDOCFLAGS"] = rustdoc_flags
else:
environment.pop("RUSTDOCFLAGS", None)
return subprocess.run( # ruff: ignore[subprocess-without-shell-equals-true] - fixed Cargo argv and pinned toolchain.
[
cargo,
"--color",
"never",
"test",
"--doc",
"--manifest-path",
str(crate / "Cargo.toml"),
],
capture_output=True,
check=False,
cwd=crate,
env=environment,
text=True,
)


def _diagnostics(completed: subprocess.CompletedProcess[str]) -> str:
"""Return every Cargo diagnostic stream for setup and failure assertions."""
return completed.stdout + completed.stderr


@pytest.mark.skipif(
sys.platform != "linux", reason="the production doctest warning route is Linux-only"
)
def test_pinned_doctest_route_rejects_a_warning(tmp_path: Path) -> None:
"""Only the corrected nightly Rustdoc route rejects a doctest warning."""
crate = tmp_path / "warning-doctest"
source = crate / "src/lib.rs"
source.parent.mkdir(parents=True)
(crate / "Cargo.toml").write_text(
'[package]\nname = "warning-doctest"\nversion = "0.1.0"\nedition = "2024"\n',
encoding="utf-8",
)
source.write_text(WARNING_DOCTEST, encoding="utf-8")
cargo = shutil.which("cargo")
assert cargo is not None, "the doctest contract requires Cargo on PATH"
former_route = _run_doctests(crate, cargo, rustdoc_flags=None)
former_diagnostics = _diagnostics(former_route)
assert former_route.returncode == 0, (
"the former RUSTFLAGS-only route must pass the warning-bearing doctest:\n"
f"{former_diagnostics}"
)
assert "test result: ok" in former_diagnostics, (
"the former route must execute the doctest rather than only build setup"
)
Comment thread
leynos marked this conversation as resolved.

corrected_route = _run_doctests(crate, cargo, DOCTEST_RUSTDOC_FLAGS)
corrected_diagnostics = _diagnostics(corrected_route)
assert corrected_route.returncode != 0, (
"the corrected Rustdoc route must reject a doctest warning:\n"
f"{corrected_diagnostics}"
)
assert "\x1b" not in corrected_diagnostics, (
"the synthetic Cargo route must disable colour for deterministic diagnostics"
)
assert "error: use of deprecated function" in corrected_diagnostics, (
"the corrected route must fail on the doctest warning rather than setup"
)
assert "-D deprecated" in corrected_diagnostics, (
"rustdoc must deny the displayed doctest warning"
)
39 changes: 30 additions & 9 deletions docs/adr-011-audited-rust-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,12 @@ focused tests (`/tmp/issue379-reader-validation-before.log` and
representability and non-negativity; it does not establish resource validity,
lifetime, or exclusive ownership.

Nine safe-crate unsafe-rejection probes, including a future target without a
crate-level attribute, are covered by the contract gate. The normal callback
compile regression passed with the expected `&W` versus `&mut W` diagnostic in
The contract derives every automatic and explicitly configured Cargo target
root, requiring each to carry `#![forbid(unsafe_code)]`; its future-target
negative cases cover binary, build-script, example, integration-test, and
benchmark roots. The current two safe roots receive six deliberate
unsafe-rejection probes. The normal callback compile regression passed with the
expected `&W` versus `&mut W` diagnostic in
`/tmp/issue379-round25-trybuild-normal.log`; the final fault run passed its
controls and detected all four mutations in `/tmp/issue379-round27-faults.log`.
Hosted Windows and macOS runtime execution passed on 2026-09-15: the "Rust
Expand Down Expand Up @@ -183,9 +186,27 @@ suppressing its owner drop. This avoids carrying the former extension-wide
`std::fs` lint exclusion into either extracted crate; `cap-std` and its handle
adapters are trusted dependencies, not verifier-proved implementations.

The safe crate also declares `unsafe_code = "forbid"` in Cargo's lint table, so
future automatically discovered test, example, and benchmark targets inherit
the prohibition. Cargo cannot override an individual workspace-inherited lint;
the safe manifest therefore carries the workspace lint tables plus this one
addition. The boundary-contract gate requires exact policy parity, preventing
that required duplication from weakening other lints or drifting silently.
Cargo cannot override an individual workspace-inherited lint. The safe crate
therefore keeps its `#![forbid(unsafe_code)]` source-level prohibition while
all three members inherit the shared workspace tables. The boundary-contract
gate checks both the common manifest inheritance and every discovered safe
target root, including future automatic Cargo targets.

## Addendum (2026-09-20): Lint baseline and target discovery

The workspace now owns the common Rust, Clippy, and Rustdoc lint baseline in
`rust/Cargo.toml`; every member inherits it. `rust/clippy.toml` records the
reviewed complexity thresholds and the environment-access injection policy. The
approved unsafe boundary remains deliberately narrower: the syscall and FFI
crates cannot use a workspace-wide `unsafe_code` prohibition, while every
effective `cuprum-streams` target root must declare `#![forbid(unsafe_code)]`.

The contract implementation evolved from a fixed target list to loading the
safe crate's manifest and effective Cargo roots. It honours `autolib`,
`autobins`, `autoexamples`, `autotests`, `autobenches`, and `build = false`,
while retaining explicit `lib`, `bin`, `example`, `test`, `bench`, and custom
build-script paths. Filesystem access, UTF-8 decoding, and TOML parsing fail
closed with the affected path; pure discovery and policy evaluation remain
separately testable. The Linux debug doctest gate uses the pinned dev-fast
nightly rustdoc route to display and deny doctest-body warnings. Stable Rust
1.85.0 remains the published MSRV and retains its separate verification route.
45 changes: 38 additions & 7 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3343,15 +3343,46 @@ Run the normal project test gate from the repository root:
make test
```

`make test` runs the Python pytest batches before the crate tests through
`cargo nextest`, including proptest cases compiled under `#[cfg(test)]`. Run
the complete Rust lint and formatting gates before committing Rust changes:
`make test` runs the Python pytest batches before the crate tests. The
`test-rust` target uses `cargo nextest run` when `cargo-nextest` is available
and otherwise falls back to `cargo test`, including proptest cases compiled
under `#[cfg(test)]`. It then runs Cargo doctests separately because Nextest
does not execute them. Run the complete Rust lint and formatting gates before
committing Rust changes:

```bash
make check-fmt
make lint
```

### Rust lint baseline

`rust/Cargo.toml` is the authoritative shared Rust lint table and every
workspace member declares `[lints] workspace = true`. `rust/clippy.toml` holds
the shared thresholds: cognitive complexity 9, four arguments, 70 lines, and
four nesting levels. New workspace members must inherit these tables rather
than replicate or weaken them.

The baseline follows Concordat revision
`cf4333c3f38b55bf3d4d5d2663e2147e2da1cec8` with local extensions for
`missing_assert_message` and environment-method injection. `std::env` readers
and mutators are disallowed in favour of injected environment or
working-directory seams. Fix findings at source; `#[allow]` is not permitted,
and a reasoned `#[expect]` is reserved for macro-expansion artefacts or genuine
floating-point arithmetic.

The workspace cannot forbid unsafe code globally: `cuprum-native-io` and
`cuprum-rust` are the audited syscall and FFI boundaries. `cuprum-streams`
keeps its explicit crate-level prohibition, which the boundary contract checks.
The
[ADR-011 addendum](adr-011-audited-rust-boundaries.md#addendum-2026-09-20-lint-baseline-and-target-discovery)
records why that source-level contract complements the shared lint tables. The
The Rust 1.85.0 maintenance toolchain provides `clippy` and `rust-analyzer`;
`RUSTFMT_TOOLCHAIN=nightly-2026-05-28` provides `rustfmt`. Linux debug Rustdoc,
Clippy, and doctests use the separately pinned dev-fast configuration, while
MSRV, coverage, release, verification, and Whitaker retain their prescribed
routes.

### Rustfmt fixture compatibility

The formatter maintenance pin is `nightly-2026-05-28`; Continuous Integration
Expand Down Expand Up @@ -4068,7 +4099,7 @@ checks the members that result.
`uv build --sdist` archive must retain:

- the workspace manifests and pins — `rust/Cargo.toml`, `rust/Cargo.lock`,
`rust/rust-toolchain.toml`, and `rust/dylint.toml`;
`rust/clippy.toml`, `rust/rust-toolchain.toml`, and `rust/dylint.toml`;
- each crate manifest — `rust/cuprum-rust/Cargo.toml`,
`rust/cuprum-streams/Cargo.toml`, and `rust/cuprum-native-io/Cargo.toml`;
- all three crate source trees, including each crate's `src/` and the
Expand All @@ -4078,9 +4109,9 @@ Cargo build output is excluded: `target` never appears in an archive, so an
sdist carries sources rather than a multi-gigabyte build cache.

Maturin builds its archive from the same workspace, and the
`[tool.maturin].include` entries carry the two files it would not otherwise
copy — `rust/rust-toolchain.toml` and `rust/dylint.toml`, both restricted with
`format = "sdist"` so they never enter a wheel.
`[tool.maturin].include` entries carry the three files it would not otherwise
copy — `rust/clippy.toml`, `rust/rust-toolchain.toml`, and `rust/dylint.toml`,
Comment thread
leynos marked this conversation as resolved.
all restricted with `format = "sdist"` so they never enter a wheel.

`tests/test_native_sdist.py` verifies both frontends, and it is deliberately
not a unit test: each case builds a genuine archive with `uv build --sdist` or
Expand Down
1 change: 1 addition & 0 deletions docs/repository-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ _Figure 1: Simplified repository tree for contributor orientation._
| `docs/` | User, maintainer, design, decision, roadmap, and reference documentation. |
| `docs/execplans/` | Durable execution plans for non-trivial implementation work. |
| `rust/` | Cargo workspace for Rust extension code and Rust-specific build/test targets. |
| `rust/clippy.toml` | Shared Clippy thresholds and environment-access policy used by every workspace member. |
| `rust/cuprum-rust/` | Thin PyO3/maturin integration crate for the optional native backend; raw Python resource hand-off lives here. |
| `rust/cuprum-streams/` | Safe stream orchestration, pump policy, state machine, errors, and checked UTF-8 decoding; unsafe Rust is forbidden. |
| `rust/cuprum-native-io/` | Audited native descriptor/handle ownership, single-call I/O, progress kernels, and platform test fixtures. |
Expand Down
Loading
Loading