Skip to content

fix(playbook): read yaml mapping keys as the text they were written as - #1057

Closed
yudongyouqing wants to merge 3 commits into
EverMind-AI:mainfrom
yudongyouqing:fix/playbook_yaml_route_keys
Closed

yudongyouqing wants to merge 3 commits into
EverMind-AI:mainfrom
yudongyouqing:fix/playbook_yaml_route_keys

Conversation

@yudongyouqing

@yudongyouqing yudongyouqing commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • YAML 1.1 resolves yes, no, on, off, true, false and bare digits to booleans and numbers, so a hand-written playbook whose decision named its routes that way arrived at the spec with non-string keys (Input should be a valid string), and the runtime's answer to a load error dropped the whole file from the library with only a log line -- item 3 of DAG conditional branches: seven follow-ups left out of #1001 #1008.
  • The store's reads now use a SafeLoader subclass that keeps mapping keys in their written form -- the same shape the store's own writer emits by quoting them, so a hand-written file and the file the store writes back meet the same spec. Values are untouched, so confirm: yes still means true. The retag covers every scalar key of a non-string tag (a bare date was still a datetime key on the first revision) and goes through a copy, so an anchor sharing one scalar between a key and a value keeps the value's type; the mapping itself is built by PyYAML's own loop, whose ConstructorError for an unhashable key -- the unquoted {{ params.X }} placeholder among them -- is the yaml.YAMLError the validators answer with. A spec block or frontmatter that parses to a non-mapping answers a ValueError, and the CLI validate command catches yaml.YAMLError like its RPC twin always did.
  • Scoped to the store's two reads (frontmatter and spec block); nothing else that parses YAML changes.

Type

  • Fix

Verification

  • Relevant tests pass locally: uv run pytest tests/test_playbook_store.py tests/test_playbook_validate.py tests/test_unified_playbook.py tests/test_playbook_executor.py tests/test_cli_playbook_commands.py -q -> 412 passed
  • New test failed first: the hand-written yes/no-routes playbook raised pydantic's Input should be a valid string on the base; on this head it loads with both routes and the when: triage.yes guard intact
  • Relevant lint / type checks pass locally: ruff check and ruff format --check on both files, make lint-types -> clean; the two S506 findings carry the repo's noqa-with-reason form (the loader is a SafeLoader subclass that only changes key handling, no constructors added)
  • User-facing docs or screenshots are updated when needed: the symptom was a dropped playbook, not copy; nothing to document beyond this change

Risk

  • Security impact considered: the loader removes no safety -- SafeLoader plus key stringification, nothing else; the S506 band is a name check answered inline
  • Backward compatibility considered: keys that already loaded as strings are unchanged; no field of the playbook schema is keyed by a boolean or number
  • Rollback path is clear: plain revert restores YAML 1.1 key coercion

Related Issues

Item 3 of #1008

@LivXue
LivXue requested review from 0xKT and gloryfromca October 22, 2026 06:58

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

Reviewed the full github/main...HEAD diff and the surrounding store, playbook schema/validation, route consumers, writer, relevant history, #1008 contract, and Runtime context terminology. I also checked backward compatibility and safety: the loader remains a SafeLoader subclass, values retain normal YAML coercion, existing string keys and YAML merge keys retain their behavior, and downstream keyed fields require strings. The tests add coverage without weakening existing assertions, and the change follows the applicable AGENTS.md rules; it introduces no architecture, asset, dependency, naming, or source-language issue.

Verification: after uv sync --all-packages supplied the workspace plugins required by the shared test fixture, uv run pytest tests/test_playbook_store.py tests/test_playbook_validate.py tests/test_unified_playbook.py tests/test_playbook_executor.py tests/test_cli_playbook_commands.py -q passed (274 passed). uv run ruff check raven/playbook/store.py tests/test_playbook_store.py, uv run ruff format --check raven/playbook/store.py tests/test_playbook_store.py, and git diff --check github/main...HEAD also passed. A direct load probe confirmed all six YAML 1.1 boolean spellings plus a bare-digit route remain written strings while confirm: yes remains boolean true. The initial test attempt failed only during fixture setup because the fresh environment lacked raven_everos; no test body ran until the workspace sync, after which the exact suite was green.

