feat(supplier): Phase 2 — federation handshake + FederationProvenance (#288)#292
Open
avrabe wants to merge 3 commits into
Open
feat(supplier): Phase 2 — federation handshake + FederationProvenance (#288)#292avrabe wants to merge 3 commits into
avrabe wants to merge 3 commits into
Conversation
…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
📐 Rivet artifact deltaNo artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph. |
There was a problem hiding this comment.
⚠️ 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 Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #288.
Implements Phase 2 of the cross-org / supplier traceability track laid
out in
docs/design/cross-org-supplier-traceability.md§6 (Phase 1shipped 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, arivet supplier pullcommand,and the cross-org provenance block that imports stamp.
Phase 2 scope coverage
1.
derives-from-externallink type with structured target — DONELink.external: Option<ExternalLinkTarget>field. When YAMLtarget:is a mapping (the cross-org*-externallink types),Link.targetmirrors the mapping'sanchor:for graph navigationand the full
{org, contract, doc-id, last-synced, sha256, anchor}payload flows into
Link.external. Flat-string targets round-tripunchanged via a custom serde impl.
derives-from-externallink type added inschemas/common.yamlwith inverse
derived-into-external.yaml_hir.rsCST link extractor handles the structured shape bydedenting 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: reqifbackend — DONECitedSourceKind::is_local_phase2()admitsReqifalongsideFile.resolve_reqif_uri()handlesreqif://,file://, and bare-pathforms (HTTP(S) ReqIF endpoints remain Phase 3+).
check_cited_sourceforkind: reqif: read bytes, sha256, verifystamp →
Match/Drift/MissingHash. Adds a ReqIF XMLwell-formedness check via
reqif::parse_reqifso a malformedsupplier delivery surfaces as
FileErrorat validate time ratherthan 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>forkind: file | reqif— DONErivet supplier pull <anchor> [--format text|json]. Looks up the anchor by ID, validates type, parsescited-source, fetches local payload, cross-checks the stampedsha256 against wire bytes. Refuses to write a poisoned cache entry
on drift (auditor must re-stamp).
parse_reqifbefore caching to gate malformedXML.
<anchor>.manifest.yamlunder.rivet/supplier-cache/<org>/<contract>/(path componentssanitised to alphanum +
-_.so a contract typo can't escape thecache root).
manifest's
fetched-at; the JSON output reportsbytes_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.
FederationProvenanceblock on imported artifacts — DONEFederationProvenancestruct:{source-org, source-tool, source-id, anchor, fetched-at, source-hash, mapping-recipe}.Wired into
Provenanceas an optionalfederation:field —Nonefor first-party / AI / human artifacts; serialises out only when
present (backward-compatible with existing AI-provenance YAML).
rivet supplier pullstamps the block into the cache manifest. Thesame 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-endsupplier_pull_kind_file_writes_cache_and_manifestasserts theemitted manifest carries
source-org,source-tool,anchor, andsource-hash.Deferred to follow-up
Per the design doc §6 Phase 3 scope (explicitly out of scope here):
schemas/supplier-mappings/.rivet supplier publish(rivet-to-rivet manifest emission).cited-source.when:clause onexternal-anchor).rivet supplier promote <anchor>for converting delegated chainsto first-party.
Commit layout
Three logical commits, all carrying mandatory traceability trailers:
feat(supplier): derives-from-external structured target + FederationProvenance (#288)— Implements: REQ-010 / Refs:REQ-020, FEAT-001.
feat(cited-source): kind: reqif backend with sha + XML well-formedness gate (#288)— Fixes: REQ-004.feat(supplier): rivet supplier pull <anchor> — federation handshake (#288)— Implements: REQ-007.Test plan
cargo fmt --allcargo clippy --workspace --all-targets -- -D warningscargo build --workspacecargo test --workspace— 1006 lib tests + every integrationtest 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 asmain(6pre-existing errors in the spar-external project unrelated to this
PR; reproduced cleanly on a
git stashof these changes).https://claude.ai/code/session_01Ms4nZDTtdfzvzTu8m3ghSj
Generated by Claude Code