Give the manifest env() helper an injectable env seam (#484) - #501
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Summary
Closes Verification
WalkthroughUse an injectable ChangesManifest environment injection
Sequence Diagram(s)sequenceDiagram
participant Caller
participant ManifestParser
participant Jinja
participant EnvReader
Caller->>ManifestParser: Call from_str_with_env(yaml, reader)
ManifestParser->>Jinja: Register env() with reader
Jinja->>EnvReader: Request variable name
EnvReader-->>Jinja: Return value or mapped error
Jinja-->>Caller: Return parsed manifest or diagnostic
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 20✅ Passed checks (20 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Reviewer's GuideInjects an environment-reading seam into the manifest env() helper by introducing env_var_with, updating env_var to delegate through it, and adding focused tests that exercise all Jinja error paths without mutating the process environment. Sequence diagram for manifest env() helper with injectable env seamsequenceDiagram
title Manifest env helper resolution via env_var_with
actor TemplateAuthor
participant Template as template
participant EnvHelper as env
participant EnvVar as env_var
participant EnvVarWith as env_var_with
participant ReadEnv as read_env_closure
TemplateAuthor->>Template: render_manifest
Template->>EnvHelper: env("FOO")
EnvHelper->>EnvVar: env_var("FOO")
EnvVar->>EnvVarWith: env_var_with("FOO", read_env_closure)
EnvVarWith->>ReadEnv: read_env_closure("FOO")
alt [value present]
ReadEnv-->>EnvVarWith: Ok(String)
EnvVarWith-->>EnvVar: Ok(String)
EnvVar-->>EnvHelper: Ok(String)
EnvHelper-->>Template: "FOO" value
else [variable missing]
ReadEnv-->>EnvVarWith: Err(VarError::NotPresent)
EnvVarWith-->>EnvVar: Err(ErrorKind::UndefinedError)
EnvVar-->>EnvHelper: Err(ErrorKind::UndefinedError)
else [value not utf8]
ReadEnv-->>EnvVarWith: Err(VarError::NotUnicode)
EnvVarWith-->>EnvVar: Err(ErrorKind::TemplateRuntimeError)
EnvVar-->>EnvHelper: Err(ErrorKind::TemplateRuntimeError)
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/manifest/mod.rs`:
- Around line 73-74: Update the manifest parsing flow around from_str and
env_var to accept a mockable::Env dependency, register env() with it, and
replace direct std::env::var access with the injected reader. Use
mockable::DefaultEnv in production and mockable::MockEnv in tests, preserving
existing environment lookup behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b0c043b2-8934-4085-9bd9-462f2dfcfd04
📒 Files selected for processing (3)
src/manifest/mod.rssrc/manifest/tests/env_function.rssrc/manifest/tests/mod.rs
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
leynos/rstest-bdd(auto-detected)leynos/ortho-config(auto-detected)leynos/shared-actions(auto-detected)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48c1b8f7e7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Addresses the review findings on #501. CodeRabbit and Codex both identified the same gap, correctly: the seam reached only the leaf mapper, while `from_str_named` still registered the hard-wired `env_var`. Tests of the actual Jinja registration therefore still needed `VarGuard` and `serial_test`, so the PR claimed to remove a mandate violation it had not removed. `from_str_named` now takes an `EnvReader` — a shared `Fn(&str) -> Result<String, VarError>`, `Send + Sync` because minijinja requires registered functions to be — and the registered `env()` closure captures it. `from_str` supplies `process_env_reader()`; `from_str_with_env` takes one explicitly. `tests/manifest_env_tests.rs` now drives the real registration path with an injected reader. It is no longer `#[serial]` and no longer uses `VarGuard`. The non-UTF-8 case drops its `OsStringExt` surgery and its `#[cfg(unix)]` gate, so it runs everywhere. Two consequences of the threading, each handled rather than suppressed: `from_str_named` reached five arguments, so the three that travel together are bundled into a `ManifestParse` struct; and `manifest::mod` passed 400 lines, so the reader type, its process-backed default, and the failure mapping move to `manifest::env_reader`. Codex also asked for the helper's reuse policy to be recorded per AGENTS.md; the developers' guide gains a "Manifest `env()` reader" section covering ownership, permitted call sites, and composition. Refs #484, #496. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/developers-guide.md`:
- Around line 541-546: The developer guide paragraph incorrectly retains
isolation requirements for manifest environment tests. Update the discussion
around the “Test isolation utilities” section to exclude
tests/manifest_env_tests.rs from EnvLock, EnvVarGuard, CwdGuard, and #[serial]
guidance, limiting those requirements to binaries that still mutate
process-global state or documenting a separate valid coverage exception.
In `@src/manifest/env_reader.rs`:
- Around line 27-33: Mark the Rust doctest fence in the process_env_reader
documentation as no_run, changing the existing rust fence to rust,no_run while
preserving the example content.
- Around line 46-50: Replace the stale [`env_var`] intra-doc link in the
surrounding documentation with plain text or a valid symbol reference, ensuring
rustdoc no longer attempts to resolve the removed helper while preserving the
description of the three outcomes tested by the helper.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 70a6ec0c-c5bc-46a7-9db9-b0e694c737da
📒 Files selected for processing (11)
docs/adr-006-adopt-polonius-nightly-toolchain.mddocs/developers-guide.mddocs/netsuke-design.mddocs/polonius.mddocs/snapshot-testing-in-netsuke-using-insta.mddocs/users-guide.mdsrc/manifest/env_reader.rssrc/manifest/mod.rssrc/manifest/tests/env_function.rssrc/manifest/tests/mod.rstests/manifest_env_tests.rs
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
leynos/rstest-bdd(auto-detected)leynos/ortho-config(auto-detected)leynos/shared-actions(auto-detected)
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai Have the following now been resolved? docs/developers-guide.md (1)
🤖 Detailed instructionsVerify each finding against current code. Fix only still-valid issues, skip the In |
This comment was marked as resolved.
This comment was marked as resolved.
|
@coderabbitai Have the following failed checks now been resolved? If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures. Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat o11y, code safety, documentation and validation coverage as in scope). Assessment against linked issues
|
This comment was marked as resolved.
This comment was marked as resolved.
|
On the pre-merge table's Developer Documentation warning: that row predates dce4728, which already updated netsuke-design.md §4.4 (it now describes Separately, the makefile_test_target decomposition requested against this PR now lives in #526, stacked on this branch, closing #524. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
`env_var` read the process environment directly, so the `env()` Jinja helper's three outcomes could only be exercised by mutating global state. One was unreachable in practice: fabricating a non-UTF-8 value in the live environment needs platform-specific OsString surgery, so the NotUnicode branch and its distinct Jinja error kind had no coverage at all. The AGENTS.md testing mandate now forbids the mutation regardless. Split the resolution into `env_var_with`, taking a `read_env` closure, consistent with the seams in runner::process::ninja_program and the two added for #486 and #487. A closure rather than a trait object: this is a keyed lookup with one caller, and the mandate permits a narrow closure where a trait object would be disproportionate. The stale doc example advertising a mutating `test_support::env::VarGuard` goes with it. Adds six cases, including that an empty value is returned rather than treated as missing, and that the missing and non-UTF-8 failures keep distinct error kinds — a missing variable is a template authoring error, whereas a non-UTF-8 value is an environment problem the author cannot fix in the template, and collapsing them would misdirect whoever reads the failure. Closes #484. Refs #496. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses the review findings on #501. CodeRabbit and Codex both identified the same gap, correctly: the seam reached only the leaf mapper, while `from_str_named` still registered the hard-wired `env_var`. Tests of the actual Jinja registration therefore still needed `VarGuard` and `serial_test`, so the PR claimed to remove a mandate violation it had not removed. `from_str_named` now takes an `EnvReader` — a shared `Fn(&str) -> Result<String, VarError>`, `Send + Sync` because minijinja requires registered functions to be — and the registered `env()` closure captures it. `from_str` supplies `process_env_reader()`; `from_str_with_env` takes one explicitly. `tests/manifest_env_tests.rs` now drives the real registration path with an injected reader. It is no longer `#[serial]` and no longer uses `VarGuard`. The non-UTF-8 case drops its `OsStringExt` surgery and its `#[cfg(unix)]` gate, so it runs everywhere. Two consequences of the threading, each handled rather than suppressed: `from_str_named` reached five arguments, so the three that travel together are bundled into a `ManifestParse` struct; and `manifest::mod` passed 400 lines, so the reader type, its process-backed default, and the failure mapping move to `manifest::env_reader`. Codex also asked for the helper's reuse policy to be recorded per AGENTS.md; the developers' guide gains a "Manifest `env()` reader" section covering ownership, permitted call sites, and composition. Refs #484, #496. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodeRabbit noted the developers' guide still required EnvLock and `#[serial]` for manifest environment tests. The prose was stale, but so was the configuration behind it. `manifest_env_tests` was a member of the `serial-env` nextest group and named in the guide as mutating process-global state. Since it moved to an injected reader it mutates nothing, so it was being serialized for no reason. Removed from `.config/nextest.toml`, from the guide, and from the test that pins the group's membership. That guard test now also asserts the binary stays *out*, so the configuration cannot silently reacquire a constraint it no longer has. The guide gains the general rule: a binary migrated to an injected seam leaves the group and drops its `#[serial]` markers in the same change. Also from the same review: the `process_env_reader` doctest is marked `no_run`, since asserting a variable is absent makes the doctest a hostage to whatever CI exports; and the intra-doc link to the removed `env_var` is replaced. Refs #484, #496. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodeRabbit: the developers' guide claims `serial-env` covers exactly two
binaries, but the test only checked that the two expected ones appear and
that `manifest_env_tests` does not. A third could have joined unnoticed,
silently serializing tests that need not be, while the guide carried on
claiming otherwise.
The filter's members are now parsed and compared as a set, so the guide's
claim is enforced rather than weakened to "including". Verified by adding
a third binary, which fails and names the intruder:
serial-env should cover exactly the two PATH- and NINJA_ENV-mutating
binaries; found ["env_path_tests", "intruder_tests", "ninja_env_tests"]
This also subsumes the previous absence check: a binary that stops
mutating process state must leave the group, or the configuration
outlives the constraint it describes.
Also reverts markdown reflow that `make fmt` had carried into five
unrelated documents; #512 fixes that at the source.
Refs #484, #496.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The branch predated #512, which normalized the whole docs tree to the mdtablefix 0.5.0 standard. Rebasing onto main therefore carried the branch's older line wrapping and table padding back over five documents the env-seam work never meant to touch. Restore main's version of those five. Only docs/developers-guide.md keeps a diff, and only for the sections this branch actually documents: the serial-env group membership and the manifest env() reader seam. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The guide claimed nothing but the public entry points constructs an `EnvReader`, and that tests must not reach past `from_str_with_env` to the leaf mapper. Both are false. `tests/manifest_env_tests.rs` builds readers with `Arc::new`, which is the point of the seam, and `src/manifest/tests/env_function.rs` drives `env_var_with` directly. Describe the split that actually exists, because the two layers cover different things: integration tests exercise registration — that the reader reaches the `env()` function Jinja calls — while unit tests cover error mapping at the leaf, where the non-UTF-8 branch is reachable without platform-specific `OsString` surgery. Rename the section's `Composition rules` heading, which collided with the one at line 503 under MD024. Discard the doctest's `Result` with `drop(...)` rather than `let _ =`. Addresses CodeRabbit findings on #501.
An accepted pre-merge item on #501: the guide named env()'s failure behaviour but not the seam behind it, so embedders and test authors had no user-facing pointer to from_str_with_env, EnvReader, or process_env_reader. The new subsection states what the seam is for, names the three entry points, and mirrors the executable doctest rather than pretending the guide snippet is run by the YAML harness. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Round feedback on #501, each point verified against manifest/mod.rs. The users' guide loses its contributor-facing pointer — that audience is the developers' guide's, and the section must stay embedder-facing. The developers' guide's ownership bullet claimed from_str_named owns the reader; in fact the caller owns it, from_str_named borrows it via ManifestParse and Arc::clones it into the registered closure, which therefore co-owns the Arc. The design document's §4.4 bullet still described env() as reading the system directly; it now names the EnvReader boundary, the process-backed default, and the caller-supplied alternative. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI caught what the local docs gates could not: every fence in the
scanned guides must carry a tested-example marker, and the new
env-reader snippet had none, failing every documentation scenario at
load. The fence is now marked and registered, and a pin asserts the
snippet keeps naming the entry points it mirrors — from_str_with_env,
EnvReader, and the env('PROFILE') read — so the guide copy cannot
drift from the doctest silently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e0e1acd to
5446272
Compare
The "User-Facing Documentation" warning was correct: `with_clock` is new public API and the guides did not mention it. Precedent says an additive public Rust API gets both artefacts — #578, #666 and #669 all added a users-guide section and a v0.1.0 migration-guide entry. The env-seam commit (#501) is not counter-evidence: it predates the migration guide. Users' guide: a "Inject the clock for deterministic tests" section under "Use Jinja safely", beside the sibling env-reader section it mirrors. It names `with_clock`, `fixed_clock`, `system_clock` and `ClockInstant`, records that the provider is consulted per call, that readings are normalized to UTC, and that manifest-query registration still refuses `now()`. Its Rust fence carries the `guide-clock-snippet` marker. Migration guide: an at-a-glance row and a short section modelled on "Configure file reading limits". Tests: register `guide-clock-snippet` in `EXPECTED_EXAMPLE_IDS`, without which the registry contract test fails, and pin the snippet to the entry points it documents, as the env-reader snippet already is. The snippet is copied from the doctest on `with_clock`, so it cannot drift from the API it advertises without the doctest failing too. Co-Authored-By: Claude Code <noreply@anthropic.com>
The "User-Facing Documentation" warning was correct: `with_clock` is new public API and the guides did not mention it. Precedent says an additive public Rust API gets both artefacts — #578, #666 and #669 all added a users-guide section and a v0.1.0 migration-guide entry. The env-seam commit (#501) is not counter-evidence: it predates the migration guide. Users' guide: a "Inject the clock for deterministic tests" section under "Use Jinja safely", beside the sibling env-reader section it mirrors. It names `with_clock`, `fixed_clock`, `system_clock` and `ClockInstant`, records that the provider is consulted per call, that readings are normalized to UTC, and that manifest-query registration still refuses `now()`. Its Rust fence carries the `guide-clock-snippet` marker. Migration guide: an at-a-glance row and a short section modelled on "Configure file reading limits". Tests: register `guide-clock-snippet` in `EXPECTED_EXAMPLE_IDS`, without which the registry contract test fails, and pin the snippet to the entry points it documents, as the env-reader snippet already is. The snippet is copied from the doctest on `with_clock`, so it cannot drift from the API it advertises without the doctest failing too. Co-Authored-By: Claude Code <noreply@anthropic.com>
The "User-Facing Documentation" warning was correct: `with_clock` is new public API and the guides did not mention it. Precedent says an additive public Rust API gets both artefacts — #578, #666 and #669 all added a users-guide section and a v0.1.0 migration-guide entry. The env-seam commit (#501) is not counter-evidence: it predates the migration guide. Users' guide: a "Inject the clock for deterministic tests" section under "Use Jinja safely", beside the sibling env-reader section it mirrors. It names `with_clock`, `fixed_clock`, `system_clock` and `ClockInstant`, records that the provider is consulted per call, that readings are normalized to UTC, and that manifest-query registration still refuses `now()`. Its Rust fence carries the `guide-clock-snippet` marker. Migration guide: an at-a-glance row and a short section modelled on "Configure file reading limits". Tests: register `guide-clock-snippet` in `EXPECTED_EXAMPLE_IDS`, without which the registry contract test fails, and pin the snippet to the entry points it documents, as the env-reader snippet already is. The snippet is copied from the doctest on `with_clock`, so it cannot drift from the API it advertises without the doctest failing too. Co-Authored-By: Claude Code <noreply@anthropic.com>
* Draft the execplan for the stdlib clock provider seam (7.1.1)
Plan the injectable `ClockProvider` seam specified in the Netsukefile
testing framework technical design section 5.2, so `now()` can be made
deterministic without changing behaviour for manifest authors.
The plan records the port shape, its ownership by `StdlibConfig`, the
verification obligations with their negative controls, and the seam
classification work ADR-008 and roadmap 7.1.1 require.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Apply canonical Markdown formatting to the 7.1.1 execplan
Run the repository's Markdown formatter over the new plan and split an
over-long trait declaration onto separate lines so the line-length lint
passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Satisfy the spelling and Markdown gates in the 7.1.1 execplan
Use "handwritten" rather than "hand-written" as the typos gate requires,
and rename the axiom identifiers from AX-n to AXIOM-n so the gate stops
reading the prefix as a misspelling. The longer identifier reflows one
paragraph.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Revise the 7.1.1 execplan after the design review
Six-lens design review found two errors of substance and three
build-blockers in the prescribed code.
Corrections of substance:
- OBL-5's non-vacuity argument was false. The refusing `now` stub is
registered after the permissive query helpers, and MiniJinja's
`add_function` is last-write-wins, so a clock leaked into
`register_query_functions` would be masked by the stub and the
obligation would still pass. The obligation now needs two tests, the
second asserting `now` is undefined after the permissive half alone.
- Nothing pinned the offset of the injected path. An arbitrary provider
may return a non-UTC instant, which would make the harness assert
behaviour production never exhibits. `WallClock::read` now normalizes
to UTC and OBL-1 gains a non-UTC-provider case.
D10's rejection of the resolved-value enum rested on a circular claim
that the enum makes the per-call negative control unwriteable; it does
not. The withdrawn claim is replaced by the cohesion argument, and the
design document's normativity is demoted to a tiebreak because D2 adds a
container the design does not name.
Rename the container to `WallClock`: `Clock` already names a monotonic
clock generic in `src/runner/process/mod.rs` alongside two other
`MonotonicClock` spellings.
Build-blockers fixed: the accessor must be `const fn` without
`#[must_use]`; the sequenced fixture violated the denied
`indexing_slicing` lint and underflowed on an empty vector; and the
`src/stdlib/mod.rs` re-export must land in EP-M1 or its doctests leave
the milestone failing to compile.
Also add `fixed_clock()`, a `ClockInstant` re-export, and an
`is_system()` discriminant so a leaked clock is observable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add failing tests for the stdlib clock seam (7.1.1, EP-M0)
Red stage for the clock provider seam. The tests reference items that do
not exist yet — `WallClock`, `ClockProvider`, `fixed_clock`, and the
two-argument `register_functions` — so the test target fails to compile,
which is the intended signal for this milestone. `make test-nextest`
reports `could not compile netsuke-build (lib test) due to 8 previous
errors`, and all eight name the missing seam items or the changed
arity.
Coverage added to `src/stdlib/time/tests.rs`:
- OBL-1: an injected instant is reported verbatim, including a non-UTC
provider whose result must still render with a UTC offset.
- OBL-2: repeated evaluations under one fixed provider agree, including
two `now()` calls within a single expression.
- OBL-3: a sequenced provider separates "consulted per call" from
"captured at registration", with the invocation count derived from the
evaluations performed rather than a hardcoded literal.
- OBL-6: offset application preserves the injected instant, as explicit
boundary cases (`Z`, `+00:00`, `+02:30`, `-05:00`, `+23:59:59`,
`-23:59:59`) and as a proptest over the valid civil range.
`now_defaults_to_utc` and the other existing cases are retained
unchanged: they are the ambient-fallback coverage constraint C1
requires.
The sequenced fixture saturates rather than panicking past the end, so
an over-reading implementation fails on the count assertion with a
legible message instead of panicking inside MiniJinja evaluation, and it
takes `first` plus `rest` so non-emptiness is a type-level precondition.
`make check-fmt` passes; `make lint` and `make typecheck` cannot pass
until EP-M1 supplies the seam, as the plan records.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Thread the stdlib clock provider seam through now() (7.1.1, EP-M1)
Green stage for the clock seam. `now()` no longer reads the host clock
directly; it reads a `ClockProvider` supplied by `StdlibConfig`, so a
caller that installs a provider gets a repeatable render.
`src/stdlib/time/clock.rs` is new and holds the port, both adapters, and
the container:
- `ClockProvider` is the `Arc<dyn Fn() -> OffsetDateTime + Send + Sync>`
alias fixed by the technical design (section 5.2, constraint C3). The
`Arc` is binding rather than stylistic: minijinja requires registered
functions to be `Send + Sync`, and `StdlibConfig` derives `Clone`,
which `Box<dyn Fn>` cannot satisfy. ADR-008 records the same shape for
the manifest environment reader.
- `system_clock()` returns the ambient adapter and `fixed_clock(t)` the
deterministic one.
- `WallClock` is the container `StdlibConfig` stores. It is named to stay
distinct from the monotonic-clock vocabulary already in the crate
(`monotony::MonotonicClock`, the `Clock` generic in
`src/runner/process/mod.rs`, and `status_timing`'s private alias).
`read()` normalizes to UTC, which is part of the contract rather than a
convenience: `now()` is documented to yield UTC and a provider is free
to return any offset, so without it a test could assert behaviour
production never exhibits.
`WallClock` hand-writes `Debug` because `StdlibConfig` derives it and a
closure is not printable. The label is `system` or `injected`, which makes
a mis-wired clock self-diagnosing in any `{:?}` of the configuration. The
impl routes through `is_system()` rather than reading the field so the
accessor has a non-test caller.
`StdlibConfig` gains the `clock` field, a `with_clock` builder, and a
crate-private `clock()` accessor; `register_with_config` passes
`config.clock().clone()`. `into_components` is unchanged, since the clock
is read by reference before that call consumes the configuration. The
public re-exports let an out-of-crate caller name a provider and its
return type without adding a `time` dependency.
Coverage added to `src/stdlib/time/tests.rs`:
- OBL-5: two parameterized cases guarding C2. One asserts that
manifest-query registration still refuses `now()` and that the refusal
names `now`, which rejects a copy-pasted stub registered under the wrong
helper name; the other asserts that the permissive half,
`register_query_functions`, does not define `now` at all. The two are
distinguished by error kind (`UnknownFunction` versus the refusal
marker), so "absent" cannot pass as "refused".
- A raw-error helper was needed for these: the marker is inspected through
`minijinja::Error`, and the existing `anyhow`-wrapping helper would have
hidden the type.
The OBL-5 cases live in `src/stdlib/time/tests.rs` rather than beside the
`manifest_query_environment` fixture as the plan proposed. That placement
assumed `register_query_functions` was reachable from `src/manifest/`; it
is not, because `stdlib::time` is private to `stdlib`. Both halves of
query registration are in scope in the time module's own test module, so
the intent -- two paired cases with no visibility widening -- is met
without exporting a seam item for a test's benefit.
One implementation change beyond the plan's split: EP-M1 and EP-M2's
configuration ownership land together. `WallClock::new` has no production
caller until `StdlibConfig` owns the clock, so EP-M1 alone fails the
workspace's `-D warnings` dead-code gate on `WallClock::new` and
`is_system`. Adding the field and its builder in the same commit restores
a compiling plateau; EP-M2 is now the integration and behavioural layer.
Evidence: `RUSTFLAGS="-D warnings" cargo check --workspace --all-targets
--all-features` is clean, `make check-fmt` passes, and
`cargo nextest run -E 'test(stdlib::time)'` reports 49/49 passing
(up from 45, the four new OBL-5 cases). The `with_clock` doctest passes
and asserts the exact rendering `2026-06-08T12:00:00Z`, which exercises
the seam through real registration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add integration and BDD coverage for the stdlib clock seam
EP-M2 of the 7.1.1 clock provider seam. EP-M1 already threaded the clock
through `StdlibConfig` and `time::register_functions` (the two milestones
landed as one commit because neither compiles alone under `-D warnings`),
so this commit supplies the coverage that exercises the seam through the
real registration path rather than through `time`'s internals.
Integration coverage in `tests/std_filter_tests/time_functions.rs` renders
through `stdlib::register_with_config` under a fixed clock:
- the configured instant is rendered verbatim, including from a fixture
at `1970-01-01T00:00:00Z`;
- a provider holding `+05:30` renders as `Z`, proving `WallClock::read`
normalizes to UTC (`Z` is only emitted for a UTC offset);
- `offset='...'` re-expresses the configured instant instead of shifting
it, asserted on both the rendered string and the `unix_timestamp`;
- with no clock configured, `now()` still reads the host clock, within a
three-second tolerance and reporting UTC.
Rendered strings are taken from `time`'s documented `Iso8601::DEFAULT`
contract rather than from captured output.
Behavioural coverage adds two `stdlib_time.feature` scenarios and the
`Given the stdlib clock is fixed at {instant:string}` step, which parses
the instant with the existing `parse_iso_timestamp` helper and stores a
provider in a new `TestWorld::stdlib_clock` slot. `RenderConfig` carries
the provider through to `render_template_with_context`, where it is
applied via `StdlibConfig::with_clock`.
Also add the two OBL-5 unit cases in `src/stdlib/time/tests.rs`:
manifest-query mode refuses `now()` (naming the helper in the error
detail), while the clock-independent `register_query_functions` leaves
`now()` undefined. Both registration halves are in scope there;
`stdlib::time` is private, so the plan's proposed home in
`src/manifest/expand_tests.rs` was not reachable.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Split the time test module and unshadow the BDD clock step
Two gate failures surfaced while running the 7.1.1 clock-seam commit gates.
Neither is behavioural; both are lint-gate violations in test code.
`clippy::shadow_reuse` is denied workspace-wide, and the new BDD step
bound its parsed instant back over the `&str` capture it came from.
Rename the binding to `parsed`, matching the convention already used in
`tests/bdd/steps/stdlib/assertions.rs`. Note that
`RUSTFLAGS="-D warnings" cargo check --all-targets` does not catch this,
so a clean check was not evidence the lint gate would pass.
The Whitaker suite caps a module at 400 lines. `src/stdlib/time/tests.rs`
had reached 472, so split it along the seam it already had:
- `clock_tests.rs` — where `now()` reads its instant from: the ambient
fallback (C1), the injected provider, per-call provider consultation,
offset application to an injected instant, and the C2 query-mode
guarantees;
- `tests.rs` — clock-independent behaviour: offset parsing, `timedelta`
arithmetic, and ISO 8601 formatting;
- `tests_support.rs` — the evaluation and value-inspection helpers both
modules need.
All three are declared under `#[cfg(test)]` in `src/stdlib/time/mod.rs`,
matching the existing `network` and `command` test layout. The lint
measures each file separately rather than recursively, so siblings are
what relieve the pressure. The test count is unchanged at 49.
Record the three findings in the exec plan, including that `make lint` on
this branch needs `PATH="$HOME/go/bin:$PATH"` because the base commit
predates the Makefile's curated `GO_BIN` lookup.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record EP-M3 gate results in the 7.1.1 exec plan
All four commit gates pass, plus the repository's Markdown and diagram
gates and `make doc-coverage`:
- `make test`: 2830 tests run, 2830 passed, 3 skipped.
- `make doc-coverage`: aggregate 99.15% against an 80% threshold.
- `make lint`: needed `PATH="$HOME/go/bin:$PATH"` on this branch; see the
Surprises entry for why.
- `make markdownlint` and `make nixie`: clean.
The first `coderabbit review --agent` pass over `e682ac98` and `ccf63eb0`
completed without rate limiting, reviewing all 16 changed files with zero
findings, so the milestone can close.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the six mutation outcomes in the 7.1.1 exec plan
All six designated mutations were applied to the working tree in turn,
the named test run, and the file reverted. Each was rejected by the test
the plan nominated:
1. `clock.read()` replaced by the ambient read — all four
`now_uses_injected_clock` cases fail.
2. Instant baked in at registration — `now_reads_the_provider_on_every_call`
fails.
3. Offset applied as an arithmetic shift — `now_offset_preserves_the_instant`
fails at `+00:00:01`.
4. Registration passes `WallClock::default()` — all 49 unit tests still
pass while the integration and BDD suites fail, confirming the
integration layer is load-bearing.
5. Refusing `now` stub deleted — both refusal cases fail, while the
sibling absence case still passes, so the two OBL-5 cases are not
redundant.
6. UTC normalization removed — only the non-UTC case fails.
Two findings are recorded. The plan's mutation 3, taken literally as
`timestamp + Duration::seconds(offset)`, is a no-op: `replace_offset`
preserves the wall-clock time rather than the instant, so the offset
shift is exactly cancelled by the added duration. The mutation was
re-run in a form that moves the instant while setting the offset. The
`to_offset` / `replace_offset` near-miss is now documented, since the
seam's contract depends on the former.
Applying that mutation also produced a genuine proptest shrink, which is
committed to `proptest-regressions/stdlib/time/clock_tests.txt`
following the precedent of the existing `home_tests.txt` seed. It passes
against the unmutated code.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Classify the stdlib clock seam in the documentation (7.1.1, EP-M4)
Four documents move together, because ADR-008's `Consequences` section
requires the ADR and the developers' guide sections to stay consistent:
- ADR-008 gains the dated addendum "2026-09-11: Stdlib clock seam",
classifying the seam in the `EnvReader` shape, recording that the
taxonomy is applied to an ambient input that is not an environment
variable, and warning that `StdlibConfig` now holds two ambient seams
in two shapes that should not be "harmonized" without revisiting the
entry. Its `Implementation references` list gains
`src/stdlib/time/clock.rs` and the config and registration call sites.
- `docs/developers-guide.md`'s "Environment and template ports" section
documents the clock's ownership, its module boundary, and the fact
that manifest-query registration keeps refusing `now`.
- Technical design section 5.2 moves from proposal to implemented state
and names `WallClock`, the mechanical container the design did not
name: it confines a hand-written `Debug` and normalizes each read to
UTC.
- RFC 0006 section 3.3 records the `now` clock gap as closed, and
section 16's question 7 as resolved, both pointing at the addendum.
RFC 0006 section 3.3's first recorded gap was also stale, independently
of this change: nine of the sixteen names it reported as absent from the
manifest-query environment have since gained refusing stubs, leaving the
seven file tests. The count, and section 14.1's slice-0 deliverable that
was built on it, are corrected in place to match
`register_disabled_query_helpers`.
`make check-fmt` is green over the result; the plan records the edits and
both discoveries.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Close roadmap 7.1.1 and complete the exec plan (EP-M5)
Every roadmap 7.1.1 sub-bullet now maps to a named artefact, so all five
boxes are ticked: registration through `StdlibConfig`, ambient
behaviour when no provider is supplied, the injected-value, repeated-call
and ambient-fallback coverage, and the ADR-008 classification.
The plan's `Outcomes & retrospective` records the reconciliation, the
two accepted deviations (the ADR addendum dated 2026-09-11, and the RFC
0006 count correction that sits beside this change), the branch-local
`GO_BIN` PATH workaround, and the follow-on work: runner wiring of
`given.clock.now` under 7.1.2, and RFC 0006 slice 0's seven remaining
file-test stubs. Status is `COMPLETE`.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the implemented clock seam in RFC 0007 and fix the spelling gate
RFC 0007's "what is missing" list and one sentence in its architecture
section both recorded the clock seam as absent. Both now record it as
supplied by roadmap item 7.1.1, for the same reason RFC 0006's gap entry
was corrected: a governing document that contradicts the code outlives
the change that closed the gap.
The spelling gate ("markdownlint: spelling", which `make check-fmt` does
not run) rejected two hyphenated compounds introduced by the
documentation milestone. `typos` splits on the hyphen, so `mis-wired`
reads as a misspelling of `miss`; the fix is to avoid the compound, not
to widen the ignore list:
- `hand-written` -> `handwritten` in the technical design;
- `mis-wired` -> `wrongly wired` in the WallClock doc comment (whose
behaviour is unchanged), in the plan's sketch of it, and in the
mutation-record entry that described it.
The plan records both findings and the gate's membership, since the
distinction between `make check-fmt` and `make markdownlint` is easy to
misread.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the final gate and CodeRabbit evidence in the 7.1.1 exec plan
Artefacts entry 8 carried a placeholder promising the final gate
transcript tails; entry 9 did not exist. Both are now filled from the
seven-gate run over d71e18e and the CodeRabbit pass that followed it.
The transcript is included because the EP-M3 evidence went stale twice —
once when the EP-M4 documentation commits landed, and again when the
spelling fix touched src/stdlib/time/clock.rs. Gate logs are named per
branch, so the second run over a branch overwrites the first run's
transcript, and a green result asserted rather than re-taken is not
evidence. The entry records that lesson alongside the numbers.
Also records that make test-podman was not run: no ansible/ path appears
in the change surface.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the pull request description replacement in the 7.1.1 exec plan
PR #696 still described itself as plan-only. The description now covers
the delivered seam, and the plan records that swap plus the fact that the
draft flag was left alone on purpose: whether to mark the PR ready before
or after CodeRabbit's PR-level review is the maintainer's call.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Validate the 7.1.1 branch tip and close its evidence chain
The Artefacts entries evidenced d71e18e, but the tip had moved two
commits past it. All seven gates were re-run at 9ebb539 and CodeRabbit
reviewed the branch again: green, 23 files, zero findings.
The entry also states why the chain terminates rather than recursing.
Recording a validation moves the tip past what it records, so a stricter
reading demands another run for ever. What stops it is that the code
surface has been frozen since d71e18e; every later commit edits this
plan alone, so a fresh run would exercise the same tree. The entry says
that argument lapses if any commit touches anything outside this file.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Extract configure_stdlib from render_template_with_context
The BDD rendering step built and configured StdlibConfig inline, which put
render_template_with_context at a cyclomatic complexity of 10 — the
method CodeScene's advisory code-health gate names when it fails this
file (10.00 -> 9.69).
Move the construction and the seven ordered option applications into a
private configure_stdlib helper returning Result<StdlibConfig>. The
application order is unchanged — network policy, clock, home override,
the fetch, command output and command stream byte limits, then the PATH
override — as are the four error-context strings, so rendering behaviour
is identical. The function now reads as the sequence a scenario performs:
localize, open the workspace, build the environment, register, reset
impure state, render, and record the outcome. Complexity drops 10 -> 3.
with_workspace_root_path takes impl AsRef<Utf8Path>, so passing the root
by reference removes the clone the inline version needed. The root type
is the Utf8PathBuf already returned by ensure_workspace, so only the
Utf8Path import is new.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the rebase and the configure_stdlib extraction in the 7.1.1 exec plan
Two post-completion events are now recorded in `Artefacts and notes`:
- Entry 12: the rebase onto `origin/main` at `3348cc0a`, why the weave merge
driver was bypassed for the replay, the arithmetic check that the replay was
clean, and the four gates re-run over the rebased tip.
- Entry 13: the CodeScene-triggered extraction of `configure_stdlib`, what
makes it behaviour-preserving, and the five gates re-run over `e1568a1b`.
`Surprises & discoveries` gains the generalizable lesson: a branch inherits the
complexity bill for the decision points it adds to a function it did not write,
and no local gate mirrors CodeScene's check.
Docs only; no code, test, or build surface changes.
* Apply canonical Markdown formatting to the 7.1.1 exec plan
`make check-fmt` rejected the wrapped Progress bullet added by the previous
commit; `make fmt` rewrapped it and touched no other file.
Docs only.
* Record the ready-for-review flip in the 7.1.1 exec plan
PR #696 was marked ready for review at `be6858fb` once all seventeen
verdict-reporting checks were green, CodeScene Code Health included. Entry 14
records that, notes that it supersedes entry 10s timing note, and states the
consequence: CodeRabbit had been skipping the branch as a draft, so the flip
hands it the branch for its own PR-level review.
Docs only; `make check-fmt` and `make markdownlint` are green over it.
* Record the withdrawn CodeRabbit finding in the 7.1.1 exec plan
CodeRabbit requested changes over `Iso8601::DEFAULT` allegedly emitting
`+00:00`; the finding described `time` 0.3.44 while `Cargo.lock` pins 0.3.55,
whose ISO-8601 formatter writes `Z` for a UTC offset. The exact-equality test it
cited passes on `Z`, so the suggested edit would have turned a green test red.
The finding was withdrawn and the thread resolved.
Entry 15 records the rebuttal and its three evidence lines; Surprises gains the
generalizable observation that a version-sensitive review finding is cheapest
to settle with the lock file plus an executed assertion.
Docs only; `make check-fmt` and `make markdownlint` are green over it.
* Record the scope-tolerance exception in the 7.1.1 exec plan
The pull request exceeds both limbs of the plan's scope tolerance: 23
changed files against a limit of 20, and 3,039 net added lines against a
limit of 600 (708 net even with this plan's 2,331 lines excluded). The
tolerance says a substantial overrun "means the design was wrong", and
the plan must not be read as conformant while its own scope check fails.
Records the escalation and its acceptance rather than a silent waiver:
- D14 states the measured scope per head, when each limb first fired
(net lines at the plan's first commit, 1,581 net in one file; file
count at 3fdd826, 21 files), the attribution of the 3,039 net lines,
and why the overrun does not bear out the tolerance's own inference:
the production seam is 150 net lines, and the excess is dominated by
the execution record plus the coverage the plan itself mandated.
- `Outcomes & retrospective` gains an explicit conformance exception, so
the delivery is marked as not fully conformant to this plan.
- `Tolerances` and `Progress` point at D14 rather than restating it.
- `Artefacts and notes` entry 16 records the reproduce commands and the
durable lesson: no gate reads a plan's Tolerances section, so a
breach is invisible to machine verification.
Docs only; no code, test, or build surface changes.
* Sharpen the non-plan remainder figure in D14
The 708 net non-plan figure holds at both tabulated heads, but it is not
invariant for the branch's whole life: it was 696 across 22 files until
the configure_stdlib extraction (e1568a1) added 12 net of real code.
State that derivation, so the figure reads as measured rather than
assumed.
Verified against the local graph as part of the conformance gate run:
all six tabulated figures reproduce exactly, the attribution of the
3,039 net lines sums correctly, and the two breach commits are the ones
named.
Docs only; no code, test, or build surface changes.
* Extract the clock seam from config/mod.rs into a sibling module
CI lints the pull request's merge tree, and in that tree Whitaker's
`module_max_lines` cap fires: "Module config spans 421 lines, exceeding
the allowed 400", at src/stdlib/mod.rs:12:5. The lint counts the whole
file behind a file-backed module, and the merged file is a purely
additive sum: 351 lines at the base, +32 from main's 5c19b8c (the
file-read budget), +38 from this branch (the clock seam). Each parent
passes the cap alone (383 and 389); only the combination reaches 421.
`git merge-tree --write-tree` reproduces the count locally, so the fix
is measured against the same artifact CI lints.
Move `StdlibConfig::with_clock` and the `clock()` accessor into
`config/clock.rs`, mirroring the existing `ambient.rs` and `which.rs`
sibling modules — the grouping `which.rs` documents, where
feature-specific configuration leaves `config/mod.rs` as the shared
surface. The merged file drops to 387 lines.
No public API or behaviour change: the builder keeps its doctest (now
at clock.rs:17), the accessor keeps its `pub(crate)` visibility, and the
field, its default, and the registration path are untouched.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Re-anchor D14's figures to the rebased branch
The second rebase orphaned the six SHAs D14's scope table cited and
changed the merge base GitHub reports, so the entry described a head
that no longer exists and numbers that no longer apply.
Replace the two-row table with three rows: the two pre-rebase heads
(`02caf3ee`, `e401f4d7`) kept for their measurements, plus the current
`f994800c` against base `a273fad3` — 24 files, +3,280 / -133, 3,147
net, 716 net excluding this plan. Rewrite the prose that quoted the old
figures, including "When it fired", in terms that survive a further
rebase, and add a note that the pre-rebase identifiers now exist only
as unreachable objects.
Recompute the attribution for the current head: this plan 2,431; src/
480 net (547 added, 67 removed) — clock.rs 150, clock_tests.rs 260 and
config/clock.rs 42 as new files, the tests.rs/tests_support.rs split 10
net after a 58-line move, 18 lines of balance from the mod.rs files and
register.rs; tests 163 net; governing docs 62 net; the proptest seed 11.
The assessment's "39-line config delta" becomes 40 lines.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Apply end-of-line table reflow to the plan's D14 table
mdtablefix does not wrap lines, so the wrapped rows need re-emitting
before its check passes. Content is unchanged.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Re-quote D14's totals at the head that carries the record
The re-anchoring commit is itself a plan commit, so it moved the
figures it had just written: 24 files, +3,369 / -133, 3,236 net, and a
2,520-line plan. Refresh the table's third row to `25960909` and update
the two prose figures that name the plan's line count.
Add a closing sentence saying each row is a snapshot, so a later reader
re-measures rather than re-quotes.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Qualify the plan's note on unreachable commit identifiers
The six pre-rebase SHAs still resolve in this checkout because the
reflog names them, so "exist only as unreachable objects" overstates
their disappearance. State it precisely: no branch reaches them, a
pruning gc would drop them, and the figures rather than the identifiers
are what a later reader can rely on.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Rewrap the plan to mdtablefix's CI flag set
`make check-fmt` runs mdtablefix with `--wrap --renumber --breaks
--ellipsis --fences`, but the earlier in-place pass over this plan used
the flagless default, so headings and prose edits landed wrapped to a
different width than CI enforces. The next push turned `build-test` and
`Windows / lint-windows` red with:
docs/execplans/7-1-1-clock-provider-seam.md +41 -42
1 file would be reformatted, 141 files left unchanged.
Re-emit the whole file with the CI flag set. Only line wrapping changes;
`mdtablefix --check` with the CI flags now reports every file unchanged.
The durable lesson (added to the plan separately if it recurs) is to
reproduce the gate's own invocation rather than a bare `--check FILE`.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the mdtablefix flag-set mistake as artefact entry 18
The rewrap failure was worth keeping: `mdtablefix --in-place <file>`
without the Makefile's flag set rewraps to a different column, and a
bare `mdtablefix --check <file>` then agrees with itself and disagrees
with the gate. Record the tell, the CI output, and the fix (copy the
invocation out of Makefile:314) alongside the other post-completion
episodes.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Bring the plan's Progress checklist up to the current head
Record the D14 re-anchoring and the mdtablefix flag-set fix as done, and
mark the pending documentation warning as in progress with the
precedent it follows. Adds the third checkbox the plan's own
"update frequently" requirement asks for.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Document the clock seam in the users' guide and migration guide
The "User-Facing Documentation" warning was correct: `with_clock` is new
public API and the guides did not mention it. Precedent says an additive
public Rust API gets both artefacts — #578, #666 and #669 all added a
users-guide section and a v0.1.0 migration-guide entry. The env-seam
commit (#501) is not counter-evidence: it predates the migration guide.
Users' guide: a "Inject the clock for deterministic tests" section under
"Use Jinja safely", beside the sibling env-reader section it mirrors.
It names `with_clock`, `fixed_clock`, `system_clock` and `ClockInstant`,
records that the provider is consulted per call, that readings are
normalized to UTC, and that manifest-query registration still refuses
`now()`. Its Rust fence carries the `guide-clock-snippet` marker.
Migration guide: an at-a-glance row and a short section modelled on
"Configure file reading limits".
Tests: register `guide-clock-snippet` in `EXPECTED_EXAMPLE_IDS`, without
which the registry contract test fails, and pin the snippet to the entry
points it documents, as the env-reader snippet already is.
The snippet is copied from the doctest on `with_clock`, so it cannot
drift from the API it advertises without the doctest failing too.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Flip the documentation bullet to done in the plan's Progress checklist
The 'User-Facing Documentation' warning is now actioned: the users' guide
gained a `with_clock` section whose Rust fence is registered and pinned to
the doctest, and the migration guide gained a matching row and section.
Record the landing commit and the two tests observed passing on it.
* Re-anchor D14's figures to the head that carries the record
The documentation fix moved the branch on by three files and 86 net lines,
and plan-maintenance commits keep adding their own length to every total.
Record the head that carries the record beside its figures, state plainly
that the non-plan remainder is the durable quantity and each total a lower
bound, and update the retrospective and artefact 16 to match.
* Add the row for the head that carries the record to D14's table
A plan-only commit moves the total by exactly its own length and the non-plan
remainder not at all, so state that invariant directly rather than letting a
later reader infer it from two rows, and give the current head its own row.
* Quote only durable figures in the scope-escalation retrospective
Recording a total changes it: every plan-only commit that maintains this
record adds its own length to the diff, so a head-specific total is stale the
moment it is written. Replace the quoted total with the two quantities that
do not move — changed-file count and the non-plan remainder — and make the
attribution section attribute that remainder rather than a total.
* Record the verified CI state of the final head in the Progress checklist
Every substantive check passes and the pull request is approved; the only red
check is the non-required CodeScene review of the base branch. Name the four
checks the ruleset actually requires, so the distinction is on the record
rather than left to be re-derived.
* Dispose of the second review round's verified findings
Three edits, each verified against current source before repair.
`tests/documentation_examples_tests.rs`: the CodeScene duplication
thread on `clock_snippet_mirrors_the_doctest` was valid — that test,
`env_reader_snippet_mirrors_the_doctest` and
`ninja_request_snippet_names_both_request_types` were three copies of
one shape. Extract `assert_snippet_names`, which carries the shared
"Rust fence, then each needle" contract and takes the example id, the
label used in failure messages, and the needles. Behaviour is
unchanged: all 32 tests in the target still pass.
`docs/adr-008-environment-seam-taxonomy.md`: the `with_clock`
injection-point link still named `src/stdlib/config/mod.rs`. That is
stale because the clock seam was split into its own
`src/stdlib/config/clock.rs` earlier in this branch, so the ADR
pointed at a file that no longer holds the function.
`docs/execplans/7-1-1-clock-provider-seam.md`: three corrections to the
living document — remove the duplicated `use std::{fmt, sync::Arc};`
and `time::OffsetDateTime` import lines from the implementation sketch;
record in D4 that the first review upheld a "User-Facing Documentation"
warning against the decision's rationale, since `with_clock` is a
public Rust API and not only a manifest-author concern; and correct the
recovery instructions, which described `git reset --hard` and
`git checkout --` as though they were scoped to the mutation when both
discard uncommitted work more broadly.
Gates: `make check-fmt` (including mdtablefix under the Makefile's own
flag set), `make markdownlint`, `make lint`, and the
`documentation_examples_tests` target.
* Record why the Windows gate fails, and that it is not this branch's
`Windows / build-test-windows` fails at this head, which looks alarming
next to a green branch history. It is an estate-wide breakage: the same
job fails on `origin/main` (`ef7ed760`) and on every unrelated branch
tested, and it last passed anywhere at 09:38Z on `36e03c7f`.
The failing case is
`stdlib::network::redirect::error_tests::protocol_failures_are_classified_from_a_live_response`,
which lives on `origin/main` in `src/stdlib/network/redirect_error_tests.rs`
(via #667). This branch's diff against its merge base `a273fad3` adds
zero bytes under `src/stdlib/network/`. The error is a Windows socket
race (`WSAECONNABORTED`, `os error 10053`) against the test's own
loopback listener.
Also recorded: the job share is not a required check. The ruleset
`main-required-checks` requires only `build-test`, `kani-smoke`,
`netsukefile` and `release / metadata`; `build-test` is a different job
and passes at this head, so the required set is green.
Gates: `make check-fmt` and `make markdownlint`.
* Record the pending review request against the head it targets
The review asked for in this round is a posted *request*, not a completed
review, and the plan should not read as though the two are the same. The
entry names the queued head, the queue id, the quoted delay, and the fact
that a comment body does not pin a revision — so whichever commit
CodeRabbit inspects has to be read back afterwards.
Gates: `make check-fmt` and `make markdownlint`.
* Re-target the branch onto the current origin/main tip
The first rebase landed at 07248a3; origin/main has since advanced to
79545e1 (the 19-update GitHub-actions group bump). Replay the 40
branch-owned commits above the new merge base with the same explicit
options used before.
The re-target is byte-for-byte identity-preserving: every commit is `=`
under range-diff, there are no merges and no conflicts, and the net diff
is unchanged at 27 files / 3603 insertions / 161 deletions. Cargo.toml
and Cargo.lock are byte-identical to origin/main, so no regeneration was
needed.
The new commit is a workflows-and-contract-test delta with zero file
overlap with this branch, and `make test` runs only Rust targets, so it
lies outside this branch's gate surface. It does not move the Windows
job's line anchors, so the recorded Windows diagnosis still holds.
Weave again did not participate: the driver is registered globally but
merge attributes are `unspecified` for every branch-owned path.
* Dispose of the review findings on the clock seam
CodeRabbit reviewed 8de3c96 and raised one inline finding plus an
Observability pre-merge warning. Both are valid against the current
source; neither was present when the branch was last reviewed.
The inline finding is a real wording defect in both guides. They said the
provider is read "rather than captured at registration", but
`register_functions` moves a `WallClock` into the registered closure, so
the clock *is* captured while the *instant* is not. The crate's own
docs, ADR-008 and the technical design all state this correctly, which
leaves the two guides as the outliers. Both passages now say that
registration captures the adapter and each call invokes it afresh.
The Observability warning asks for a bounded debug field at the
clock-registration decision point. PR #669 added exactly such an event
for the file filters one line below, so the gap is genuine and the shape
is settled. `WallClock::source_label` now names the provenance from a
closed set, and `register_with_config` records `clock_source` alongside
"registered stdlib time helpers". `Debug` reuses the same accessor, so
the label has one definition.
`registration_reports_the_clock_source` covers both provenances and
asserts the event never carries a provider's instant. It lives in the
integration suite because `register_with_config` is public and the event
is emitted there, not in `time::register_functions`. Removing the label
mutation turns the injected case red, so the assertion has teeth.
* Apply rustfmt to the review-disposition commit
`make check-fmt` rejected two spots: the `source_label` if-else on one
line, and an over-long `assert!` in the new integration test. Both are
formatting only; no behaviour changed.
* Record the completed review and both findings' dispositions in the plan
The queued review is closed as a *completed* review rather than a pending
request: its read-back shows CodeRabbit inspected 8de3c96, not the
pre-rebase head the queue comment named, and returned CHANGES_REQUESTED with
one inline finding and an Observability pre-merge warning.
Both findings are disposed of with evidence. The inline wording finding is
valid — register_functions moves a WallClock into the registered closure, so
the clock is captured while the instant is not — and both guides now say so.
The Observability warning is valid and answered with source_label plus the
clock_source debug field, covered by a mutation-tested integration case.
Adds two evidence entries: check-fmt is two gates behind one name, and the
re-target boundary is the current merge base rather than an earlier one.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Propagate the registration failure instead of asserting in a Result test
The registration event case returned Result while asserting with assert!,
which clippy::panic_in_result_fn rejects under -D warnings; make lint
aborted at lint-clippy on the std_filter_tests target.
The assertion is replaced by a contextual `?`, so a registration failure
propagates as the error the signature already promises. The test still
fails on the same condition and the closed-set assertion is untouched:
mutating source_label to report "system" for both provenances turns
case_2_injected red, and the source is restored byte-identically.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record why the local make test timeout is not this branch's defect
The -8 gate run failed on one test outside the change surface that timed out
at the 300 s per-test allowance. Recorded as artefact entry 21 with the
measurement that settles it: raising the ceiling shows the test completing in
292.1 s, of which 291.6 s is its nested cold cargo build, matching the 688.6 s
figure the developers' guide already records for that build under contention.
Also records that my first explanation -- heavy load -- was refuted by two
isolated re-runs that timed out at load 9.3 and 7.0, while its conclusion was
right. The mechanism is a cold build behind a shared package-cache lock, which
bites at moderate load; the entry keeps the measurement and drops the story.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the tracker for the local make test timeout
The `-9` gate run on `25787722` reproduced the single-test signature of
`-8`: `harness_compiles_under_a_split_build_dir` timed out at its 300 s
allowance with 3245 of 3250 passing, and no other test failed.
Issue #732 already describes this mechanism, names this test, and states
that the harnesses' repeated compilation "is what puts these tests near the
300 s per-test allowance". Recording it turns "not this branch's defect"
from an assertion into a citation, and keeps the plan from re-deriving the
diagnosis a third time.
Folded into entry 21's body rather than added as a sibling list item: a new
marker at that position restarts markdown's ordered list, and mdtablefix's
`--renumber` rewrites it to `1.`, which is not canonical.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the published re-target and the reply heads
The re-target push advanced the branch to `f81f2f98` on base `79545e12`,
under a lease bound to the previously recorded remote head `8de3c963` so a
concurrent rewrite would fail the push rather than be overwritten.
Records the gate state at that head and, specifically, why the `lint` re-run
mattered: the `panic_in_result_fn` error was only confirmed fixed by running
the gate at a head containing the fix, since the earlier `-8` log predates
it. Also records that both review replies were posted against this head, and
that the CI run the push triggered is not yet claimed as green.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the CI verdict for the published head
All four required checks pass on `74822cc2`: `build-test` and `kani-smoke`
(CI run 35454416505), `netsukefile` (35454416354), and `release / metadata`
(35454416662). The sole red job is the non-required
`Windows / build-test-windows`, failing for the already-recorded
pre-existing reason, re-verified here against `main` at `ef7ed760` rather
than assumed.
Also corrects a wrong premise I supplied while briefing the monitor: I
described that job as failing in `git submodule` before project code runs.
It does not — those lines are post-job cleanup from a successful checkout,
and the real failure is the loopback race at the test step. The conclusion
held, which is why the premise had to be checked rather than inherited.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the reconciled review surfaces and the open decision
CodeRabbit's separate answer to the pre-merge reconciliation confirms the
implementation by static inspection and instructs that the observability
warning be marked resolved, with no follow-up issue and no further code work.
Records two facts separately rather than merging them: both review threads
are resolved and the queue is empty, while the `CHANGES_REQUESTED` decision
persists and is anchored to `8de3c963`, which is no longer an ancestor of
the branch. A stale anchor is not an approval, and clearing it would mean
dismissing a review or approving on the bot's behalf — so the decision is
left with its designated owner, and the four required checks are recorded as
`SUCCESS` on the published head.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Name the verified head and stop re-verifying it
Recording "CI is green at head H" is itself a commit, and that commit moves
the head, invalidating the verdict it records. The plan is not a passive
record here: `build-test` runs `make check-fmt`, markdownlint over `**/*.md`,
`make spelling`, and the workspace test suite, and
`tests/execplan_status_contract_tests.rs` reads `docs/execplans/` — so an edit
to this document is an input to the same required checks whose result it
reports.
Three pushes were spent rediscovering that. The fix is not to keep
re-verifying but to name the head that was verified rather than implying the
newest one is, so this is the last plan commit for the re-target.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Correct the reason the Observability row still renders
The reconciliation entry recorded that the walkthrough's Observability row
persists because the comment "has not been re-edited". That is wrong: the
comment's updated_at moved to 2026-09-19T17:25:12Z, after CodeRabbit's
confirmation at 16:13:51Z, so it was touched.
What did not happen is any change to what it says. Three captures of the body
taken after that edit (17:55, 18:51, 20:06) are byte-identical to the live
body, so the touch was content-preserving and left the row standing.
The distinction is load-bearing. "The table is stale" would justify asking for
another pass; "the table was refreshed and the finding still stands" would not.
Only the first reading fits the evidence, and the second is the one the
recorded reason implied.
Dispositions are unchanged: both threads isResolved, both fixes verified
present at current source, all four required checks SUCCESS on 74822cc. The
row remains CodeRabbit's to flip; ticking its Ignore checkbox or dismissing
the review to force the table green is not done here.
Gates: make check-fmt, make markdownlint, make spelling all exit 0, and
execplan_status_contract_tests 9/9 passed -- the one test reading docs/execplans/.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record the re-rebase and correct the superseded timeout disposition
Two entries, both about replacing a record that no longer describes reality.
The Progress section gains the re-rebase onto the current origin/main:
boundary 79545e1, target 397fb58, 52 commits replayed, exactly one
conflict, in docs/v0-1-0-migration-guide.md where main's #737 rewrote the
at-a-glance table while this branch appended a row to it. Both intents were
additive, so the resolution keeps main's table and appends the row;
range-diff reports 51 of 52 pairs identical and the one divergence is
padding. The substantive fixes are shown to survive by whole-file patch-id
comparison rather than by reading subjects.
The timeout disposition in entry 21 is marked superseded rather than left to
read as current. It cited issue 732 as a live tracker and concluded there
was nothing to fix here. The rebase adopted #752, which closed 732 and
replaced that test's live private rebuild with a recorded Cargo JSON
fixture, so the test no longer spawns Cargo and was dropped from the
nested-cargo-builds group. The first rebased gate run confirms it: make test
is 3394 of 3394 passing, 0 failed, 5 skipped in 276 s, with the
formerly-timing-out test green and no longer slow.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Record publication and the disposition of the two declined checks
The rebased head was published with a force-with-lease bound to the
previously recorded remote head, and the PR base now reads the same SHA
as the replay target.
Records how CodeRabbit's two explicitly-unresolved checks were disposed
of. The Windows close-abort failure is superseded by this rebase, proved
by ancestry: the fix was absent from the head that failed and is present
afterwards. The CodeScene coverage timeout is trunk-only by design and
not in the required set.
Also records that the new head's green CodeRabbit status carries the
description "Review paused" rather than "Review completed", so it is a
pause stamp and not evidence of a review.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: leynos <leynos@rohga>
Summary
env_var— backing theenv()Jinja helper — read the process environment directly:Its three outcomes could only be reached by mutating global state, which the AGENTS.md testing mandate no longer permits. One was unreachable in practice regardless: fabricating a non-UTF-8 value in the live environment needs platform-specific
OsStringsurgery, so theNotUnicodebranch and its distinct Jinja error kind had no coverage at all.The function's doc comment also advertised a
test_support::env::VarGuardexample that mutates global state; that goes with it.Approach
Split resolution into
env_var_with, taking aread_envclosure — consistent with the existing seam inrunner::process::ninja_programand the two added in #486 and #487. A closure rather than a trait object: keyed lookup, one caller, and the mandate permits a narrow closure where a trait object would be disproportionate.Coverage
Six cases, none mutating anything:
Checked against upstream RFCs
ortho-config RFC 0001 governs field-level environment aliases for configuration structs. It has no bearing on the manifest
env()helper, which is Netsuke's own template surface, so the closure seam here is not in tension with theEnvSourceabstraction being added upstream.Verification
All gates pass:
check-fmt,lint,typecheck,test(1194 nextest),markdownlint,nixie. CodeScene delta: no issues.Closes #484.
Refs #496.
🤖 Generated with Claude Code
Summary by Sourcery
Introduce an injectable environment lookup seam for the manifest env() helper to enable full testing of all resolution outcomes without mutating process state.
Enhancements:
Tests: