From 363aab6abc0bc51a885ba2acbc6a09377f48be07 Mon Sep 17 00:00:00 2001 From: Enrico Piovesan Date: Tue, 21 Jul 2026 15:12:12 -0600 Subject: [PATCH] Fix two pre-existing stale governing-spec-ID constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit graph.rs's METADATA_GRAPH_GOVERNING_SPEC and federation.rs's test fixtures referenced spec IDs (015-metadata-graph, 026-federation- registry-routing) that don't exist in approved-specs.json — both slots were reassigned to unrelated specs (015-capability-discovery- mcp, 026-event-broker) during the v0.2.0 batch, so approved_spec_registry_contains() has silently returned false for both since that batch landed, independent of any later work. Investigation found real spec content had been written for both but never merged into approved-specs.json before their slots were taken. Rather than repoint the constants at an existing-but- unrelated approved spec (the same category of error), land the two specs under fresh IDs: - 077-metadata-graph: written retroactively against graph.rs's shipped behavior (no committed draft of the original existed on any branch). - 078-federation-registry-routing: the original spec document, recovered from an abandoned branch (b81a17b), reviewed against federation.rs/federation_operator.rs and found still accurate. See docs/decision-log.md Decision 33 for full rationale and alternatives considered. Co-Authored-By: Claude Sonnet 5 --- .../traverse-cli/src/federation_operator.rs | 4 +- crates/traverse-registry/src/federation.rs | 8 +- crates/traverse-registry/src/graph.rs | 2 +- docs/decision-log.md | 80 ++++++++ specs/077-metadata-graph/spec.md | 192 ++++++++++++++++++ .../data-model.md | 134 ++++++++++++ specs/078-federation-registry-routing/spec.md | 165 +++++++++++++++ specs/governance/approved-specs.json | 24 +++ 8 files changed, 602 insertions(+), 7 deletions(-) create mode 100644 specs/077-metadata-graph/spec.md create mode 100644 specs/078-federation-registry-routing/data-model.md create mode 100644 specs/078-federation-registry-routing/spec.md diff --git a/crates/traverse-cli/src/federation_operator.rs b/crates/traverse-cli/src/federation_operator.rs index 7f41706b..cf9310c9 100644 --- a/crates/traverse-cli/src/federation_operator.rs +++ b/crates/traverse-cli/src/federation_operator.rs @@ -393,7 +393,7 @@ mod tests { "peer_id": "peer-a", "trust_model": "allowlist", "allowed_scopes": ["Public"], - "approved_spec_refs": ["026-federation-registry-routing"], + "approved_spec_refs": ["078-federation-registry-routing"], "approved_at": "2026-04-10T00:00:00Z", "revoked_at": null }}, @@ -492,7 +492,7 @@ mod tests { RegistryScopeManifest::Public, RegistryScopeManifest::Private, ], - approved_spec_refs: vec!["026-federation-registry-routing".to_string()], + approved_spec_refs: vec!["078-federation-registry-routing".to_string()], approved_at: "2026-04-10T00:00:00Z".to_string(), revoked_at: None, } diff --git a/crates/traverse-registry/src/federation.rs b/crates/traverse-registry/src/federation.rs index 27eceed1..f81c2b6e 100644 --- a/crates/traverse-registry/src/federation.rs +++ b/crates/traverse-registry/src/federation.rs @@ -2398,7 +2398,7 @@ mod tests { assert_eq!(private_snapshot.provenance.origin_peer_id, "peer-governed"); assert_eq!( private_snapshot.provenance.approval_chain[0].spec_ref, - "026-federation-registry-routing" + "078-federation-registry-routing" ); assert!(federation.governance_decisions().iter().any(|decision| { decision.decision_kind == GovernanceDecisionKind::SnapshotAcceptance @@ -2648,7 +2648,7 @@ mod tests { peer_id: "peer-local".to_string(), trust_model: "local-only".to_string(), allowed_scopes: Vec::new(), - approved_spec_refs: vec!["026-federation-registry-routing".to_string()], + approved_spec_refs: vec!["078-federation-registry-routing".to_string()], approved_at: "2026-04-09T19:30:00Z".to_string(), revoked_at: None, }), @@ -2683,7 +2683,7 @@ mod tests { peer_id: peer_id.to_string(), trust_model: "shared-api-token".to_string(), allowed_scopes: scopes, - approved_spec_refs: vec!["026-federation-registry-routing".to_string()], + approved_spec_refs: vec!["078-federation-registry-routing".to_string()], approved_at: "2026-04-09T19:30:00Z".to_string(), revoked_at: None, } @@ -2771,7 +2771,7 @@ mod tests { source: traverse_contracts::ProvenanceSource::Greenfield, author: "enricopiovesan".to_string(), created_at: "2026-04-09T19:00:00Z".to_string(), - spec_ref: Some("026-federation-registry-routing".to_string()), + spec_ref: Some("078-federation-registry-routing".to_string()), adr_refs: vec![], exception_refs: vec![], }, diff --git a/crates/traverse-registry/src/graph.rs b/crates/traverse-registry/src/graph.rs index f49ef74c..d9e1f6fd 100644 --- a/crates/traverse-registry/src/graph.rs +++ b/crates/traverse-registry/src/graph.rs @@ -8,7 +8,7 @@ use traverse_contracts::Lifecycle; const METADATA_GRAPH_KIND: &str = "metadata_graph_snapshot"; const METADATA_GRAPH_SCHEMA_VERSION: &str = "1.0.0"; -const METADATA_GRAPH_GOVERNING_SPEC: &str = "015-metadata-graph"; +const METADATA_GRAPH_GOVERNING_SPEC: &str = "077-metadata-graph"; #[derive(Debug, Clone, PartialEq, Eq)] pub struct MetadataGraphSnapshot { diff --git a/docs/decision-log.md b/docs/decision-log.md index 1557358d..6c0eb07c 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -925,3 +925,83 @@ ADR-0016, and its own artifacts. The `embedded-trace-api` implementation ticket may move from Blocked to Ready. Its first delivery must prove that a Trace Explorer-equivalent Web consumer can browse local traces without HTTP and that baseline embedder conformance remains compatible. + +## Decision 33: Land Two Orphaned Governing Specs to Fix a Pre-Existing Stale-Spec-ID Bug + +- **Date**: 2026-07-21 +- **Status**: Accepted +- **Governing specs**: `077-metadata-graph`, `078-federation-registry-routing` +- **Related pull request**: (this change) +- **Related repo**: flagged from `traverse-framework/registry`'s + `specs/014-extraction-compatibility` decision-log entry 33, which found + this bug while auditing `traverse-registry` ahead of extraction but left + it unfixed as out of that repo's scope (registry-scope-only). + +### Context + +Two literal spec-ID strings embedded in `crates/traverse-registry/src/` +did not correspond to any spec in `specs/governance/approved-specs.json`: +`"015-metadata-graph"` (a `const` in `graph.rs`, used as every projected +metadata-graph snapshot's `governing_spec`) and +`"026-federation-registry-routing"` (used only in `federation.rs` and +`federation_operator.rs` test fixtures, as sample `TrustRecord. +approved_spec_refs` / `ApprovalChainEntry.spec_ref` values). Both slots +(`015`, `026`) were reassigned to unrelated specs (`015-capability- +discovery-mcp`, `026-event-broker`) during the v0.2.0 governance batch +(issue #209, issue #207), so `approved_spec_registry_contains()` has +returned `false` for both original strings since that batch landed — +independent of the registry-extraction work that surfaced it. + +Investigation (issue #37 comment history, git history of `graph.rs` via +issue #62, and the abandoned `origin/022-mcp-wasm-server` branch at commit +`b81a17b`) found that in both cases a real, complete spec document had been +written and, in the federation case, even self-marked "Status: Approved" — +but neither was ever merged into `approved-specs.json` before its numeric +slot was taken by different work. The implementations (`graph.rs` via PR +#97, `federation.rs` via PR #240 and follow-ons) shipped anyway, each +assuming its governing spec had been formally approved when it had not. + +### Decision + +Rather than point the two stale constants at an existing-but-unrelated +approved spec (which would repeat the same category of error in a new +form), land the two already-written spec documents under fresh IDs: +`077-metadata-graph` (written retroactively against the shipped +`graph.rs` behavior, since no committed draft of the original +`015-metadata-graph` could be found in any branch) and +`078-federation-registry-routing` (the original `026-federation-registry- +routing` document, recovered from `b81a17b`, reviewed against the shipped +`federation.rs`/`federation_operator.rs` behavior and found still +accurate, with its own stale cross-references to two other still- +unapproved draft specs removed rather than carried forward). Both new +`approved-specs.json` entries govern the specific source files their spec +actually describes (`graph.rs`; `federation.rs` and +`federation_operator.rs`) rather than the whole crate. `graph.rs`'s +`METADATA_GRAPH_GOVERNING_SPEC` constant and all `federation.rs`/ +`federation_operator.rs` test-fixture literals are updated to the new IDs. + +### Alternatives Considered + +- Point the constants at `015-capability-discovery-mcp` / + `026-event-broker` (today's real owners of those numeric slots) — + rejected, since neither actually governs metadata-graph projection or + federation routing; this would fabricate a false governance claim + identical in kind to the bug being fixed. +- Point the constants at the closest broad existing spec that already + governs `traverse-registry/` (e.g. `007-workflow-registry-traversal`) — + rejected; `007` explicitly excludes "full metadata graph query model" + from its own scope, and no approved spec describes federation routing. +- Leave the constants unfixed, matching `traverse-framework/registry`'s + deliberate choice not to paper over this with passthrough entries — + appropriate for that repo (out of its scope, and a passthrough there + would mask rather than fix), but this repo owns the actual bug and can + close it properly instead of leaving it permanently broken. + +### Outcome + +`077-metadata-graph` and `078-federation-registry-routing` are approved and +immutable, each governing exactly the source file(s) it describes. +`approved_spec_registry_contains()` now returns `true` for the spec IDs +actually embedded in `traverse-registry`'s shipped code. All existing +`traverse-registry` and `traverse-cli` tests pass unchanged in behavior — +only the literal spec-ID strings changed. diff --git a/specs/077-metadata-graph/spec.md b/specs/077-metadata-graph/spec.md new file mode 100644 index 00000000..bfdd461f --- /dev/null +++ b/specs/077-metadata-graph/spec.md @@ -0,0 +1,192 @@ +# Feature Specification: Metadata Graph Projection + +**Feature Branch**: `077-metadata-graph` +**Created**: 2026-07-21 +**Status**: Approved +**Input**: User description: "Land the governing spec for the metadata graph projection that `crates/traverse-registry/src/graph.rs` already implements, closing a governance gap where the shipped code referenced a spec ID (`015-metadata-graph`) that was never merged into the approved spec registry before that numeric slot was reassigned to an unrelated spec (`015-capability-discovery-mcp`, issue #209)." + +## Background + +Issue #37 ("Specify metadata graph model") tracked a draft spec at +`specs/015-metadata-graph/`, but a 2026-03-29 comment on that issue states it +must remain unapproved until "reviewed, approved, and merged into the +approved spec registry." That draft was never committed to any branch and no +such merge ever happened. Issue #62 ("Implement metadata graph projection") +nonetheless shipped `project_metadata_graph` and its supporting types +referencing `015-metadata-graph` as `governing_spec`, on the assumption the +slice had been approved. It had not. The `015` slot was later assigned to an +unrelated spec (`015-capability-discovery-mcp`, issue #209) during the +v0.2.0 governance batch, permanently orphaning the original constant. + +This spec is written retroactively against the already-shipped, already- +tested implementation (`crates/traverse-registry/src/graph.rs`, landed via +PR #97) rather than inventing new behavior — its purpose is to close the +governance gap, not to change what the code does. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Project a Unified Graph Across Registries (Priority: P1) + +As a platform developer, I want the capability, event, and workflow +registries projected into a single graph so that I can answer discovery and +explainability questions ("what does this capability publish or depend on?") +without querying each registry independently. + +**Why this priority**: Without a unified projection, callers must +cross-reference three independent registries by hand to answer any +relationship question, which does not scale and is error-prone. + +**Independent Test**: Register a mix of capabilities, events, and workflows +with cross-references between them, call `project_metadata_graph`, and +verify every registered artifact appears as exactly one node and every +declared relationship appears as an edge of the correct kind. + +**Acceptance Scenarios**: + +1. **Given** registered capabilities, events, and workflows, **When** the + metadata graph is projected, **Then** the snapshot contains one node per + distinct `(kind, scope, id, version)` artifact. +2. **Given** a capability that emits or consumes a registered event, **When** + the graph is projected, **Then** a `Publishes` or `SubscribesTo` edge + connects the capability node to the event node. +3. **Given** a capability with a workflow reference, or a workflow whose + definition composes capabilities and references events, **When** the + graph is projected, **Then** `References` and `Composes` edges connect the + corresponding nodes. +4. **Given** multiple versions of the same artifact are registered, **When** + the graph is projected, **Then** `Supersedes` edges connect each version to + its immediate predecessor in version order. + +### User Story 2 - Look Up a Node Under an Explicit Scope Policy (Priority: P2) + +As a caller resolving a capability, event, or workflow reference, I want to +look up its graph node under an explicit public/private preference so that +scope-sensitive callers get deterministic results instead of an arbitrary +match. + +**Why this priority**: Ambiguous scope resolution (returning whichever +matching node happens to sort first) would make caller behavior +unpredictable across otherwise-identical requests. + +**Independent Test**: Register the same `(kind, id, version)` in both public +and private scope, call `find_node` under each `MetadataGraphLookupScope` +variant, and verify the returned node matches that variant's documented +policy. + +**Acceptance Scenarios**: + +1. **Given** a node exists only in public scope, **When** looked up under + `PublicOnly` or `PreferPrivate`, **Then** the public node is returned. +2. **Given** the same artifact exists in both scopes, **When** looked up + under `PreferPrivate`, **Then** the private node is returned. +3. **Given** the same artifact exists in both scopes, **When** looked up + under `PublicOnly`, **Then** the public node is returned, never the + private one. +4. **Given** no matching node exists, **When** looked up under any scope, + **Then** `find_node` returns `None` rather than panicking. + +### User Story 3 - Traverse Outgoing Relationships From a Node (Priority: P3) + +As a caller exploring the graph, I want to list a node's outgoing edges so +that I can traverse relationships (e.g., "what does this workflow compose?") +without re-deriving them from the source registries. + +**Why this priority**: Traversal is the payoff of building the graph at all; +without it, callers would still need to re-inspect raw registry records. + +**Independent Test**: Project a graph with a node that has multiple outgoing +edges of different kinds, call `outgoing_edges`, and verify exactly the +expected edges are returned in a deterministic order. + +**Acceptance Scenarios**: + +1. **Given** a node with outgoing edges, **When** `outgoing_edges` is called + with that node's ID, **Then** every edge whose `from_node_id` matches is + returned and no others. +2. **Given** a node with no outgoing edges, **When** `outgoing_edges` is + called, **Then** an empty list is returned. + +### Edge Cases + +- Two registered artifacts collide on the same `(kind, scope, id, version)` + node ID: the projection MUST deduplicate to a single node rather than + emitting duplicates. +- An event, capability, or workflow reference points at an artifact that is + not registered in the target scope: no edge is emitted for that reference + rather than the projection failing. +- The same relationship is derivable from both scopes (e.g., a public and a + private copy of the referenced event both exist): edges MUST be + deduplicated by `edge_id` so the same logical relationship is not emitted + twice. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The system MUST project every registered capability, event, + and workflow into a `MetadataGraphNode`, keyed by `(kind, scope, id, + version)`, with no duplicate node IDs in the resulting snapshot. +- **FR-002**: The system MUST derive `Publishes` and `SubscribesTo` edges + from each capability's declared `emits` and `consumes` event references, + for every scope in which the referenced event is actually registered. +- **FR-003**: The system MUST derive a `References` edge from a capability to + its declared workflow reference, and from a workflow to each event + referenced by its definition's edges, when the referenced artifact is + registered in that scope. +- **FR-004**: The system MUST derive a `Composes` edge from a workflow to + each capability referenced by its definition's nodes, when that capability + is registered in that scope. +- **FR-005**: The system MUST derive a `Supersedes` edge chain linking each + version of an artifact to its immediate predecessor, ordered by version + comparison, independently per `(kind, scope, id)`. +- **FR-006**: The system MUST deterministically sort nodes and edges and + deduplicate edges by `edge_id`, so identical inputs always produce an + identical snapshot. +- **FR-007**: The system MUST support looking up a node by kind, artifact ID, + and version under an explicit lookup scope policy (`All`, `PublicOnly`, + `PreferPrivate`) with documented, deterministic precedence. +- **FR-008**: The system MUST support listing a node's outgoing edges by + node ID. +- **FR-009**: Every produced snapshot MUST record its `kind`, + `schema_version`, `governing_spec`, `generated_at` timestamp, and the set + of source specs (`005-capability-registry`, `007-workflow-registry- + traversal`, `011-event-registry`, and this spec) as generation evidence. + +### Key Entities *(include if feature involves data)* + +- **MetadataGraphSnapshot**: The full projected graph at a point in time — + kind, schema version, governing spec, generation evidence, nodes, edges. +- **MetadataGraphNode**: A single capability, event, or workflow artifact + represented in the graph, carrying its scope, ID, version, lifecycle, + summary, and owning team. +- **MetadataGraphEdge**: A directed, typed relationship (`References`, + `Publishes`, `SubscribesTo`, `Composes`, `Supersedes`) between two nodes. +- **MetadataGraphGenerationEvidence**: The set of source specs a given + snapshot was projected under. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: Projecting the graph for a fixed set of registry inputs always + yields byte-identical node and edge ordering across repeated calls. +- **SC-002**: Every relationship expressed in a capability, event, or + workflow contract (emits, consumes, workflow reference, composition, + version lineage) is discoverable as exactly one edge in the projected + graph, with no relationship silently dropped or duplicated. +- **SC-003**: `find_node` under any lookup scope returns a result consistent + with that scope's documented precedence in 100% of registered-artifact + lookups, and `None` for unregistered lookups. + +## Assumptions + +- The graph is projected on demand from the three existing registries; it is + not independently persisted or incrementally maintained. +- Cross-registry references that point at unregistered artifacts are treated + as absent relationships, not validation errors — reference validation is + the responsibility of the owning registry (`005-capability-registry`, + `007-workflow-registry-traversal`, `011-event-registry`), not this + projection. +- This spec governs the projection and lookup surface implemented in + `crates/traverse-registry/src/graph.rs`; exposing the graph through a + runtime, CLI, or MCP surface is a separate, future governed slice. diff --git a/specs/078-federation-registry-routing/data-model.md b/specs/078-federation-registry-routing/data-model.md new file mode 100644 index 00000000..b91a296c --- /dev/null +++ b/specs/078-federation-registry-routing/data-model.md @@ -0,0 +1,134 @@ +# Data Model: Federated Traverse registry, routing, and trust model (spec 078) + +## FederationPeer + +Represents a trusted Traverse instance participating in federation. + +### Fields + +- `peer_id`: stable peer identifier +- `display_name`: human-readable peer label +- `trust_state`: trusted, blocked, pending, or revoked +- `identity_fingerprint`: certificate or trust-anchor fingerprint +- `sync_enabled`: whether the peer participates in sync +- `last_sync_at`: timestamp of the most recent sync +- `last_sync_status`: success, partial, failed, or unknown +- `visible_registry_scopes`: which registry scopes this peer may see + +## TrustRecord + +Represents the approved relationship that allows a peer to participate in federation. + +### Fields + +- `peer_id`: referenced FederationPeer +- `trust_model`: how the peer is trusted +- `allowed_scopes`: scopes or registry classes the peer may access +- `approved_spec_refs`: governing spec references that authorize trust +- `approved_at`: timestamp of approval +- `revoked_at`: optional timestamp if trust is revoked + +## FederationSyncSession + +Represents one manual sync attempt. + +### Fields + +- `session_id`: unique sync session identifier +- `peer_id`: peer being synced +- `started_at`: sync start time +- `finished_at`: sync end time +- `status`: success, partial, failed +- `registry_types`: capability, event, workflow, or a subset +- `validated_entries`: number of accepted entries +- `rejected_entries`: number of rejected entries +- `conflict_count`: number of conflicts detected +- `evidence_ref`: pointer to sync evidence or audit artifact + +## PeerRegistrySnapshot + +Represents the remote registry state accepted from a peer. + +### Fields + +- `peer_id`: source peer +- `registry_type`: capability, event, or workflow +- `entry_id`: stable registry entry id +- `version`: semver or equivalent version identifier +- `scope`: public or private +- `approval_state`: approved, draft, deprecated, or rejected +- `contract_ref`: reference to the governing contract artifact +- `provenance_ref`: origin metadata for the remote entry + +## FederatedInvocation + +Represents a routed request sent to the owning peer. + +### Fields + +- `invocation_id`: stable routed invocation id +- `origin_peer_id`: peer initiating the invocation +- `target_peer_id`: owning peer +- `capability_id`: requested capability +- `request_ref`: governing request or payload reference +- `status`: success, failure, retryable_failure +- `response_ref`: response or failure payload reference +- `trace_provenance_ref`: provenance for the routed trace + +## ConflictRecord + +Represents a detected divergence between peers. + +### Fields + +- `conflict_id`: stable conflict identifier +- `peer_ids`: peers involved in the divergence +- `registry_type`: capability, event, or workflow +- `entry_key`: conflicting scope/id/version key +- `conflict_reason`: human-readable explanation +- `resolution_state`: open, resolved, escalated +- `audit_ref`: evidence pointer for review + +## CrossPeerTraceProvenance + +Represents the audit trail for a routed invocation across peers. + +### Fields + +- `trace_id`: governing trace id +- `origin_peer_id`: peer that initiated the request +- `owning_peer_id`: peer that executed the request +- `route_reason`: why the owning peer was selected +- `sync_session_ref`: federation sync evidence ref if relevant +- `response_status`: success or failure outcome +- `evidence_ref`: reviewable evidence pointer + +## FederationStatusSummary + +Represents the operator-facing summary for federation health. + +### Fields + +- `peer_count`: number of trusted peers +- `last_sync_outcome`: latest sync result +- `sync_age`: age since last successful sync +- `conflict_count`: current unresolved conflicts +- `blocked_entries`: number of entries rejected by trust or spec validation +- `route_failures`: number of failed cross-peer invocations + +## Relationships + +- A `FederationPeer` can have many `TrustRecord` entries. +- A `FederationPeer` can have many `FederationSyncSession` records. +- A `FederationSyncSession` can produce many `PeerRegistrySnapshot` entries. +- A `FederatedInvocation` references exactly one origin peer and one owning peer. +- A `ConflictRecord` can reference many peers and many rejected registry entries. +- A `CrossPeerTraceProvenance` record can be attached to a `FederatedInvocation`. + +## Validation Rules + +- A peer without a valid `TrustRecord` cannot participate in federation. +- A `PeerRegistrySnapshot` must match an approved spec or be rejected. +- A `FederatedInvocation` must always identify the owning peer. +- A `ConflictRecord` must retain an audit reference. +- A `CrossPeerTraceProvenance` record must not omit origin and owning peer ids. diff --git a/specs/078-federation-registry-routing/spec.md b/specs/078-federation-registry-routing/spec.md new file mode 100644 index 00000000..8babe47d --- /dev/null +++ b/specs/078-federation-registry-routing/spec.md @@ -0,0 +1,165 @@ +# Feature Specification: Federated Traverse registry, routing, and trust model + +**Feature Branch**: `078-federation-registry-routing` +**Created**: 2026-04-09 +**Approved**: 2026-07-21 +**Status**: Approved +**Input**: User description: "Create the governing federation spec from the long-term end-to-end decisions, with future work split into separate tickets." + +## Provenance + +This spec was originally authored as `026-federation-registry-routing` on +2026-04-09 and marked "Status: Approved" in its own header, but the document +was only ever committed to an abandoned branch +(`origin/022-mcp-wasm-server`, commit `b81a17b`) and was never merged into +`specs/governance/approved-specs.json`. The federation implementation +(`crates/traverse-registry/src/federation.rs`, `crates/traverse-cli/src/ +federation_operator.rs`, landed via PR #240 and follow-ons #247–#250) +nonetheless shipped referencing `026-federation-registry-routing` as an +approved spec ID in its test fixtures, on the mistaken assumption that this +document had been formally approved. It had not, and the numeric slot `026` +was later assigned to an unrelated spec (`026-event-broker`, issue #207) +during the v0.2.0 governance batch, permanently orphaning the original ID. + +The content below is the original document, reviewed against the shipped +implementation and found to still accurately describe it, renumbered and +landed under `078` to close the governance gap. The original's "Governing +Relationship" section referenced open issue numbers and two sibling specs +(`020-downstream-integration-validation`, `021-app-facing-operational- +constraints`) that are themselves still unapproved drafts in this repo; +those unverifiable references have been removed rather than carried forward +uncritically — see the trimmed "Governing Relationship" section below. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Register and Sync Trusted Peers (Priority: P1) + +As a Traverse operator, I want to register trusted peers and manually sync registry state so that distributed Traverse instances can discover each other and share governed registry entries without a central coordinator. + +**Why this priority**: Peer registration and sync are the foundation of federation; nothing else in the federation chain is useful if peers cannot establish trusted shared state. + +**Independent Test**: A reviewer can verify that a peer can be added, listed, synced on demand, and reported as healthy or unhealthy without needing to inspect implementation code. + +**Acceptance Scenarios**: + +1. **Given** two trusted Traverse peers, **When** one peer is registered and a manual sync is run, **Then** the local instance records the remote peer, sync outcome, and the discovered registry entries. +2. **Given** a remote entry fails trust or spec validation, **When** sync runs, **Then** the entry is rejected and the sync evidence reports the reason. + +### User Story 2 - Discover and Route Cross-Peer Invocations (Priority: P1) + +As a downstream consumer or agent, I want a capability discovered on a remote peer to be invoked through the owning peer so that federation is useful as a real end-to-end execution path, not just a registry mirror. + +**Why this priority**: Federation must deliver a real cross-peer call path; otherwise it only proves discovery and state replication. + +**Independent Test**: A reviewer can verify that a discovered remote capability is routed to its owning peer, that the result or failure is returned, and that trace provenance is preserved. + +**Acceptance Scenarios**: + +1. **Given** a capability is registered on peer B and synced to peer A, **When** peer A invokes that capability, **Then** the request is routed to peer B and the response is returned with cross-peer provenance. +2. **Given** the owning peer is unavailable, **When** peer A invokes the remote capability, **Then** the failure is explicit, retryable if policy allows, and the trace records the reason. + +### User Story 3 - Trust, Visibility, and Auditability Across Peers (Priority: P2) + +As a federation steward, I want remote registry entries to be validated against local approved specs and tracked with audit evidence so that federation remains governable and explainable over time. + +**Why this priority**: The federation model needs a durable trust story and reviewable evidence so the system can grow without becoming opaque. + +**Independent Test**: A reviewer can confirm that remote entries are validated before acceptance, peer visibility is exposed, and conflicts or rejections produce audit evidence. + +**Acceptance Scenarios**: + +1. **Given** a remote registry entry from a trusted peer, **When** it is synced, **Then** the entry is validated against the local approved spec registry before acceptance. +2. **Given** a sync detects a divergent or conflicting entry, **When** the sync completes, **Then** the conflict is reported with audit evidence instead of being silently hidden. +3. **Given** an operator inspects federation state, **When** they query peers and sync status, **Then** the current peer list, sync outcomes, and trust state are visible through the supported operator surface. + +## Scope + +In scope: + +- peer-to-peer federation with no central coordinator in the first governed slice +- trusted peer registration and peer identity +- manual on-demand synchronization of registry state +- federated discovery for capability, event, and workflow registries +- public/private visibility rules across trusted peers, with explicit authorization for private entries +- routed cross-peer invocation to the owning peer +- explicit trust and validation of remote entries against local approved specs +- peer listing, sync status, and audit evidence for operators and agents +- cross-peer trace provenance for routed invocations +- explicit conflict reporting when sync detects divergence +- a governed first transport path that can be implemented and validated without guessing at the federation boundary + +Out of scope: + +- a central federation coordinator +- automatic background sync after registration +- streaming sync transport +- gossip-based replication +- load-balanced invocation to any eligible peer +- transport protocols beyond the first governed peer-sync path + +## Edge Cases + +- A peer presents a registry entry that is valid structurally but not approved locally. +- A remote peer is reachable for discovery but unavailable for invocation. +- Two peers present the same `(scope, id, version)` entry with different provenance. +- A peer is trusted for discovery but not trusted for private capability visibility. +- A sync completes successfully for some registry types but not others. +- An invocation is routed successfully but the remote peer returns a structured failure. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: Traverse MUST support trusted peer registration for federation. +- **FR-002**: Traverse MUST support manual on-demand synchronization of registry state between peers. +- **FR-003**: Traverse MUST expose peer listing and sync status to operators and agents. +- **FR-004**: Traverse MUST validate remote registry entries against the local approved spec registry before accepting them. +- **FR-005**: Traverse MUST federate capability, event, and workflow registry entries through the first governed sync path. +- **FR-006**: Traverse MUST route a discovered remote capability invocation to the owning peer. +- **FR-007**: Traverse MUST preserve cross-peer trace provenance for routed invocations. +- **FR-008**: Traverse MUST produce explicit failure evidence when the owning peer is unavailable. +- **FR-009**: Traverse MUST report conflicts or divergent registry entries with audit evidence. +- **FR-010**: Traverse MUST preserve public/private visibility rules across peers and deny unauthorized access to private registry entries. +- **FR-011**: Approved implementation under this slice MUST be checked against the governing federation spec before merge. +- **FR-012**: Future federation extensions beyond this slice MUST be split into separate tickets rather than expanded in place. + +### Key Entities *(include if feature involves data)* + +- **FederationPeer**: A trusted Traverse instance participating in federation, identified by peer identity and trust metadata. +- **PeerRegistrySnapshot**: A synchronized view of the capability, event, and workflow registry entries received from a peer. +- **FederationSyncSession**: A single manual sync operation with outcome, timing, and validation evidence. +- **FederatedInvocation**: A routed request sent to the owning peer and the resulting response or failure. +- **TrustRecord**: The approved relationship that allows a peer to participate in federation. +- **ConflictRecord**: Audit evidence that records divergent or conflicting registry entries. +- **CrossPeerTraceProvenance**: Evidence showing which peer owned the invocation and how the result returned. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: A reviewer can register a peer, run a manual sync, and verify the peer and sync outcome without reading implementation code. +- **SC-002**: A discovered remote capability can be invoked through the owning peer and return a recorded response or explicit failure. +- **SC-003**: Remote entries are accepted only after local approved-spec validation, and rejected entries produce reviewable evidence. +- **SC-004**: Peer list, sync status, and conflict evidence are visible through the supported operator surface. +- **SC-005**: Cross-peer provenance is present in routed invocation evidence and can be reviewed after the call completes. + +## Assumptions + +- The first governed federation slice is peer-to-peer and does not require a central coordinator. +- The first supported sync path is manual and on-demand. +- The first supported transport path is explicit and reviewable rather than inferred from internal implementation structure. +- Future improvements such as automatic sync, streaming updates, and central coordination will be tracked as separate tickets. +- The federation model must stay compatible with the approved spec registry and the existing runtime/tracing model. + +## Governing Relationship + +This specification governs `crates/traverse-registry/src/federation.rs` and +`crates/traverse-cli/src/federation_operator.rs`. + +This specification is governed by: + +- `001-foundation-v0-1` +- `019-downstream-consumer-contract` + +Coordinator behavior, automatic sync, streaming updates, and other +nonessential federation extensions remain future, separately governed work. diff --git a/specs/governance/approved-specs.json b/specs/governance/approved-specs.json index 8974c5ff..04b3a46c 100644 --- a/specs/governance/approved-specs.json +++ b/specs/governance/approved-specs.json @@ -919,6 +919,30 @@ "specs/076-production-swift-wasmi-cabi/" ] }, + { + "id": "077-metadata-graph", + "version": "1.0.0", + "status": "approved", + "immutable": true, + "path": "specs/077-metadata-graph/spec.md", + "governs": [ + "crates/traverse-registry/src/graph.rs", + "specs/077-metadata-graph/", + "specs/governance/" + ] + }, + { + "id": "078-federation-registry-routing", + "version": "1.0.0", + "status": "approved", + "immutable": true, + "path": "specs/078-federation-registry-routing/spec.md", + "governs": [ + "crates/traverse-registry/src/federation.rs", + "crates/traverse-cli/src/federation_operator.rs", + "specs/078-federation-registry-routing/" + ] + }, { "id": "188-codex-agent-coordination", "version": "1.0.0",