fix(yaml-hir): preserve plain-scalar field values that contain commas (#747) - #749
fix(yaml-hir): preserve plain-scalar field values that contain commas (#747)#749avrabe wants to merge 1 commit into
Conversation
…#747) The CST lexer breaks plain scalars at `,`, `]`, `}` (see `lex_plain_scalar` in yaml_cst.rs) so a value like test-name: tests::a, tests::b lands in the Value node as three sibling tokens (`tests::a` / `, ` / `tests::b`). `scalar_text` already handles this via `next_sibling_or_token`, but `node_to_yaml_value` — the path taken for `fields.*` and unknown top-level keys — read only the first token and returned `"tests::a"`. The trailing evidence was silently discarded and rendered identically to a value that had only ever claimed one test. Route the plain-scalar branch through `scalar_text` so the sibling tokens the lexer split are reassembled before type inference. Quoted scalars are unaffected (they arrive as a single token). Two regression tests cover the `fields.*` path and the unknown-top-level-key fallthrough that share this code. Fixes: REQ-028 Refs: #747
📐 Rivet artifact deltaNo artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph. |
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark 'Rivet Criterion Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.
| Benchmark suite | Current: a22db62 | Previous: 3d03ee3 | Ratio |
|---|---|---|---|
validate/10000 |
1433211822 ns/iter (± 16151187) |
1053140348 ns/iter (± 17884749) |
1.36 |
traceability_matrix/1000 |
62438 ns/iter (± 849) |
44614 ns/iter (± 201) |
1.40 |
diff/10000 |
11918925 ns/iter (± 1730861) |
9171430 ns/iter (± 402956) |
1.30 |
query/10000 |
341921 ns/iter (± 3812) |
226741 ns/iter (± 1748) |
1.51 |
This comment was automatically generated by workflow using github-action-benchmark.
|
The perf-alert is picking up something real on the runner, but it can't be caused by this diff. Reasoning: The four benchmarks that regressed —
The variance the alert reports ( The other CI gates from my end are:
Generated by Claude Code |
|
Kani check failed on runner shutdown, not on a proof result. Tail of the job log: Exit 143 is SIGTERM — the self-hosted runner disappeared while Nothing to fix in the patch. A re-run should be green (or fail at the actual codegen step, which would then be a real signal). Same offer as on the bench alert: happy to push a no-op commit to trigger the rerun, or wait if it's easier from your side. Generated by Claude Code |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Closes #747.
Root cause
The CST lexer in
yaml_cst.rs::lex_plain_scalarintentionally breaksplain scalars at
,,],}(line 401). So a value likelands in the
Valuenode as three sibling tokens:PlainScalar("tests::boolean_matches_aadl_boolean")/,+whitespace /
PlainScalar("tests::boolean_mismatches_aadl_string").scalar_text()already handles this — it walksnext_sibling_or_token()until the nextNewline/Comment. Butnode_to_yaml_value()— the path taken for thefields.*mappingand for unknown top-level keys — read only the first plain-scalar
token via
descendants_with_tokens()and returned it verbatim. Thetail of the value was silently discarded, and the truncated result
rendered identically to a value that had only ever claimed one
element. Every comma-containing
test-nameinsparlost itssecond entry;
stpa-yamlnever disagreed withgeneric-yamlon anyscalar that happened not to contain a comma, which is why it went
unnoticed.
Fix
In
node_to_yaml_value's plain-scalar branch, reassemble the valuevia
scalar_text(value_node)before running type inference throughplain_scalar_to_value. Quoted scalars are unaffected — they arriveas a single token and are still routed through
scalar_to_yaml_valueunchanged.The parser's inline-scalar branch already bumps every token up to
the newline as a direct child of
Value(seeparse_mapping_entryin
yaml_cst.rs), soscalar_text's sibling walk sees the wholerun.
Acceptance criteria (from the issue's "Expected" section)
generic-yamlbehaviour).A comma-containing plain scalar round-trips through
stpa-yamlunchanged. Reproducer from the issue —test-name: tests::first, tests::second— now returns"tests::first, tests::second"instead of"tests::first"(regression test
schema_driven_preserves_field_value_with_commas).fields.*path and the unknown-top-level-keyfallthrough (both delegate to
node_to_yaml_value). Both arecovered by regression tests
(
schema_driven_preserves_field_value_with_commas,schema_driven_preserves_unknown_key_value_with_commas), sothe two paths can't regress independently.
The alternative reading in the issue — "if comma-splitting into
listis intended for this field, emit all elements and declarethe field's type as a list" — is not adopted: the schema-driven
parser has no way to know a
dev-schema custom field liketest-nameshould be list-typed, and doing so would break everyexisting scalar consumer. Preserving the scalar matches
generic-yaml, matches the on-disk YAML, and is the only outcomethat's right under both readings the issue proposes.
Test plan
cargo test -p rivet-core --lib yaml_hir::tests::schema_driven_preserves— 2/2 new tests pass
cargo test -p rivet-core --lib— 1184/1184 pass (no regressions)cargo test -p rivet-core --tests— integration + proptest + YAMLtest-suite (83 in the last block) pass
cargo fmt --all --checkcleancargo clippy -p rivet-core --lib -- -D warningsclean (onlythe pre-existing MSRV note from
clippy.toml)cargo run -p rivet-cli --release -- validate— PASS (592warnings; no schema/artifact changes in this PR)
Follow-ups (out of scope)
The issue's "Note on how it was found" mentions that
rivet list --format json(without--full) omitsfields, so anA/B on that output missed this class of change. That's a real sharp
edge but it's a separate CLI-output design decision — worth its own
issue if you want it tracked.
Generated by Claude Code