Summary
The stpa-yaml source parser silently truncates a scalar field value at the first
comma, keeping only the leading element. The same bytes parsed as generic-yaml
round-trip intact. Discovered while removing a double-load in a consumer repo
(pulseengine/spar#360) — the format change was believed lossless and wasn't.
Affects rivet 0.28.0 (schemas: stpa@0.1.0 (embedded)).
Reproduction
safety/stpa/validation.yaml, artifact VAL-010, on disk:
- id: VAL-010
type: feature
fields:
method: unit-test
test-location: crates/spar-hir-def/src/property_check.rs
test-name: tests::boolean_matches_aadl_boolean, tests::boolean_mismatches_aadl_string
A/B on identical bytes, only rivet.yaml's format: differs:
sources: entry |
rivet get VAL-010 --format json | jq -r '.fields["test-name"]' |
format: generic-yaml |
tests::boolean_matches_aadl_boolean, tests::boolean_mismatches_aadl_string |
format: stpa-yaml |
tests::boolean_matches_aadl_boolean |
Five artifacts affected in that repo — exactly the five whose on-disk test-name
contains a comma (VAL-010, VAL-011, VAL-014, VAL-015, VAL-021). Every comma-free
test-name in the same file is preserved. No artifact has two commas, so I can't
distinguish "keeps [0] of a split" from "truncates at first comma", but the observed
behaviour is consistent with both.
$ grep -c 'test-name:.*,' safety/stpa/validation.yaml
5 # == the number of artifacts that lose data
Why this is more than a cosmetic loss
test-name is evidence. It's the pointer from a verification artifact to the tests
that discharge it. Dropping the second name means a traceability audit sees one test
where two were claimed, and — worse — it renders identically to an artifact that
only ever claimed one test. There is no diagnostic: rivet validate is silent, and
the value is a well-formed string either way.
This is the shape that keeps biting: an operation that discards data must render
differently from one that doesn't.
Note on how it was found (relevant to the diagnosis)
rivet list --format json without --full emits only the summary view —
--help states it omits description, tags, and fields. An equivalence check
run on that view compares byte-identical output while field-level content diverges
underneath. That's not a rivet bug, but it is a sharp edge: the natural
"did my config change alter anything?" command is blind to exactly the class of
change a format: switch produces. A note in list --help — or having --format json default to the full view — would have surfaced this immediately.
Expected
Either preserve the scalar verbatim (generic-yaml behaviour), or — if
comma-splitting into list<string> is intended for this field — emit all elements
and declare the field's type as a list so consumers can see the shape. Silently
keeping one of N is the only outcome that's wrong under both readings.
Summary
The
stpa-yamlsource parser silently truncates a scalar field value at the firstcomma, keeping only the leading element. The same bytes parsed as
generic-yamlround-trip intact. Discovered while removing a double-load in a consumer repo
(pulseengine/spar#360) — the format change was believed lossless and wasn't.
Affects rivet 0.28.0 (
schemas: stpa@0.1.0 (embedded)).Reproduction
safety/stpa/validation.yaml, artifactVAL-010, on disk:A/B on identical bytes, only
rivet.yaml'sformat:differs:sources:entryrivet get VAL-010 --format json | jq -r '.fields["test-name"]'format: generic-yamltests::boolean_matches_aadl_boolean, tests::boolean_mismatches_aadl_stringformat: stpa-yamltests::boolean_matches_aadl_booleanFive artifacts affected in that repo — exactly the five whose on-disk
test-namecontains a comma (VAL-010, VAL-011, VAL-014, VAL-015, VAL-021). Every comma-free
test-namein the same file is preserved. No artifact has two commas, so I can'tdistinguish "keeps [0] of a split" from "truncates at first comma", but the observed
behaviour is consistent with both.
Why this is more than a cosmetic loss
test-nameis evidence. It's the pointer from a verification artifact to the teststhat discharge it. Dropping the second name means a traceability audit sees one test
where two were claimed, and — worse — it renders identically to an artifact that
only ever claimed one test. There is no diagnostic:
rivet validateis silent, andthe value is a well-formed string either way.
This is the shape that keeps biting: an operation that discards data must render
differently from one that doesn't.
Note on how it was found (relevant to the diagnosis)
rivet list --format jsonwithout--fullemits only the summary view —--helpstates it omitsdescription,tags, andfields. An equivalence checkrun on that view compares byte-identical output while field-level content diverges
underneath. That's not a rivet bug, but it is a sharp edge: the natural
"did my config change alter anything?" command is blind to exactly the class of
change a
format:switch produces. A note inlist --help— or having--format jsondefault to the full view — would have surfaced this immediately.Expected
Either preserve the scalar verbatim (
generic-yamlbehaviour), or — ifcomma-splitting into
list<string>is intended for this field — emit all elementsand declare the field's type as a list so consumers can see the shape. Silently
keeping one of N is the only outcome that's wrong under both readings.