diff --git a/packages/kits/GeniusLocusKit/Sources/GeniusLocusKit/Brain/ContradictionHunt.swift b/packages/kits/GeniusLocusKit/Sources/GeniusLocusKit/Brain/ContradictionHunt.swift index 8c12cdd4..9c4657f5 100644 --- a/packages/kits/GeniusLocusKit/Sources/GeniusLocusKit/Brain/ContradictionHunt.swift +++ b/packages/kits/GeniusLocusKit/Sources/GeniusLocusKit/Brain/ContradictionHunt.swift @@ -230,6 +230,10 @@ public extension GeniusLocusKit { // at rank #399) and a whole-partition float scan is ~3 s/probe. BM25 // returns SOURCE (drawer) IDs directly, so no chunk→drawer remap. // `seenPairs` keys on drawer IDs, so both lanes dedupe together. + func huntDrawerVisible(_ drawer: Drawer) -> Bool { + drawer.adjectiveSensitivity.rawValue <= AdjectiveSensitivity.elevated.rawValue + } + if let corpus = corpusKits[handle] { // Probe drawers = the owning drawers of the recent probe chunks. let probeChunkUUIDs = probeIDs.compactMap { UUID(uuidString: $0) } @@ -240,12 +244,16 @@ public extension GeniusLocusKit { if !probeDrawerIDs.isEmpty { probeDrawers = try await estate.hydrateBodies(ids: probeDrawerIDs) } - for probeDrawer in probeDrawers where !probeDrawer.content.isEmpty { + for probeDrawer in probeDrawers + where !probeDrawer.content.isEmpty && huntDrawerVisible(probeDrawer) { let query = String(probeDrawer.content.prefix(Self.huntBM25QueryCharLimit)) guard let hits = try? await corpus.bm25TopKBySource( query: query, limit: Self.huntBM25CandidateK) else { continue } for hit in hits where hit.sourceID != probeDrawer.id { + let hitDrawers = (try? await estate.hydrateBodies(ids: [hit.sourceID])) ?? [] + guard let hitDrawer = hitDrawers.first, + huntDrawerVisible(hitDrawer) else { continue } let key = Self.pairKey(probeDrawer.id, hit.sourceID) guard seenPairs.insert(key).inserted else { continue } candidatePairs.append((min(probeDrawer.id, hit.sourceID), @@ -272,7 +280,8 @@ public extension GeniusLocusKit { for pair in candidatePairs { guard let a = drawersByID[pair.a], let b = drawersByID[pair.b], - a.tombstonedAt == nil, b.tombstonedAt == nil else { continue } + a.tombstonedAt == nil, b.tombstonedAt == nil, + huntDrawerVisible(a), huntDrawerVisible(b) else { continue } // Incremental watermark: at least one side must be new enough. if let watermark = filedAfter, a.filedAt <= watermark, b.filedAt <= watermark { continue } diff --git a/packages/kits/GeniusLocusKit/rust/src/coordinator.rs b/packages/kits/GeniusLocusKit/rust/src/coordinator.rs index 00672e09..81e973c5 100644 --- a/packages/kits/GeniusLocusKit/rust/src/coordinator.rs +++ b/packages/kits/GeniusLocusKit/rust/src/coordinator.rs @@ -2799,6 +2799,10 @@ impl EstateCoordinator { let node_names = build_node_name_map(self.node_stores.get(handle), &all_drawers); let drawers_by_id: std::collections::HashMap<&str, &locus_kit::drawer::Drawer> = all_drawers.iter().map(|d| (d.id.as_str(), d)).collect(); + let hunt_drawer_visible = |drawer: &locus_kit::drawer::Drawer| { + drawer.adjective_sensitivity().raw_value() + <= locus_kit::adjectives::AdjectiveSensitivity::Elevated.raw_value() + }; // Lane 2 — the corpus lane, the ONLY lane a production estate populates // (the encode drain writes chunk-keyed rows under the corpus provider's @@ -2828,7 +2832,7 @@ impl EstateCoordinator { Some(d) => *d, None => continue, }; - if pd.content.is_empty() { + if pd.content.is_empty() || !hunt_drawer_visible(pd) { continue; } // Cap the query length so per-probe cost is independent of body size. @@ -2842,6 +2846,12 @@ impl EstateCoordinator { if source_id == *pd_id { continue; } + let Some(hit_drawer) = drawers_by_id.get(source_id.as_str()) else { + continue; + }; + if !hunt_drawer_visible(hit_drawer) { + continue; + } let key = pair_key(pd_id, &source_id); if !seen_pairs.insert(key) { continue; @@ -2867,7 +2877,11 @@ impl EstateCoordinator { (Some(a), Some(b)) => (*a, *b), _ => continue, }; - if a.tombstoned_at.is_some() || b.tombstoned_at.is_some() { + if a.tombstoned_at.is_some() + || b.tombstoned_at.is_some() + || !hunt_drawer_visible(a) + || !hunt_drawer_visible(b) + { continue; } // Incremental watermark: at least one side must be new enough.