Skip to content

fix(bloom): restrict outgoing filter merge to tree peers only - #88

Merged
pablof7z-agent merged 2 commits into
mainfrom
fix/bloom-tree-only-merge
Jul 24, 2026
Merged

fix(bloom): restrict outgoing filter merge to tree peers only#88
pablof7z-agent merged 2 commits into
mainfrom
fix/bloom-tree-only-merge

Conversation

@pablof7z-agent

Copy link
Copy Markdown
Owner

The bug

bloom_snapshot() in crates/fips-engine/src/engine_bloom_events.rs computed each node's OUTGOING bloom filter by OR-merging the stored filter view of every active neighbor (full-mesh) — iterating graph.active_neighbors(from) and union_assign-ing every peer view except the destination.

Real FIPS mandates a TREE-ONLY merge: only spanning-tree peers (parent + children) may be folded into an outgoing filter. Non-tree mesh peers' filters are stored/consulted for routing but MUST NOT be propagated transitively.

Why it matters

Folding mesh shortcuts into outgoing filters saturates every node's filter toward the full network (bloom saturation) and destroys the directional asymmetry along tree edges:

  • Upward (child → parent): contains the child's subtree
  • Downward (parent → child): contains the complement

The full-mesh merge blurred both directions toward "everything", inflating FPR and cardinality and undermining the routing model.

Ground truth

fips/docs/design/fips-bloom-filters.md:

  • lines 133-144 — tree-only merge rule
  • lines 147-154 — split-horizon exclusion (still applies)
  • lines 188-194 — per-peer table (out→A = Self+B, excludes mesh C)
  • lines 340-359 — the all-peer OR-union that is legitimate ONLY for the separate mesh-size cardinality estimator (left untouched)

The fix

  • bloom_snapshot() now filters active_neighbors(from) down to tree peers before merging, via a new bloom_is_tree_peer() helper derived from the exact same parent/child classification bloom_peer_role() uses (no duplicated/subtly-different rule).
  • Split-horizon preserved: the destination peer to is still excluded from the merge.
  • Send path unchanged: mesh peers still RECEIVE FilterAnnounce — only what gets MERGED into the outgoing filter changed.
  • The separate mesh-size cardinality estimator's all-peer union (bloom_model in the recovery path) is untouched.
  • docs/graph-native-bloom.md corrected from "every peer view" to tree-only, citing the FIPS rule.

Tests

  • cargo test --workspace: 152 passed, 0 failed.
  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings: clean.
  • scripts/check-fips-codecs.sh: clean — pinned fixtures/codecs/fips-80c956a.json unaffected (this fix is merge scope, not wire sizes).

Regenerated goldens

The corrected merge scope shifts bloom snapshots, which ripple into derived artifacts. Regenerated via the blessed example generators (generate_m1_fixtures, generate_m2_fixtures, regenerate_m3_fixtures, generate_m4_fixtures) — no hand-edited bytes:

  • fixtures/m1/root-ratchet-12-artifact.json, -reproduction.json
  • fixtures/m2/root-ratchet-recovery-artifact.json, -reproduction.json
  • fixtures/m3/search-result.json, shrink-result.json, fixtures/corpus/m3-root-ratchet/reproduction.json
  • fixtures/m4/billion-demo.json, hybrid-anomaly.json

Reproducibility/logic assertions (e.g. assert_eq!(first, second), schema validation, all-assertions-pass) remained green; only the derived bit-for-bit golden comparisons required regeneration.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MbLLiWJfyxPXjxy2u6JxKd

pablof7z and others added 2 commits July 24, 2026 13:49
fipsd computed each node's outgoing bloom filter by OR-merging the stored
view of EVERY active neighbor (full-mesh). FIPS mandates a TREE-ONLY merge:
only spanning-tree peers (parent + children) may be folded into an outgoing
filter. Non-tree mesh peers still receive/store FilterAnnounce for routing
queries, but their filters must not be propagated transitively.

Folding mesh shortcuts into outgoing filters saturates every node's filter
toward the full network and destroys the upward=subtree / downward=complement
directional asymmetry along tree edges.

Ground truth: fips/docs/design/fips-bloom-filters.md lines 133-144 (tree-only
merge rule), 147-154 (split-horizon still applies), 188-194 (per-peer table:
out->A = Self+B, excludes mesh C). The all-peer OR-union remains correct and
untouched for the separate mesh-size cardinality estimator (lines 340-359).

Change:
- bloom_snapshot() now filters active_neighbors down to tree peers before
  merging, reusing a new bloom_is_tree_peer() helper derived from the exact
  same parent/child classification used by bloom_peer_role(). Split-horizon
  exclusion of the destination peer is preserved; the send-to-all-neighbors
  path (who receives filters) is unchanged.
- docs/graph-native-bloom.md corrected from "every peer view" to tree-only,
  citing the FIPS rule and noting the estimator path is intentionally distinct.

Regenerated derived golden fixtures (m1/m2/m3/m4) whose snapshots shift under
the corrected merge scope, via the blessed example generators. The pinned
codec fixture is unaffected (merge scope, not wire sizes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MbLLiWJfyxPXjxy2u6JxKd
These derived fixtures are only exercised by scripts/check.sh (not the
cargo test suite): the m6 analysis index is computed from the m2 artifact,
and the m8 release audit records artifact/analysis sha256 hashes. Both shift
mechanically under the corrected tree-only bloom merge. Regenerated via the
blessed CLI paths (analyze index / release audit); only the derived hashes
changed (atlas_sha256 unchanged, confirming scope).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MbLLiWJfyxPXjxy2u6JxKd
@pablof7z-agent
pablof7z-agent merged commit 0c7c9a4 into main Jul 24, 2026
8 checks passed
@pablof7z-agent
pablof7z-agent deleted the fix/bloom-tree-only-merge branch July 24, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants