Skip to content

stpa-yaml parser silently truncates comma-containing scalars at the first element (test-name evidence loss) #747

Description

@avrabe

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions