Skip to content

Knowledge-Policy Scoring Pipeline (Pre-Visibility) ​

End-to-end architecture of NornicDB's knowledge-policy scoring stage, from a read-path entry point through the compiled binding lookup, decay/promotion math, and suppression decision — right up to the hand-off to the Visibility Layer.

This document diagrams the code as it exists in the repository. Every stage in the flowchart maps to a specific file and function in pkg/knowledgepolicy/ and pkg/storage/badger_decay_filter.go. See the Legend below for line-level references.

For the user-facing policy DDL and semantics, see Knowledge-Layer Policies. For what happens after an entity is marked suppressed, see Visibility Suppression and Deindex.


Full Pipeline ​


Legend — Source References ​

Every box in the diagram corresponds to a real function in the codebase.

Diagram boxSource
filterNodeByDecay / filterEdgeByDecaypkg/storage/badger_decay_filter.go:56, 107
revealAll gate & BeginQueryRevealScopepkg/storage/badger_decay_filter.go:30, 37
Resolver.ResolveNode label-subset walk & conflict resolutionpkg/knowledgepolicy/resolver.go:23, 97, 130
BindingTable (wildNode/wildEdge, sorted-label key)pkg/knowledgepolicy/compiled_binding.go:39
CompiledBinding (flattened bundle + binding + promotion)pkg/knowledgepolicy/compiled_binding.go:21
Scorer.score (main scoring path)pkg/knowledgepolicy/scorer.go:187
resolveAnchor (CREATED / VERSION / LAST_ACCESSED / CUSTOM)pkg/knowledgepolicy/scorer.go:316
computeDecay (inverse-in-place via negative half-life)pkg/knowledgepolicy/scorer.go:355
selectPromotionProfile (WHEN predicate eval)pkg/knowledgepolicy/scorer.go:290
computeFinalScore (multiplier + floors + cap)pkg/knowledgepolicy/scorer.go:402
SuppressionEligible = FinalScore < VisibilityThreshold && !HasNoDecayPropertypkg/knowledgepolicy/scorer.go:271
Suppression reason classification (score_floor vs below_threshold)pkg/knowledgepolicy/scorer.go:277–285
AccessMetaEntry (Fixed / Overflow / KalmanFilters)pkg/knowledgepolicy/access_meta.go:11
AccessAccumulator (P-local sharded)pkg/knowledgepolicy/access_accumulator.go:24
AccessFlusher.applyOnAccessMutationspkg/knowledgepolicy/on_access_runtime.go:9
ProcessKalmanMutation (velocity + gain)pkg/knowledgepolicy/kalman_accumulator.go:9
SuppressionRecheckFunc / EmbedInvalidateFunc feedbackpkg/knowledgepolicy/access_flusher.go:42, 39

Pipeline Stages (Pre-Visibility) ​

  1. Query entry. The read path in BadgerEngine calls filterNodeByDecay / filterEdgeByDecay. A reveal() scope short-circuits scoring for that query only.
  2. Scorer selection. The namespace extracted from the entity ID picks a per-tenant Scorer. If none exists, the explicit VisibilitySuppressed flag is honored (and counted as explicit_flag in metrics).
  3. Resolver. Sorted-label subset walk against BindingTable; ties are resolved by lowest DecayBinding.Order; wildcard fallback for unmatched labels or edge types.
  4. CompiledBinding. Pre-flattened bundle + binding overrides + promotion policy + property rules + compiled WHEN predicates. NoDecay short-circuits to a neutral score of 1.0 with NoDecay=true; the read filter still honors an explicit VisibilitySuppressed flag for those entities.
  5. Anchor + age. resolveAnchor picks the start time (CREATED / VERSION / LAST_ACCESSED / CUSTOM). Age is now − anchor, clamped to zero.
  6. Base decay. Exponential, linear, step, or none. A negative HalfLifeNanos inverts the curve in place (1 − f) — this is how idle-time consolidation is implemented, without adding new function/type/flag values to the schema.
  7. Promotion. WHEN predicates are evaluated over AccessMetaEntry + entity properties; the profile with the highest Multiplier (then ScoreFloor) wins.
  8. Final score. base × multiplier, clamped by [promoFloor, promoCap], then floored by DecayFloor.
  9. Suppression decision. FinalScore < VisibilityThreshold && !HasNoDecayProperty sets SuppressionEligible=true; the scorer classifies the reason as score_floor or below_threshold, and the read filter drops the entity from the result set.
  10. Access feedback (out of band). Surviving entities feed the AccessAccumulator, drained by AccessFlusher, which applies ON ACCESS mutations (optionally through the Kalman filter), merges fixed-field deltas, re-evaluates property suppression, optionally calls EmbedInvalidate when property suppression state changes, persists AccessMetaEntry, and finally triggers SuppressionRecheck.

Next Stage ​

Everything downstream of SuppressionEligible now has its own architecture page: Knowledge-Policy Visibility Layer and Deindex.

That follow-on document covers:

  • query-time reveal() bypass behavior
  • reveal-scope locking with BeginQueryRevealScope
  • persisted suppression flags and deindex work items
  • tombstone-based hiding of BM25 and vector-backed index entries
  • retention sweep behavior once suppressed entities remain in storage long enough

See Also ​

Go consumers read metadata through ReadAccessMeta. Replace GetAccessMeta(id) with ReadAccessMeta(knowledgepolicy.AccessMetaReadRequest{EntityID: id}) and use the result's Entry. Replace ScanAccessMeta() with ReadAccessMeta(knowledgepolicy.AccessMetaReadRequest{Mode: knowledgepolicy.AccessMetaReadAll}) and use Entries. Both return the storage error separately. Point reads preserve work and tombstone entries; all reads enumerate entity access state in one captured transaction and skip internal work keys.

Analytics privacy