feat(graph): temporal-decay traversal scoring — agent-memory recency (P1–P4)#146
Conversation
…Neighbor Phase 1a of user-facing temporal-decay scoring (agent-memory recency). - MutableEdge.created_ms: u64 — Unix-millis creation stamp, written inside MemGraph::add_edge from the shard-cached thread-local clock (storage::entry::current_time_ms — zero syscall on the insert path, 1ms-tick staleness budget). No add_edge signature change, so all call sites (GRAPH.ADDEDGE handler, Cypher CREATE, WAL replay) inherit the stamp automatically. - created_ms is distinct from valid_from (user-owned bi-temporal valid-time, defaults to 0) — documented on the field. - MergedNeighbor.created_ms propagates the stamp through the segment merge reader. MemGraph edges carry their exact stamp; CSR edges report 0 (= unknown -> decay-neutral) until P3 persists per-edge stamps — deliberately NOT the segment compaction LSN, which would make old edges look freshly created (freshness inversion). - Tests: add_edge stamps from pinned cached clock (ClockPin RAII guard resets the thread-local on drop, panic-safe); merge reader propagates created_ms end-to-end. Plan + verified trace facts: .planning/feat-graph-temporal-decay/CONTEXT.md author: Tin Dang
Phase 1b: GRAPH.QUERY ... --decay <lambda_per_sec> [--time-weight <w>]
steers Cypher shortestPath() toward recently-created edges — the
agent-memory recency primitive. Decay off (no flag) keeps the exact
distance-only behavior (time term is zero, edge ages never read).
- scoring: WeightedCostFn::cost_ms — wall-clock cost variant; age is
(now_ms - created_ms)/1000 so time_weight reads as cost per SECOND of
edge age. created_ms == 0 (unknown: pre-upgrade edge or CSR without
per-edge stamps) drops the time term — neutral, never maximally old.
Future stamps saturate to age 0 (clock skew safe).
- scoring: DecayConfig { lambda_per_sec, time_weight, now_ms } +
cost_fn() builder (distance weight stays 1.0 — decay biases, it does
not replace weight-based shortest paths).
- ExecutionContext.decay: Option<DecayConfig> — rides the same pattern
as valid_time_as_of (VALID_AT). Threaded through run_shortest_path
(all three callers: MATCH-form, expression-form in read.rs, and
eval.rs ShortestPathCall) and eval_expr (write-path callers pass None
— CREATE/SET expressions do not traverse).
- Dijkstra + bounded DFS now cost via cost_ms(created_ms, weight); all
pre-existing call sites use time_weight = 0.0 so behavior is
unchanged when decay is off.
- parse_decay in graph_read.rs (pattern of parse_params): strict
validation — finite non-negative numbers only, dangling flags
rejected, --time-weight without --decay rejected. Applied to the
read, write-aware, and PROFILE execution paths.
Tests (red/green): cost_ms unit semantics (sec conversion, zero-stamp
neutrality, future saturation); Dijkstra decay flips stale-direct vs
fresh-detour path choice; dispatch-level GRAPH.QUERY --decay changes
the chosen path, garbage values error, --time-weight combos validated.
test_dijkstra_composite_cost migrated to pinned-clock ms semantics
(same expected costs — seconds units match the old unitless numbers).
Full lib suite: 3451 passed. Clippy clean.
author: Tin Dang
Phase 2: FT.NAVIGATE idx <query> HOPS N [HOP_PENALTY p] [DECAY <lambda_per_sec>] ages graph-expanded hits by the wall-clock stamp of the edge that discovered them: final_score += lambda * age_seconds (lower = better, consistent with the additive hop penalty and with the GRAPH.QUERY --decay cost semantics). KNN direct hits (hop 0) have no discovery edge and are unaffected. - ExpandedResult.edge_created_ms — the BFS result already carried the discovery edge (Option<MergedNeighbor>) per visited node; expand_results_via_graph now surfaces its created_ms alongside the min-hop bookkeeping instead of discarding it. 0 = unknown -> neutral, matching WeightedCostFn::cost_ms semantics. - parse_decay (DECAY keyword, HOP_PENALTY pattern): strict validation, finite non-negative only, dangling keyword or garbage -> Frame::Error. Reuses graph::scoring::DecayConfig (one knob vocabulary across both surfaces); now_ms from the shard-cached clock. - build_search_args also strips DECAY so the synthetic FT.SEARCH never sees the keyword. Tests (red/green): expansion carries the discovery edge's created_ms end-to-end (pinned clock); decay re-ranks stale-edge hit below fresh-edge hit at equal hop depth and is a no-op when absent; DECAY parse accepts valid values, Ok(None) when absent, rejects nan/negative/ garbage/empty. Full lib suite: 3454 passed. Clippy clean. author: Tin Dang
Phase 3: per-edge wall-clock stamps now survive freeze -> CSR -> disk -> mmap -> compaction, so temporal-decay scoring (GRAPH.QUERY --decay, FT.NAVIGATE DECAY) sees true edge age instead of 0 = unknown once data leaves the mutable segment. Format (follows the v1 -> v2 NodeMeta version-bump precedent): - v3 layout: header | row_offsets | col_indices | edge_meta | node_meta | edge_created_ms (u64 LE per edge, parallel to col_indices). - Header gains edge_created_ms_offset at byte 80 (informational; eats zero padding, struct stays 128B compile-asserted). Parsers stay positional and version-gated with >= thresholds (48B NodeMeta at >= 2, stamp section at >= 3) so a future v4 still reads v3 fields. - Back-compat: v1/v2 files parse with an empty stamp vec (= unknown, decay-neutral); readers only use checked access (.get -> 0). Both parsers (heap from_bytes, mmap from_mmap_file) updated, including the checked-arithmetic expected_len and the mmap zero-copy pointer with the same runtime alignment-check + heap-fallback pattern as node_meta. Fixes a latent durability bug found while tracing producers: compact_segments stamped `version: 1` while to_bytes always writes 48-byte v2+ NodeMeta records — a vacuumed segment written to disk by recovery save misparsed on reload with the 32-byte v1 stride (panic in boomphf MPH build on debug, silent node_meta corruption on release). Merged segments are now v3, and MergedEdge carries created_ms through the dedup (winner-by-LSN keeps its stamp), so vacuum no longer zeroes edge recency. - CsrStorage::for_each_neighbor_edge_ms — zero-alloc neighbor iteration that also yields the stamp; SegmentMergeReader CSR fill now surfaces real per-edge created_ms on MergedNeighbor (was hardcoded 0). - New fuzz target csr_from_bytes (heap + mmap decoders, per repo rule: modified decoders need fuzz coverage), wired into fuzz/Cargo.toml and both fuzz.yml matrices. Known gap (deferred, recorded in plan context): GRAPH.ADDEDGE WAL records carry no created_ms, so WAL-replayed not-yet-frozen edges are re-stamped to replay time after restart — the least-harmful direction (newest edges look new); CSR-resident edges keep true age. Tests (red/green): compacted-segment serialize/parse round-trip (RED on the version bug before the fix); from_frozen populates stamps; v3 round-trip via from_bytes and via mmap (alignment-verified Mmap variant); hand-built v2 bytes parse with empty stamps on both parsers; v2 re-serialization is byte-stable; compaction carries stamps and dedup keeps the winning edge's stamp; merge reader surfaces CSR stamps. Full lib suite: 3462 passed. Clippy clean (default + tokio,jemalloc). author: Tin Dang
Phase 4 of the temporal-decay stack: user-facing documentation and shell test coverage for GRAPH.QUERY --decay / FT.NAVIGATE DECAY. - guides/temporal.mdx: "Temporal-decay traversal scoring" section — cost formula, lambda units (1/seconds), decay-off exactness, 0 = unknown = neutral semantics, CSR v3 stamp lifecycle, and the transaction-time vs valid-time distinction. Limitations gains the WAL-replay re-stamp note. - commands.mdx: decay examples + summary under the Graph section. - CHANGELOG: 0.2.0-alpha "Added" entry covering P1a..P3, including the compact_segments version-stamp durability fix. - scripts/test-commands.sh: TEMPORAL DECAY block (6 cases) — wall-clock stale-direct vs fresh-detour path flip on/off, strict validation errors for --decay/--time-weight/DECAY, and valid-DECAY pass-through. All commands verified against a live dev server (path render is one node id per line; detour detected via B's node id). - scripts/test-consistency.sh: GRAPH.QUERY --decay path-flip parity captured across 1/4/12 shard configs inside the temporal loop. Docs link check (mint broken-links, node 22): no broken links in the touched files; pre-existing parse failures in comparison-valkey.md and production-guide.md are unrelated (present on main). author: Tin Dang
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughImplements temporal-decay traversal scoring end-to-end: per-edge created_ms stamping, CSR v3 per-edge timestamp persistence (heap + mmap), compaction-preserved timestamps, decay-aware cost functions and Cypher wiring, FT.NAVIGATE DECAY ranking, tests, docs, and fuzz/CI updates. ChangesTemporal-Decay Traversal Scoring
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@fuzz/fuzz_targets/csr_from_bytes.rs`:
- Around line 21-22: The code currently calls
tempfile::NamedTempFile::new().unwrap() and
temp.as_file().write_all(data).unwrap() which can panic and produce non-target
crashes; change both unwraps to propagate a safe early return on error (e.g.,
use match/if let Err(_) { return; } or .ok()? and similarly handle the write_all
Result) so the fuzz harness quietly skips this input on setup failure; locate
the temp binding and the calls to NamedTempFile::new and
temp.as_file().write_all in the csr_from_bytes fuzz target and replace unwrap()
usages with early-return error handling.
In `@src/command/graph/mod.rs`:
- Around line 350-354: The current assertion compares the full debug-formatted
Frames (format!("{off:?}") vs format!("{on:?}")) which can differ for unrelated
fields; instead assert on the returned path payload itself: locate the test
variables off and on (the Frame results) and replace the debug-string comparison
with an assertion that the chosen route/sequence of node ids or the path length
differs (e.g., compare off.path vs on.path or map their nodes to ids and
assert_ne on that), ensuring you reference the Frame fields that represent the
route rather than the whole Debug output.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7969687a-851e-49af-907b-86736f5b6c54
⛔ Files ignored due to path filters (1)
fuzz/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (30)
.github/workflows/fuzz.ymlCHANGELOG.mdREADME.mddocs/commands.mdxdocs/guides/temporal.mdxfuzz/Cargo.tomlfuzz/fuzz_targets/csr_from_bytes.rsfuzz/fuzz_targets/rdb_load.rsfuzz/fuzz_targets/resp_parse.rsscripts/test-commands.shscripts/test-consistency.shsrc/command/graph/graph_read.rssrc/command/graph/mod.rssrc/command/vector_search/graph_expand.rssrc/command/vector_search/navigate.rssrc/command/vector_search/tests.rssrc/graph/compaction.rssrc/graph/csr/mmap.rssrc/graph/csr/mod.rssrc/graph/csr/storage.rssrc/graph/cypher/executor/eval.rssrc/graph/cypher/executor/mod.rssrc/graph/cypher/executor/read.rssrc/graph/cypher/executor/shortest_path.rssrc/graph/cypher/executor/write.rssrc/graph/memgraph.rssrc/graph/scoring.rssrc/graph/traversal.rssrc/graph/types.rssrc/graph/visibility.rs
…ay consolidation Code-review pass over the temporal-decay stack (PR #146) surfaced one correctness gap plus consolidation/test-quality issues; all fixed here. Correctness: - GRAPH.QUERY write queries (CREATE/SET/DELETE/MERGE) now REJECT --decay/--time-weight instead of silently ignoring them (and skipping their validation). The guard runs before LSN allocation / any mutation, on both the live auto-routing write branch (graph_query_or_write) and the pub API graph_query_write. Red/green tests at the dispatch level. - The decay path-flip test asserted format!("{off:?}") != format!("{on:?}"), which is vacuously true: the stats row embeds execution_time_us, so any two runs differ. It now asserts node B's id appears in the decay-on path rows and not in the decay-off rows. Consolidation (one home per invariant): - DecayConfig::age_penalty_ms(): single home for the decay formula and the 0-neutral rule; FT.NAVIGATE re-rank delegates instead of inlining the formula (cross-surface drift risk gone). Unit-tested against cost_ms equivalence. - CSR_CURRENT_VERSION const stamped at both segment construction sites (from_frozen, compact_segments) — stale-literal stamping is exactly the bug class behind the v1-stamp durability bug this PR fixed. Parse gates stay numeric (they encode feature-introduction versions). - ClockPin test helper consolidated from 3 copies into storage::entry (cfg(test)); all raw tl_clock_set set/reset pairs in this PR's tests replaced with the panic-safe RAII pin (an expect() failure no longer leaks a pinned clock into the next test on the thread). - downgrade_to_v2() test helper extracts the duplicated v2-bytes construction in csr tests. Hygiene: - navigate.rs parse_decay: dropped a to_owned() String alloc in command dispatch (house rule); the f64 parse now happens inside the borrow. - to_bytes() debug_assert: edge_created_ms is empty or exactly parallel to col_indices. - csr_from_bytes fuzz target: tempfile/write unwraps replaced with early returns (env failures are not findings); doc-comment warning fixed. Docs/scripts: - temporal.mdx + commands.mdx: write-query rejection documented, and the steer-vs-re-rank asymmetry made explicit (GRAPH.QUERY --decay steers the Dijkstra traversal; FT.NAVIGATE DECAY re-ranks an already discovered expansion whose BFS frontier is not decay-aware). - CHANGELOG wording updated; test-commands.sh DECAY-07 covers the write-path rejection. Deliberately unchanged (profile-first rule): cost_ms decay-off short-circuit — the decay-off bench showed zero delta, so no hot-path churn without a measured win. PR #146 author: Tin Dang
…ecay # Conflicts: # .github/workflows/fuzz.yml # fuzz/Cargo.toml
…ocked build
`cargo install cargo-fuzz --locked` stopped compiling on the current
nightly: the lockfile pins rustix 0.36.5, which uses `rustc_*` attributes
the new compiler rejects as reserved ("attributes starting with `rustc`
are reserved for use by the `rustc` compiler"). All 10 Fuzz PR jobs died
in the install step in ~20s; main's own tip (f76901a) failed identically,
so the breakage is environmental, not from this branch.
Switch both fuzz jobs (PR + nightly) to taiki-e/install-action, pinned by
commit SHA (house style per 601b2a7), which downloads the released
cargo-fuzz binary: immune to toolchain drift and ~2 min faster per job.
PR #146
author: Tin Dang
With the prebuilt cargo-fuzz binary the install step passes, but every target then dies in the run step with E0463 "can't find crate for `core`": cargo-fuzz builds the fuzz crate with -Zbuild-std (sanitizer- instrumented std), which requires the rust-src component that the minimal dtolnay/rust-toolchain@nightly profile does not include. Note: the Fuzz workflow has zero successful runs in its history (28 failures / 12 skipped in the last 40) — this job has been broken since introduction, independent of any PR. This commit is the second of two repairs (binary install + rust-src); if more breakage remains beyond these, it is tracked as follow-up work, not a gate for PR #146 (the csr_from_bytes target is validated locally: 1.5M execs, zero crashes). PR #146 author: Tin Dang
…o musl The actual root cause of E0463 "can't find crate for `core`" (visible in the failed build command): the taiki-e prebuilt cargo-fuzz binary is musl-linked and self-detects x86_64-unknown-linux-musl as the default build target, whose std is not installed on the runner. Not a build-std / rust-src issue — revert that addition and pass an explicit `--target x86_64-unknown-linux-gnu` to both fuzz run commands instead. PR #146 author: Tin Dang
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/storage/entry.rs (1)
41-50: 💤 Low valueConsider adding
#[must_use]and documenting the non-nesting constraint.The RAII pattern is correctly implemented, but two optional improvements would make it more robust:
#[must_use]attribute: Prevents accidental immediate drops ifClockPin::set(...)is called without assignment (e.g., forgetting thelet _pin = ...binding).- Non-nesting caveat: Nested
ClockPin::set()calls would not restore the outer clock values when the inner guard drops—the innerDropresets to(0, 0)unconditionally. Current usage (per snippets) is non-nested, but documenting this prevents future footguns.📝 Suggested improvements
/// RAII pin for the thread-local cached clock in tests; resets to 0 on drop /// (panic-safe), restoring the syscall fallback. Single shared home so test /// modules don't each carry their own copy or leak a pinned clock into the -/// next test on the same thread. +/// next test on the same thread. Do NOT nest ClockPin instances—the inner +/// drop resets to (0,0) instead of restoring the outer pin's values. #[cfg(test)] +#[must_use = "ClockPin does nothing unless assigned to a binding (e.g., `let _pin = ...`)"] pub struct ClockPin;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/storage/entry.rs` around lines 41 - 50, Add the #[must_use] attribute to the ClockPin type and document its non-nesting constraint: explain on ClockPin (and its pub fn set) that the returned guard must be held (e.g., let _pin = ClockPin::set(...)) to avoid immediate drop, and note that nested ClockPin::set() calls are unsupported because the Drop implementation unconditionally resets the clock (tl_clock_set) and will not restore an outer guard's state; update the struct and the set() API docs accordingly so callers are warned..github/workflows/fuzz.yml (1)
53-53: 💤 Low valueConsider pinning download-artifact to a commit SHA for supply-chain hardening.
The
actions/download-artifact@v4references are not pinned to a hash, whiletaiki-e/install-actionis properly pinned. For consistency with the blanket policy noted by static analysis and to harden the supply chain, consider pinning these actions to a specific commit SHA.🔐 Example pinning approach
- uses: actions/download-artifact@v4 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8(Choose the appropriate SHA for the v4 release you want to pin to.)
Also applies to: 102-102
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/fuzz.yml at line 53, Replace the floating tag "actions/download-artifact@v4" with a pinned commit SHA for supply-chain hardening; locate all occurrences of the string "actions/download-artifact@v4" in the workflow and change them to the corresponding SHA-pinned ref (e.g. "actions/download-artifact@<commit-sha>") so the action version is immutable and consistent with how "taiki-e/install-action" is pinned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.github/workflows/fuzz.yml:
- Line 53: Replace the floating tag "actions/download-artifact@v4" with a pinned
commit SHA for supply-chain hardening; locate all occurrences of the string
"actions/download-artifact@v4" in the workflow and change them to the
corresponding SHA-pinned ref (e.g. "actions/download-artifact@<commit-sha>") so
the action version is immutable and consistent with how "taiki-e/install-action"
is pinned.
In `@src/storage/entry.rs`:
- Around line 41-50: Add the #[must_use] attribute to the ClockPin type and
document its non-nesting constraint: explain on ClockPin (and its pub fn set)
that the returned guard must be held (e.g., let _pin = ClockPin::set(...)) to
avoid immediate drop, and note that nested ClockPin::set() calls are unsupported
because the Drop implementation unconditionally resets the clock (tl_clock_set)
and will not restore an outer guard's state; update the struct and the set() API
docs accordingly so callers are warned.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e46b911e-f0a8-4bf3-9205-fc92c9fb172f
📒 Files selected for processing (18)
.github/workflows/fuzz.ymlCHANGELOG.mdREADME.mddocs/commands.mdxdocs/guides/temporal.mdxfuzz/Cargo.tomlfuzz/fuzz_targets/csr_from_bytes.rsscripts/test-commands.shsrc/command/graph/graph_read.rssrc/command/graph/mod.rssrc/command/vector_search/navigate.rssrc/graph/compaction.rssrc/graph/csr/mod.rssrc/graph/memgraph.rssrc/graph/scoring.rssrc/graph/traversal.rssrc/graph/types.rssrc/storage/entry.rs
✅ Files skipped from review due to trivial changes (3)
- docs/commands.mdx
- README.md
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (13)
- src/graph/memgraph.rs
- fuzz/fuzz_targets/csr_from_bytes.rs
- fuzz/Cargo.toml
- src/graph/scoring.rs
- src/command/graph/graph_read.rs
- src/graph/types.rs
- docs/guides/temporal.mdx
- src/graph/compaction.rs
- src/command/graph/mod.rs
- src/command/vector_search/navigate.rs
- src/graph/traversal.rs
- scripts/test-commands.sh
- src/graph/csr/mod.rs
… finding
The repaired Fuzz PR job found a real crash in csr_from_bytes after
217K execs: a corrupted/malicious segment file whose node_meta carries
duplicate external_ids feeds duplicate keys into MphNodeIndex::build,
which panics inside boomphf ("attempt to shift left with overflow",
boomphf-0.6.0/src/lib.rs:62). Recovery must fail with CsrError on
corrupted files, never crash.
Both decoders (heap from_bytes + zero-copy mmap loader) now detect the
duplicate during node_id_to_row construction — a free is_some() check on
the existing HashMap insert — and return
CsrError::InvalidData("duplicate node external_id ...").
Red/green: test_duplicate_external_id_rejected_not_panic byte-patches a
valid v3 segment to duplicate an external_id (checksum recomputed) and
asserts both parsers reject it; before the fix it reproduced the exact
boomphf panic. The CI crash input also runs clean now, plus a 5-minute
seeded local fuzz session on the fixed parser.
Note: the panic predates this PR (any v1/v2 file with duplicate ids
triggered it); it surfaces now because this PR added the fuzz target and
repaired the never-green fuzz CI job.
PR #146
author: Tin Dang
…ecay # Conflicts: # CHANGELOG.md
Summary
Wires the dormant temporal-decay scoring engine to user-facing knobs — the agent-memory recency primitive. Paths through recently created edges now win over stale ones, on demand, on two surfaces:
Before this stack,
shortest_path.rshardcodedlambda = 0— the scorers and Dijkstra composite-cost machinery existed but were unreachable (the reason the public docs claim was downgraded in PR #145).Commits (one phase each, red/green TDD)
7590ef3MutableEdge.created_msstamped at insert from the shard-cached clock (zero syscall); surfaced viaMergedNeighbor0e189fbGRAPH.QUERY --decay <λ> [--time-weight <w>]→ExecutionContext→run_shortest_path(all 3 callers);WeightedCostFn::cost_ms; strict validationc525ba1FT.NAVIGATE ... DECAY <λ>— discovery-edge age penalty on graph-expanded hits40e28f6created_msarray survives freeze → disk → mmap → compaction; back-compat with v1/v2 files; newcsr_from_bytesfuzz target2c32d6aguides/temporal.mdx,commands.mdx, README), CHANGELOG, script coverage (test-commands.sh6 cases,test-consistency.sh1/4/12-shard parity)Semantics
|weight| + λ·w·age_seconds(lower = better).created_ms == 0(pre-upgrade edge, pre-v3 segment file) is neutral — no age penalty, never "maximally old". Future stamps saturate to age 0 (clock-skew safe).valid_from/valid_to(VALID_AT).Latent durability bug fixed (found during P3 tracing)
compact_segmentsstamped merged segmentsversion: 1whileto_bytesalways writes 48-byte v2+ NodeMeta records. A vacuumed segment written to disk by recovery save misparsed on reload with the 32-byte v1 stride — panic in boomphf MPH build on debug builds, silent node_meta corruption on release. RED test (test_compacted_segment_serialize_parse_roundtrip) reproduced it; merged segments are now v3 and additionally carry per-edge stamps through dedup (winner-by-LSN keeps its stamp).Known gap (deferred, documented)
GRAPH.ADDEDGE WAL records carry no
created_ms, so not-yet-frozen mutable edges are re-stamped to replay time after restart. Least-harmful direction (newest edges look new); CSR-resident edges keep exact age. Noted inguides/temporal.mdxLimitations; fix would be an additive trailing WAL field — happy to do as a follow-up.Validation
fmt --check,clippy -D warnings(default +runtime-tokio,jemalloc),cargo test --release,cargo test --no-default-features --features runtime-tokio,jemalloc— all greengraph_neighbor_1hop_csrno change (1.0 ns);bfs_csr/1000and/10000within ±2% noise band (sign flips between runs);graph_csr_freeze_64kno change (4.77 ms vs 4.83 ms baseline, p = 0.25)csr_from_byteswired into both fuzz.yml matrices; locally verified on the Linux VM —cargo +nightly fuzz run csr_from_bytes -- -max_total_time=60: 1.5M runs, zero crashes (thefuzz-prCI job needs theci-fuzzlabel, applied to this PR)Follow-up (after PR #145 merges)
docs/design-advantages.mdxepisodic-memory claim can be upgraded from "AS_OF only" to include the now-user-facing decay knob.Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests