Grand Diomande Research · Full HTML Reader

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.

Agents That Account for Themselves technical note experiment writeup candidate score 34 .md

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

sql
-- 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`):

rust
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`):

rust
// 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

clarity
;; 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)

python
"""
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

python
#!/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)

#TaskOwnerInputOutputValidationStatus
0.1Create `gvc` schema in Supabase with all 5 tables (SPEC-1)ClaudeSPEC-1 SQLSchema created, RLS active`\dt gvc.*` shows 5 tables, RLS blocks anon writesTODO
0.2Add pgvector extension to `gvc` schema if not presentClaudeSupabase dashboardExtension enabled`CREATE EXTENSION IF NOT EXISTS vector` succeedsTODO
0.3Seed `gvc.trajectory_versions` with existing 141 KARL trajectoriesClaudeKARL trajectory_bridge data141 rows in trajectory_versions`SELECT count(*) FROM gvc.trajectory_versions` = 141TODO
0.4Create `gvc.trajectory_edges` for existing trajectory lineage (Derives edges)ClaudeKARL session_id chainsEdges connecting sequential trajectories`SELECT count(*) FROM gvc.trajectory_edges` > 0TODO
0.5Register 3 default policies in `gvc.agent_policies` (restricted/standard/wide)ClaudeSPEC-2 defaults3 rows in agent_policies`SELECT * FROM gvc.agent_policies` shows 3 policiesTODO
0.6Get testnet STX from faucetMohamedStacks faucet URLTestnet STX in wallet`stacks-cli balance` shows > 0 STX on testnetTODO

Wave 0 gate: All 6 tasks complete. Trajectory data is in Supabase, testnet STX available.

---

Wave 1: Graph Kernel Extension (Days 3-7)

#TaskOwnerInputOutputValidationStatus
1.1Add trajectory edge types to `types/edge.rs` (Derives, Supersedes, Conflicts, Refines, Composes, Grafts)ClaudeSPEC-2 edge typesUpdated edge.rs`cargo test` passes, new variants parse from stringsTODO
1.2Create `types/trajectory.rs` with TrajectorySlicePolicy (SPEC-2)ClaudeSPEC-2 policyNew file, registered in mod.rs`cargo build` succeedsTODO
1.3Extend PostgresGraphStore to read from `gvc.trajectory_versions` and `gvc.trajectory_edges`ClaudeSPEC-1 schemaNew store methodsIntegration test reads seeded dataTODO
1.4Implement trajectory slicer (extends ContextSlicer with reward-aware scoring)ClaudeSPEC-2 slicerTrajectoryContextSlicerUnit test: anchor at trajectory, get expected slice with HMACTODO
1.5Add `/api/trajectory/slice` route (SPEC-2)Claude1.4 slicerWorking endpoint`curl -X POST /api/trajectory/slice -d '{"anchor_trajectory_id":"..."}' ` returns sliceTODO
1.6Add `/api/trajectory/lineage` routeClaude1.3 storeWorking endpointReturns Derives chain from any trajectory to rootTODO
1.7Add `/api/trajectory/conflicts` routeClaude1.3 storeWorking endpointReturns all Conflicts edges for a trajectoryTODO
1.8Add `/api/trajectory/main-path` route (Dijkstra on reward-weighted DAG)Claude1.3 store, Step 4 algorithmWorking endpointReturns highest-reward path with correct cumulative scoreTODO
1.9Write integration tests for all new routes (minimum 10 tests)Claude1.5-1.8 routesTest suite`cargo test trajectory` -- all passTODO

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)

#TaskOwnerInputOutputValidationStatus
2.1Write `trajectory-anchor.clar` (SPEC-3)ClaudeSPEC-3Contract compiles with Clarinet`clarinet check` passesTODO
2.2Write Clarinet unit tests for trajectory-anchor (minimum 8 tests)Claude2.1 contractTest file`clarinet test` -- all passTODO
2.3Deploy trajectory-anchor to Stacks testnetMohamed2.1 contract, testnet STXDeployed contractContract callable via Stacks APITODO
2.4Implement anchor_bridge.py (SPEC-4)ClaudeSPEC-4Python moduleUnit tests pass (Merkle root computation, proof generation)TODO
2.5Register anchor bridge as Prefect flowClaude2.4 moduleFlow registered`prefect deployment ls` shows gvc-anchor-bridgeTODO
2.6Test end-to-end: create trajectory -> anchor bridge batches -> submit to testnet -> verify proof on-chainClaude + Mohamed2.1-2.5Full pipeline worksMerkle proof verified on testnetTODO
2.7Add `trajectory-anchored` chainhook to `chainhook-config.json`Claude2.3 deployed contractUpdated configChainhook fires on anchor submissionTODO
2.8Update chain-bridge.py to handle trajectory-anchored events (mark batches as confirmed)Claude2.7 chainhookUpdated bridgeBatch status transitions to 'confirmed' after Stacks blockTODO

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)

#TaskOwnerInputOutputValidationStatus
3.1Implement merge engine (Python module: merge_engine.py) with REWARD_MAX and SKILL_UNION strategiesClaudeStep 4 designmerge_engine.pyUnit tests: merge two synthetic branches, verify correct winnerTODO
3.2Implement epsilon tie-breaking (escalate to SKILL_UNION when delta < 0.03)ClaudeR8 mitigationUpdated merge_engine.pyTest: two branches within epsilon -> SKILL_UNION usedTODO
3.3Implement merge audit logging to `gvc.merge_log`ClaudeSPEC-1 merge_log tableMerge records loggedMerge creates row in merge_log with full detailTODO
3.4Integrate admissibility check into merge: verify both branches are in requester's sliceClaudeWave 1 slice APIMerge rejects if branch outside requester's sliceTest: attempt merge on branch outside slice -> 403TODO
3.5Implement main-path computation (topological sort + DP) as standalone functionClaudeStep 4 algorithmmain_path.pyTest: known DAG -> known highest-reward pathTODO
3.6Wire KARL trajectory capture into GVC: on_trajectory_captured() stores to gvc tables + creates Derives edgeClaudeStep 8 integrationUpdated trajectory_bridge.pyNew KARL trajectory appears in gvc.trajectory_versions within 5 secondsTODO

Wave 3 gate: Merges work. Main path computes correctly. KARL trajectories automatically flow into GVC.

---

Wave 4: CLI Tool (Days 17-19)

#TaskOwnerInputOutputValidationStatus
4.1Implement `gvc` CLI core (SPEC-5): status, log, slice, viz commandsClaudeSPEC-5[home-path] executable`gvc status` returns trajectory countsTODO
4.2Implement `gvc merge` commandClaude3.1 merge engineCLI merge`gvc merge --a ID --b ID --strategy reward_max` creates merge trajectoryTODO
4.3Implement `gvc anchor` and `gvc verify` commandsClaudeWave 2 bridgeCLI anchoring`gvc verify ID` returns Merkle proof verification resultTODO
4.4Implement `gvc policy` commands (list, set)Claudegvc.agent_policies tableCLI policy mgmt`gvc policy list` shows 3 default policiesTODO
4.5Symlink `gvc` to `[home-path]` and verify PATHClaude4.1-4.4 CLIgvc 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)

#TaskOwnerInputOutputValidationStatus
5.1Refactor Spatial-Git into modular structure (index.html + src/*.js)ClaudeCurrent monolithModular codebaseAll existing features work after refactorTODO
5.2Implement TrajectoryNode class (position from UMAP, color from skill, brightness from reward)ClaudeSPEC, trajectory datatrajectory-node.jsTrajectories render as colored spheres in 3DTODO
5.3Implement AdmissibilityFrustum class (translucent cone from policy params)ClaudeSPEC-2 policiesfrustum.jsCone renders, nodes outside dim correctlyTODO
5.4Implement edge rendering (Derives=green, Supersedes=orange, Conflicts=red, Composes=white)ClaudeEdge typesdag-renderer.jsEdges render between nodes with correct colorsTODO
5.5Implement anchor status overlay (golden ring for anchored, translucent for unanchored)ClaudeAnchor dataanchor-overlay.jsAnchored trajectories visually distinctTODO
5.6Implement main-path highlighting (glowing highway through constellation)ClaudeMain path APImain-path overlayMain path visually prominentTODO
5.7Implement API client for Graph Kernel integrationClaudeWave 1 APIapi-client.jsVisualization loads real data from Graph KernelTODO
5.8Add `gvc viz` command to open visualizationClaude5.1-5.7 vizCLI opens browser`gvc viz --agent claw-vm` opens with agent's frustumTODO

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)

#TaskOwnerInputOutputValidationStatus
6.1Wire agent task dispatch to include topology-constrained context (Step 8 integration)Claudeclaim_next_task RPCAgent sees trajectory history in contextAgent prompt includes visible trajectory summariesTODO
6.2Implement Graph Kernel fallback (cached bundles + restrictive policy when kernel down)ClaudeR1 mitigationFallback logicSimulate kernel crash, verify agents continueTODO
6.3Implement reward anomaly detection (flag branches > 2 sigma from mean)ClaudeR2 mitigationAnomaly detectorInject inflated rewards, verify flaggingTODO
6.4Add policy-hash to agent-registry.clar (on-chain policy anchoring)ClaudeStep 3 designUpdated contractPolicy hash stored on-chain per agentTODO
6.5Deploy updated agent-registry to testnetMohamed6.4 contractDeployedPolicy-hash field readable on testnetTODO
6.6End-to-end integration test: KARL captures trajectory -> GVC stores -> Graph Kernel indexes -> anchor bridge anchors -> CLI verifies -> visualization rendersClaude + MohamedAll wavesFull pipelineAll 8 steps work in sequenceTODO
6.7Update KARL trajectory_bridge.py with GVC hooks (automate: true)Claude3.6 integrationProduction wiringEvery KARL trajectory automatically enters GVCTODO
6.8Stress test: create 200 synthetic trajectories, verify merge, anchor, visualizeClaudeSynthetic dataPerformance baselineMerge completes in <5s, anchor batch submits, viz renders at 30fpsTODO
6.9Write RUNBOOK.md for GVC operationsClaudeAll specsRunbookCovers: anchor failures, merge conflicts, kernel fallback, testnet opsTODO

Wave 6 gate: Full system operational on testnet with real KARL data flowing. Performance validated. Runbook written.

---

Summary Statistics

MetricValue
Total tasks42
Waves7 (0-6)
Critical path30 days
New Clarity contracts1 (trajectory-anchor.clar, ~120 LOC)
Modified Clarity contracts1 (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 tables5 (in gvc schema)
Estimated on-chain cost (testnet)0 STX (faucet)
Estimated on-chain cost (mainnet)~0.1 STX for first 50 batches
Risks3 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 SessionTasksEstimated Duration
`gvc-schema-setup`0.1, 0.2, 0.3, 0.4, 0.52 hours
`gvc-graph-kernel-ext`1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.98 hours
`gvc-clarity-contract`2.1, 2.2, 2.44 hours
`gvc-anchor-bridge`2.5, 2.7, 2.83 hours
`gvc-merge-engine`3.1, 3.2, 3.3, 3.4, 3.5, 3.66 hours
`gvc-cli`4.1, 4.2, 4.3, 4.4, 4.54 hours
`gvc-viz-refactor`5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.88 hours
`gvc-integration`6.1, 6.2, 6.3, 6.6, 6.7, 6.8, 6.96 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