Escape backend dollar syntax after Netsuke placeholder lowering (3.14.7) - #565
Conversation
Reviewer's GuideAdds a detailed execution plan document for roadmap task 3.14.7 describing how to implement backend dollar escaping in the Ninja generator while preserving IR purity, including milestones, risks, verification strategy, and design decisions. Sequence diagram for planned command generation with Ninja escapingsequenceDiagram
participant Manifest as Manifest
participant IR as IrGraph
participant CmdInterp as cmd_interpolate
participant NinjaGen as ninja_gen
participant Escape as ninja_gen_escape
participant Ninja as ninja_binary
Manifest->>CmdInterp: interpolate_command_with_bindings(template, bindings)
CmdInterp-->>IR: Action.command(shell_text)
IR->>NinjaGen: generate_into(graph, writer)
NinjaGen->>Escape: escape_ninja_value(ShellText)
Escape-->>NinjaGen: NinjaValue or NinjaGenError
NinjaGen->>writer: write "command = " + NinjaValue
NinjaGen->>Ninja: build.ninja
Ninja-->>NinjaGen: parses & expands dollars correctly
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
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:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (14)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
💤 Files with no reviewable changes (2)
Included review availability: 4 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour. Summary
WalkthroughThe change moves Ninja dollar escaping to the backend boundary. It adds typed validation for shell text, metadata, and paths. Recipe lowering, real-Ninja tests, integration fixtures, documentation, and CI coverage now follow this contract. ChangesNinja escaping pipeline
Sequence Diagram(s)sequenceDiagram
participant Manifest
participant IR
participant NamedAction
participant Ninja
participant Shell
Manifest->>IR: resolve command or script placeholders
IR->>NamedAction: provide completed shell text
NamedAction->>Ninja: escape dollars and write build rule
Ninja->>Shell: execute generated command
Shell-->>Ninja: return expanded command output
Poem
Merge Risk: 🔵 Low · up to The PR changes Ninja dollar escaping and script placeholder handling, while its debugging plan still contains conflicting remediation guidance and its migration documentation overstates which script placeholders remain compatible. The bounded risk is user or maintainer confusion, so the change is mergeable with explicit owner follow-up. Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error, 4 warnings)
✅ Passed checks (15 passed)
Full details: Description checkExplanation The description clearly relates to the dollar-escaping work and roadmap task 3.14.7. However, it incorrectly states that the pull request is plan-only and contains no production changes, while the changeset implements the feature and adds tests, CI updates, and documentation. Full details: Docstring CoverageExplanation Docstring coverage is 89.58% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 96 functions across 23 files. (4 skipped: 4 unsupported.) Full details: Testing (Overall)Explanation The new tests cover the main Ninja escaping paths well, but they do not cover all changed behaviour. Resolution Add a manifest rendering test that defines Full details: User-Facing DocumentationExplanation The users' guide documents ordinary shell-dollar syntax, placeholder lowering, backtick rejection, path restrictions, metadata control characters, and the Resolution Update Full details: Developer DocumentationExplanation The pull request documents the main architecture and updates the roadmap, but the documentation is not fully current. The new ADR-014 was introduced as accepted, then its decision was changed in a later commit without a logged addendum. Its implementation reference also links to the non-existent Resolution Add a dated addendum to ADR-014 that records the metadata-decision change and preserves the decision history. Change the ADR implementation reference to Full details: Module-Level DocumentationExplanation Pass the module-level documentation check. The PR's new Rust modules, including Full details: Testing (Unit And Behavioural)Explanation Pass the testing check. The change adds local tests for dollar escaping, metadata escaping and rejection, unsafe control characters, path validation, empty recipes, command-list errors, and output invariants. Property tests cover scalar shell syntax, braced expansions, command-list ordering, and serial graph invariants. Real-Ninja tests exercise generated-file parsing, shell execution for scalar, script, and command-list recipes, placeholder lowering, heredocs, environment defaults, backtick rejection, unsafe input, and dollar-free output. The BDD scenario also runs a generated target and checks its output, so the behavioural coverage uses the external Ninja boundary rather than only private helpers. Full details: Testing (Property / Proof)Explanation Pass the check. The change introduces range-based invariants for Ninja escaping and placeholder lowering, and it adds Rust Full details: Testing (Compile-Time / Ui)Explanation Fail the compile-time testing requirement. The pull request introduces a Rust compile-time seam: Resolution Add a Rust compile-fail UI test, using the repository's direct- Full details: Unit ArchitectureExplanation PASS — the changed production code keeps the boundaries explicit. Full details: Domain ArchitectureExplanation Keep the boundary. The changed IR code performs Netsuke placeholder lowering for Full details: ObservabilityExplanation Pass the observability check. The new Ninja failure path is covered at the runner generation boundary. Full details: Security And PrivacyExplanation PASS — The pull request introduces no secrets, credentials, permission changes, or authentication or authorization changes. The new Ninja boundary validates and escapes residual shell dollars, rejects newline, carriage-return, and NUL values before binding emission, and rejects unsafe graph-path characters. Command-list failure context hashes action identifiers, and Full details: Performance And Resource UseExplanation The PR adds avoidable per-entry allocation and scanning in the manifest-to-IR path. Resolution Combine backtick detection and placeholder substitution into one pass, or reuse one character buffer, so ordinary commands do not allocate and scan the same text twice. Prepare escaped metadata once before emitting an action, then reuse those values and remove the separate validation pass; keep the fallible pre-write behaviour so invalid metadata cannot produce partial output. Add a representative large command-list and metadata workload check, or a benchmark, to verify linear work and bounded temporary memory. Full details: Concurrency And StateExplanation Pass the concurrency and state check. Keep the new state isolated: Full details: Architectural Complexity And MaintainabilityExplanation Accept the architectural changes. The new Full details: Rust Compiler Lint IntegrityExplanation Accept the Rust changes. The PR diff adds no ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Your free Security trial is over. An organization admin can activate Security or dismiss this notice. Comment |
39a2090 to
546d45b
Compare
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph. If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message in a code block that I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced. tests/ninja_dollar_escaping_tests.rs Comment on lines +56 to +80 fn ninja_commands(ninja_file: &str, target: &str) -> Result<String> {
let workspace = required_ninja_workspace()?;
let path = Utf8PathBuf::from_path_buf(workspace.path().to_path_buf())
.map_err(|non_utf8| anyhow::anyhow!("non-UTF-8 temporary path: {non_utf8:?}"))?;
let directory = Dir::open_ambient_dir(&path, ambient_authority())
.with_context(|| format!("open Ninja workspace {path}"))?;
directory
.write("build.ninja", ninja_file)
.context("write generated Ninja file")?;
let output = Command::new("ninja")
.args(["-f", "build.ninja", "-t", "commands", target])
.current_dir(path.as_std_path())
.env_clear()
.env(SENTINEL, SENTINEL_VALUE)
.output()
.context("run Ninja command oracle")?;
if !output.status.success() {
bail!(
"Ninja rejected generated file: {}",
String::from_utf8_lossy(&output.stderr)
);
}
String::from_utf8(output.stdout).context("Ninja command output was not UTF-8")
}❌ New issue: Code Duplication |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
7a56169 to
d15f3bc
Compare
|
@coderabbitai Please investigate the cause of the following issue using codegraph exploration and research, identify a fix and provide an AI coding agent prompt for the fix: |
This comment was marked as resolved.
This comment was marked as resolved.
|
@coderabbitai please assess the implementation in this PR for completeness and correctness against the execplan:
|
This comment was marked as resolved.
This comment was marked as resolved.
00a6f3c to
04ca96a
Compare
|
@coderabbitai Please investigate the cause of the following issue using codegraph exploration and research, identify a fix and provide an AI coding agent prompt for the fix: https://github.com/leynos/netsuke/actions/runs/33030842021/job/98382749611?pr=565 |
This comment was marked as resolved.
This comment was marked as resolved.
The rebase onto `origin/main` was clean, but "Add target descriptions and netsuke help targets" (#551) restructured `src/manifest/render.rs` and shifted every documentation section the plan cites, so the plan's file:line references no longer resolved. Description and recipe rendering are now the shared helpers `render_description` and `render_recipe`, each taking a `subject` for diagnostics. That makes the EP-M1 render change a single-arm edit to `render_recipe` rather than the two call sites the plan described. Targets also gained descriptions, consumed by the new `netsuke help targets` catalogue, while `src/ir/from_manifest.rs` deliberately keeps target descriptions out of the generated Ninja file. This strengthens decision `D-METADATA`: `description` now feeds a non-backend consumer, so applying a Ninja-specific transform to it would repeat the layering mistake the task exists to correct. The recommendation to scope escaping to command and script text stands. The core findings are untouched. `src/ir/from_manifest_support.rs:54` still passes script recipes through unlowered, so script `$in`/`$out` lowering must still precede escaping. Milestones, verification obligations, and the two open decisions are unchanged. The plan remains DRAFT pending approval.
Lower Netsuke placeholders before converting completed shell text to a Ninja binding, so shell variables survive generation without coupling the IR to Ninja syntax. Reject ambiguous paths and control characters, require real Ninja coverage in CI, and document the migration from historical `$$` recipe spellings.
Retain main's path escaping instead of the superseded path rejection guard. Update fixtures to supply raw shell dollars now that the backend owns Ninja escaping, and satisfy the target branch's new graph and documentation requirements.
Record the successful deterministic gates and zero-finding CodeRabbit review that complete the rebased implementation.
Centralize temporary workspace creation while keeping the parsed-command and executed-output oracles separate, so each test retains its distinct contract.
Consume `ShellText` at the backend boundary so the one-way conversion to `NinjaValue` is structural. Exercise scalar commands and braced expansions through real Ninja, execute lowered script placeholders, and make the BDD sentinel scenario observe the generated target's output.
Capture the focused checks, deterministic gate results, CodeRabbit outcome, and implementation decisions so the ExecPlan remains an accurate completion record.
Keep real-Ninja child environments scrubbed while restoring executable resolution. This preserves the unset-sentinel contract and lets the BDD and oracle helpers spawn Ninja on each supported platform.
Keep Ninja child-process tests isolated while retaining executable lookup, compare real-Ninja oracle output without discarding command whitespace, and restore the approved rejection policy for ambiguous Ninja path characters.
Use each platform shell's native environment-variable spelling in the user-visible Ninja scenario. This keeps the assertion focused on the child process receiving the sentinel, rather than requiring POSIX tools or syntax from Windows `cmd.exe`.
Preserve each platform's native shell expansion while using Python to write the target file. This avoids relying on a Windows shell built-in that Ninja does not resolve consistently.
Use the environment API within the generated Python command because Windows Ninja launches commands directly rather than through a shell. Keep the real-Ninja property as the dedicated shell-dollar parser oracle.
Exercise the real `script` backend only where its documented `/bin/sh -e` contract exists. Keep the cross-platform lowering regression and record the platform boundary in the ExecPlan.
Run the POSIX shell execution regression only where `printf` and `${…}`
expansion are available. Retain Windows coverage for Ninja parsing and
explicit child-process environment propagation.
Keep POSIX-only execution helpers out of Windows builds while retaining the workspace capability directory as the owner of generated test artefacts.
Capture the successful hosted Windows validation and close the ExecPlan with its final platform evidence.
Reject command placeholders protected by backticks and escape every metadata binding at the Ninja emission boundary. Cover unsafe metadata, the real-Ninja command oracle, and full script heredoc execution. Correct apostrophe escaping in the script wrapper, so valid scripts pass through its double-quoted shell invocation. Reconcile the ExecPlan, ADR, and user and developer documentation with the implemented contract.
Add real-Ninja command-list and script coverage for residual shell defaults, and make the property oracle's publication and execution operations explicit. Remove a dead Ninja path error branch and align the completion, migration, and safety documentation with the implemented contract.
Keep the README's forward-looking work list aligned with the completed backend dollar-escaping implementation.
Cover script recipe placeholder reservation and prove the consuming escape boundary with the repository's direct-rustc UI harness. Reconcile the user, architecture, and execution-plan documentation with metadata emission.
Reject backtick-protected placeholders while performing the existing substitution traversal, avoiding a second allocation and scan for every command and script recipe.
Keep the single-pass placeholder substitution while moving per-character decisions into a feature-local helper that satisfies the code-health and module-size constraints.
2f0c28e to
5210146
Compare
….7) (#565) * Add execplan for backend dollar escaping (3.14.7) Draft the execution plan for roadmap task 3.14.7, which makes the Ninja backend escape residual literal dollars as `$$` after Netsuke's own placeholder lowering, so shell variables survive to the shell while the IR stays free of Ninja-specific escaping. Reconnaissance and an adversarial design review established several facts that reshape the task beyond its roadmap wording: - Two distinct failures exist today, not one. `$PATH` is silently erased by Ninja's lexer; `${CARGO:-cargo}` is a hard parse error. Assertions must distinguish them. - `$in` and `$out` already work inside `script:` recipes by accident, via Ninja's own built-ins, because `register_action` lowers only command recipes. Escaping alone would regress every such script, so script lowering must land first. - A scalar command containing a newline injects raw Ninja syntax into the generated file. The new escaping constructor is therefore fallible and rejects control characters. - Escaping commands while leaving path emission raw would make the dependency edge and the command disagree for a path such as `input$1`, which an existing fixture already uses. The plan proposes a `ShellText` to `NinjaValue` seam that makes "escaped exactly once" a compile-time property, sequences the work as four milestones, and grounds verification in differential testing against the real Ninja binary rather than a hand-written lexer model. Kani and Verus are explicitly rejected with reasons. Two decisions are marked as needing approval before implementation: handling of `$in`/`$out` inside backtick regions, and whether the escape extends to `description` and `depfile`. Refs: docs/roadmap.md 3.14.7; netsuke-design.md 2.6, 5.4. * Re-resolve execplan citations after rebase onto 7e5c267 The rebase onto `origin/main` was clean, but "Add target descriptions and netsuke help targets" (#551) restructured `src/manifest/render.rs` and shifted every documentation section the plan cites, so the plan's file:line references no longer resolved. Description and recipe rendering are now the shared helpers `render_description` and `render_recipe`, each taking a `subject` for diagnostics. That makes the EP-M1 render change a single-arm edit to `render_recipe` rather than the two call sites the plan described. Targets also gained descriptions, consumed by the new `netsuke help targets` catalogue, while `src/ir/from_manifest.rs` deliberately keeps target descriptions out of the generated Ninja file. This strengthens decision `D-METADATA`: `description` now feeds a non-backend consumer, so applying a Ninja-specific transform to it would repeat the layering mistake the task exists to correct. The recommendation to scope escaping to command and script text stands. The core findings are untouched. `src/ir/from_manifest_support.rs:54` still passes script recipes through unlowered, so script `$in`/`$out` lowering must still precede escaping. Milestones, verification obligations, and the two open decisions are unchanged. The plan remains DRAFT pending approval. * Escape shell dollars in Ninja recipes Lower Netsuke placeholders before converting completed shell text to a Ninja binding, so shell variables survive generation without coupling the IR to Ninja syntax. Reject ambiguous paths and control characters, require real Ninja coverage in CI, and document the migration from historical `$$` recipe spellings. * Repair post-rebase Ninja escaping integration Retain main's path escaping instead of the superseded path rejection guard. Update fixtures to supply raw shell dollars now that the backend owns Ninja escaping, and satisfy the target branch's new graph and documentation requirements. * Complete post-rebase escape plan Record the successful deterministic gates and zero-finding CodeRabbit review that complete the rebased implementation. * Deduplicate Ninja oracle workspaces Centralize temporary workspace creation while keeping the parsed-command and executed-output oracles separate, so each test retains its distinct contract. * Strengthen Ninja dollar escaping verification Consume `ShellText` at the backend boundary so the one-way conversion to `NinjaValue` is structural. Exercise scalar commands and braced expansions through real Ninja, execute lowered script placeholders, and make the BDD sentinel scenario observe the generated target's output. * Record Ninja escaping correction evidence Capture the focused checks, deterministic gate results, CodeRabbit outcome, and implementation decisions so the ExecPlan remains an accurate completion record. * Restore PATH for isolated Ninja tests Keep real-Ninja child environments scrubbed while restoring executable resolution. This preserves the unset-sentinel contract and lets the BDD and oracle helpers spawn Ninja on each supported platform. * Restore Ninja path and CRLF contracts Keep Ninja child-process tests isolated while retaining executable lookup, compare real-Ninja oracle output without discarding command whitespace, and restore the approved rejection policy for ambiguous Ninja path characters. * Make BDD sentinel recipe cross-platform Use each platform shell's native environment-variable spelling in the user-visible Ninja scenario. This keeps the assertion focused on the child process receiving the sentinel, rather than requiring POSIX tools or syntax from Windows `cmd.exe`. * Use executable for BDD sentinel output Preserve each platform's native shell expansion while using Python to write the target file. This avoids relying on a Windows shell built-in that Ninja does not resolve consistently. * Test BDD sentinel as a process environment Use the environment API within the generated Python command because Windows Ninja launches commands directly rather than through a shell. Keep the real-Ninja property as the dedicated shell-dollar parser oracle. * Scope script execution regression to Unix Exercise the real `script` backend only where its documented `/bin/sh -e` contract exists. Keep the cross-platform lowering regression and record the platform boundary in the ExecPlan. * Scope shell-default execution test to Unix Run the POSIX shell execution regression only where `printf` and `${…}` expansion are available. Retain Windows coverage for Ninja parsing and explicit child-process environment propagation. * Compile Ninja workspace helpers on Windows Keep POSIX-only execution helpers out of Windows builds while retaining the workspace capability directory as the owner of generated test artefacts. * Record Windows validation completion Capture the successful hosted Windows validation and close the ExecPlan with its final platform evidence. * Repair Ninja escaping review findings (#565) Reject command placeholders protected by backticks and escape every metadata binding at the Ninja emission boundary. Cover unsafe metadata, the real-Ninja command oracle, and full script heredoc execution. Correct apostrophe escaping in the script wrapper, so valid scripts pass through its double-quoted shell invocation. Reconcile the ExecPlan, ADR, and user and developer documentation with the implemented contract. * Complete roadmap dependency sentence * Resolve Ninja escaping review findings (#565) Add real-Ninja command-list and script coverage for residual shell defaults, and make the property oracle's publication and execution operations explicit. Remove a dead Ninja path error branch and align the completion, migration, and safety documentation with the implemented contract. * Remove completed backend work from roadmap (#565) Keep the README's forward-looking work list aligned with the completed backend dollar-escaping implementation. * Add Ninja escaping boundary regressions (#565) Cover script recipe placeholder reservation and prove the consuming escape boundary with the repository's direct-rustc UI harness. Reconcile the user, architecture, and execution-plan documentation with metadata emission. * Fuse interpolation placeholder validation (#565) Reject backtick-protected placeholders while performing the existing substitution traversal, avoiding a second allocation and scan for every command and script recipe. * Flatten interpolation traversal control flow (#565) Keep the single-pass placeholder substitution while moving per-character decisions into a feature-local helper that satisfies the code-health and module-size constraints.
….7) (#565) * Add execplan for backend dollar escaping (3.14.7) Draft the execution plan for roadmap task 3.14.7, which makes the Ninja backend escape residual literal dollars as `$$` after Netsuke's own placeholder lowering, so shell variables survive to the shell while the IR stays free of Ninja-specific escaping. Reconnaissance and an adversarial design review established several facts that reshape the task beyond its roadmap wording: - Two distinct failures exist today, not one. `$PATH` is silently erased by Ninja's lexer; `${CARGO:-cargo}` is a hard parse error. Assertions must distinguish them. - `$in` and `$out` already work inside `script:` recipes by accident, via Ninja's own built-ins, because `register_action` lowers only command recipes. Escaping alone would regress every such script, so script lowering must land first. - A scalar command containing a newline injects raw Ninja syntax into the generated file. The new escaping constructor is therefore fallible and rejects control characters. - Escaping commands while leaving path emission raw would make the dependency edge and the command disagree for a path such as `input$1`, which an existing fixture already uses. The plan proposes a `ShellText` to `NinjaValue` seam that makes "escaped exactly once" a compile-time property, sequences the work as four milestones, and grounds verification in differential testing against the real Ninja binary rather than a hand-written lexer model. Kani and Verus are explicitly rejected with reasons. Two decisions are marked as needing approval before implementation: handling of `$in`/`$out` inside backtick regions, and whether the escape extends to `description` and `depfile`. Refs: docs/roadmap.md 3.14.7; netsuke-design.md 2.6, 5.4. * Re-resolve execplan citations after rebase onto 7e5c267 The rebase onto `origin/main` was clean, but "Add target descriptions and netsuke help targets" (#551) restructured `src/manifest/render.rs` and shifted every documentation section the plan cites, so the plan's file:line references no longer resolved. Description and recipe rendering are now the shared helpers `render_description` and `render_recipe`, each taking a `subject` for diagnostics. That makes the EP-M1 render change a single-arm edit to `render_recipe` rather than the two call sites the plan described. Targets also gained descriptions, consumed by the new `netsuke help targets` catalogue, while `src/ir/from_manifest.rs` deliberately keeps target descriptions out of the generated Ninja file. This strengthens decision `D-METADATA`: `description` now feeds a non-backend consumer, so applying a Ninja-specific transform to it would repeat the layering mistake the task exists to correct. The recommendation to scope escaping to command and script text stands. The core findings are untouched. `src/ir/from_manifest_support.rs:54` still passes script recipes through unlowered, so script `$in`/`$out` lowering must still precede escaping. Milestones, verification obligations, and the two open decisions are unchanged. The plan remains DRAFT pending approval. * Escape shell dollars in Ninja recipes Lower Netsuke placeholders before converting completed shell text to a Ninja binding, so shell variables survive generation without coupling the IR to Ninja syntax. Reject ambiguous paths and control characters, require real Ninja coverage in CI, and document the migration from historical `$$` recipe spellings. * Repair post-rebase Ninja escaping integration Retain main's path escaping instead of the superseded path rejection guard. Update fixtures to supply raw shell dollars now that the backend owns Ninja escaping, and satisfy the target branch's new graph and documentation requirements. * Complete post-rebase escape plan Record the successful deterministic gates and zero-finding CodeRabbit review that complete the rebased implementation. * Deduplicate Ninja oracle workspaces Centralize temporary workspace creation while keeping the parsed-command and executed-output oracles separate, so each test retains its distinct contract. * Strengthen Ninja dollar escaping verification Consume `ShellText` at the backend boundary so the one-way conversion to `NinjaValue` is structural. Exercise scalar commands and braced expansions through real Ninja, execute lowered script placeholders, and make the BDD sentinel scenario observe the generated target's output. * Record Ninja escaping correction evidence Capture the focused checks, deterministic gate results, CodeRabbit outcome, and implementation decisions so the ExecPlan remains an accurate completion record. * Restore PATH for isolated Ninja tests Keep real-Ninja child environments scrubbed while restoring executable resolution. This preserves the unset-sentinel contract and lets the BDD and oracle helpers spawn Ninja on each supported platform. * Restore Ninja path and CRLF contracts Keep Ninja child-process tests isolated while retaining executable lookup, compare real-Ninja oracle output without discarding command whitespace, and restore the approved rejection policy for ambiguous Ninja path characters. * Make BDD sentinel recipe cross-platform Use each platform shell's native environment-variable spelling in the user-visible Ninja scenario. This keeps the assertion focused on the child process receiving the sentinel, rather than requiring POSIX tools or syntax from Windows `cmd.exe`. * Use executable for BDD sentinel output Preserve each platform's native shell expansion while using Python to write the target file. This avoids relying on a Windows shell built-in that Ninja does not resolve consistently. * Test BDD sentinel as a process environment Use the environment API within the generated Python command because Windows Ninja launches commands directly rather than through a shell. Keep the real-Ninja property as the dedicated shell-dollar parser oracle. * Scope script execution regression to Unix Exercise the real `script` backend only where its documented `/bin/sh -e` contract exists. Keep the cross-platform lowering regression and record the platform boundary in the ExecPlan. * Scope shell-default execution test to Unix Run the POSIX shell execution regression only where `printf` and `${…}` expansion are available. Retain Windows coverage for Ninja parsing and explicit child-process environment propagation. * Compile Ninja workspace helpers on Windows Keep POSIX-only execution helpers out of Windows builds while retaining the workspace capability directory as the owner of generated test artefacts. * Record Windows validation completion Capture the successful hosted Windows validation and close the ExecPlan with its final platform evidence. * Repair Ninja escaping review findings (#565) Reject command placeholders protected by backticks and escape every metadata binding at the Ninja emission boundary. Cover unsafe metadata, the real-Ninja command oracle, and full script heredoc execution. Correct apostrophe escaping in the script wrapper, so valid scripts pass through its double-quoted shell invocation. Reconcile the ExecPlan, ADR, and user and developer documentation with the implemented contract. * Complete roadmap dependency sentence * Resolve Ninja escaping review findings (#565) Add real-Ninja command-list and script coverage for residual shell defaults, and make the property oracle's publication and execution operations explicit. Remove a dead Ninja path error branch and align the completion, migration, and safety documentation with the implemented contract. * Remove completed backend work from roadmap (#565) Keep the README's forward-looking work list aligned with the completed backend dollar-escaping implementation. * Add Ninja escaping boundary regressions (#565) Cover script recipe placeholder reservation and prove the consuming escape boundary with the repository's direct-rustc UI harness. Reconcile the user, architecture, and execution-plan documentation with metadata emission. * Fuse interpolation placeholder validation (#565) Reject backtick-protected placeholders while performing the existing substitution traversal, avoiding a second allocation and scan for every command and script recipe. * Flatten interpolation traversal control flow (#565) Keep the single-pass placeholder substitution while moving per-character decisions into a feature-local helper that satisfies the code-health and module-size constraints.
Summary
Drafts the execution plan for roadmap task 3.14.7, which makes the Ninja backend escape residual literal dollars as
$$after Netsuke's own placeholder lowering, so shell variables such as$PATH,${CARGO:-cargo}, and$RUSTFLAGSsurvive to the shell while the intermediate representation stays free of Ninja-specific escaping.Plan:
docs/execplans/3-14-7-escape-backend-dollar-syntax-after-netsuke-placeholder-lowering.mdThis is a plan only. No production code changes. The plan requires approval before implementation begins.
What reconnaissance found
Reconnaissance and an adversarial design review established several facts that reshape the task beyond its roadmap wording. All were reproduced against
ninja1.11.1.command = echo PATH is $PATHis silently erased by Ninja's lexer and printsPATH is.${CARGO:-cargo}is a hard parse error —bad $-escape (literal $ must be written as $$). A test matrix built only on "the variable came back empty" would miss the case the roadmap names explicitly.$inand$outalready work insidescript:recipes by accident, via Ninja's own built-in variables, becauseregister_action(src/ir/from_manifest_support.rs:54) lowers only command recipes and passes scripts through unchanged. Escaping alone would regress every such script, so script lowering must land before escaping.printf %b 'echo HOME is \$HOME'executes asprintf %b 'echo HOME is \'— Ninja eats$HOMEand leaves a trailing backslash inside the quoted argument.command:containing a newline injects raw Ninja syntax into the generated file, creating targets the manifest never declared.shlex::splittreats\nas whitespace, so neither the IR validation nor theassert_shell_commanddebug guard rejects it. Command lists are guarded; scalar commands are not.input$1— whichtests/command_escaping_tests.rs:55already uses as a fixture.$at all, and it comes from the command-list wrapper rather than user text. The proptest generator atsrc/ninja_gen_property_tests.rs:177isecho [a-z]{1,12}, which cannot produce a$.Proposed approach
A
ShellText→NinjaValueseam in a newsrc/ninja_gen_escape.rs, with private fields and a single fallible constructor, so "escaped exactly once" becomes a compile-time property rather than a review finding.escape_ninja_valuealso rejects control characters, closing the injection hole at the same seam.Four milestones, sequenced so each is a coherent plateau:
ninjadifferential oracle and the red regression matrix.$in/$outforscript:recipes (must precede escaping).command_list_entrydrops its hand-baked$$.Verification
The oracle is the real
ninjabinary (ninja -t commands), not a hand-written lexer model — a model would be written from the same mental model as the escaper, so a shared misconception would pass green, and it structurally cannot express "Ninja accepts this file". Seven obligations (I1–I7) are stated with non-vacuity controls and seeded faults for each.Kani and Verus are explicitly rejected with reasons: the introduced function is a pure total string map, and a Verus proof would require axiomatising
str::replacethen proving the axiom implies the specification.Decisions needing approval before implementation
D-BACKTICK—substitutepreserves backtick regions, socat `basename $in`leaves$inunlowered; after escaping the shell receives a literal$inand silently produces nothing. Recommendation: reject with a typed diagnostic rather than silently diverge.D-METADATA— whether to escapedescription,depfile,deps, andpool. Recommendation: no. Descriptions are never$in/$out-lowered, so escaping them alone removes the workingdescription = CC $outidiom and gives nothing back;depfile = $out.dis the canonical Ninja idiom that roadmap 3.14.6 will depend on. Scope to command and script text exactly as the approved design states, and record the gap as a follow-up.Note on branch naming
The task brief asked for branch
3-14-5-regression-coverage-for-conditional-action-dependency-manifestsand a(3.14.5)PR title, but the task body, the roadmap entry, and the requested plan filename are all 3.14.7. That 3.14.5 branch already exists onoriginata857fdecarrying the separate 3.14.5 plan in #387, so pushing here would have collided with it. I treated the 3.14.5 naming as a stale carry-over and used a 3.14.7 branch. Happy to move it if that was wrong.Validation
make markdownlintpasses (82 files, 0 errors). Docs-only change; no Rust source touched, so the cargo gates are unaffected.References
docs/roadmap.md:212-219(3.14.7)docs/netsuke-design.md§§2.6 and 5.4🤖 Generated with Claude Code
Summary by Sourcery
Preserve shell dollar syntax through Ninja generation by lowering Netsuke placeholders first, escaping residual dollars at the backend boundary, and adding comprehensive validation and real-Ninja coverage.
New Features:
$inand$outplaceholders consistently in script recipes and reject unsupported backtick usage with diagnostics.Bug Fixes:
Enhancements:
CI:
NETSUKE_REQUIRE_NINJA=1.Documentation:
$$recipe syntax, script placeholder behaviour, and path restrictions.Tests:
Chores: