merge(478): WebSocket CRDT sync layer (manual squash from feature/story-478)

Manual squash-merge of feature/story-478_… into master after the in-pipeline
mergemaster runs failed silently. The 478 agent did substantial real work
across multiple respawn cycles before being interrupted; commits on the
feature branch were intact and verified high-quality but never merged via
the normal pipeline path due to compounding bugs:

- The first mergemaster attempt ran ($0.82 in tokens) and exited "Done"
  cleanly but didn't push anything to master — likely the worktree was
  briefly on master rather than the feature branch when the merge_agent_work
  MCP tool ran, so it found nothing to merge.
- Subsequent timer fires defaulted to spawning coders instead of resuming
  mergemaster, burning more tokens for no progress.
- Bug 510 (split-brain shadows yanking done stories back to current) and
  bug 501 (timers don't cancel on stop/completion) compounded the cost.

What this commit lands:
- server/src/crdt_sync.rs (new, ~518 lines): GET /crdt-sync WebSocket
  handler that subscribes to locally-applied SignedOps and streams them as
  binary frames. Per-peer bounded queue (256 ops) drops slow peers.
- server/src/crdt_state.rs: new public functions subscribe_ops(),
  all_ops_json(), apply_remote_op() backing the sync handler. Adds the
  CRDT_OP_TX broadcast channel (capacity 1024).
- server/src/main.rs: wires up the sync subsystem at startup.
- server/src/http/mod.rs: registers the new endpoint.
- server/src/config.rs: adds optional rendezvous field for outbound peers.
- server/src/worktree.rs: minor changes from the original branch.
- server/Cargo.toml: cfg lint suppression for CrdtNode derive.
- crates/bft-json-crdt/src/debug.rs: fix unused-variable warnings.

Resolved a trivial test-mod merge conflict in crdt_state.rs (both 478 and
503 added new tests at the end of the test module — kept both sets).

Note: this is the squash of the original 478 work that the user explicitly
authorized landing. The earlier rogue commit ac9f3ecf — which added a
DIFFERENT, broken implementation of the same feature directly to master
under the user's identity without consent — was reverted earlier in this
session. The forensic tags rogue-commit-2026-04-09-ac9f3ecf and
pre-502-reset-2026-04-09 still exist for incident audit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-04-09 19:46:29 +01:00
parent 41515e3b8f
commit 5765fb57be
8 changed files with 754 additions and 11 deletions
+12
View File
@@ -528,6 +528,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// Should complete without panic
run_setup_commands(tmp.path(), &config).await;
@@ -552,6 +553,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// Should complete without panic
run_setup_commands(tmp.path(), &config).await;
@@ -576,6 +578,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// Setup command failures are non-fatal — should not panic or propagate
run_setup_commands(tmp.path(), &config).await;
@@ -600,6 +603,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// Teardown failures are best-effort — should not propagate
assert!(run_teardown_commands(tmp.path(), &config).await.is_ok());
@@ -623,6 +627,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
let info = create_worktree(&project_root, "42_fresh_test", &config, 3001)
.await
@@ -653,6 +658,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// First creation
let _info1 = create_worktree(&project_root, "43_reuse_test", &config, 3001)
@@ -724,6 +730,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
let result = remove_worktree_by_story_id(tmp.path(), "99_nonexistent", &config).await;
@@ -753,6 +760,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
create_worktree(&project_root, "88_remove_by_id", &config, 3001)
.await
@@ -829,6 +837,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// Even though setup commands fail, create_worktree must succeed
// so the agent can start and fix the problem itself.
@@ -861,6 +870,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// First creation — no setup commands, should succeed
create_worktree(&project_root, "173_reuse_fail", &empty_config, 3001)
@@ -883,6 +893,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
// Second call — worktree exists, setup commands fail, must still succeed
let result = create_worktree(&project_root, "173_reuse_fail", &failing_config, 3002).await;
@@ -911,6 +922,7 @@ mod tests {
base_branch: None,
rate_limit_notifications: true,
timezone: None,
rendezvous: None,
};
let info = create_worktree(&project_root, "77_remove_async", &config, 3001)
.await