Skip to content

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

Open
avrabe wants to merge 3 commits into
mainfrom
feat/issue-288-supplier-phase-2
Open

feat(supplier): Phase 2 — federation handshake + FederationProvenance (#288)#292
avrabe wants to merge 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

claude added 3 commits May 16, 2026 16:59
…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
@github-actions
Copy link
Copy Markdown

📐 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: 1e5ebf9 Previous: e2466db Ratio
store_lookup/100 2154 ns/iter (± 8) 1670 ns/iter (± 33) 1.29
store_lookup/1000 25849 ns/iter (± 255) 19188 ns/iter (± 119) 1.35
traceability_matrix/1000 63724 ns/iter (± 308) 41149 ns/iter (± 163) 1.55
diff/1000 704534 ns/iter (± 13417) 586263 ns/iter (± 2550) 1.20
query/1000 7416 ns/iter (± 264) 5467 ns/iter (± 16) 1.36

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!

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