Repository navigation
fix(agent): resolve DAG file references by the host's path rules - #1066
Conversation
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the full github/main...HEAD diff and the surrounding validation, rendering, backend-read, node-artifact, and DAG dispatch callers. I also checked the change history; the AGENTS.md/CLAUDE.md constraints and CONTEXT-MAP.md routing; domain terminology and architecture boundaries; backward compatibility on POSIX paths; Windows relative, absolute, drive-relative, rooted-without-drive, UNC, separator, case, and symlink semantics; and that the tests remove only obsolete platform skips rather than weakening assertions.
Verification on Python 3.12: uv run --python 3.12 --extra dev pytest tests/test_subagent_prompt_template.py tests/test_playbook_validate.py tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py -k 'template or validate or reference or node_root or nodes_prefix or may_not_execute or both_ref_forms or history or addressable or reachable_by_path' -q -n 0 --tb=short passed with 170 passed, 3 Windows-only skipped, and 706 deselected. The checked-in large-file and source-language scripts also passed for github/main..HEAD; I invoked them directly because make is unavailable in this environment.
|
No Windows compatibility regression found in the inspected scope. Reviewed head
Native Windows verification: Python 3.13.12: 168 passed, 5 skipped, 706 deselected with the new CI selection below. The five skips are symlink creation denied on this account. A separate Python 3.12.13 head/base probe passed: for Commands ran in an isolated detached checkout with a frozen dev environment and isolated Raven/profile/OAuth directories: uv sync --frozen --python 3.13 --extra dev
uv run --frozen --no-sync pytest tests/test_subagent_prompt_template.py tests/test_playbook_validate.py tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py -k 'template or validate or reference or node_root or nodes_prefix or may_not_execute or both_ref_forms or history or addressable or reachable_by_path' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\07e3ab35a8ca4ce8a79558f02fad7b30\checks\1066-windows313\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\07e3ab35a8ca4ce8a79558f02fad7b30\checks\1066-windows313\junit.xml'
uv run --no-project --python 3.12 --with 'pytest>=9,<10' pytest 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\07e3ab35a8ca4ce8a79558f02fad7b30\probes\test_rooted_backend.py' -c 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\07e3ab35a8ca4ce8a79558f02fad7b30\probes\pytest.ini' -x -s --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\07e3ab35a8ca4ce8a79558f02fad7b30\checks\1066-python312-probe'Limitations: symlink cases could not run; no full suite, live sub-agent/model calls, or remote-filesystem verification. This is a Windows-scoped review, not a general merge verdict. |
|
Native Windows re-verification at head Correction to the first version of this comment: the 173 passed figure below is the CI runner's, not this host's. Locally the same selection gives 168 passed / 5 skipped / 706 deselected, because this account cannot create symlinks. Exact figures:
This agrees with the earlier 3.13.12 run on the same head, which gave 168 passed / 5 skipped over the selection; the two interpreters agree on the pass and skip sets. The "Test DAG file-reference confinement" step in the No code change: this run only re-measured the existing head. |
|
Merge conflict: @LivXue, this PR no longer merges onto |
|
Blocking: resolve the CHANGELOG.md merge conflict against current main. I independently confirmed this after fetching main at a7bf9f8: |
a84f63d to
d4b4da2
Compare
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The rebase resolves the prior CHANGELOG.md conflict and git merge-tree --write-tree HEAD github/main now succeeds against current main. I compared the rebased commit with the previously reviewed patch using git range-diff, re-read the resulting eight-file github/main...HEAD diff, and checked the affected callers, history, AGENTS.md/CLAUDE.md and CONTEXT-MAP.md rules, architecture boundaries, backward compatibility, and test assertions. The implementation patch is preserved; only the changelog insertion context changed.
Verification on Python 3.12: the relevant four-file pytest selection passed with 171 passed, 3 Windows-only skipped, and 741 deselected. check_large_files.py, check_source_language.py, and git diff --check passed for github/main..HEAD; the worktree remains clean.
|
Windows compatibility re-review at head The earlier Windows review on this PR was at head Verdict: the PR's own change is unchanged, so the earlier review still stands. This is a rebase onto the current The PR's diff against its merge base is 8 files, +175/-51 - the same eight files and the same line counts as at the reviewed head. Comparing the two PR diffs line by line, the added and removed lines are identical: zero differing lines out of 226. What did change is the context around them, because That is the whole of the delta, and it means the Windows review does not have to be redone from scratch: nothing in the code changed. For the record, that review's findings still stand as written - no new Windows regression was found, and the two items it did raise were reported as inherited and as a Windows CI coverage note, not as defects in this PR. Native Windows verification: the two PR diffs were compared line by line on this machine (226 changed lines on each side, 0 differing), the eight changed files were confirmed to be the same eight as at the reviewed head, and the merge bases were confirmed to be the ones named above. No test suite was re-run for this rebase, because no line of the change under test differs from the line the earlier review verified. This is a scoped compatibility report, not a merge verdict. Exact verification commands and results$root = 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\run-20261024'
$repo = "$root\repo"
# the PR's diff at the reviewed head and at the current head
git -C $repo diff --no-color "43159933..a84f63dcd54ca893951bbc0a42d6a99" > "$root\scratch\rb1066-old.patch"
git -C $repo diff --no-color "e8332e5d4c..d4b4da2af5896bb94341d2460c8273fff9e8bbb4" > "$root\scratch\rb1066-new.patch"
# compare the added and removed lines only, ignoring the hunk context that main's move changed
$o = Get-Content "$root\scratch\rb1066-old.patch" | Where-Object { ($_ -match '^[+-]') -and ($_ -notmatch '^(\+\+\+|---)') }
$n = Get-Content "$root\scratch\rb1066-new.patch" | Where-Object { ($_ -match '^[+-]') -and ($_ -notmatch '^(\+\+\+|---)') }
"changed lines old=$($o.Count) new=$($n.Count)"
Compare-Object $o $n
# no output: the two sets are identical |
## Summary A still-running DAG node is labelled Finished in the TUI. Truncated node output also includes the system's cut marker inside the node's own Markdown, and expanding a long delivery body has no height limit. This follow-up addresses the three review findings on #1063. - Keep the first 20,000 characters of node text in `output` and carry a separate optional `output_truncated` boolean through the manager, spine, three RPC delivery shapes, generated clients and web live/replay paths. Render the translated system notice outside the node's Markdown. A whitespace-only truncated prefix still exposes the fold; older records and node-authored suffixes retain their contents. - Bound expanded delivery bodies with `max-height: min(50vh, 32rem)` and scrolling. The labelled region accepts keyboard focus, including navigation to its final line. This applies to every delivery fold. - Use the existing Still running translation for TUI notice rows in both live and replay views. - Replace the foreground test helper's external cat command with a real Python echo process and platform-aware argument quoting. The release/re-send regression explicitly refuses cat and uses a script path containing a space. - Apply the pinned formatter to two existing TUI component expressions so the whole-tree formatting hook passes. These changes only reflow whitespace. The model-facing report and untrusted-content handling remain unchanged. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification After applying the patch to current main, using Python 3.13.13 on Linux: - `uv run --frozen --no-sync pytest -q -n 0 --tb=short` with branch coverage enabled: **1,575 passed** across `test_subagent_dag_adjudication.py`, `test_subagent_dag_runner.py`, `test_subagent_manager.py`, `test_playbook_stint.py`, `test_rpc_spine.py`, `test_rpc_schema_match.py`, `test_spine_scheduler_lane.py`, `test_spine_events.py`, `test_rpc_turn_cancel.py`, `test_spine_scheduler.py` and `test_security_untrusted_context.py` under `tests/`. This includes the newly merged cancellation path. - `node node_modules/vitest/vitest.mjs run --maxWorkers=1 --no-file-parallelism`: web **253 files, 4,234 passed**; TUI **144 files, 2,251 passed, 13 skipped**. Both exited 0. - The repository diff-coverage gate passed: **100% (10/10 executable changed Python lines)**. Coverage was collected for the five changed runtime/RPC modules; this is diff coverage, not repository-wide coverage. - `uv run --frozen --no-sync ruff check`, `ruff format --check`, `ty check`, `lint-imports` and `deptry raven` passed. Ruff covered the repository lint targets; ty covered the source targets. All 10 import contracts were kept. - In `ui-web`: `npm run gen:check`, `npm run type-check`, `npm run lint` and `npm run build` passed. In `ui-tui`: `npm run lint:rpc`, `npm run lint:i18n`, `npm run type-check`, `npm run lint` and `npm run build` passed; lint retains 23 existing warnings and no errors. - `uv run --frozen --no-sync pre-commit run --files` over every changed file passed, including the whole-tree TUI formatting hook. - `UV_NO_SYNC=1 make check-commits check-large-files check-source-language PYTHON_VERSION=3.13.13 COMMIT_RANGE=origin/main...HEAD` passed, including commitlint and the source-language carrier rules. - Real Chromium on the offline page, driving the production history renderer: long output is bounded at 500px in a 1440x1000 viewport and 422px in 780x844 and 390x844 viewports. End reaches the last line, the cut notice is outside Markdown, and collapse works. No page errors. The page's existing 780px minimum width still limits its narrow layout. - Patch preparation included failing regressions before each fix and four rejected single-construct truncation mutants. - Native Windows validation and a fresh Python suite on the follow-up base have since been run, on the branch rebased onto `e8332e5d`: `pytest tests/test_subagent_manager.py tests/test_subagent_dag_runner.py tests/test_rpc_spine.py tests/test_rpc_schema_match.py tests/test_spine_events.py tests/test_spine_turn.py tests/test_rpc_turn_cancel.py tests/test_playbook_stint.py` -> the same 11 failures as `origin/main` on the same files, 0 introduced, all of them this Windows account's (a `~/.raven/oauth` permission refusal, symlink privilege, `os.killpg`, and the POSIX path rules #1066 fixes). The full `ui-web` suite passed (254 files, 4,278 tests) and the full `ui-tui` suite passed in CI. `ruff check`, `ruff format --check`, `ty check`, `lint-imports`, `npm run gen:check`, `npm run type-check`, `npm run lint` (ui-web and ui-tui), `npm run lint:rpc`, `npm run lint:i18n` and the repo's commit, large-file and source-language gates all pass on the rebased branch. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk The truncation flag is additive. Older clients still display the output prefix but do not gain the separate notice. Previously stored records with an embedded suffix are preserved because the client cannot distinguish that suffix from node-authored text. The height cap changes scrolling for spawn and whole-graph results as well as node reports. Original run files retain the complete output. Delivered text follows the existing content policy; no dependency, migration or permission change is introduced. Rollback: revert this change. Previously stored marks may retain the optional field, which the older code ignores. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues #1063
|
Merge conflict: @LivXue this PR no longer merges cleanly into Measured just now with After a rebase onto current main I re-check what moved before the grade comes back. |
|
Blocking: resolve the renewed CHANGELOG.md conflict against current main. Confirmed independently after fetching main at dacfc64: |
On native Windows every reference into the working directory or the sub-agent history was refused as outside its roots: check_confined resolved it with posixpath and within tested for a "/" prefix, so a drive, a backslash and even a plain notes.md landed outside every root. - resolve_reference and within follow the host's path rules, the ones the read uses. Case is kept as realpath leaves it, so two directories that differ only in case stay two. - The lexical check on @nodes/, and on a reference checked without roots, refuses what escapes under POSIX or Windows rules on every host. On Windows, @nodes/..\..\x and @nodes/C:\...\x passed it and the read returned the file they named. - LocalFileBackend.abspath joins even an absolute reference to its base, so a rooted path without a drive is read on the drive the check resolved it against, under Python 3.12 as under 3.13. - The tests marked POSIX-only for this run everywhere again, and the Windows CI job runs the reference tests. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
d4b4da2 to
e396c4e
Compare
|
Rebased onto current main ( Re-checked at the new head:
The description's rebase base and verification numbers are updated to match. @0xKT @gloryfromca the renewed conflict is resolved at the new head; please re-check it when you get a chance. |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The rebase resolves the renewed CHANGELOG.md conflict and git merge-tree --write-tree HEAD github/main succeeds against current main at 4718421324ee. I compared the commit to the previously reviewed patch with git range-diff, read the complete resulting github/main...HEAD diff, and rechecked the affected callers, history, AGENTS.md/CLAUDE.md and CONTEXT-MAP.md rules, architecture boundaries, backward compatibility, and test assertions. Only the changelog insertion context changed; both sides of the conflict are retained.
Verification on Python 3.12: the relevant four-file pytest selection passed with 171 passed, 3 Windows-only skipped, and 745 deselected. The large-file and source-language gates plus git diff --check passed, and the worktree is clean.
|
Windows compatibility re-review — PR #1066 @ Scope: full base Result: the host-path-rule fix is correct on native Windows and removes the inherited ref-path outage; no new Windows issue found. This is also the fix that unblocks the seven new path-gate tests in PR #1118. What I checked:
Verification (native Windows 11, CPython 3.12.13, isolated worktree All five remaining failures are in the base failing set ( Limitations: symlink-based cases skip on this host (no privilege); a live reference to a UNC share inside a root was not exercised; no full DAG run through a remote backend. |
… the docs (#1064) ## Summary Items 1 to 6 of #1008, the follow-ups the review of #1001 left out. Item 7, file references on Windows, is handled separately. 1. **A decision node's route is no longer the first thing cut on a tasks-board card.** The agent's name kept its full width (`min-width: auto`) and the tool-count chip never shrinks, so the selection took all of the line's overflow: with "Raven-Research" and "3 tools" a 196px card showed only the arrow. The selection now renders as the arrow with its first route, which keeps its width, then the routes after it. Those, and the instance handle, start from nothing and grow into the room the line has left (`flex: 1 1 0; max-width: max-content`), and the agent's name gives way after them, with an ellipsis. The later routes keep room for a letter and an ellipsis: narrower, a line's first character is clipped rather than replaced by one. The 6px spacing separates fields only: the two selection spans have no additional gap, and the comma-space separator stays with the first route, outside the text that can be cut. A card with no selection lays out as before, a long minted handle included. 2. **Route conditions are filled and validated for params.** The executor filled `${params.X}` only in `promptTemplate`, and `validate_graph_nodes` checked only `promptTemplate` and `inputs`, so "the deal is above ${params.threshold}" reached the judge as written and `${params.nobody}` passed validation. Conditions now go through the same `_fill_text` as the prompt, a secret withheld with the same log line, naming the route, and a reference to no declared param is an error. The filling and the check are on `main` now, carried by #1058; this branch holds the executor's docstring and the tests that pin both. 3. **Hand-written route names `yes`, `no`, `on`, `off`, `true`, `false` and bare numbers load.** The store parsed the spec block with `yaml.safe_load`; YAML 1.1 read those keys as booleans or integers, validation refused them, and the runtime dropped the playbook from the library. Every mapping is now keyed by a name, and the value keeps its YAML type. The loader is the one #1057 drafted, so the store carries one key loader rather than two: a node that is not a mapping is left to PyYAML to answer, which keeps the error the validate paths catch, and a key node is retagged through a copy, so an anchor that shares one scalar between a key and a value leaves the value's type alone. #1057 is closed as superseded by this branch. 4. **The generator's dag-mode instructions describe branches.** That section called `dependsOn` the whole graph language and never named `routes`, `select` or `when`; it now describes a decision node and the rule that a node depending on one names it in its `when`, and says a condition may use a param. A new test reads that section on its own. 5. **An ordinary node's judge request is held to a literal golden.** `test_judge_without_routes_sends_the_old_request_verbatim` compared the request with the module's own constants, so changing the instruction or a tool schema's description passed. It now compares the whole request -- messages, tools, model, tool choice -- with the fence nonce fixed, against `tests/data/dag_verdict_plain_request.json`. The golden is the request from before #1001: the tree before it and this one send byte-identical requests over 32 input combinations. 6. **Domain terms around branches.** `CONTEXT.md` gains three `_Avoid_` notes: "decision" alone for a decision node or for what it chose, since the word already names the main agent's answer to an exception report in a DAG run and a choice the host puts to the user; "skipped" for a node a branch left out; and "branch" for any other part of a graph. The uses those notes retire are rewritten in the files this pull request touches: the DAG orchestration skill (the `abandon` paragraphs and the conditional-branch section), the orchestration and playbook pages in both languages, a runner docstring, and test docstrings, one test helper and one test name; two ui-tui test comments that called a path through a drawn graph a branch now say path and edge. The playbook page's "not a runtime branching language" had read, since #1001, as saying a prompt-mode playbook cannot branch; it now says the guidance decides nothing while the graph runs, and a branch is chosen by a decision node in the graph it composes. Left as they are, outside the files this pull request touches: "decision" alone still names a decision node or its selection in text a model reads -- the `routes` description in the `run_subagent_dag` schema and on `DagNodeSpec`, which the playbook generator's tool carries; the guard-size error in `raven/agent/subagent/dag_guard.py`; the note `raven/agent/subagent/dag_render.py` writes into a sub-agent's prompt; the workflow compiler's prompt -- and in the RPC model's field description and both generated clients, the "Decision missing" reason in `i18n/messages.json`, the DAG Panel entry of `ui-tui/CONTEXT.md` (which `test_the_tui_glossary_says_what_the_turned_in_line_carries` quotes), and docstrings, comments and test names across the DAG modules and both UIs. Retiring those reaches the RPC schema, the generated clients, a translated label and pinned tests, so it is left to #1065. Python test function names, which predate the note, were left alone in these files too. Two things the review of this pull request left open are closed here: - **The card's layout test now lives where a real-environment test belongs.** `tests/test_web_task_card_route_real_browser.py` measured a card in a real Chromium, so it moved to `tests/integration/test_web_task_card_real_browser.py`, and a new `web-browser` CI job runs it on a runner that ships a system Chrome. It is the only place the tasks board card's agent line is measured: happy-dom lays nothing out, so the stylesheet gate beside it can only read the declarations. Without a browser it skips with the reason, except under `CI`, where a skip would leave the line unmeasured on every push and so fails instead. - **The store's loader answers a node that is not a mapping the way PyYAML does.** `raven playbook validate` now answers a YAML syntax error in a spec block as a finding, the way the RPC validator already did, instead of a traceback. The branch is rebased onto `main` at `471842132` (#1114). The `CHANGELOG.md` conflict that rebasing raised is resolved by keeping both sides' entries, and the branch merges clean. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed Native Windows 11, CPython 3.12.13: - `uv run --frozen --no-sync pytest tests/test_playbook_store.py tests/test_playbook_executor.py tests/test_playbook_validate.py tests/test_subagent_dag_verdict.py tests/test_subagent_dag_core.py tests/test_cli_playbook_commands.py tests/integration/test_web_task_card_real_browser.py -m "integration or not integration" -q -p no:randomly --tb=short`: 770 passed, 11 failed, 15 skipped. The same 11 fail at the merge base on this host, with the same IDs: `test_playbook_executor.py::test_a_playbook_run_s_node_attempts_nest_under_the_call_that_ran_it`, `test_playbook_validate.py::test_path_confinement_waits_for_dispatch_roots`, `test_subagent_dag_core.py::test_replanning_refuses_a_redeclared_id`, `test_subagent_dag_core.py::test_a_refused_submission_still_records_the_link_as_unstarted`, the five `test_subagent_dag_core.py::test_a_reference_landing_in_either_root_is_accepted` parameters, and `test_cli_playbook_commands.py::test_run_judges_its_nodes_so_a_branching_playbook_takes_its_branch` and `::test_run_judges_on_the_users_own_subagent_dag_settings`. The first group is the file-reference rules #1066 fixes, and the last two shell out to `cat`. The branch adds the passing cases. - The baseline comparison above, run over the same six files at the merge base: 746 passed, 11 failed, 15 skipped -- the same failing IDs. - `uv run --frozen --no-sync pytest tests/integration/test_web_task_card_real_browser.py -m integration -q -n 0 -p no:randomly --tb=short`: 10 passed, the board's real 196px card measured in a real Chromium. Playwright could not create its artifacts directory under this host's default temp path, so the run needed a writable `TEMP`; with the default one the nine browser cases skip and the path-lookup case still passes. - After the rebase onto `main` at `471842132`, with `-n 0`: the six playbook/DAG files (`tests/test_playbook_store.py`, `tests/test_playbook_executor.py`, `tests/test_playbook_validate.py`, `tests/test_subagent_dag_verdict.py`, `tests/test_subagent_dag_core.py`, `tests/test_cli_playbook_commands.py`): 790 passed; `tests/integration/test_web_task_card_real_browser.py -m integration`: 10 passed; `scripts/check_commit_messages.py`, `scripts/check_source_language.py` and `scripts/check_large_files.py` over `origin/main..HEAD`, and `git diff --check`: passed. - `uv run --frozen --no-sync ruff check raven/playbook/store.py raven/cli/playbook_commands.py tests/test_playbook_store.py tests/test_cli_playbook_commands.py tests/integration/test_web_task_card_real_browser.py` and `ruff format --check` over the same files: passed. - `uv run --frozen --no-sync ty check raven/playbook/store.py raven/cli/playbook_commands.py`: passed. - `uv run --frozen --no-sync lint-imports`: 10 kept, 0 broken. - `uv run --frozen --no-sync --extra dev python scripts/check_commit_messages.py origin/main..HEAD`, `scripts/check_source_language.py origin/main..HEAD` and `scripts/check_large_files.py origin/main..HEAD`: passed. - `git merge-tree` against `origin/main`: no conflict, through `--write-tree` on the Windows host and through the three-argument form on the Linux workstation, whose git predates `--write-tree`. Before the rebase the same check reported one conflict, in `CHANGELOG.md`. ui-web, run in `ui-web/`: - `node node_modules/vitest/vitest.mjs run src/features/tasks/BoardCard.test.tsx scripts/gates/task-card-selection-css.test.mjs --no-file-parallelism`: 2 files, 36 tests passed. - `node node_modules/vitest/vitest.mjs run src/features/tasks scripts/gates --no-file-parallelism --testTimeout=60000`: 62 files, 560 tests passed. The same command over `main` at the time of the run reported 61 files and 552 tests passed; the branch adds the stylesheet gate and the cases beside it. Without the longer timeout two of the gates time out on a loaded host; they pass on their own and the files they read are byte-identical to `main`. At head `211d226d`, the branch's CI run: 23 checks passed, 2 skipped (`claude`, `docs deploy`), 0 failed. The rebased head re-runs the same workflow, including the `web layout in a real browser` job, which runs the browser cases on a runner that ships a system Chrome. Not run here: macOS, a browser other than Chromium, and the full repository suite. ## Risk A stored playbook whose route condition names an undeclared param now fails validation, and so leaves the library, as one doing the same in `promptTemplate` already did. On the board, a crowded agent line now shortens the agent's name where it used to cut the selection. The store's loader now reads the frontmatter through the same path as the spec block; a frontmatter key that YAML 1.1 would have resolved to a boolean or a number is now read as the text it was written as, which the frontmatter's two string fields never were. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues #1008 (items 1 to 6)
Summary
On native Windows every file reference a DAG node or a spawn made into the session working directory or its sub-agent history was refused as outside them.
check_confinedresolved a reference withposixpathand compared it with each root throughwithin, which tested for a/prefix, so a drive path, a backslash, and even a relativenotes.mdlanded outside every root. This is item 7 of #1008.resolve_referenceresolves by the host's path rules, the ones the read uses, andwithincompares with the host's separators. It keeps case asrealpathleaves it, deliberately: on Windowsrealpathalready gives every existing component its on-disk case, so folding would change the answer only for a path that is not there to read, and would take two directories differing only in case, which a case-sensitive Windows directory can hold, for one.@nodes/, and on a reference checked without roots, now refuses what escapes under POSIX or Windows rules, on every host. On Windows@nodes/..\..\xand@nodes/C:\...\xpassed it and the read returned the file they named: a probe read a file outside the node root through both spellings, as arefand as a file input, 4 of 4 on the base and on the 0.2.0 and 0.2.5 release code; on this branch it reads none. This is GHSA-8mqc-c629-9qc8.LocalFileBackend.abspathjoins a reference to its base even when the reference is absolute, whichos.path.joinkeeps whole. Python 3.12'sntpath.isabscalls\notes.mdabsolute, so the read opened it on the process's current drive while the check had resolved it against the working directory's drive. 3.13 no longer calls it absolute.CONTEXT.md's Reference roots entry says which path rules apply and why case is not folded;CHANGELOG.mdhas the entry.CI runs on this public pull request, including the new Windows file-reference step. The Verification below records local runs.
Type
Verification
Native Windows 11, CPython 3.12.13, the seven files that reach references (
tests/test_subagent_dag_core.py tests/test_subagent_dag_guard.py tests/test_subagent_dag_runner.py tests/test_subagent_dag_verdict.py tests/test_playbook_executor.py tests/test_playbook_validate.py tests/test_subagent_prompt_template.py -n 8): base 26 failed / 1076 passed / 17 skipped, this branch 4 failed / 1126 passed / 5 skipped. By failing ID: 22 fixed, 0 introduced. The 4 left fail the same way on the base:TestBackgroundRun's two process-group tests (os.killpg), and two replan tests whose fixture splits a native path on/. The 5 skips are symlink tests this account cannot create a link for. (The branch figure re-measured after the rebase onto themainof thatdate: on
e8332e5d, the same five files gave 4 failed / 1,029passed / 5 skipped on the branch against 25 failed / 1,022 passed /
17 skipped on
origin/main, the same 4 failing IDs on both sides(
os.killpg, the two process-group reaping tests and the two replantests whose fixture splits a native path on
/), so the branch fixed21 and introduced none; the 5 skips there are the symlink tests this
account cannot create a link for. Rebased again onto
471842132, thesame five files under CPython 3.12 on Linux give 1,043 passed / 3
skipped on the branch against 1,030 passed on
origin/main, nofailing ID on either side, so nothing is introduced; the 3 skips are
the Windows-only reference cases a Linux host cannot exercise.)
The new CI step's command, run under CPython 3.13.12 with only the
devextra and Git'susr\binfirst on PATH: 168 passed, 5 skipped (the same symlink tests; the runner can create links).Linux, Docker, CPython 3.12, full suite, rebased on the current
main: base 23 failed / 34188 passed / 247 skipped, this branch 23 failed / 34201 passed / 250 skipped, identical failing IDs (container artifacts: process trees in a container without an init process, and root reading a file it was denied).Each part of the fix was reverted on its own and the new CI step's selection run. On Windows, under 3.12 and 3.13 alike:
withinback toposixpath37 failed, its child's separators left unnormalised 2, case folded 1,resolve_referenceback toposixpath18, the shape check on POSIX rules alone 12,realpathdropped from the physical path 12. The shape check on the host's rules alone survives there, sincentpathalready reads/, and fails 10 on Linux, as do the POSIX-only shape check and the droppedrealpath. Thewithinandresolve_referencereverts survive on Linux, whereos.pathisposixpath. Revertingabspathfails 1 under 3.12 and survives under 3.13, where the old code already joined\notes.md: it is the one revert neither the Windows step, which runs 3.13, nor the Linux shards catch.Diff coverage:
scripts/coverage_gate.py diff --base-ref origin/mainover a Linux coverage run of the four files the new CI step selects from (--cov=raven --cov-branch, thencoverage json): 100.00% (11/11 changed executable lines), as a Windows run of the same four gave. No changed line depends on the platform, so the Linux shards reach every one.ruff check,ruff format --checkandty checkon the changed files;lint-imports: 10 kept, 0 broken.Not run: macOS.
Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
On a POSIX host, a
@nodes/reference holding a backslash..or a drive, and a reference checked without roots that holds one, is now refused; no node artifact's name holds either. On Windows, references that were all refused now resolve, inside the same roots.Related Issues
#1008 (item 7), GHSA-8mqc-c629-9qc8