-
Notifications
You must be signed in to change notification settings - Fork 0
feat(replication): v0.7 graph-plane replication — live GRAPH.* stream + PSYNC snapshot backfill #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(replication): v0.7 graph-plane replication — live GRAPH.* stream + PSYNC snapshot backfill #279
Changes from all commits
6ad4fb9
fe9851c
fa193ba
2ba72c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,6 +83,70 @@ pub fn validate_invalidate(args: &[Frame]) -> Result<(u64, bool, Bytes), Frame> | |
| Ok((entity_id, is_node, graph_name)) | ||
| } | ||
|
|
||
| /// Serialize the deterministic, wall-clock-pinned replication form of | ||
| /// TEMPORAL.INVALIDATE (v0.7 graph replication, adversarial round-2 finding | ||
| /// B): `TEMPORAL.INVALIDATE-AT <graph> <N|E> <entity_id> <wall_ms>`. | ||
| /// | ||
| /// The user command captures `wall_ms` at execution time, so streaming it | ||
| /// verbatim would let master and replica disagree on `valid_to`; and the | ||
| /// drained `GraphTemporal` WAL record is a binary wal_v3 payload the RESP | ||
| /// replication link cannot carry. This internal RESP form pins the master's | ||
| /// wall clock; the replica applies it via `apply_invalidate` with the SAME | ||
| /// `wall_ms` (see `replication::apply`). | ||
| #[cfg(feature = "graph")] | ||
| pub fn serialize_invalidate_at( | ||
| graph_name: &[u8], | ||
| is_node: bool, | ||
| entity_id: u64, | ||
| wall_ms: i64, | ||
| ) -> Vec<u8> { | ||
| fn write_bulk(buf: &mut Vec<u8>, data: &[u8]) { | ||
| let mut n = itoa::Buffer::new(); | ||
| buf.push(b'$'); | ||
| buf.extend_from_slice(n.format(data.len()).as_bytes()); | ||
| buf.extend_from_slice(b"\r\n"); | ||
| buf.extend_from_slice(data); | ||
| buf.extend_from_slice(b"\r\n"); | ||
| } | ||
| let mut id_buf = itoa::Buffer::new(); | ||
| let mut ms_buf = itoa::Buffer::new(); | ||
| let mut buf = Vec::with_capacity(96 + graph_name.len()); | ||
| buf.extend_from_slice(b"*5\r\n"); | ||
| write_bulk(&mut buf, b"TEMPORAL.INVALIDATE-AT"); | ||
| write_bulk(&mut buf, graph_name); | ||
| write_bulk(&mut buf, if is_node { b"N" } else { b"E" }); | ||
| write_bulk(&mut buf, id_buf.format(entity_id).as_bytes()); | ||
| write_bulk(&mut buf, ms_buf.format(wall_ms).as_bytes()); | ||
| buf | ||
| } | ||
|
|
||
| /// Parse the argument list of a replicated `TEMPORAL.INVALIDATE-AT` record | ||
| /// (inverse of [`serialize_invalidate_at`], minus the command name). | ||
| /// Returns `(graph_name, is_node, entity_id, wall_ms)` or `None` on any | ||
| /// malformed field — the replica warns and skips rather than diverging | ||
| /// silently on garbage. | ||
| #[cfg(feature = "graph")] | ||
| pub fn parse_invalidate_at(args: &[Frame]) -> Option<(Bytes, bool, u64, i64)> { | ||
| if args.len() != 4 { | ||
| return None; | ||
| } | ||
| let bulk = |f: &Frame| -> Option<Bytes> { | ||
| match f { | ||
| Frame::BulkString(b) | Frame::SimpleString(b) => Some(b.clone()), | ||
| _ => None, | ||
| } | ||
| }; | ||
| let graph_name = bulk(&args[0])?; | ||
| let is_node = match bulk(&args[1])?.as_ref() { | ||
| b"N" => true, | ||
| b"E" => false, | ||
| _ => return None, | ||
| }; | ||
| let entity_id: u64 = std::str::from_utf8(&bulk(&args[2])?).ok()?.parse().ok()?; | ||
| let wall_ms: i64 = std::str::from_utf8(&bulk(&args[3])?).ok()?.parse().ok()?; | ||
| Some((graph_name, is_node, entity_id, wall_ms)) | ||
| } | ||
|
Comment on lines
+86
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Look for existing unit tests exercising the invalidate-at codec.
rg -nP 'serialize_invalidate_at|parse_invalidate_at' -g '*.rs' -C2Repository: pilotspace/moon Length of output: 153 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n## temporal.rs outline\n'
ast-grep outline src/command/temporal.rs --view expanded || true
printf '\n## relevant code around serialize/parse\n'
sed -n '1,220p' src/command/temporal.rs | cat -n
printf '\n## search for invalidate-at and related tests\n'
rg -n --hidden -S 'TEMPORAL\.INVALIDATE-AT|serialize_invalidate_at|parse_invalidate_at|TEMPORAL\.INVALIDATE|consistency-test|consistency test|unit test' src test tests . || true
printf '\n## command module files\n'
git ls-files 'src/command/**' | sed -n '1,200p'Repository: pilotspace/moon Length of output: 50371 Add a round-trip unit test for 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| /// Apply a TEMPORAL.INVALIDATE mutation to a graph store. | ||
| /// | ||
| /// Sets `valid_to = wall_ms` on the entity and pushes the WAL payload into | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Clarify this as ignored/manual coverage.
tests/replication_graph.rsis#[ignore], and CI only runsreplication_hardening, so the zero-reconnect assertion won’t run in standard test jobs. Either note that here or add an explicit--ignoredjob forreplication_graph.🤖 Prompt for AI Agents