Skip to content

Commit ef559c0

Browse files
mahabubul470claude
andcommitted
Phase 5: P2P sync (Noise) + replay + merge + federation
gpp-core: read_raw/write_raw (verified frame transfer) + iter_ids. gpp-sync: Noise_XX over TCP (snow), chunked encrypted transport, state-vector exchange, set-delta object transfer, ref reconcile with fork-preserve on divergence, add-only policy union, optional zero-knowledge Graphex index sync, TOFU static-key pinning, repo-id gate. Loopback integration tests (converge / fork / mismatch). gpp-replay: content-addressed environment snapshots, re-materialization, drift diff, dry-run. gpp-cli: gpp sync (add/remove/status/serve/peer/all), gpp replay, gpp merge (two-parent fork merge), gpp graphex federation (add/list). ROADMAP Phase 5 marked done. 104 workspace tests pass; clippy + rustfmt clean. Verified end-to-end: two repos sync over real Noise TCP, peer replays the synced changeset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 06f92ea commit ef559c0

16 files changed

Lines changed: 1679 additions & 35 deletions

File tree

Cargo.lock

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/gpp-cli/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ gpp-trust = { path = "../gpp-trust" }
2222
gpp-policy = { path = "../gpp-policy" }
2323
gpp-cost = { path = "../gpp-cost" }
2424
gpp-anomaly = { path = "../gpp-anomaly" }
25+
gpp-sync = { path = "../gpp-sync" }
26+
gpp-replay = { path = "../gpp-replay" }
2527
globset.workspace = true
2628
clap.workspace = true
2729
anyhow.workspace = true

crates/gpp-cli/src/cli.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,62 @@ pub enum Command {
8787
Anomaly(AnomalyArgs),
8888
/// Cross-layer audit report
8989
Audit(AuditArgs),
90+
/// Peer-to-peer synchronization
91+
Sync(SyncArgs),
92+
/// Reproduce a changeset's environment
93+
Replay(ReplayArgs),
94+
/// Merge a divergent fork branch into the current branch
95+
Merge(MergeArgs),
96+
}
97+
98+
#[derive(Args)]
99+
pub struct SyncArgs {
100+
#[command(subcommand)]
101+
pub action: Option<SyncSub>,
102+
/// Sync only the Graphex layer (no code objects)
103+
#[arg(long, global = true)]
104+
pub graph_only: bool,
105+
/// Also sync the Graphex index
106+
#[arg(long, global = true)]
107+
pub include_graphex: bool,
108+
}
109+
110+
#[derive(Subcommand)]
111+
pub enum SyncSub {
112+
/// Register a peer
113+
Add { name: String, address: String },
114+
/// Remove a peer
115+
Remove { name: String },
116+
/// Show configured peers
117+
Status,
118+
/// Accept inbound syncs on an address (Ctrl-C to stop)
119+
Serve { address: String },
120+
/// Sync with one peer (default: all configured peers)
121+
Peer { name: String },
122+
}
123+
124+
#[derive(Args)]
125+
pub struct ReplayArgs {
126+
/// Changeset to reproduce (HEAD, a branch, or a hash)
127+
pub changeset: String,
128+
/// Compare a reproduced dir to the snapshot instead of writing
129+
#[arg(long)]
130+
pub diff: bool,
131+
/// Show what would be reproduced without writing
132+
#[arg(long)]
133+
pub dry_run: bool,
134+
/// Directory to materialize into
135+
#[arg(long, default_value = "replay-out")]
136+
pub output: std::path::PathBuf,
137+
/// Capture/override an env var (key=value, repeatable)
138+
#[arg(long = "env", value_name = "K=V")]
139+
pub env: Vec<String>,
140+
}
141+
142+
#[derive(Args)]
143+
pub struct MergeArgs {
144+
/// The fork ref to merge (e.g. "main.fork.office")
145+
pub fork_ref: String,
90146
}
91147

92148
#[derive(Args)]
@@ -323,6 +379,26 @@ pub enum GraphexAction {
323379
},
324380
/// Auto-infer proposed module nodes from the HEAD changeset
325381
Infer,
382+
/// Manage cross-project subgraph federation
383+
Federation {
384+
#[command(subcommand)]
385+
action: FederationAction,
386+
},
387+
}
388+
389+
#[derive(Subcommand)]
390+
pub enum FederationAction {
391+
/// Register a federated source (peer project subgraph)
392+
Add {
393+
#[arg(long)]
394+
project: String,
395+
#[arg(long)]
396+
address: String,
397+
#[arg(long, default_value = "default")]
398+
subgraph: String,
399+
},
400+
/// List federated sources
401+
List,
326402
}
327403

328404
#[derive(Args)]

crates/gpp-cli/src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod phase1;
1212
mod phase2;
1313
mod phase3;
1414
mod phase4;
15+
mod phase5;
1516
mod repo;
1617

1718
use std::process::ExitCode;
@@ -46,6 +47,9 @@ fn main() -> ExitCode {
4647
Command::Cost(a) => phase4::cost(a, repo_override, args.json),
4748
Command::Anomaly(a) => phase4::anomaly(a, repo_override),
4849
Command::Audit(a) => phase4::audit(a, repo_override),
50+
Command::Sync(a) => phase5::sync(a, repo_override),
51+
Command::Replay(a) => phase5::replay(a, repo_override),
52+
Command::Merge(a) => phase5::merge(a, repo_override),
4953
};
5054

5155
match result {

crates/gpp-cli/src/phase3.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use gpp_graphex::{
1111
};
1212
use gpp_history::{Author, RefStore};
1313

14-
use crate::cli::{GraphexAction, GraphexArgs, KeysAction, KeysArgs, McpServerArgs};
14+
use crate::cli::{
15+
FederationAction, GraphexAction, GraphexArgs, KeysAction, KeysArgs, McpServerArgs,
16+
};
1517
use crate::config;
1618
use crate::phase1::parse_time;
1719
use crate::repo::Repo;
@@ -390,6 +392,37 @@ pub fn graphex(args: &GraphexArgs, repo_override: Option<&Path>, json: bool) ->
390392
println!("\nReview with `gpp graphex pending`.");
391393
Ok(())
392394
}
395+
396+
GraphexAction::Federation { action } => {
397+
let fed_dir = repo.gpp_dir().join("graphex").join("federation");
398+
std::fs::create_dir_all(&fed_dir)?;
399+
let sources = fed_dir.join("sources.toml");
400+
match action {
401+
FederationAction::Add {
402+
project,
403+
address,
404+
subgraph,
405+
} => {
406+
let mut body = std::fs::read_to_string(&sources).unwrap_or_default();
407+
body.push_str(&format!(
408+
"[[source]]\nproject = {project:?}\naddress = {address:?}\nsubgraph = {subgraph:?}\n\n"
409+
));
410+
std::fs::write(&sources, body)?;
411+
println!(
412+
"federated source added: {project} ({subgraph}) @ {address}\n\
413+
pull it with `gpp sync --graph-only` once added as a peer"
414+
);
415+
Ok(())
416+
}
417+
FederationAction::List => {
418+
match std::fs::read_to_string(&sources) {
419+
Ok(b) if !b.trim().is_empty() => print!("{b}"),
420+
_ => println!("(no federated sources)"),
421+
}
422+
Ok(())
423+
}
424+
}
425+
}
393426
}
394427
}
395428

0 commit comments

Comments
 (0)