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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions