Skip to content

feat(supplier): Phase 2 — federation handshake + FederationProvenance (#288)#292

Merged
avrabe merged 3 commits into
mainfrom
feat/issue-288-supplier-phase-2
May 19, 2026
Merged

feat(supplier): Phase 2 — federation handshake + FederationProvenance (#288)#292
avrabe merged 3 commits into
mainfrom
feat/issue-288-supplier-phase-2

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 16, 2026

Closes #288.

Implements Phase 2 of the cross-org / supplier traceability track laid
out in docs/design/cross-org-supplier-traceability.md §6 (Phase 1
shipped in #286 / v0.10.0). The MVP described the boundary; this PR
makes the boundary crossable: structured cross-org link semantics, a
ReqIF backend for cited-source, a rivet supplier pull command,
and the cross-org provenance block that imports stamp.

Phase 2 scope coverage

1. derives-from-external link type with structured target — DONE

  • New Link.external: Option<ExternalLinkTarget> field. When YAML
    target: is a mapping (the cross-org *-external link types),
    Link.target mirrors the mapping's anchor: for graph navigation
    and the full {org, contract, doc-id, last-synced, sha256, anchor}
    payload flows into Link.external. Flat-string targets round-trip
    unchanged via a custom serde impl.
  • derives-from-external link type added in schemas/common.yaml
    with inverse derived-into-external.
  • yaml_hir.rs CST link extractor handles the structured shape by
    dedenting the value text and round-tripping through serde_yaml.
    Regression-guarded against the previous "silently mis-target at the
    first mapping key" behaviour.

Tests: model::tests::link_flat_target_yaml_roundtrip,
link_structured_target_yaml_parse,
link_structured_target_yaml_serialize_then_parse,
link_structured_target_requires_anchor,
yaml_hir::tests::links_extraction_structured_external_target.

2. cited-source: kind: reqif backend — DONE

  • CitedSourceKind::is_local_phase2() admits Reqif alongside File.
  • resolve_reqif_uri() handles reqif://, file://, and bare-path
    forms (HTTP(S) ReqIF endpoints remain Phase 3+).
  • check_cited_source for kind: reqif: read bytes, sha256, verify
    stamp → Match / Drift / MissingHash. Adds a ReqIF XML
    well-formedness check via reqif::parse_reqif so a malformed
    supplier delivery surfaces as FileError at validate time rather
    than poisoning the cache later.

Tests: cited_source::tests::check_cited_source_reqif_* (4 tests +
resolve_reqif_uri_handles_scheme_and_relative).

3. rivet supplier pull <anchor> for kind: file | reqif — DONE

  • New CLI subcommand: rivet supplier pull <anchor> [--format text|json]. Looks up the anchor by ID, validates type, parses
    cited-source, fetches local payload, cross-checks the stamped
    sha256 against wire bytes. Refuses to write a poisoned cache entry
    on drift (auditor must re-stamp).
  • For ReqIF, also runs parse_reqif before caching to gate malformed
    XML.
  • Writes payload + sibling <anchor>.manifest.yaml under
    .rivet/supplier-cache/<org>/<contract>/ (path components
    sanitised to alphanum + -_. so a contract typo can't escape the
    cache root).
  • Idempotent: a re-pull with identical bytes refreshes only the
    manifest's fetched-at; the JSON output reports
    bytes_unchanged: true.

Tests: cli_commands::supplier_pull_kind_file_writes_cache_and_manifest,
supplier_pull_refuses_on_sha256_drift,
supplier_pull_idempotent_on_re_run,
supplier_pull_kind_reqif_writes_reqif_extension,
supplier_pull_unknown_anchor_errors.

4. FederationProvenance block on imported artifacts — DONE

  • New FederationProvenance struct: {source-org, source-tool, source-id, anchor, fetched-at, source-hash, mapping-recipe}.
    Wired into Provenance as an optional federation: field — None
    for first-party / AI / human artifacts; serialises out only when
    present (backward-compatible with existing AI-provenance YAML).
  • rivet supplier pull stamps the block into the cache manifest. The
    same shape is what Phase 3 mapping-recipe imports will write onto
    the imported artifacts themselves.

Tests: model::tests::federation_provenance_yaml_roundtrip,
provenance_federation_block_is_optional. The end-to-end
supplier_pull_kind_file_writes_cache_and_manifest asserts the
emitted manifest carries source-org, source-tool, anchor, and
source-hash.

Deferred to follow-up

Per the design doc §6 Phase 3 scope (explicitly out of scope here):

  • Field-mapping recipes under schemas/supplier-mappings/.
  • rivet supplier publish (rivet-to-rivet manifest emission).
  • OSLC / Polarion / GitHub-issues backends for cited-source.
  • Variant-aware anchors (when: clause on external-anchor).
  • rivet supplier promote <anchor> for converting delegated chains
    to first-party.

Commit layout

Three logical commits, all carrying mandatory traceability trailers:

  1. feat(supplier): derives-from-external structured target + FederationProvenance (#288) — Implements: REQ-010 / Refs:
    REQ-020, FEAT-001.
  2. feat(cited-source): kind: reqif backend with sha + XML well-formedness gate (#288) — Fixes: REQ-004.
  3. feat(supplier): rivet supplier pull <anchor> — federation handshake (#288) — Implements: REQ-007.

Test plan

  • cargo fmt --all
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo build --workspace
  • cargo test --workspace — 1006 lib tests + every integration
    test passes; the 6 new model unit tests, 4 new cited-source
    reqif tests, 1 new yaml_hir CST test, and 5 new supplier-pull
    integration tests all pass; oracle-gated (each fails without the
    corresponding change).
  • rivet validate — same baseline result as main (6
    pre-existing errors in the spar-external project unrelated to this
    PR; reproduced cleanly on a git stash of these changes).

https://claude.ai/code/session_01Ms4nZDTtdfzvzTu8m3ghSj


Generated by Claude Code

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 16, 2026

📐 Rivet artifact delta

No artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 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: 514b57a Previous: 284b51c Ratio
store_insert/10000 16257587 ns/iter (± 587105) 12624029 ns/iter (± 339805) 1.29
link_graph_build/10000 31648069 ns/iter (± 1900109) 26094482 ns/iter (± 963800) 1.21
validate/10000 17416126 ns/iter (± 589201) 12995506 ns/iter (± 350432) 1.34
traceability_matrix/1000 67388 ns/iter (± 292) 45966 ns/iter (± 1418) 1.47
query/10000 115461 ns/iter (± 728) 90580 ns/iter (± 1181) 1.27

This comment was automatically generated by workflow using github-action-benchmark.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 98.33333% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rivet-core/src/cited_source.rs 96.12% 5 Missing ⚠️
rivet-core/src/diff.rs 50.00% 1 Missing ⚠️
rivet-core/src/yaml_hir.rs 98.83% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

claude added 3 commits May 19, 2026 09:32
…rovenance (#288)

Items 1 and 4 of issue #288 (Phase 2 federation handshake). Lays the
data-model foundation for cross-org link semantics and the provenance
block that `rivet supplier pull` will stamp on imported artifacts.

- `Link.external: Option<ExternalLinkTarget>` — when YAML `target:` is
  a mapping (the cross-org `*-external` link types), `Link.target`
  mirrors the mapping's `anchor:` field for graph navigation while the
  full `{org, contract, doc-id, last-synced, sha256, anchor}` payload
  flows into `Link.external`. Existing flat-string targets round-trip
  unchanged via a custom serde impl that emits whichever shape the
  link carries.

- `derives-from-external` link type in `schemas/common.yaml`. Companion
  inverse: `derived-into-external`.

- `FederationProvenance` block on `Provenance` for federated artifacts:
  `{source-org, source-tool, source-id, anchor, fetched-at,
  source-hash, mapping-recipe}`. Optional — first-party / AI / human
  artifacts continue to serialise without the block.

- `yaml_hir.rs` CST link extractor handles the structured-target shape
  by dedenting the raw value text and round-tripping through
  serde_yaml. Regression-tested against the previous behaviour, which
  silently mis-targeted the link at the first key ("org") of the
  mapping value.

- Mechanical: every `Link { link_type, target }` initialiser across
  core + cli + tests now includes `external: None`, and the same
  pattern is added to a handful of stub Provenance constructions for
  the new `federation: None` field.

Tests (oracle-gated, fail without the change):
- `model::tests::link_flat_target_yaml_roundtrip`
- `model::tests::link_structured_target_yaml_parse`
- `model::tests::link_structured_target_yaml_serialize_then_parse`
- `model::tests::link_structured_target_requires_anchor`
- `model::tests::federation_provenance_yaml_roundtrip`
- `model::tests::provenance_federation_block_is_optional`
- `yaml_hir::tests::links_extraction_structured_external_target`

Phase 2 cited-source ReqIF backend and `rivet supplier pull` ship in
follow-up commits on the same branch.

Implements: REQ-010
Refs: REQ-020, FEAT-001

https://claude.ai/code/session_01Ms4nZDTtdfzvzTu8m3ghSj
…s gate (#288)

Item 2 of issue #288 (Phase 2 federation handshake). Promotes ReqIF
from "round-trip only" to a first-class local-file backend alongside
`kind: file`.

- `CitedSourceKind::is_local_phase2()` admits `Reqif` in addition to
  `File`.
- `resolve_reqif_uri()` handles `reqif://`, `file://`, and bare-path
  forms — all degrade to local-file semantics. HTTP(S) ReqIF endpoints
  remain Phase 3+ (auth / fetch backend out of scope here).
- `check_cited_source` for `kind: reqif`: read bytes, sha256, verify
  against stamped hash → `Match` / `Drift` / `MissingHash`. Plus a
  ReqIF XML well-formedness check via `reqif::parse_reqif` so a
  malformed supplier delivery surfaces as a typed `FileError` at
  `rivet validate` time rather than poisoning the supplier cache at
  pull time.

Tests (oracle-gated, fail without the change):
- `cited_source::tests::check_cited_source_reqif_match_when_hash_agrees`
- `cited_source::tests::check_cited_source_reqif_drift_when_hash_differs`
- `cited_source::tests::check_cited_source_reqif_missing_hash_returns_computed`
- `cited_source::tests::check_cited_source_reqif_rejects_malformed_xml`
- `cited_source::tests::resolve_reqif_uri_handles_scheme_and_relative`

Fixes: REQ-004
Refs: REQ-020, FEAT-001

https://claude.ai/code/session_01Ms4nZDTtdfzvzTu8m3ghSj
…288)

Item 3 of issue #288 (Phase 2 federation handshake). Wires the
`external-anchor` artifact's `cited-source` to a local supplier cache
under `.rivet/supplier-cache/<org>/<contract>/`. Phase 2 backends:
`kind: file` and `kind: reqif`; both are read-only on the source side
and idempotent on the cache side.

- New CLI subcommand: `rivet supplier pull <anchor> [--format text|json]`.
- Looks up the anchor by ID, validates it's `external-anchor`-typed,
  parses its `cited-source` field, fetches the local payload, and
  cross-checks the stamped sha256 against the wire bytes. Refuses to
  write a poisoned cache entry when the stamped hash drifts — the
  auditor must re-stamp the anchor and retry.
- For ReqIF, runs `reqif::parse_reqif` to verify XML well-formedness
  before caching.
- Writes payload as `<anchor>.<ext>` (`.reqif` for reqif kind,
  inherits source extension for file kind) and a sibling
  `<anchor>.manifest.yaml` carrying the `FederationProvenance` block
  + cache metadata.
- Idempotent: a re-pull with identical bytes refreshes the manifest's
  `fetched-at` but leaves the payload untouched (the JSON output
  reports `bytes_unchanged: true`).
- `sanitize_path_component()` clamps `<org>` / `<contract>` to ASCII
  alphanum + `-_.` so an injected path separator can't escape the
  cache root.

Made `check::sources::current_iso8601_utc()` crate-public for reuse
as the fetch-timestamp source.

Tests (oracle-gated, fail without the change):
- `cli_commands::supplier_pull_kind_file_writes_cache_and_manifest`
- `cli_commands::supplier_pull_refuses_on_sha256_drift`
- `cli_commands::supplier_pull_idempotent_on_re_run`
- `cli_commands::supplier_pull_kind_reqif_writes_reqif_extension`
- `cli_commands::supplier_pull_unknown_anchor_errors`

Implements: REQ-007
Refs: REQ-020, FEAT-001

https://claude.ai/code/session_01Ms4nZDTtdfzvzTu8m3ghSj
@avrabe avrabe force-pushed the feat/issue-288-supplier-phase-2 branch from 1e5ebf9 to 514b57a Compare May 19, 2026 12:03
@avrabe avrabe merged commit bfebc59 into main May 19, 2026
20 of 40 checks passed
@avrabe avrabe deleted the feat/issue-288-supplier-phase-2 branch May 19, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(supplier): Phase 2 — federation handshake (rivet supplier pull) + FederationProvenance

2 participants