237 lines
12 KiB
Markdown
237 lines
12 KiB
Markdown
# ADR-047: Proof-Gated Mutation Protocol
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Date
|
|
|
|
2026-02-25
|
|
|
|
## Context
|
|
|
|
RuVector's graph transformer operates on mutable graph state -- nodes are added, edges are rewired, attention weights are updated, and topology evolves during self-organizing operations. In safety-critical deployments (genomic pipelines, financial computation, cognitive containers), every mutation must be auditable and formally justified.
|
|
|
|
The existing `ruvector-verified` crate provides `ProofEnvironment`, `VerifiedOp<T>`, `ProofAttestation` (82-byte witnesses), and three-tier proof routing (`Reflex`, `Standard`, `Deep`) in `crates/ruvector-verified/src/gated.rs`. However, there is no protocol for composing these primitives into a mutation control substrate -- no defined lifecycle for how a graph mutation acquires its proof, how local proofs compose into regional proofs, how proof scopes align with min-cut partition boundaries, or how the attestation chain grows without unbounded memory.
|
|
|
|
We need a protocol that makes "no proof, no mutation" the default, while keeping hot-path overhead below 2%.
|
|
|
|
## Decision
|
|
|
|
We will implement the Proof-Gated Mutation Protocol as the `proof_gated` module within `ruvector-graph-transformer`. The protocol defines a type-level gate (`ProofGate<T>`), a scoping mechanism (`ProofScope`), a composition algebra for attestation chains, and epoch boundaries for protocol upgrades.
|
|
|
|
### The ProofGate<T> Type
|
|
|
|
`ProofGate<T>` is a wrapper that makes the inner value inaccessible without a valid proof:
|
|
|
|
```rust
|
|
/// A value gated behind a machine-checked proof.
|
|
///
|
|
/// The inner `T` cannot be accessed without presenting a proof that
|
|
/// satisfies the gate's `ProofRequirement`. This is enforced at the
|
|
/// type level -- there is no `unsafe` escape hatch.
|
|
pub struct ProofGate<T> {
|
|
/// The gated value. Private -- only accessible via `unlock()`.
|
|
inner: T,
|
|
/// The proof requirement that must be satisfied.
|
|
requirement: ProofRequirement,
|
|
/// Attestation produced when the gate was satisfied.
|
|
attestation: Option<ProofAttestation>,
|
|
}
|
|
|
|
impl<T> ProofGate<T> {
|
|
/// Create a new proof gate with the given requirement.
|
|
pub fn new(value: T, requirement: ProofRequirement) -> Self;
|
|
|
|
/// Attempt to unlock the gate by providing a proof.
|
|
/// Returns `&T` on success, `Err(ProofGateRejected)` on failure.
|
|
pub fn unlock(&self, env: &mut ProofEnvironment) -> Result<&T>;
|
|
|
|
/// Consume the gate, returning the value and its attestation chain.
|
|
pub fn into_inner(self, env: &mut ProofEnvironment) -> Result<(T, ProofAttestation)>;
|
|
|
|
/// Check if this gate has been satisfied (attestation present).
|
|
pub fn is_satisfied(&self) -> bool;
|
|
}
|
|
```
|
|
|
|
`ProofRequirement` is an enum that maps to `ruvector-verified::gated::ProofKind`:
|
|
|
|
```rust
|
|
pub enum ProofRequirement {
|
|
/// Dimension equality: vector has expected dimension.
|
|
DimensionMatch { expected: u32 },
|
|
/// Type constructor: node/edge type matches schema.
|
|
TypeMatch { schema_id: u64 },
|
|
/// Invariant preservation: graph property holds after mutation.
|
|
InvariantPreserved { invariant_id: u32 },
|
|
/// Coherence bound: attention coherence above threshold.
|
|
CoherenceBound { min_coherence: f64 },
|
|
/// Composition: all sub-requirements must be satisfied.
|
|
Composite(Vec<ProofRequirement>),
|
|
}
|
|
```
|
|
|
|
### Three-Tier Routing
|
|
|
|
Every mutation routes through the existing `ruvector-verified::gated::route_proof` function, which selects the cheapest sufficient proof tier:
|
|
|
|
| Tier | Target Latency | Use Case | Implementation |
|
|
|------|---------------|----------|----------------|
|
|
| **Reflex** | < 10 ns | Dimension checks, reflexivity, literal equality | Direct comparison, no reduction engine. Maps to `ProofTier::Reflex` |
|
|
| **Standard** | < 1 us | Type application (depth <= 5), short pipelines (<=3 stages) | Bounded fuel via `ProofTier::Standard { max_fuel }`, auto-escalates on failure |
|
|
| **Deep** | < 100 us | Long pipelines, custom proofs, invariant verification | Full 10,000-step kernel via `ProofTier::Deep` |
|
|
|
|
Routing is automatic: the `ProofRequirement` is classified into a `ProofKind`, passed to `route_proof()`, and the returned `TierDecision` determines which verification path to take. If a tier fails, it escalates to the next tier (Reflex -> Standard -> Deep) via `verify_tiered()` as implemented in `crates/ruvector-verified/src/gated.rs`.
|
|
|
|
### Attestation Chain
|
|
|
|
Each successful proof produces a `ProofAttestation` (82 bytes, defined in `crates/ruvector-verified/src/proof_store.rs`). Attestations are stored in a `MutationLedger`:
|
|
|
|
```rust
|
|
pub struct MutationLedger {
|
|
/// Append-only log of attestations for this scope.
|
|
attestations: Vec<ProofAttestation>,
|
|
/// Running content hash (FNV-1a) over all attestation bytes.
|
|
chain_hash: u64,
|
|
/// Epoch counter for proof algebra versioning.
|
|
epoch: u64,
|
|
/// Maximum attestations before compaction.
|
|
compaction_threshold: usize,
|
|
}
|
|
|
|
impl MutationLedger {
|
|
/// Append an attestation. Returns the chain position.
|
|
pub fn append(&mut self, att: ProofAttestation) -> u64;
|
|
|
|
/// Compact old attestations into a single summary attestation.
|
|
/// Preserves the chain hash but reduces memory.
|
|
pub fn compact(&mut self) -> ProofAttestation;
|
|
|
|
/// Verify the chain hash is consistent.
|
|
pub fn verify_integrity(&self) -> bool;
|
|
}
|
|
```
|
|
|
|
### Proof Composition
|
|
|
|
Local proofs compose into regional proofs via `compose_chain`:
|
|
|
|
```rust
|
|
/// Compose a sequence of local proof attestations into a regional proof.
|
|
///
|
|
/// The regional proof's `proof_term_hash` is the hash of all constituent
|
|
/// attestation hashes. The `reduction_steps` field is the sum of all
|
|
/// constituent steps. This is sound because proofs are append-only and
|
|
/// each attestation covers a disjoint mutation.
|
|
pub fn compose_chain(attestations: &[ProofAttestation]) -> ProofAttestation;
|
|
```
|
|
|
|
Composition respects partition boundaries: a `ProofScope` is defined by a min-cut partition (from `ruvector-mincut`), and proofs within a scope compose locally. Cross-scope composition requires a `GlobalCoherenceProof` that verifies the boundary edges between partitions maintain coherence above the threshold.
|
|
|
|
### Proof Scope and Min-Cut Alignment
|
|
|
|
```rust
|
|
pub struct ProofScope {
|
|
/// Partition ID from ruvector-mincut.
|
|
partition_id: u32,
|
|
/// Boundary nodes shared with adjacent partitions.
|
|
boundary_nodes: Vec<u64>,
|
|
/// The ledger for this scope.
|
|
ledger: MutationLedger,
|
|
/// Coherence measurement for this scope.
|
|
coherence: Option<f64>,
|
|
}
|
|
```
|
|
|
|
When the graph self-organizes (topology changes via `ruvector-mincut`), proof scopes are re-derived from the new partition. Attestations from the old scope are sealed with a `ScopeTransitionAttestation` that records the old and new partition IDs, the min-cut value at transition, and the composition proof of the old scope.
|
|
|
|
### Monotonic Semantics
|
|
|
|
Attestations are append-only. There is no `delete` operation on the `MutationLedger`. Rollback is achieved by appending a **supersession proof** -- a new attestation that proves the rolled-back state is valid, referencing the original attestation by position:
|
|
|
|
```rust
|
|
pub struct SupersessionProof {
|
|
/// Position of the attestation being superseded.
|
|
superseded_position: u64,
|
|
/// The new attestation that replaces it.
|
|
replacement: ProofAttestation,
|
|
/// Proof that the replacement is sound (e.g., inverse mutation).
|
|
soundness_proof_id: u32,
|
|
}
|
|
```
|
|
|
|
### Epoch Boundaries
|
|
|
|
The proof algebra may be upgraded (new invariants, changed reduction limits, new built-in symbols). Epoch boundaries are explicit:
|
|
|
|
```rust
|
|
pub struct EpochBoundary {
|
|
/// Previous epoch number.
|
|
from_epoch: u64,
|
|
/// New epoch number.
|
|
to_epoch: u64,
|
|
/// Summary attestation sealing all proofs in the previous epoch.
|
|
seal: ProofAttestation,
|
|
/// New proof environment configuration.
|
|
new_config: ProofEnvironmentConfig,
|
|
}
|
|
```
|
|
|
|
At an epoch boundary, the `MutationLedger` is compacted, a seal attestation is produced, and the `ProofEnvironment` is reconfigured with new symbols and fuel budgets. Old proofs remain valid (sealed) but new proofs use the updated algebra.
|
|
|
|
### Performance Budget
|
|
|
|
The target is less than 2% overhead on the hot path. This is achieved by:
|
|
|
|
1. **Reflex tier dominance**: In steady-state graph transformer inference, 90%+ of mutations are dimension checks and reflexivity proofs, which route to Reflex (< 10 ns)
|
|
2. **FastTermArena**: Bump allocation with O(1) dedup from `crates/ruvector-verified/src/fast_arena.rs` avoids heap allocation
|
|
3. **Proof caching**: `ProofEnvironment::cache_lookup` avoids re-proving identical obligations
|
|
4. **Lazy attestation**: `ProofAttestation` is constructed only when the caller requests `proof_chain()`, not on every mutation
|
|
5. **Batch gating**: Multiple mutations within a single forward pass share one `ProofScope`, amortizing the scope setup cost
|
|
|
|
Benchmarks must demonstrate: Reflex < 10 ns, Standard < 1 us, Deep < 100 us, composition of 1000 attestations < 50 us, ledger compaction of 10,000 entries < 1 ms.
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
- Every graph mutation carries a machine-checked proof -- auditable, reproducible, and tamper-evident
|
|
- Three-tier routing keeps the common case (Reflex) at near-zero cost
|
|
- Attestation chains provide a complete audit trail for compliance (GDPR provenance, SOC2 audit logs)
|
|
- Epoch boundaries allow upgrading the proof system without invalidating historical proofs
|
|
- Monotonic semantics prevent accidental attestation loss
|
|
|
|
### Negative
|
|
|
|
- `ProofGate<T>` adds one level of indirection to every graph access
|
|
- Developers must reason about `ProofRequirement` when defining new mutation types
|
|
- Supersession proofs add complexity compared to simple deletion
|
|
- The `MutationLedger` grows linearly with mutations until compaction (mitigated by compaction threshold)
|
|
|
|
### Risks
|
|
|
|
- If Reflex tier coverage drops below 90%, the 2% overhead budget may be exceeded. Mitigated by monitoring `ProofStats::cache_hits` ratio in production
|
|
- Attestation chain integrity depends on FNV-1a hash -- not cryptographically secure. For production audit trails, upgrade to BLAKE3 (available via `ruvector-graph`'s `blake3` dependency)
|
|
- Epoch boundary migration is a manual operation -- if forgotten, the ledger grows unbounded. Mitigated by a configurable auto-epoch threshold in `GraphTransformerConfig`
|
|
|
|
## Implementation
|
|
|
|
1. Implement `ProofGate<T>` and `ProofRequirement` in `crates/ruvector-graph-transformer/src/proof_gated/gate.rs`
|
|
2. Implement `MutationLedger` with append, compact, and verify in `crates/ruvector-graph-transformer/src/proof_gated/mod.rs`
|
|
3. Implement `compose_chain` and `ProofScope` in `crates/ruvector-graph-transformer/src/proof_gated/attestation.rs`
|
|
4. Implement `EpochBoundary` in `crates/ruvector-graph-transformer/src/proof_gated/epoch.rs`
|
|
5. Add benchmark suite: `benches/proof_gate_bench.rs` covering all three tiers, composition, and compaction
|
|
6. Integration test: full forward pass with 10,000 mutations, verifying attestation chain integrity
|
|
|
|
## References
|
|
|
|
- ADR-045: Lean-Agentic Integration (establishes `ProofEnvironment`, `ProofAttestation`, `FastTermArena`)
|
|
- ADR-046: Graph Transformer Unified Architecture (module structure)
|
|
- `crates/ruvector-verified/src/gated.rs`: `ProofTier`, `ProofKind`, `route_proof`, `verify_tiered`
|
|
- `crates/ruvector-verified/src/proof_store.rs`: `ProofAttestation`, `ATTESTATION_SIZE` (82 bytes)
|
|
- `crates/ruvector-verified/src/fast_arena.rs`: `FastTermArena`, bump allocation with FxHash dedup
|
|
- `crates/ruvector-verified/src/error.rs`: `VerificationError` variants
|
|
- `crates/ruvector-mincut/Cargo.toml`: `canonical` feature for pseudo-deterministic min-cut
|
|
- `crates/ruvector-mincut-gated-transformer/src/energy_gate.rs`: `EnergyGate` decision model
|