Skip to content

Schema extensions replace same-name types instead of merging; stpa-yaml shorthand isn't expanded into link graph #154

@avrabe

Description

@avrabe

Two distinct but related rivet issues observed while analyzing the spar project (pulseengine/spar). Both block users from resolving schema-drift / link-cardinality errors without an ecosystem-wide fix.

1. extends: on a schema replaces existing type definitions instead of merging

Observed

Project-local schema under schemas/ declaring an artifact type whose name matches a type already declared in a parent schema replaces the parent's definition. All fields from the parent schema vanish.

Reproducer

Spar already ships schemas/sysml2.yaml which extends common but only adds new types (sysml-requirement, sysml-component, …) — this works fine.

Adding schemas/spar-extensions.yaml that tries to add a single field to feature:

schema:
  name: spar-extensions
  version: "0.1.0"
  extends: [common, stpa]

artifact-types:
  - name: feature
    fields:
      - name: method
        type: string
        required: false

Then registering it in rivet.yaml under project.schemas.

Effect:

$ rivet schema show feature
Type: feature
Description: Spar extension of the common 'feature' type with test-evidence fields

Fields:
  method                   string     optional

# Gone: phase, acceptance-criteria, baseline
# Gone: satisfies, implements link-fields

The built-in common fields and link-fields are lost. rivet validate errors jumped from 625 → 646 (the original drift errors remained, plus new ones as previously-declared fields became undeclared).

Expected

extends: [common] combined with redeclaring a type should merge — that is, keep the parent's fields + link-fields and union with the new declaration (project-local fields take precedence on conflict). The sysml2 schema's use of extends: to add entirely new types works; the merge case is the missing mode.

Why it matters

Without merge-on-extends, any project that has authoring conventions not covered by the built-in schemas (e.g., feature.method, requirement.mitigates, requirement.traces-to, design-decision.interfaces — see #2 below) has three equally unpleasant options:

  1. Migrate every artifact to drop the undeclared fields (lose semantic content).
  2. Live with INFO: field X is not defined in schema for type Y noise forever.
  3. Fork rivet's schemas.

Spar took option 2 (981 INFOs in rivet validate) because 1 and 3 both have outsized costs.

2. stpa-yaml source format doesn't expand shorthand into the link graph

Observed

Artifacts authored in the shorthand form declared by the stpa schema's link-field names — e.g.:

- id: UCA-1
  type: uca
  controller: CTRL-PARSER
  hazards: [H-5]

are not resolved into entries of the artifact's links: list during parsing. The schema declares these link-fields correctly:

$ rivet schema show uca
Link fields:
  controller               issued-by -> [controller]  required  exactly-one
  hazards                  leads-to-hazard -> [hazard, sub-hazard]  required  one-or-many

but validation fails with:

ERROR: [UCA-4] link 'issued-by' requires exactly 1 target, found 0
ERROR: [UCA-4] link 'leads-to-hazard' requires at least 1 target, found 0

Meanwhile rivet validate reports 0 broken cross-refs — confirming the targets do resolve to existing artifacts; they just aren't counted as links of their declared type.

Scale (in spar)

  • 625 link-cardinality ERRORs (217 artifacts, most with 2-3 missing links each) across:
    • UCA-* (57) lacking issued-by / leads-to-hazard
    • CC-* (47) lacking constrains-controller / inverts-uca / prevents
    • LS-* (36) lacking leads-to-hazard
    • H-* (32) lacking leads-to-loss
    • SC-* (24) lacking prevents
    • CA-* (21) lacking issued-by / acts-on
  • 981 INFO "field not defined" messages for the same shorthand fields the schema already has declared as link-fields (see Add ASPICE 4.0, ReqIF, OSLC, WASM, and CI pipelines #1).

Expected

When a source-format: stpa-yaml file contains a field whose name matches a declared link-field of the artifact's type, rivet should synthesize a canonical links: entry: { type: <declared-link-type>, target: <field-value> }. This would:

  • Make rivet list --format json report accurate links counts (currently zero for these artifacts, giving a false "172 orphaned artifacts" signal when nothing is actually orphaned).
  • Eliminate the 625 cardinality ERRORs in projects that use the idiomatic STPA shorthand.
  • Remove the 981 "field not defined" INFOs for names that are in fact declared link-field shorthands.

The generic-yaml format wouldn't need this — it'd be specific to stpa-yaml since stpa is where the shorthand convention lives.

Alternative

If the shorthand isn't the intended authoring model, that's worth documenting and providing a migration tool. Every STPA-adjacent project I've seen (including the spar STPA analysis and the built-in rivet examples) writes the shorthand.

Concrete evidence

Spar repository: https://github.com/pulseengine/spar

$ rivet validate
...
Result: FAIL (625 errors, 174 warnings, 0 broken cross-refs)

Full analysis filed as memory notes during spar's April 2026 issue-batch PR (pulseengine/spar#130 — unrelated fixes, mentions rivet drift in its "Not in this PR" section).

Suggested priority

#1 (merge-on-extends) is the more impactful fix: it unblocks any downstream project from silencing drift INFOs locally without upstream changes.

#2 (stpa-yaml shorthand expansion) could even be worked around downstream once #1 is resolved — if schemas can be locally extended to re-declare the link-fields in a way the validator honors.

🤖 Generated with Claude Code

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