Comment thread raven/playbook/store.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: the custom construct_mapping drops PyYAML's mapping checks, so playbooks.validate now raises on a file it promises to answer for -- including the unquoted {{ params.X }} placeholder.

_KeyAsStringLoader.construct_mapping (raven/playbook/store.py) rebuilds the mapping loop itself instead of delegating, so it loses BaseConstructor.construct_mapping's isinstance(key, Hashable) check (a ConstructorError, i.e. a yaml.YAMLError, with the line and column). An unhashable key now reaches mapping[key] = ... and raises a bare TypeError. The commonest way to write one by hand is the playbook's own placeholder left unquoted: promptTemplate: {{ params.topic }} is a YAML flow mapping whose key is a mapping.

playbooks_validate (raven/rpc/methods/playbooks.py) says it "Answers rather than raises", and its except (ValidationError, ValueError, yaml.YAMLError) was widened for exactly the hand-edited case ("Left out, the one method whose whole point is to answer with findings turned that case into an internal error with a traceback"). TypeError is not in that tuple, so the call ends in the dispatcher's generic handler as internal_error.

Measured by calling the real playbooks_validate on a hand-written dag playbook in a scratch library (HOME and RAVEN_HOME in scratch):

promptTemplate: main dcc019a this PR
{{ params.topic }} (unquoted) answers ok=False: "while constructing a mapping ... line 10, column 21" raises TypeError: unhashable type: 'dict'
'{{ params.topic }}' (control) answers ok=False (semantic finding) same

The paths that catch Exception (the runtime's library refresh, playbooks.list, raven playbook list) still skip the file, but now report "unhashable type: 'dict'" with no location. The # noqa: S506 ... only mapping keys change notes are no longer accurate either.

A direction I tested: keep PyYAML's own construction and only retag the keys -- in construct_mapping, for a MappingNode, call self.flatten_mapping(node) first, then set key_node.tag = "tag:yaml.org,2002:str" on each ScalarNode key whose tag is bool/int/float, then return super().construct_mapping(node, deep=deep). (Flattening first matters: without it a key that arrives through a <<: merge, e.g. <<: {on: 1}, still becomes True.) With that, yes/no/1/1.5 route keys load as strings, merged keys too, confirm: yes stays True, unhashable keys raise ConstructorError again, the validate case above answers as on main, and tests/test_playbook_store.py + tests/test_rpc_playbooks.py pass (159). A test with an unquoted placeholder, and one for an int/float key (only the bool path is tested now), would pin it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed on the delegation direction you tested, and one step broader: every scalar key of a non-string tag is retagged through a copy now, not a bool/int/float whitelist -- a route named 2024-01-01 was still arriving as a datetime key and being dropped. The copy, rather than an in-place retag, is because anchors can share one scalar between a key and a value: retagging the shared node turned the value into text or was eaten by the constructed-objects cache. The unquoted placeholder answers ConstructorError again (pinned by a test that fails with the bare TypeError on the previous head), the digit arm pins 007, and a self-review round in the same push answers a non-mapping spec block with a ValueError instead of a TypeError from the name assignment, and adds yaml.YAMLError to the CLI validate catches the RPC twin always had. Ten-form sweep of the loader (bool/date/null/merge/alias/placeholder/sequence-key/list-document/value-coercion/leading-zero) all behave; 412 across the playbook suites, ruff, format and the type gate clean.

YAML 1.1 resolves yes, no, on, off, true, false and bare digits to
booleans and numbers, so a hand-written playbook whose decision named
its routes that way arrived at the spec with non-string keys and the
runtime dropped the whole file from the library as unloadable. The
store's reads now use a SafeLoader subclass that keeps mapping keys in
their written form -- the same shape its own writer emits by quoting
them -- while values are untouched, so confirm: yes still means true.
The custom construct_mapping rebuilt the mapping loop, which dropped
PyYAML's own answer for an unhashable key: a ConstructorError with a
location, the yaml.YAMLError playbooks_validate catches to answer with a
finding. The commonest hand-written unhashable key is the placeholder
itself left unquoted (promptTemplate: {{ params.topic }} is a mapping
keyed by a mapping), and it escaped as a bare TypeError instead. The
loader now flattens, retags the bool/int/float scalar key nodes to
strings -- including one arriving through a <<: merge -- and hands the
mapping back to PyYAML's own loop, so yes/no/digit route names still
load as written, confirm: yes still means true, and an unquoted
placeholder answers yaml again. Pins the placeholder case and the
leading-zero digit key.
…mappings

Two escapes the key-retag left, and two older ones in the same contract.
The retag whitelist knew bool, int and float; a bare date is a timestamp
and ~ is null, so a route named 2024-01-01 still arrived as a datetime
key and the playbook was dropped as unloadable -- every scalar key of a
non-string tag is retagged now, not three tags. Retagging the node in
place also reached through anchors: one scalar shared between a key and
a value turned the value into text or was eaten by the constructed-
objects cache; the key is replaced with a retagged copy instead, so the
value keeps its type. A spec block that parses to a list, and a
frontmatter that does, answered a bare TypeError from the assignment
below; both are ValueErrors now, the family playbooks_validate turns
into findings -- as does yaml.YAMLError, which the CLI validate command
never caught and the RPC twin always did. The load is one _load_yaml
helper carrying its single suppression, and the comments that still
named safe_load follow the loader they describe.
@LivXue

LivXue commented Oct 22, 2026

Copy link
Copy Markdown
Member

No new Windows compatibility issue found in the inspected YAML-loading and CLI-validation changes.

Reviewed head 24dc9b7331ee905cb223c9094cb928895eef814f, base/merge base 4315993300e538ec954c79565cbc8585006c8053: complete four-file diff, SafeLoader subclass and scalar-key copies, UTF-8 load/save paths, CLI name/path validation, RPC error handling, tests and existing discussions.

Native Windows verification (Python 3.13.12, UTF-8 mode):

  • 44 store cases and 3 CLI validation cases passed, including written route keys, leading zeros/dates, shared scalar aliases, unquoted placeholders and YAML-error reporting. The loaded hand-written file fixture had 26 CRLF sequences and no bare LF, verified from its bytes.
  • 6 focused RPC validation cases passed (114 unrelated cases deselected).
  • The wider store/CLI/RPC run stopped at 64 passed, 1 failed in test_run_judges_its_nodes_so_a_branching_playbook_takes_its_branch: its unchanged fixture launches external cat, absent on this host (WinError 2). The same case fails at the exact base.
  • A second run focused on store/CLI validation and the RPC file stopped at 49 passed, 1 failed in test_get_answers_one_whole_spec. Its unchanged assertion expects the literal suffix competitor-scan/playbook.md, while native Windows returns backslashes. This also fails at the exact base. Neither failure is evidence of a regression from this PR; the passing changed-scope cases above are individual results, not a claim that those wider suites passed.

Code reading: the change retains PyYAML's own mapping construction/errors, copies scalar keys before retagging, and keeps values typed. It adds no shell command, executable discovery, filesystem operation or dependency. Limitations: the CLI checks use CliRunner, and full CLI/RPC/repository suites, live graph/model execution, UNC/non-ASCII directory and code-page combinations were not verified. This is a scoped Windows assessment, not a merge verdict.

Exact verification commands

Each run used an isolated detached head/base checkout, frozen dev dependencies, source imports pinned to that checkout, and isolated profile/Raven/OAuth state.

# native; exit 1; cwd C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\pr1057\head
uv run --frozen --no-sync pytest tests/test_playbook_store.py tests/test_cli_playbook_commands.py tests/test_rpc_playbooks.py -n 0 -x --tb=short --basetemp C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-native\pytest --junitxml C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-native\junit.xml

# focused; exit 1; cwd C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\pr1057\head
uv run --frozen --no-sync pytest tests/test_playbook_store.py tests/test_cli_playbook_commands.py::test_validate_accepts_a_good_file_by_path_and_by_name tests/test_cli_playbook_commands.py::test_validate_reports_errors_with_the_file_position tests/test_cli_playbook_commands.py::test_validate_answers_a_yaml_syntax_error_as_a_finding tests/test_rpc_playbooks.py -n 0 -x --tb=short --basetemp C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-focused\pytest --junitxml C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-focused\junit.xml

# rpc-validation; exit 0; cwd C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\pr1057\head
uv run --frozen --no-sync pytest tests/test_rpc_playbooks.py -k validate -n 0 -x --tb=short --basetemp C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-rpc-validation\pytest --junitxml C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-rpc-validation\junit.xml

# base-cat; exit 1; cwd C:/Users/livxu/AppData/Local/Temp/raven-windows-review/76440228003d458eb50cf2d4a3438c5e/base431599
uv run --frozen --no-sync pytest tests/test_cli_playbook_commands.py::test_run_judges_its_nodes_so_a_branching_playbook_takes_its_branch -n 0 -x --tb=short --basetemp C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-base-cat\pytest --junitxml C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-base-cat\junit.xml

# base-path; exit 1; cwd C:/Users/livxu/AppData/Local/Temp/raven-windows-review/76440228003d458eb50cf2d4a3438c5e/base431599
uv run --frozen --no-sync pytest tests/test_rpc_playbooks.py::test_get_answers_one_whole_spec -n 0 -x --tb=short --basetemp C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-base-path\pytest --junitxml C:\Users\livxu\AppData\Local\Temp\raven-windows-review\76440228003d458eb50cf2d4a3438c5e\checks\1057-base-path\junit.xml

@LivXue
LivXue requested review from 0xKT and gloryfromca October 23, 2026 07:54

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

Reviewed the complete current four-file diff and the changes since 60d8d5307eb8, including the existing constructor discussion. The new implementation delegates mapping construction back to PyYAML, preserves ConstructorError and source locations for unhashable keys, copies retagged scalar key nodes so aliased values retain their types, handles merged and timestamp/null-like keys, and keeps value coercion unchanged. The new mapping-shape checks and CLI yaml.YAMLError handling turn malformed hand-edited files into validation findings rather than internal exceptions.

I also covered the store/RPC/CLI callers, writer and schema constraints, relevant history, backward compatibility, SafeLoader boundary, tests for weakening, AGENTS.md and CONTEXT.md terminology, and architecture/dependency/asset constraints. No new issue remained after checking those paths.

Verification: after syncing all workspace packages, uv run --no-sync pytest tests/test_playbook_store.py tests/test_cli_playbook_commands.py tests/test_rpc_playbooks.py tests/test_playbook_validate.py tests/test_unified_playbook.py tests/test_playbook_executor.py -q passed (400 passed). Ruff check, Ruff format check, git diff --check github/main...HEAD, and direct probes for scalar keys, merges, aliases, and the unquoted-placeholder ConstructorError also passed. The type-gate wrapper was unavailable because make is not installed; invoking its underlying command directly could not build the unchanged optional datrie dependency because this environment lacks x86_64-linux-gnu-gcc, so no type-check result was produced.

@LivXue

LivXue commented Oct 24, 2026

Copy link
Copy Markdown
Member

Closing this in favour of #1064, which now carries the same loader.

The key-preserving loader drafted here landed in #1064 as well, so the store would otherwise end up with two of them. #1064 holds the loader this branch wrote, plus the two cases that keep it honest:

  • a node that is not a mapping is left to PyYAML, so the error the validate paths catch stays a yaml.YAMLError rather than a bare TypeError;
  • 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.

#1064 also wires the store's playbook validate command to answer a YAML syntax error as a finding rather than a traceback.

Nothing here is lost: the branch is open for reference, and its commits are readable in #1064's history.

@LivXue

LivXue commented Oct 24, 2026

Copy link
Copy Markdown
Member

Superseded by #1064, which carries the same key-preserving loader.

@LivXue LivXue closed this Oct 24, 2026
0xKT pushed a commit that referenced this pull request Oct 27, 2026
… 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants