Skip to content

Give the manifest env() helper an injectable env seam (#484) - #501

Merged
leynos merged 9 commits into
mainfrom
issue-484-inject-env-seam-into-manifest-env-function
Aug 5, 2026
Merged

leynos merged 9 commits into
mainfrom
issue-484-inject-env-seam-into-manifest-env-function

Conversation

@leynos

@leynos leynos commented Aug 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

env_var — backing the env() Jinja helper — read the process environment directly:

fn env_var(name: &str) -> std::result::Result<String, Error> {
    match std::env::var(name) { ... }
}

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 OsString surgery, so the NotUnicode branch and its distinct Jinja error kind had no coverage at all.

The function's doc comment also advertised a test_support::env::VarGuard example that mutates global state; that goes with it.

Approach

Split resolution into env_var_with, taking a read_env closure — consistent with the existing seam in runner::process::ninja_program and 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:

  • a present variable resolves to its value
  • an empty value is returned rather than treated as missing — an empty value is a value, not an absence
  • both failures map to their documented Jinja error kind
  • the two failure kinds stay distinct — 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. Collapsing them onto one kind would misdirect whoever reads the failure, and nothing previously stopped that
  • the requested name reaches the seam unaltered and appears in the message

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 the EnvSource abstraction 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:

  • Add env_var_with helper that accepts a closure for environment variable resolution while preserving existing env_var behavior and error mapping semantics.

Tests:

  • Add dedicated env_function tests that cover successful resolution, empty values, distinct failure kinds, and propagation of variable names through error messages without touching the real environment.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary

  • Inject environment lookups into manifest env() evaluation through EnvReader.
  • Add manifest::from_str_with_env, process_env_reader, and env_var_with.
  • Retain process-environment defaults at public boundaries.
  • Preserve distinct error mappings for missing and non-UTF-8 values.
  • Add platform-independent tests without mutating process-global state.
  • Remove manifest_env_tests from the serial environment test group.
  • Update user, developer, and design documentation.
  • Replace the VarGuard example with a MockEnv example.
  • Add documentation coverage for from_str_with_env, EnvReader, and env('PROFILE').

Closes #484. References #496.

Verification

  • check-fmt
  • lint
  • typecheck
  • test — 1194 nextest tests
  • markdownlint
  • nixie
  • No CodeScene delta issues

Walkthrough

Use an injectable EnvReader for manifest Jinja lookups. Keep process-environment defaults for standard and file-based loading. Update tests, documentation, and serial-test configuration.

Changes

Manifest environment injection

Layer / File(s) Summary
Injectable environment helper and parser integration
src/manifest/env_reader.rs, src/manifest/mod.rs
Add EnvReader, process_env_reader, and from_str_with_env. Route Jinja env() lookups through the configured reader. Preserve Jinja error mappings.
Injected lookup tests
src/manifest/tests/*, tests/manifest_env_tests.rs
Test present, empty, missing, invalid UTF-8, special-character, and requested-name cases without changing process-global environment state.
Parallel environment test configuration and documentation
.config/nextest.toml, tests/makefile_test_target.rs, docs/developers-guide.md, docs/users-guide.md, docs/netsuke-design.md, tests/documentation_examples_tests.rs
Remove manifest_env_tests from serial-env. Document injected-reader ownership, execution rules, and the public API. Validate the documented example.

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
Loading

Possibly related issues

Possibly related PRs

  • leynos/netsuke#330 — Uses a related injected environment-reader pattern.
  • leynos/netsuke#473 — Updates the same serial environment test configuration and validation.
  • leynos/netsuke#515 — Directly overlaps the manifest reader, parser, and environment test changes.

Suggested labels: Issue

Suggested reviewers: codescene-access

Poem

Inject the reader; keep globals still.
Render manifests with deterministic skill.
Map missing names and invalid bytes.
Run manifest tests through parallel gates.
Keep process access at the boundary.

🚥 Pre-merge checks | ✅ 20
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the injectable manifest env() seam and links issue #484 as required.
Description check ✅ Passed The description directly explains the injectable environment seam, test coverage, documentation, and verification results.
Linked Issues check ✅ Passed The changes satisfy issue #484 by injecting EnvReader, preserving process defaults, removing environment mutation, and covering non-UTF-8 errors.
Out of Scope Changes check ✅ Passed The code, tests, configuration, and documentation changes support the linked issue objectives and contain no unrelated scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed Unit tests cover success, empty, missing, non-UTF-8, error kinds, and variable names; integration tests exercise injected readers through real manifest Jinja registration.
User-Facing Documentation ✅ Passed The users' guide documents the new EnvReader API, live-environment default, deterministic injection, error behaviour, and a concrete from_str_with_env example.
Developer Documentation ✅ Passed Accept the change: the developer's guide documents EnvReader ownership, call sites, composition, and test isolation; the design document records the injected env() boundary and defaults.
Module-Level Documentation ✅ Passed All touched Rust modules have //! documentation; env_reader explains its purpose, process-reader relationship, and composition rules, and test modules state their scope.
Testing (Unit And Behavioural) ✅ Passed Accept the check: unit tests cover present, empty, missing, non-UTF-8, error kinds, and names; behavioural tests exercise public from_str_with_env and rendered commands.
Testing (Property / Proof) ✅ Passed The change has a finite outcome mapping, not a range-based state or ordering invariant; tests cover Ok, empty, NotPresent, NotUnicode, error kinds, names, and integration wiring. No proof obligatio...
Testing (Compile-Time / Ui) ✅ Passed The change has no compile-time behaviour; its doctest and focused assertions cover runtime values, ErrorKind, names, and message fragments, so trybuild or snapshots are not required.
Unit Architecture ✅ Passed EnvReader is an explicit Result-returning seam; only process_env_reader calls std::env::var, and public parse boundaries inject it. Unit and integration tests avoid environment mutation.
Domain Architecture ✅ Passed from_str_with_env injects EnvReader; the sole std::env::var call is confined to process_env_reader, and tests exercise parsing without process mutation.
Observability ✅ Passed Retain the existing diagnostic path: missing and invalid-UTF-8 lookups expose distinct error kinds and the requested variable name; no service boundary or metric-worthy runtime behaviour was added.
Security And Privacy ✅ Passed The PR adds no credentials or secret literals; EnvReader returns values without logging them, and errors include only the requested name while discarding non-UTF-8 contents.
Performance And Resource Use ✅ Passed Accept the change: production paths create one EnvReader and clone one Arc per parse; lookups remain bounded by template use, with no new unbounded collection, loop, or repeated I/O.
Concurrency And State ✅ Passed EnvReader is caller-owned and Send + Sync; parsing clones it into a local Jinja environment, while migrated tests inject readers without global mutation or serialisation.
Architectural Complexity And Maintainability ✅ Passed Keep the change: EnvReader isolates one global lookup, supports both parse entry points and real registration tests, and has documented ownership without new dependencies or cycles.
Rust Compiler Lint Integrity ✅ Passed The PR adds no broad unused-code suppressions or artificial anchors; all new helpers and re-exports have real uses, and Arc/String clones serve callback ownership or repeated test results.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-484-inject-env-seam-into-manifest-env-function

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Injects 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 seam

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Introduce an injectable environment seam for the manifest env() helper and adjust its documentation.
  • Add env_var helper that delegates to env_var_with using std::env::var
  • Extract env_var_with that takes a FnOnce(&str) closure returning Result<String, VarError> and maps outcomes to existing Jinja ErrorKind values
  • Update the doc comment example to use env_var_with instead of VarGuard and remove global environment mutation from documentation
src/manifest/mod.rs
Add targeted tests for env_var_with to cover success, empty, missing, and non-UTF-8 cases and ensure error kind distinctions and name propagation.
  • Create env_function test module for env() resolution behavior
  • Add tests verifying present and empty variable values are returned correctly
  • Add rstest-based parameterized tests that map VarError variants to the documented Jinja ErrorKind values
  • Add tests confirming the two failure kinds remain distinct and that the requested variable name is passed through to the seam and included in error messages
  • Wire the new env_function module into the manifest tests suite
src/manifest/tests/mod.rs
src/manifest/tests/env_function.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#484 Introduce an injectable environment seam for the manifest env() helper, including removing direct std::env::var usage from src/manifest/mod.rs and supplying a mockable DefaultEnv at the manifest public boundary. ❌ The PR introduces env_var_with(name, read_env) and has env_var delegate to it via a closure, which does provide an injectable seam. However, env_var still calls std::env::var inside src/manifest/mod.rs via env_var_with(name,
#484 Rewrite the env_var documentation example to use a non-mutating mock/injected environment instead of test_support::env::VarGuard, eliminating in-process mutation. ✅
#484 Migrate tests for the env() helper to the injected environment seam so they no longer mutate the process environment, and ensure the NotUnicode branch is covered by tests. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review August 2, 2026 07:14
@coderabbitai coderabbitai Bot added the Issue A pull request originating from an issue label Aug 2, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3f84545 and 48c1b8f.

📒 Files selected for processing (3)
  • src/manifest/mod.rs
  • src/manifest/tests/env_function.rs
  • src/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)

Comment thread src/manifest/mod.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/manifest/mod.rs Outdated
Comment thread src/manifest/mod.rs Outdated
leynos pushed a commit that referenced this pull request Aug 2, 2026
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>
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@buzzybee-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3f84545 and d10fa0c.

📒 Files selected for processing (11)
  • docs/adr-006-adopt-polonius-nightly-toolchain.md
  • docs/developers-guide.md
  • docs/netsuke-design.md
  • docs/polonius.md
  • docs/snapshot-testing-in-netsuke-using-insta.md
  • docs/users-guide.md
  • src/manifest/env_reader.rs
  • src/manifest/mod.rs
  • src/manifest/tests/env_function.rs
  • src/manifest/tests/mod.rs
  • tests/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)

Comment thread docs/developers-guide.md
Comment thread src/manifest/env_reader.rs Outdated
Comment thread src/manifest/env_reader.rs Outdated
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@buzzybee-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]

This comment was marked as resolved.

codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following now been resolved?

docs/developers-guide.md (1)

837-839: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Restrict the ownership claim to production code.
tests/manifest_env_tests.rs constructs EnvReader directly with Arc::new. Therefore, “Nothing else constructs one except the public entry points” is too broad. State that production callers use the public entry points, while tests may construct injected readers directly.
Triage: [type:docstyle]

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/developers-guide.md around lines 837 - 839, Update the ownership
statement near manifest::from_str_named to scope it to production callers: say
production code uses the public entry points, while tests such as
manifest_env_tests.rs may construct injected EnvReader instances directly with
Arc::new.

@coderabbitai

This comment was marked as resolved.

@leynos

leynos commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

@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

Issue Objective Addressed Explanation
#484 Introduce an injectable environment seam for the manifest env() helper, including removing direct std::env::var usage from src/manifest/mod.rs and supplying a mockable DefaultEnv at the manifest public boundary. ❌ The PR introduces env_var_with(name, read_env) and has env_var delegate to it via a closure, which does provide an injectable seam. However, env_var still calls std::env::var inside src/manifest/mod.rs via env_var_with(name,

@coderabbitai

This comment was marked as resolved.

@leynos

leynos commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

On the pre-merge table's Developer Documentation warning: that row predates dce4728, which already updated netsuke-design.md §4.4 (it now describes env() reading through the injected EnvReader, names process_env_reader() and from_str_with_env(), and states the caller-owns-the-reader model) and corrected the developers' guide's from_str_named ownership claim in the same commit. The refreshed table on the next round should reflect it.

Separately, the makefile_test_target decomposition requested against this PR now lives in #526, stacked on this branch, closing #524.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

leynos and others added 9 commits August 5, 2026 23:08
`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>
@leynos
leynos force-pushed the issue-484-inject-env-seam-into-manifest-env-function branch from e0e1acd to 5446272 Compare August 5, 2026 21:15
codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No quality gates enabled for this code.

@leynos
leynos merged commit d6841b5 into main Aug 5, 2026
17 checks passed
@leynos
leynos deleted the issue-484-inject-env-seam-into-manifest-env-function branch August 5, 2026 21:42
leynos pushed a commit that referenced this pull request Sep 19, 2026
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>
leynos pushed a commit that referenced this pull request Sep 19, 2026
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>
leynos pushed a commit that referenced this pull request Sep 24, 2026
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>
wafflecat-df12 pushed a commit that referenced this pull request Sep 24, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Issue A pull request originating from an issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inject an Env seam into the manifest env() Jinja function

3 participants