Stage 3: EXPAND + MASTER PLAN
**Failure scenario:** The Graph Kernel (Rust, port 8001) crashes or becomes unreachable. All admissibility checks fail. No agent can determine what trajectories it can see. The entire permission model collapses.
Full Public Reader
Stage 3: EXPAND + MASTER PLAN
---
3a. Risk Audit
R1: CRITICAL -- Graph Kernel Becomes Single Point of Failure
Failure scenario: The Graph Kernel (Rust, port 8001) crashes or becomes unreachable. All admissibility checks fail. No agent can determine what trajectories it can see. The entire permission model collapses.
Probability: HIGH (single process, no redundancy today, Graph Kernel has had downtime before)
Impact: CRITICAL (system is blind without it -- agents either see everything or nothing)
Mitigation:
- Offline cache: Each agent caches its most recent `AdmissibleEvidenceBundle` locally. If Graph Kernel is unreachable, agent uses cached bundle (stale but functional). Cache TTL: 5 minutes.
- Fallback policy: If Graph Kernel is down AND cache is stale, agents fall back to a restrictive "read-only, own trajectories only" policy. This prevents over-exposure.
- SQLite WAL backup: Graph Kernel already supports SQLite backend. Deploy SQLite WAL as read-only fallback on each node. Sync every 60 seconds from Supabase.
- Health probe integration: Graph Kernel has `/health/ready` endpoint. Wire into existing `infra_watchdog.py` flow. Auto-restart via systemd on failure. Discord alert within 60 seconds.
Validation criteria: Simulate Graph Kernel crash. Verify agents continue operating with cached bundles. Verify no trajectory data is exposed beyond the cached slice. Verify auto-restart completes within 120 seconds.
---
R2: CRITICAL -- Reward Score Gaming / Inflation
Failure scenario: An agent (or its operator) inflates reward scores to win merge conflicts via REWARD_MAX strategy. Since reward scores determine the "main" path, inflated scores corrupt the entire version history's integrity.
Probability: MEDIUM (requires modifying the reward engine or intercepting the trajectory capture pipeline)
Impact: CRITICAL (corrupted main path, wrong merge outcomes, erosion of trust in the system)
Mitigation:
- On-chain reward anchoring: Reward scores are included in the `reward-sum-x1000` field of the `trajectory-anchor` Merkle batch. Once anchored, the reward sum is immutable. Any post-anchor modification to a trajectory's reward score would invalidate its Merkle proof.
- Reward engine integrity: KARL's reward engine runs on vm with controlled access. The composite reward signal uses 5 independent signals (completion, tool-use quality, file diff size, session duration, outcome quality). Manipulating all 5 simultaneously is difficult.
- Statistical anomaly detection: Before merging, check if either branch's reward distribution deviates >2 sigma from the global mean (0.602, std=0.055). Flag anomalous branches for human review.
- Dual attestation: For merges of high-priority branches (both branches have >5 trajectories), require the reward scores to be attested by TWO independent reward computations (e.g., KARL primary + a simplified secondary scorer).
Validation criteria: Inject 3 trajectories with artificially inflated reward scores (0.95). Verify anomaly detection flags them. Verify merge engine escalates to HUMAN_GATE. Verify on-chain anchor with original (un-inflated) scores remains valid.
---
R3: HIGH -- Clarity Merkle Proof Verification Limits
Failure scenario: The `verify-membership` function in `trajectory-anchor.clar` supports 5 levels of Merkle proof (32 trajectories per batch). If batches grow beyond 32, the proof verification fails or requires a contract upgrade.
Probability: HIGH (once the system is active, 32 trajectories per batch may be reached within a day)
Impact: MEDIUM (limits batch size, increases on-chain cost if multiple smaller batches needed)
Mitigation:
- Configurable proof depth: Write the Clarity function with 10 levels (1024 trajectories per batch) from the start. Clarity has no loops, so each level is an explicit `let` binding. Verbose but safe.
- Variable batch sizing: The anchor bridge adjusts batch size to stay within proof depth limits. If proof depth is 10, max batch = 1024. If trajectories accumulate faster, split into parallel batches.
- Off-chain verification option: For routine checks, verify Merkle proofs off-chain (Python/Rust). On-chain verification reserved for dispute resolution where public verifiability matters.
Validation criteria: Deploy contract with 10-level proof on testnet. Submit a batch of 500 trajectories. Verify on-chain Merkle proof for random 10 trajectories. Measure gas cost per verification.
---
R4: HIGH -- 10-Minute Block Time UX Gap
Failure scenario: An agent commits a trajectory and another agent needs to see it immediately for a dependent task. But the trajectory isn't anchored yet (waiting for next Stacks block). The dependent agent either waits 10 minutes or proceeds with an unanchored trajectory.
Probability: HIGH (10-min blocks are a fundamental property of Stacks)
Impact: MEDIUM (delays dependent work, or forces trust in unanchored data)
Mitigation:
- Two-tier trust model: Trajectories have two states: `local_verified` (Graph Kernel HMAC token proves it was captured by a known agent) and `chain_anchored` (Merkle proof against on-chain root). For most operations, `local_verified` is sufficient. `chain_anchored` is required only for merges and external verification.
- Optimistic execution: Agents proceed with `local_verified` trajectories immediately. If anchoring later fails (unlikely), the merge engine retroactively invalidates dependent work. In practice, anchoring never fails unless the Stacks network is down.
- Pre-anchoring notification: When a batch is submitted, the anchor bridge immediately notifies all agents that these trajectories are "pending anchor." Agents can reference them with a weaker guarantee.
Validation criteria: Measure time from trajectory capture to local_verified (target: <1 second). Measure time from capture to chain_anchored (target: <15 minutes). Verify dependent agents can proceed with local_verified data.
---
R5: HIGH -- Embedding PCA Dimension Loss for Visualization
Failure scenario: 3072-dimensional embeddings reduced to 3D via PCA lose most semantic structure. Two trajectories appear close in the visualization but are semantically distant. A user adjusts topology (drags nodes) based on misleading spatial proximity.
Probability: HIGH (PCA from 3072 to 3 preserves <5
Impact: MEDIUM (misleading visualization, but underlying data plane is unaffected)
Mitigation:
- UMAP instead of PCA: Use UMAP (Uniform Manifold Approximation and Projection) for dimensionality reduction. UMAP preserves local neighborhood structure much better than PCA for high-dimensional data.
- No topology editing from visualization: Remove the "drag to reposition" feature from Step 6. The visualization is read-only. Topology changes happen via CLI/API only, with explicit confirmation.
- Semantic distance overlay: When hovering over a node, show its actual cosine similarity to the nearest 5 nodes. This reveals when visual proximity is misleading.
- Multiple projection views: Offer PCA, UMAP, and t-SNE projections. Users can switch to see which projection best represents the true structure.
Validation criteria: Generate UMAP projection of existing 141 KARL trajectories. Verify that skill clusters (comp-core, karl, infrastructure, creative-director) form visually distinct neighborhoods. Measure silhouette score of UMAP clusters vs PCA clusters.
---
R6: MEDIUM -- Supabase Table Proliferation
Failure scenario: Adding trajectory_versions, trajectory_edges, anchor_batches, agent_policies to an already 131-table Supabase instance. Schema management becomes unwieldy. Migration coordination across mesh nodes introduces bugs.
Probability: MEDIUM (Supabase handles large schemas, but operational complexity increases)
Impact: LOW (manageable with good migration discipline)
Mitigation:
- Dedicated schema: Create a `gvc` schema in Supabase (not `public`). All graph-native VC tables live in `gvc.*`. This isolates them from the 131 existing tables.
- Migration scripts: All schema changes in numbered migration files (`001_trajectory_versions.sql`, `002_trajectory_edges.sql`, etc.). No manual DDL.
- RLS policies: Enforce that only the Graph Kernel service role and the anchor bridge service role can write to `gvc.*` tables. Other services have read-only access.
Validation criteria: Create `gvc` schema with all 4 tables on Supabase. Verify RLS blocks unauthorized writes. Run KARL trajectory capture and verify data flows to `gvc.trajectory_versions`.
---
R7: MEDIUM -- Agent Policy Configuration Complexity
Failure scenario: Each agent needs a `TrajectorySlicePolicy` configured. With 5 mesh nodes, each potentially running multiple agents, the policy configuration matrix grows quickly. Misconfigured policies could give agents too much or too little visibility.
Probability: MEDIUM (policy configuration is manual today)
Impact: MEDIUM (too-wide = security issue, too-narrow = agent can't function)
Mitigation:
- Default policies: Ship 3 default policies: `restricted` (own trajectories only, max_depth=1), `standard` (skill-scoped, max_depth=3), `wide` (cross-skill, max_depth=5). Agents default to `standard`.
- Policy templates by role: Map agent roles to policies. `code-generator` -> `restricted`, `orchestrator` -> `wide`, `reviewer` -> `standard`.
- Policy validation: Before applying a policy, the Graph Kernel computes the resulting slice size. If it exceeds 200 trajectories or includes trajectories from >3 different agent owners, warn and require confirmation.
Validation criteria: Configure all 5 mesh nodes with default policies. Verify each agent sees the expected trajectory count. Verify policy changes on-chain (policy-hash update in agent-registry) match local policy.
---
R8: MEDIUM -- Merge Strategy Ambiguity at Equal Rewards
Failure scenario: Two divergent branches have nearly identical cumulative rewards (within 1 std = 0.055). REWARD_MAX picks one arbitrarily based on floating-point comparison. The "loser" branch had valuable unique work that is effectively discarded.
Probability: HIGH (KARL rewards have std=0.055, many branches will be within this range)
Impact: MEDIUM (loss of valuable work, but Grafts edge can recover it)
Mitigation:
- Epsilon threshold: If `|reward_a - reward_b| < EPSILON` (default: 0.03), escalate to SKILL_UNION instead of REWARD_MAX. This preserves unique contributions from both branches.
- Tie-breaking by recency: If rewards are within epsilon AND skills overlap, prefer the more recent branch (it likely incorporates more current context).
- Mandatory Grafts edge: Even in REWARD_MAX merges, always create Grafts edges from the loser's unique trajectories. This preserves lineage for potential future recovery.
- Merge audit log: Every merge records both branch rewards, the delta, the strategy used, and the trajectories grafted/discarded. Stored in `gvc.merge_log` table.
Validation criteria: Create two synthetic branches with cumulative rewards differing by 0.02 (within epsilon). Verify system escalates to SKILL_UNION. Verify Grafts edges are created. Verify merge audit log contains full detail.
---
R9: LOW -- Spatial-Git Refactoring Risk
Failure scenario: Refactoring the 447KB monolithic HTML into modules introduces regressions in the existing commit visualization. Time-travel controls, branch rendering, or commit panels break.
Probability: LOW (standard refactoring, existing features are visual and easy to test)
Impact: LOW (visualization is informational, not operational)
Mitigation:
- Screenshot diffing: Before refactoring, capture screenshots of all major views (commit graph, time-travel, branch legend, commit panel). After refactoring, compare. Visual regression = fix before proceeding.
- Incremental extraction: Extract one component at a time (timeline first, then minimap, then commit panel). Test after each extraction.
- Keep original as fallback: Keep `index-v21.html` as a backup. If v22 has issues, revert to v21.
Validation criteria: After refactoring, all existing Spatial-Git features work. Load the same git JSON data and verify visual output matches v21.
---
R10: LOW -- $21.64 Budget for Testnet/Mainnet
Failure scenario: Testnet deployment is free (faucet STX), but mainnet deployment costs real STX. The trajectory-anchor contract deployment + initial anchor submissions might exceed the $21.64 budget.
Probability: LOW (contract deployment costs ~0.01 STX, anchor submissions ~0.001 STX each)
Impact: LOW (can delay mainnet until budget grows from crypto-gpu-fund revenue)
Mitigation:
- Testnet-first development: All development and testing on Stacks testnet (free STX from faucet). No mainnet deployment until the system is stable.
- Mainnet gating: Mainnet deployment requires: (a) testnet stable for 2+ weeks, (b) crypto-gpu-fund generating revenue, (c) at least 500 trajectories anchored on testnet without issues.
- Minimal mainnet cost: trajectory-anchor.clar + 50 initial anchor batches = ~0.06 STX = ~$0.10. Well within budget.
Validation criteria: Deploy trajectory-anchor.clar to testnet. Submit 50 anchor batches. Verify all Merkle proofs. Estimate mainnet cost from testnet gas usage.
---
3b. Expanded Specifications
SPEC-1: TrajectoryVersion Table Schema
-- gvc schema
CREATE SCHEMA IF NOT EXISTS gvc;
-- Main trajectory version table
CREATE TABLE gvc.trajectory_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
parent_ids UUID[] DEFAULT '{}',
content_hash TEXT NOT NULL, -- SHA-256 hex
agent_id INTEGER NOT NULL, -- matches agent-registry on-chain ID
agent_device TEXT NOT NULL, -- vm, mac1, mac3, mac4, laptop
skill_label TEXT NOT NULL, -- from KARL routing
reward_score DOUBLE PRECISION NOT NULL CHECK (reward_score >= 0 AND reward_score <= 1),
tool_events JSONB DEFAULT '[]', -- tool calls, file paths, bash commands
file_diffs JSONB DEFAULT '{}', -- files touched + patch summaries
embedding vector(3072), -- gemini-embedding-001
prompt_hash TEXT, -- SHA-256 of original prompt
outcome_summary TEXT, -- natural language summary
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
anchored BOOLEAN NOT NULL DEFAULT false,
anchor_batch_id INTEGER, -- FK to anchor_batches
merkle_proof BYTEA, -- serialized Merkle proof
CONSTRAINT valid_hash CHECK (length(content_hash) = 64)
);
-- Indexes
CREATE INDEX idx_tv_agent ON gvc.trajectory_versions(agent_id);
CREATE INDEX idx_tv_skill ON gvc.trajectory_versions(skill_label);
CREATE INDEX idx_tv_reward ON gvc.trajectory_versions(reward_score DESC);
CREATE INDEX idx_tv_created ON gvc.trajectory_versions(created_at DESC);
CREATE INDEX idx_tv_anchored ON gvc.trajectory_versions(anchored) WHERE NOT anchored;
CREATE INDEX idx_tv_embedding ON gvc.trajectory_versions USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 20); -- adjusted for current scale (~150 trajectories)
-- Edge table
CREATE TABLE gvc.trajectory_edges (
id BIGSERIAL PRIMARY KEY,
parent_id UUID NOT NULL REFERENCES gvc.trajectory_versions(id),
child_id UUID NOT NULL REFERENCES gvc.trajectory_versions(id),
edge_type TEXT NOT NULL CHECK (edge_type IN (
'Derives', 'Supersedes', 'Conflicts', 'Refines', 'Composes', 'Grafts'
)),
weight DOUBLE PRECISION NOT NULL DEFAULT 1.0 CHECK (weight >= 0 AND weight <= 1),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(parent_id, child_id, edge_type)
);
CREATE INDEX idx_te_parent ON gvc.trajectory_edges(parent_id);
CREATE INDEX idx_te_child ON gvc.trajectory_edges(child_id);
CREATE INDEX idx_te_type ON gvc.trajectory_edges(edge_type);
-- Anchor batches
CREATE TABLE gvc.anchor_batches (
id SERIAL PRIMARY KEY,
merkle_root TEXT NOT NULL, -- hex
trajectory_count INTEGER NOT NULL,
data_plane_hash TEXT NOT NULL, -- hex
agent_count INTEGER NOT NULL,
reward_sum DOUBLE PRECISION NOT NULL,
stacks_block_height INTEGER, -- null until confirmed
stacks_tx_id TEXT, -- null until confirmed
submitted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
confirmed_at TIMESTAMPTZ,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'submitted', 'confirmed', 'failed'))
);
-- Agent policies
CREATE TABLE gvc.agent_policies (
agent_id INTEGER PRIMARY KEY,
policy_name TEXT NOT NULL,
policy_json JSONB NOT NULL, -- full TrajectorySlicePolicy
policy_hash TEXT NOT NULL, -- SHA-256 of canonical policy JSON
on_chain_synced BOOLEAN NOT NULL DEFAULT false,
last_synced_block INTEGER,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Merge log
CREATE TABLE gvc.merge_log (
id SERIAL PRIMARY KEY,
branch_a_tip UUID NOT NULL REFERENCES gvc.trajectory_versions(id),
branch_b_tip UUID NOT NULL REFERENCES gvc.trajectory_versions(id),
merge_result_id UUID REFERENCES gvc.trajectory_versions(id),
strategy TEXT NOT NULL,
reward_a DOUBLE PRECISION NOT NULL,
reward_b DOUBLE PRECISION NOT NULL,
reward_delta DOUBLE PRECISION NOT NULL,
grafted_ids UUID[] DEFAULT '{}',
discarded_ids UUID[] DEFAULT '{}',
initiated_by TEXT NOT NULL, -- agent_id or 'human'
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- RLS policies
ALTER TABLE gvc.trajectory_versions ENABLE ROW LEVEL SECURITY;
ALTER TABLE gvc.trajectory_edges ENABLE ROW LEVEL SECURITY;
ALTER TABLE gvc.anchor_batches ENABLE ROW LEVEL SECURITY;
ALTER TABLE gvc.agent_policies ENABLE ROW LEVEL SECURITY;
ALTER TABLE gvc.merge_log ENABLE ROW LEVEL SECURITY;
-- Service role can read/write everything
CREATE POLICY gvc_service_all ON gvc.trajectory_versions
FOR ALL TO service_role USING (true) WITH CHECK (true);
CREATE POLICY gvc_service_all ON gvc.trajectory_edges
FOR ALL TO service_role USING (true) WITH CHECK (true);
CREATE POLICY gvc_service_all ON gvc.anchor_batches
FOR ALL TO service_role USING (true) WITH CHECK (true);
CREATE POLICY gvc_service_all ON gvc.agent_policies
FOR ALL TO service_role USING (true) WITH CHECK (true);
CREATE POLICY gvc_service_all ON gvc.merge_log
FOR ALL TO service_role USING (true) WITH CHECK (true);
-- Anon/authenticated can only read
CREATE POLICY gvc_read ON gvc.trajectory_versions
FOR SELECT TO anon, authenticated USING (true);
CREATE POLICY gvc_read ON gvc.trajectory_edges
FOR SELECT TO anon, authenticated USING (true);
CREATE POLICY gvc_read ON gvc.anchor_batches
FOR SELECT TO anon, authenticated USING (true);
CREATE POLICY gvc_read ON gvc.merge_log
FOR SELECT TO anon, authenticated USING (true);---
SPEC-2: Graph Kernel TrajectorySlicePolicy and New Routes
New Rust types (in `types/trajectory.rs`):
use serde::{Deserialize, Serialize};
/// Policy for slicing the trajectory DAG.
/// Extends SlicePolicyV1 with trajectory-specific parameters.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrajectorySlicePolicy {
// Inherited from SlicePolicyV1
pub max_nodes: usize,
pub max_depth: u32,
pub max_siblings: usize,
pub salience_weight: f32,
pub depth_weight: f32,
pub temporal_weight: f32,
pub min_salience: f32,
// Trajectory-specific
pub reward_weight: f32,
pub min_reward: f32,
pub follow_supersedes: bool,
pub include_conflicts: bool,
pub skill_filter: Option<Vec<String>>,
pub device_filter: Option<Vec<String>>,
}
impl Default for TrajectorySlicePolicy {
fn default() -> Self {
Self {
max_nodes: 50,
max_depth: 5,
max_siblings: 3,
salience_weight: 0.3,
depth_weight: 0.2,
temporal_weight: 0.2,
min_salience: 0.1,
reward_weight: 0.3,
min_reward: 0.0,
follow_supersedes: true,
include_conflicts: false,
skill_filter: None,
device_filter: None,
}
}
}
/// Named trajectory policy presets.
impl TrajectorySlicePolicy {
pub fn restricted() -> Self {
Self {
max_nodes: 10,
max_depth: 1,
max_siblings: 2,
min_reward: 0.4,
include_conflicts: false,
..Default::default()
}
}
pub fn standard() -> Self {
Self::default()
}
pub fn wide() -> Self {
Self {
max_nodes: 200,
max_depth: 10,
max_siblings: 10,
min_salience: 0.0,
min_reward: 0.0,
include_conflicts: true,
..Default::default()
}
}
}New Axum routes (in `service/trajectory_routes.rs`):
// POST /api/trajectory/slice
async fn trajectory_slice_handler(
State(state): State<Arc<AppState>>,
Json(request): Json<TrajectorySliceRequest>,
) -> Result<Json<TrajectorySliceResponse>, (StatusCode, Json<ErrorResponse>)>;
// POST /api/trajectory/lineage
async fn trajectory_lineage_handler(
State(state): State<Arc<AppState>>,
Json(request): Json<TrajectoryLineageRequest>,
) -> Result<Json<TrajectoryLineageResponse>, (StatusCode, Json<ErrorResponse>)>;
// POST /api/trajectory/conflicts
async fn trajectory_conflicts_handler(
State(state): State<Arc<AppState>>,
Json(request): Json<TrajectoryConflictsRequest>,
) -> Result<Json<TrajectoryConflictsResponse>, (StatusCode, Json<ErrorResponse>)>;
// GET /api/trajectory/main-path
async fn main_path_handler(
State(state): State<Arc<AppState>>,
) -> Result<Json<MainPathResponse>, (StatusCode, Json<ErrorResponse>)>;Lines of code estimate: ~800 new Rust LOC (types: ~150, slicer extension: ~300, routes: ~200, tests: ~150).
---
SPEC-3: trajectory-anchor.clar Full Contract
;; title: trajectory-anchor
;; version: 1.0.0
;; summary: Merkle root anchoring for agent trajectory batches
;; description: Stores Merkle roots of trajectory batches on-chain.
;; Each anchor covers N trajectories. Any trajectory can be verified
;; against its batch's Merkle root using the verify-membership function.
;; Supports up to 10-level Merkle proofs (1024 trajectories per batch).
(define-data-var contract-owner principal tx-sender)
(define-data-var pending-owner (optional principal) none)
(define-data-var anchor-count uint u0)
(define-constant ERR_NOT_AUTHORIZED (err u700))
(define-constant ERR_INVALID_PROOF (err u701))
(define-constant ERR_NO_ANCHOR (err u702))
(define-constant ERR_INVALID_INPUT (err u703))
;; Batch anchors
(define-map anchors uint {
merkle-root: (buff 32),
trajectory-count: uint,
data-plane-hash: (buff 32),
agent-count: uint,
reward-sum-x1000: uint,
prev-anchor-id: uint,
block: uint
})
;; Per-agent latest anchor tracking
(define-map agent-latest-anchor uint uint)
;; Anchor statistics
(define-data-var total-trajectories-anchored uint u0)
(define-data-var total-reward-anchored uint u0)
;; Submit a new batch anchor
(define-public (submit-anchor
(merkle-root (buff 32))
(trajectory-count uint)
(data-plane-hash (buff 32))
(agent-count uint)
(reward-sum-x1000 uint))
(let ((anchor-id (var-get anchor-count))
(prev-id (if (> anchor-id u0) (- anchor-id u1) u0)))
(asserts! (is-eq tx-sender (var-get contract-owner)) ERR_NOT_AUTHORIZED)
(asserts! (> trajectory-count u0) ERR_INVALID_INPUT)
(map-set anchors anchor-id {
merkle-root: merkle-root,
trajectory-count: trajectory-count,
data-plane-hash: data-plane-hash,
agent-count: agent-count,
reward-sum-x1000: reward-sum-x1000,
prev-anchor-id: prev-id,
block: block-height
})
(var-set anchor-count (+ anchor-id u1))
(var-set total-trajectories-anchored
(+ (var-get total-trajectories-anchored) trajectory-count))
(var-set total-reward-anchored
(+ (var-get total-reward-anchored) reward-sum-x1000))
(ok anchor-id)))
;; Update per-agent latest anchor
(define-public (set-agent-anchor (agent-id uint) (anchor-id uint))
(begin
(asserts! (is-eq tx-sender (var-get contract-owner)) ERR_NOT_AUTHORIZED)
(asserts! (is-some (map-get? anchors anchor-id)) ERR_NO_ANCHOR)
(map-set agent-latest-anchor agent-id anchor-id)
(ok true)))
;; 10-level Merkle proof verification (supports up to 1024 leaves)
;; Each sibling-N/side-N pair represents one level of the tree.
;; Pass 0x00...00 for unused levels (leaves < 2^10).
(define-read-only (verify-membership
(anchor-id uint) (leaf-hash (buff 32))
(s0 (buff 32)) (d0 bool)
(s1 (buff 32)) (d1 bool)
(s2 (buff 32)) (d2 bool)
(s3 (buff 32)) (d3 bool)
(s4 (buff 32)) (d4 bool)
(s5 (buff 32)) (d5 bool)
(s6 (buff 32)) (d6 bool)
(s7 (buff 32)) (d7 bool)
(s8 (buff 32)) (d8 bool)
(s9 (buff 32)) (d9 bool))
(match (map-get? anchors anchor-id)
anchor
(let (
(h0 (if d0 (sha256 (concat s0 leaf-hash)) (sha256 (concat leaf-hash s0))))
(h1 (if d1 (sha256 (concat s1 h0)) (sha256 (concat h0 s1))))
(h2 (if d2 (sha256 (concat s2 h1)) (sha256 (concat h1 s2))))
(h3 (if d3 (sha256 (concat s3 h2)) (sha256 (concat h2 s3))))
(h4 (if d4 (sha256 (concat s4 h3)) (sha256 (concat h3 s4))))
(h5 (if d5 (sha256 (concat s5 h4)) (sha256 (concat h4 s5))))
(h6 (if d6 (sha256 (concat s6 h5)) (sha256 (concat h5 s6))))
(h7 (if d7 (sha256 (concat s7 h6)) (sha256 (concat h6 s7))))
(h8 (if d8 (sha256 (concat s8 h7)) (sha256 (concat h7 s8))))
(h9 (if d9 (sha256 (concat s9 h8)) (sha256 (concat h8 s9))))
)
(ok (is-eq h9 (get merkle-root anchor))))
ERR_NO_ANCHOR))
;; Read-only
(define-read-only (get-anchor (anchor-id uint))
(map-get? anchors anchor-id))
(define-read-only (get-anchor-count)
(var-get anchor-count))
(define-read-only (get-agent-latest-anchor (agent-id uint))
(map-get? agent-latest-anchor agent-id))
(define-read-only (get-system-stats)
{
anchor-count: (var-get anchor-count),
total-trajectories: (var-get total-trajectories-anchored),
total-reward-x1000: (var-get total-reward-anchored)
})
;; Two-step ownership transfer
(define-public (propose-owner (new-owner principal))
(begin
(asserts! (is-eq tx-sender (var-get contract-owner)) ERR_NOT_AUTHORIZED)
(var-set pending-owner (some new-owner))
(ok new-owner)))
(define-public (accept-ownership)
(let ((pending (unwrap! (var-get pending-owner) ERR_NOT_AUTHORIZED)))
(asserts! (is-eq tx-sender pending) ERR_NOT_AUTHORIZED)
(var-set contract-owner pending)
(var-set pending-owner none)
(ok true)))Lines: ~120 Clarity LOC.
---
SPEC-4: Anchor Bridge (Python, Prefect Flow)
"""
anchor_bridge.py -- Batches and anchors trajectories to Stacks blockchain.
Runs as a Prefect flow on vm. Polls for unanchored trajectories, computes
Merkle roots, submits to Stacks via epoch_sdk.
Schedule: Every 10 minutes (aligned with Stacks block time).
"""
import hashlib
import json
import os
from datetime import datetime, timezone
from typing import Optional
from prefect import flow, task, get_run_logger
# Config
BATCH_SIZE = int(os.getenv("GVC_BATCH_SIZE", "20"))
MAX_WAIT_MINUTES = int(os.getenv("GVC_MAX_WAIT", "30"))
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_KEY = os.environ["SUPABASE_SERVICE_KEY"]
STACKS_NODE = os.getenv("STACKS_NODE", "https://stacks-node-api.mainnet.stacks.co")
@task
def get_unanchored_trajectories() -> list[dict]:
"""Fetch all unanchored trajectory versions from Supabase."""
from supabase import create_client
sb = create_client(SUPABASE_URL, SUPABASE_KEY)
result = sb.schema("gvc").table("trajectory_versions") \
.select("id, content_hash, agent_id, reward_score, created_at") \
.eq("anchored", False) \
.order("created_at") \
.execute()
return result.data
@task
def compute_merkle_root(hashes: list[bytes]) -> tuple[bytes, list]:
"""Build Merkle tree, return (root, proofs)."""
if not hashes:
return b'\x00' * 32, []
leaves = list(hashes)
# Pad to power of 2
target = 1
while target < len(leaves):
target *= 2
while len(leaves) < target:
leaves.append(b'\x00' * 32)
tree = [leaves]
while len(tree[-1]) > 1:
level = tree[-1]
next_level = []
for i in range(0, len(level), 2):
combined = hashlib.sha256(level[i] + level[i + 1]).digest()
next_level.append(combined)
tree.append(next_level)
root = tree[-1][0]
# Compute individual proofs
proofs = []
for leaf_idx in range(len(hashes)): # only real leaves, not padding
proof = []
idx = leaf_idx
for level in tree[:-1]:
sibling_idx = idx ^ 1
side = idx % 2 == 1
proof.append({"sibling": level[sibling_idx].hex(), "side": side})
idx //= 2
proofs.append(proof)
return root, proofs
@task
def submit_anchor_to_stacks(
merkle_root: bytes,
trajectory_count: int,
data_plane_hash: bytes,
agent_count: int,
reward_sum_x1000: int,
) -> Optional[str]:
"""Submit anchor transaction to Stacks blockchain."""
logger = get_run_logger()
try:
from epoch_sdk import EpochClient
client = EpochClient()
result = client.call_contract(
contract="trajectory-anchor",
function="submit-anchor",
args={
"merkle-root": merkle_root.hex(),
"trajectory-count": trajectory_count,
"data-plane-hash": data_plane_hash.hex(),
"agent-count": agent_count,
"reward-sum-x1000": reward_sum_x1000,
}
)
logger.info(f"Anchor submitted: tx={result.get('txid', 'unknown')}")
return result.get("txid")
except Exception as e:
logger.error(f"Anchor submission failed: {e}")
return None
@task
def mark_trajectories_anchored(
trajectory_ids: list[str],
batch_id: int,
proofs: list[dict],
):
"""Update trajectories as anchored with Merkle proofs."""
from supabase import create_client
sb = create_client(SUPABASE_URL, SUPABASE_KEY)
for tid, proof in zip(trajectory_ids, proofs):
sb.schema("gvc").table("trajectory_versions") \
.update({
"anchored": True,
"anchor_batch_id": batch_id,
"merkle_proof": json.dumps(proof),
}) \
.eq("id", tid) \
.execute()
@flow(name="gvc-anchor-bridge", log_prints=True)
def anchor_bridge_flow():
"""Main anchor bridge flow. Runs every 10 minutes."""
logger = get_run_logger()
# 1. Get unanchored trajectories
unanchored = get_unanchored_trajectories()
if not unanchored:
logger.info("No unanchored trajectories. Skipping.")
return
logger.info(f"Found {len(unanchored)} unanchored trajectories")
# 2. Check if batch is ready
oldest_created = min(t["created_at"] for t in unanchored)
oldest_age_minutes = (datetime.now(timezone.utc) -
datetime.fromisoformat(oldest_created)).total_seconds() / 60
if len(unanchored) < BATCH_SIZE and oldest_age_minutes < MAX_WAIT_MINUTES:
logger.info(
f"Batch not ready: {len(unanchored)}/{BATCH_SIZE} trajectories, "
f"oldest {oldest_age_minutes:.0f}/{MAX_WAIT_MINUTES} min"
)
return
# 3. Take batch (up to BATCH_SIZE)
batch = unanchored[:BATCH_SIZE]
hashes = [bytes.fromhex(t["content_hash"]) for t in batch]
# 4. Compute Merkle root
root, proofs = compute_merkle_root(hashes)
data_plane_hash = hashlib.sha256(b''.join(hashes)).digest()
agent_ids = set(t["agent_id"] for t in batch)
reward_sum = sum(t["reward_score"] for t in batch)
logger.info(
f"Merkle root: {root.hex()[:16]}..., "
f"{len(batch)} trajectories, {len(agent_ids)} agents, "
f"reward_sum={reward_sum:.3f}"
)
# 5. Record batch in Supabase
from supabase import create_client
sb = create_client(SUPABASE_URL, SUPABASE_KEY)
batch_result = sb.schema("gvc").table("anchor_batches").insert({
"merkle_root": root.hex(),
"trajectory_count": len(batch),
"data_plane_hash": data_plane_hash.hex(),
"agent_count": len(agent_ids),
"reward_sum": reward_sum,
"status": "pending",
}).execute()
batch_id = batch_result.data[0]["id"]
# 6. Submit to Stacks
tx_id = submit_anchor_to_stacks(
merkle_root=root,
trajectory_count=len(batch),
data_plane_hash=data_plane_hash,
agent_count=len(agent_ids),
reward_sum_x1000=int(reward_sum * 1000),
)
if tx_id:
# Update batch status
sb.schema("gvc").table("anchor_batches").update({
"stacks_tx_id": tx_id,
"status": "submitted",
}).eq("id", batch_id).execute()
# Mark trajectories as anchored
trajectory_ids = [t["id"] for t in batch]
mark_trajectories_anchored(trajectory_ids, batch_id, proofs)
logger.info(f"Batch {batch_id} anchored: tx={tx_id}")
else:
sb.schema("gvc").table("anchor_batches").update({
"status": "failed",
}).eq("id", batch_id).execute()
logger.error(f"Batch {batch_id} anchor FAILED")
if __name__ == "__main__":
anchor_bridge_flow()Lines: ~180 Python LOC.
---
SPEC-5: gvc CLI Tool
#!/usr/bin/env python3
"""
gvc -- Graph Version Control CLI
Usage:
gvc status Show unanchored trajectories, current main path
gvc log [--skill S] [--min-reward R] [--limit N]
Show trajectory DAG
gvc slice --anchor ID Show visible trajectories from anchor
gvc slice --agent DEVICE Show agent's view
gvc diff ID1 ID2 Compare two trajectories
gvc merge --a ID --b ID --strategy S
Merge divergent branches
gvc anchor --batch Anchor current unanchored batch
gvc verify ID Verify trajectory's Merkle proof
gvc policy list List registered policies
gvc policy set AGENT POLICY Set agent's topology policy
gvc viz [--agent DEVICE] Open Spatial-Git visualization
gvc main-path Show highest-reward path
"""
import argparse
import json
import sys
import os
import webbrowser
GRAPH_KERNEL_URL = os.getenv("GRAPH_KERNEL_URL", "http://localhost:8001")
SUPABASE_URL = os.environ.get("SUPABASE_URL", "")
SUPABASE_KEY = os.environ.get("SUPABASE_SERVICE_KEY", "")
def cmd_status(args):
"""Show current system status."""
import httpx
# Get unanchored count
from supabase import create_client
sb = create_client(SUPABASE_URL, SUPABASE_KEY)
unanchored = sb.schema("gvc").table("trajectory_versions") \
.select("id", count="exact") \
.eq("anchored", False).execute()
total = sb.schema("gvc").table("trajectory_versions") \
.select("id", count="exact").execute()
anchored = sb.schema("gvc").table("anchor_batches") \
.select("id", count="exact") \
.eq("status", "confirmed").execute()
print(f"Trajectory Versions: {total.count}")
print(f" Anchored: {total.count - unanchored.count}")
print(f" Unanchored: {unanchored.count}")
print(f"Anchor Batches: {anchored.count} confirmed")
print()
# Get main path
try:
r = httpx.get(f"{GRAPH_KERNEL_URL}/api/trajectory/main-path", timeout=5)
if r.status_code == 200:
data = r.json()
path = data.get("path", [])
reward = data.get("cumulative_reward", 0)
print(f"Main Path: {len(path)} trajectories, cumulative reward {reward:.3f}")
for t in path[-5:]:
print(f" {t['id'][:8]}... [{t['skill_label']}] reward={t['reward_score']:.3f}")
except Exception:
print("Main Path: Graph Kernel unreachable")
def cmd_log(args):
"""Show trajectory DAG."""
from supabase import create_client
sb = create_client(SUPABASE_URL, SUPABASE_KEY)
query = sb.schema("gvc").table("trajectory_versions") \
.select("id, parent_ids, skill_label, reward_score, agent_device, created_at, anchored") \
.order("created_at", desc=True) \
.limit(args.limit or 20)
if args.skill:
query = query.eq("skill_label", args.skill)
if args.min_reward:
query = query.gte("reward_score", args.min_reward)
result = query.execute()
for t in result.data:
anchor_mark = "A" if t["anchored"] else "."
parents = ",".join(p[:8] for p in (t["parent_ids"] or []))
print(
f"[{anchor_mark}] {t['id'][:12]} "
f"r={t['reward_score']:.3f} "
f"{t['skill_label']:20s} "
f"{t['agent_device']:6s} "
f"parents=[{parents}] "
f"{t['created_at'][:19]}"
)
def cmd_slice(args):
"""Show visible trajectories from anchor."""
import httpx
payload = {}
if args.anchor:
payload["anchor_trajectory_id"] = args.anchor
elif args.agent:
payload["agent_device"] = args.agent
r = httpx.post(f"{GRAPH_KERNEL_URL}/api/trajectory/slice",
json=payload, timeout=10)
data = r.json()
print(f"Visible trajectories: {len(data.get('trajectory_ids', []))}")
print(f"Admissibility [sensitive field redacted], 'N/A')[:16]}...")
for tid in data.get("trajectory_ids", [])[:20]:
print(f" {tid}")
def cmd_viz(args):
"""Open Spatial-Git visualization."""
url = f"file://{os.path.expanduser('Desktop/spatial-git/index.html')}"
if args.agent:
url += f"?agent={args.agent}"
webbrowser.open(url)
print(f"Opened: {url}")
def main():
parser = argparse.ArgumentParser(prog="gvc", description="Graph Version Control")
sub = parser.add_subparsers(dest="command")
sub.add_parser("status")
p_log = sub.add_parser("log")
p_log.add_argument("--skill", type=str)
p_log.add_argument("--min-reward", type=float)
p_log.add_argument("--limit", type=int, default=20)
p_slice = sub.add_parser("slice")
p_slice.add_argument("--anchor", type=str)
p_slice.add_argument("--agent", type=str)
p_viz = sub.add_parser("viz")
p_viz.add_argument("--agent", type=str)
args = parser.parse_args()
commands = {
"status": cmd_status,
"log": cmd_log,
"slice": cmd_slice,
"viz": cmd_viz,
}
if args.command in commands:
commands[args.command](args)
else:
parser.print_help()
if __name__ == "__main__":
main()Lines: ~150 Python LOC (core CLI; additional commands added incrementally).
---
3c. Master Execution Checklist
Wave 0: Prerequisites (Days 1-2)
| # | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 0.1 | Create `gvc` schema in Supabase with all 5 tables (SPEC-1) | Claude | SPEC-1 SQL | Schema created, RLS active | `\dt gvc.*` shows 5 tables, RLS blocks anon writes | TODO |
| 0.2 | Add pgvector extension to `gvc` schema if not present | Claude | Supabase dashboard | Extension enabled | `CREATE EXTENSION IF NOT EXISTS vector` succeeds | TODO |
| 0.3 | Seed `gvc.trajectory_versions` with existing 141 KARL trajectories | Claude | KARL trajectory_bridge data | 141 rows in trajectory_versions | `SELECT count(*) FROM gvc.trajectory_versions` = 141 | TODO |
| 0.4 | Create `gvc.trajectory_edges` for existing trajectory lineage (Derives edges) | Claude | KARL session_id chains | Edges connecting sequential trajectories | `SELECT count(*) FROM gvc.trajectory_edges` > 0 | TODO |
| 0.5 | Register 3 default policies in `gvc.agent_policies` (restricted/standard/wide) | Claude | SPEC-2 defaults | 3 rows in agent_policies | `SELECT * FROM gvc.agent_policies` shows 3 policies | TODO |
| 0.6 | Get testnet STX from faucet | Mohamed | Stacks faucet URL | Testnet STX in wallet | `stacks-cli balance` shows > 0 STX on testnet | TODO |
Wave 0 gate: All 6 tasks complete. Trajectory data is in Supabase, testnet STX available.
---
Wave 1: Graph Kernel Extension (Days 3-7)
| # | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 1.1 | Add trajectory edge types to `types/edge.rs` (Derives, Supersedes, Conflicts, Refines, Composes, Grafts) | Claude | SPEC-2 edge types | Updated edge.rs | `cargo test` passes, new variants parse from strings | TODO |
| 1.2 | Create `types/trajectory.rs` with TrajectorySlicePolicy (SPEC-2) | Claude | SPEC-2 policy | New file, registered in mod.rs | `cargo build` succeeds | TODO |
| 1.3 | Extend PostgresGraphStore to read from `gvc.trajectory_versions` and `gvc.trajectory_edges` | Claude | SPEC-1 schema | New store methods | Integration test reads seeded data | TODO |
| 1.4 | Implement trajectory slicer (extends ContextSlicer with reward-aware scoring) | Claude | SPEC-2 slicer | TrajectoryContextSlicer | Unit test: anchor at trajectory, get expected slice with HMAC | TODO |
| 1.5 | Add `/api/trajectory/slice` route (SPEC-2) | Claude | 1.4 slicer | Working endpoint | `curl -X POST /api/trajectory/slice -d '{"anchor_trajectory_id":"..."}' ` returns slice | TODO |
| 1.6 | Add `/api/trajectory/lineage` route | Claude | 1.3 store | Working endpoint | Returns Derives chain from any trajectory to root | TODO |
| 1.7 | Add `/api/trajectory/conflicts` route | Claude | 1.3 store | Working endpoint | Returns all Conflicts edges for a trajectory | TODO |
| 1.8 | Add `/api/trajectory/main-path` route (Dijkstra on reward-weighted DAG) | Claude | 1.3 store, Step 4 algorithm | Working endpoint | Returns highest-reward path with correct cumulative score | TODO |
| 1.9 | Write integration tests for all new routes (minimum 10 tests) | Claude | 1.5-1.8 routes | Test suite | `cargo test trajectory` -- all pass | TODO |
Wave 1 gate: Graph Kernel builds, all tests pass, trajectory slice API returns valid HMAC-signed bundles. Deployed on vm port 8001.
---
Wave 2: Proof Plane -- Clarity Contract + Bridge (Days 8-12)
| # | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 2.1 | Write `trajectory-anchor.clar` (SPEC-3) | Claude | SPEC-3 | Contract compiles with Clarinet | `clarinet check` passes | TODO |
| 2.2 | Write Clarinet unit tests for trajectory-anchor (minimum 8 tests) | Claude | 2.1 contract | Test file | `clarinet test` -- all pass | TODO |
| 2.3 | Deploy trajectory-anchor to Stacks testnet | Mohamed | 2.1 contract, testnet STX | Deployed contract | Contract callable via Stacks API | TODO |
| 2.4 | Implement anchor_bridge.py (SPEC-4) | Claude | SPEC-4 | Python module | Unit tests pass (Merkle root computation, proof generation) | TODO |
| 2.5 | Register anchor bridge as Prefect flow | Claude | 2.4 module | Flow registered | `prefect deployment ls` shows gvc-anchor-bridge | TODO |
| 2.6 | Test end-to-end: create trajectory -> anchor bridge batches -> submit to testnet -> verify proof on-chain | Claude + Mohamed | 2.1-2.5 | Full pipeline works | Merkle proof verified on testnet | TODO |
| 2.7 | Add `trajectory-anchored` chainhook to `chainhook-config.json` | Claude | 2.3 deployed contract | Updated config | Chainhook fires on anchor submission | TODO |
| 2.8 | Update chain-bridge.py to handle trajectory-anchored events (mark batches as confirmed) | Claude | 2.7 chainhook | Updated bridge | Batch status transitions to 'confirmed' after Stacks block | TODO |
Wave 2 gate: Trajectory anchoring works end-to-end on testnet. Merkle proofs verify on-chain. Chain bridge updates batch status.
---
Wave 3: Merge Engine + Admissibility Integration (Days 13-16)
| # | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 3.1 | Implement merge engine (Python module: merge_engine.py) with REWARD_MAX and SKILL_UNION strategies | Claude | Step 4 design | merge_engine.py | Unit tests: merge two synthetic branches, verify correct winner | TODO |
| 3.2 | Implement epsilon tie-breaking (escalate to SKILL_UNION when delta < 0.03) | Claude | R8 mitigation | Updated merge_engine.py | Test: two branches within epsilon -> SKILL_UNION used | TODO |
| 3.3 | Implement merge audit logging to `gvc.merge_log` | Claude | SPEC-1 merge_log table | Merge records logged | Merge creates row in merge_log with full detail | TODO |
| 3.4 | Integrate admissibility check into merge: verify both branches are in requester's slice | Claude | Wave 1 slice API | Merge rejects if branch outside requester's slice | Test: attempt merge on branch outside slice -> 403 | TODO |
| 3.5 | Implement main-path computation (topological sort + DP) as standalone function | Claude | Step 4 algorithm | main_path.py | Test: known DAG -> known highest-reward path | TODO |
| 3.6 | Wire KARL trajectory capture into GVC: on_trajectory_captured() stores to gvc tables + creates Derives edge | Claude | Step 8 integration | Updated trajectory_bridge.py | New KARL trajectory appears in gvc.trajectory_versions within 5 seconds | TODO |
Wave 3 gate: Merges work. Main path computes correctly. KARL trajectories automatically flow into GVC.
---
Wave 4: CLI Tool (Days 17-19)
| # | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 4.1 | Implement `gvc` CLI core (SPEC-5): status, log, slice, viz commands | Claude | SPEC-5 | [home-path] executable | `gvc status` returns trajectory counts | TODO |
| 4.2 | Implement `gvc merge` command | Claude | 3.1 merge engine | CLI merge | `gvc merge --a ID --b ID --strategy reward_max` creates merge trajectory | TODO |
| 4.3 | Implement `gvc anchor` and `gvc verify` commands | Claude | Wave 2 bridge | CLI anchoring | `gvc verify ID` returns Merkle proof verification result | TODO |
| 4.4 | Implement `gvc policy` commands (list, set) | Claude | gvc.agent_policies table | CLI policy mgmt | `gvc policy list` shows 3 default policies | TODO |
| 4.5 | Symlink `gvc` to `[home-path]` and verify PATH | Claude | 4.1-4.4 CLI | gvc in PATH | `which gvc` returns [home-path] | TODO |
Wave 4 gate: `gvc status`, `gvc log`, `gvc slice`, `gvc merge`, `gvc anchor`, `gvc verify`, `gvc policy` all functional.
---
Wave 5: Visualization Extension (Days 20-25)
| # | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 5.1 | Refactor Spatial-Git into modular structure (index.html + src/*.js) | Claude | Current monolith | Modular codebase | All existing features work after refactor | TODO |
| 5.2 | Implement TrajectoryNode class (position from UMAP, color from skill, brightness from reward) | Claude | SPEC, trajectory data | trajectory-node.js | Trajectories render as colored spheres in 3D | TODO |
| 5.3 | Implement AdmissibilityFrustum class (translucent cone from policy params) | Claude | SPEC-2 policies | frustum.js | Cone renders, nodes outside dim correctly | TODO |
| 5.4 | Implement edge rendering (Derives=green, Supersedes=orange, Conflicts=red, Composes=white) | Claude | Edge types | dag-renderer.js | Edges render between nodes with correct colors | TODO |
| 5.5 | Implement anchor status overlay (golden ring for anchored, translucent for unanchored) | Claude | Anchor data | anchor-overlay.js | Anchored trajectories visually distinct | TODO |
| 5.6 | Implement main-path highlighting (glowing highway through constellation) | Claude | Main path API | main-path overlay | Main path visually prominent | TODO |
| 5.7 | Implement API client for Graph Kernel integration | Claude | Wave 1 API | api-client.js | Visualization loads real data from Graph Kernel | TODO |
| 5.8 | Add `gvc viz` command to open visualization | Claude | 5.1-5.7 viz | CLI opens browser | `gvc viz --agent claw-vm` opens with agent's frustum | TODO |
Wave 5 gate: Spatial-Git v22 renders trajectory DAG, frustums, edges, anchor status, main path. Works with real data.
---
Wave 6: Integration + Hardening (Days 26-30)
| # | Task | Owner | Input | Output | Validation | Status |
|---|---|---|---|---|---|---|
| 6.1 | Wire agent task dispatch to include topology-constrained context (Step 8 integration) | Claude | claim_next_task RPC | Agent sees trajectory history in context | Agent prompt includes visible trajectory summaries | TODO |
| 6.2 | Implement Graph Kernel fallback (cached bundles + restrictive policy when kernel down) | Claude | R1 mitigation | Fallback logic | Simulate kernel crash, verify agents continue | TODO |
| 6.3 | Implement reward anomaly detection (flag branches > 2 sigma from mean) | Claude | R2 mitigation | Anomaly detector | Inject inflated rewards, verify flagging | TODO |
| 6.4 | Add policy-hash to agent-registry.clar (on-chain policy anchoring) | Claude | Step 3 design | Updated contract | Policy hash stored on-chain per agent | TODO |
| 6.5 | Deploy updated agent-registry to testnet | Mohamed | 6.4 contract | Deployed | Policy-hash field readable on testnet | TODO |
| 6.6 | End-to-end integration test: KARL captures trajectory -> GVC stores -> Graph Kernel indexes -> anchor bridge anchors -> CLI verifies -> visualization renders | Claude + Mohamed | All waves | Full pipeline | All 8 steps work in sequence | TODO |
| 6.7 | Update KARL trajectory_bridge.py with GVC hooks (automate: true) | Claude | 3.6 integration | Production wiring | Every KARL trajectory automatically enters GVC | TODO |
| 6.8 | Stress test: create 200 synthetic trajectories, verify merge, anchor, visualize | Claude | Synthetic data | Performance baseline | Merge completes in <5s, anchor batch submits, viz renders at 30fps | TODO |
| 6.9 | Write RUNBOOK.md for GVC operations | Claude | All specs | Runbook | Covers: anchor failures, merge conflicts, kernel fallback, testnet ops | TODO |
Wave 6 gate: Full system operational on testnet with real KARL data flowing. Performance validated. Runbook written.
---
Summary Statistics
| Metric | Value |
|---|---|
| Total tasks | 42 |
| Waves | 7 (0-6) |
| Critical path | 30 days |
| New Clarity contracts | 1 (trajectory-anchor.clar, ~120 LOC) |
| Modified Clarity contracts | 1 (agent-registry.clar, ~5 LOC) |
| New Rust code | ~800 LOC (Graph Kernel extension) |
| New Python code | ~500 LOC (bridge, merge, CLI) |
| New JavaScript code | ~600 LOC (visualization) |
| New SQL | ~150 LOC (schema) |
| New Supabase tables | 5 (in gvc schema) |
| Estimated on-chain cost (testnet) | 0 STX (faucet) |
| Estimated on-chain cost (mainnet) | ~0.1 STX for first 50 batches |
| Risks | 3 critical, 3 high, 2 medium, 2 low |
Minimum Viable Graph-Native VC (Waves 0-3, 16 days)
If time is constrained, Waves 0-3 deliver:
- Trajectory DAG in Supabase (storage)
- Graph Kernel trajectory slicing with HMAC tokens (admissibility)
- On-chain Merkle root anchoring on testnet (proof)
- Reward-weighted merging (reconciliation)
- KARL integration (data flow)
Missing from MVP: CLI (Wave 4), Visualization (Wave 5), Hardening (Wave 6). These are important but not structurally essential.
Pulse Auto-Spawn (Dry Run)
Tasks tagged `automate: true` would create these Pulse sessions:
| Pulse Session | Tasks | Estimated Duration |
|---|---|---|
| `gvc-schema-setup` | 0.1, 0.2, 0.3, 0.4, 0.5 | 2 hours |
| `gvc-graph-kernel-ext` | 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9 | 8 hours |
| `gvc-clarity-contract` | 2.1, 2.2, 2.4 | 4 hours |
| `gvc-anchor-bridge` | 2.5, 2.7, 2.8 | 3 hours |
| `gvc-merge-engine` | 3.1, 3.2, 3.3, 3.4, 3.5, 3.6 | 6 hours |
| `gvc-cli` | 4.1, 4.2, 4.3, 4.4, 4.5 | 4 hours |
| `gvc-viz-refactor` | 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8 | 8 hours |
| `gvc-integration` | 6.1, 6.2, 6.3, 6.6, 6.7, 6.8, 6.9 | 6 hours |
Total: 8 Pulse sessions, ~41 hours of Claude execution time.
---
Key Insight (for Noosphere)
The deepest structural discovery: "topology as permission" is already implemented twice in the existing codebase (Graph Kernel HMAC admissibility + Stacks agent-registry ownership), but these two implementations are unaware of each other. Graph-Native Version Control is their unification. The Graph Kernel's slice boundary, backed by NFTree Merkle proofs on Stacks, becomes the SINGLE source of truth for what any agent can see or modify. The attention mask IS the admissibility graph IS the on-chain permission tree. This is not a new idea -- it's the recognition that the existing pieces already form this system and just need to be wired together.
Promotion Decision
Attach run IDs, datasets, metrics, and reproduction commands.
Source Anchor
evo-cube-output/graph-native-version-control/stage3-expand-master-plan.md
Detected Structure
Method · Evaluation · References · Figures · Code Anchors · Architecture · is Stage Research