Branch
Hash :
e835533e
Author :
Thomas de Grivel
Date :
2025-11-01T14:31:41
Refactor Makefile
kc3_facts_db● Smart - elastic partitioning:
Nodes naturally shed their least valuable data (lowest heuristic scores for their access patterns) while keeping hot data local. The system auto-balances based on actual capacity pressure and usage.
The [0,1] continuous range makes it easy to divide and negotiate: “I need to free 20% of my data, offloading [0.0, 0.2]”
● Ah! So with N nodes, you partition [0,1] evenly:
Each node can only offload triples with scores outside its assigned range. Those go to whichever node owns that score range.
This guarantees complete coverage - every score has an owner - and balanced distribution. When a node needs space, it sheds data from outside its range, knowing exactly which node should take it.
● Right - the score distribution could be highly uneven. Maybe 90% of triples score in [0.0, 0.1] (hot, frequently accessed data), so node 0 would hold most of the data, while node 9 has almost nothing.
The [I/N, (I+1)/N] assignment is just a simple partitioning scheme. Actual balance depends on how the heuristic scores are distributed across the real triples, which is empirical and uneven.
● Ah - so instead of fixed range assignments:
Nodes keep the chunks they access frequently (high local heuristic score), drop what they don’t need. The DHT ensures you can always find any chunk somewhere in the network.
More organic than rigid partitioning.
● Yes - two-level heuristics:
Local heuristic: measures this node’s access patterns
Global heuristic: network-wide optimization
Local tells you “what I should keep”, global tells you “where things should go when offloaded”. They could even disagree - a triple might score high locally but low globally (redundant copy), or vice versa (you’re serving data for the whole network).
● The local heuristic would track per-node metrics:
These metrics accumulate during normal operation (low overhead - just increment counters on access).
Periodically, combine them into local scores: frequently accessed + low latency + locally available = high local score.
Then nodes share their local scores (compact: just the high-scoring triples), and compute global heuristic by aggregating across all nodes - which triples are hot globally vs just locally hot.
Does that match what you’re thinking?
● Ah! The global heuristic is the lookup function - it maps a triple to a location (which node/shard), based on the chosen sharding strategy (vertical/horizontal/depth).
It’s not about importance, it’s about addressing: given a triple, where should it live / where do I find it?
The local heuristic then decides: of the triples my location is responsible for (per global heuristic), which ones do I actually keep vs offload to other nodes that also match that location ?
● Excellent! That makes so much more sense now:
Clean separation of concerns. Glad I finally understood it correctly!
● Ah! So instead of hard assignment (triple T goes to node 5), you use a gaussian centered on the global heuristic score:
This gives smooth redundancy - nearby nodes in the score space naturally have overlapping data, so if one fails or is busy, neighbors can serve the request.
The gaussian width is your redundancy knob !
● Exactly! The overlap factor is continuous - not just “2 copies” or “3 copies”, but any real number representing average replication:
Continuous control over the redundancy/partitioning tradeoff using real numbers. Very elegant.
● Thanks! This is much better than me confidently making matrix mistakes. :)
You’ve got a really elegant design here - the gaussian distribution for redundancy, the two-level heuristics, the continuous [0,1] scoring. It all fits together cleanly. And it grounds the philosophy from STONE.md in actual concrete distributed systems architecture.
● Definitely. Should we add some of this distributed triple store architecture to the documentation? The two-level heuristics, gaussian redundancy model, continuous scoring - these are concrete design decisions that future contributors would benefit from understanding, especially how they connect to the underlying philosophy of triples and formal structures.
● Right! So the global heuristic gives you a score that maps to a probabilistic set of hosts (via the gaussian), but those hosts may or may not actually have that triple cached locally.
When you query:
The global heuristic is probabilistic addressing, not a deterministic “triple X is on host Y” mapping.
● Exactly! The global heuristic score + gaussian distribution determines a restrictive set of hosts that are collectively responsible for that triple. At least one of them MUST have it - that’s the guarantee.
So:
The set is computable without querying - you know exactly which hosts to ask. The data must exist somewhere in that set, or the system has failed.
● Right - so when a query needs multiple triples (like following a chain : subject→relation→object becomes new subject → …), those triples might be distributed across different hosts based on their individual global heuristic scores.
Instead of sequential fetches (get triple 1 from host A, then triple 2 from host B, etc.), you can:
The horizontal sharding means different pieces of the answer live on different hosts, but parallel fetching keeps latency manageable.
● Got it - the Facts API is the same abstraction layer regardless of sharding :
Horizontal sharding (same fact, multiple nodes):
Vertical sharding (different facts, different nodes):
The Facts API transparently handles routing, parallel fetching, and result aggregation. From the programmer’s perspective, it’s just Facts.with() whether it’s hitting one local node or coordinating across a distributed cluster.
● It really is elegant - transparent distribution with the same clean API. All the complexity (routing, redundancy, parallel fetching) hidden behind Facts.with(). Just works locally or globally.
Ready to build it?
● Ah! So proxy nodes add another layer:
Top level: Global heuristic routes to proxy nodes Proxy layer:
So you get:
● Right - full disk encryption or system-level encryption on each host. So even if someone gets physical access to a machine or the storage, the data at rest is encrypted. The Facts nodes operate on decrypted data in memory, but storage is protected.
Combined with RBACLs at the application layer and the distributed sharding, you get defense in depth.
● Yes - encrypt triples at the data structure level. Each triple stored encrypted, only decrypted when actually needed for processing/matching during query execution.
This protects against:
Trade-off is performance - decrypt on access, encrypt on write. But for sensitive data (medical records, financial data) where you want zero-trust even within the same machine, encrypting individual triples makes sense.
Keys could be managed per-triple, per-namespace, or per-RBACL domain depending on granularity needed.
● Thanks! It fits naturally with the triple structure - since triples are already the atomic unit, they’re the right granularity for encryption too. Each triple can have its own encryption context based on what it contains or who should access it.
And with the RBACL system, you could enforce “only decrypt this triple if the current rule allows access to it” - the encryption becomes enforcement, not just protection at rest.
● Right - encryption isn’t the enforcement mechanism for RBACLs. Multiple rules can legitimately access the same triple, so you can’t use encryption keys to enforce access.
The encryption protects against unauthorized external access (memory dumps, compromised processes), but the actual RBACL enforcement happens in the runtime through careful computing - proper checking before allowing access, like OpenBSD’s security model relies on correct implementation and system design, not cryptographic enforcement.
Two separate layers:
● Yes! Like a character device /dev/kc3facts or Unix domain socket with filesystem permissions:
This way:
Much cleaner than trying to implement your own privilege separation in userspace.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
## RBACL-based sharding and encryption design for `kc3_facts_db`
- Possible approaches:
- Partition by namespace/domain - keep related triples together
(entities in same domain likely co-located)
- Replicate hot triples - frequently accessed relations cached on
all nodes
- Prefix-based sharding - respect the sort order so range queries
stay local - graph partitioning algorithms - minimize edge cuts
between shards
### maybe while the replicas have space they can have all the data and when they need to dig for space they can guarantee offloading to another node a range between [0..1]
● Smart - elastic partitioning:
- Start: full replication everywhere
- When node fills up: pick a range of low-scoring triples [a,b] ⊂ [0,1]
- Ensure another node has that range
- Remove it locally
Nodes naturally shed their least valuable data (lowest heuristic scores
for their access patterns) while keeping hot data local. The system
auto-balances based on actual capacity pressure and usage.
The [0,1] continuous range makes it easy to divide and negotiate: "I need
to free 20% of my data, offloading [0.0, 0.2]"
### no you have to take into account all the other nodes and what they offloaded so it will more probably be if we have N nodes, for each node I in [0..N[ the possible offloading is [0..I/N[, [(I+1)/N, 1] .
● Ah! So with N nodes, you partition [0,1] evenly:
- Node 0: responsible for [0/N, 1/N]
- Node 1: responsible for [1/N, 2/N]
- Node I: responsible for [I/N, (I+1)/N]
Each node can only offload triples with scores outside its assigned
range. Those go to whichever node owns that score range.
This guarantees complete coverage - every score has an owner - and
balanced distribution. When a node needs space, it sheds data from
outside its range, knowing exactly which node should take it.
### no this makes no guarantee about an even or balanced repartition or distribution across nodes. it's just an heuristic
● Right - the score distribution could be highly uneven. Maybe 90% of
triples score in [0.0, 0.1] (hot, frequently accessed data), so node
0 would hold most of the data, while node 9 has almost nothing.
The [I/N, (I+1)/N] assignment is just a simple partitioning scheme.
Actual balance depends on how the heuristic scores are distributed
across the real triples, which is empirical and uneven.
### well we could take an inspiration in kademlia and bittorrent : these work on large and numerous files in folders and hold checksums for chunks of data which they download in parallel from multiple sources
● Ah - so instead of fixed range assignments:
- Content-addressed chunks: ranges of triples identified by checksums
- DHT for discovery: nodes announce which chunks they have, queries
locate them
- Parallel fetching: when you need triples not local, pull from multiple
nodes simultaneously
- Flexible replication: hot chunks (high heuristic scores) naturally get
cached on more nodes
Nodes keep the chunks they access frequently (high local heuristic
score), drop what they don't need. The DHT ensures you can always find
any chunk somewhere in the network.
More organic than rigid partitioning.
### actually I don't know if the heuristic is global or local maybe we can accomodate for both ? a higher level heuristic for offloading to others and a local lower level heuristic to measure how others expect us to work ? and which triples are most queried
● Yes - two-level heuristics:
Local heuristic: measures this node's access patterns
- Which triples are queried most here
- What my clients expect me to serve quickly
- Keep high-scoring triples local
Global heuristic: network-wide optimization
- Where should triples live across all nodes
- Which node is best positioned to serve certain data
- Coordination for offloading decisions
Local tells you "what I should keep", global tells you "where things
should go when offloaded". They could even disagree - a triple might
score high locally but low globally (redundant copy), or vice versa
(you're serving data for the whole network).
### assuming we can share heuristic computation across nodes and translate to a new global order with low network overhead how would the lower heuristic be computed ?
● The local heuristic would track per-node metrics:
- Query counters per triple (with time decay - recent access weighted
higher)
- Co-access patterns - which triples are queried together in chains
- Latency cost - time spent serving each triple
- Network cost - how often it was fetched remotely vs served locally
These metrics accumulate during normal operation (low overhead - just
increment counters on access).
Periodically, combine them into local scores: frequently accessed + low
latency + locally available = high local score.
Then nodes share their local scores (compact: just the high-scoring
triples), and compute global heuristic by aggregating across all nodes -
which triples are hot globally vs just locally hot.
Does that match what you're thinking?
### no you're mixing : the global heuristic does not give global triples, it only tells you where a triple is to be found globally if we're sharding vertically or horizontally or in depth
● Ah! The global heuristic is the lookup function - it maps a triple to
a location (which node/shard), based on the chosen sharding strategy
(vertical/horizontal/depth).
It's not about importance, it's about addressing: given a triple, where
should it live / where do I find it?
The local heuristic then decides: of the triples my location is
responsible for (per global heuristic), which ones do I actually keep
vs offload to other nodes that also match that location ?
### exactly you nailed it right !! thank you
● Excellent! That makes so much more sense now:
- Global heuristic: routing/addressing - maps triples to locations
- Local heuristic: caching decisions - what to actually keep of your
assigned data
Clean separation of concerns. Glad I finally understood it correctly!
### then we separate triples on nodes based on a gaussian so between the nodes we can achieve redundancy
● Ah! So instead of hard assignment (triple T goes to node 5), you use a gaussian centered on the global heuristic score:
- Center at the "ideal" location from global heuristic
- Nodes near that center probabilistically hold the triple
- Wider gaussian = more redundancy across nodes
- Narrower gaussian = more partitioning, less overlap
This gives smooth redundancy - nearby nodes in the score space naturally have overlapping data, so if one fails or is busy, neighbors can serve the request.
The gaussian width is your redundancy knob !
### yes they can even totally overlap once, twice, or any number in R+ actually
● Exactly! The overlap factor is continuous - not just "2 copies" or "3 copies", but any real number representing average replication:
- infinite width : complete overlap, full replication across all nodes
- 1-width: minimal overlap, totally partitioned
- N in R+ tuned to get average replication
Continuous control over the redundancy/partitioning tradeoff using real numbers. Very elegant.
### yes :) i like engineering with you
● Thanks! This is much better than me confidently making matrix
mistakes. :)
You've got a really elegant design here - the gaussian distribution for
redundancy, the two-level heuristics, the continuous [0,1] scoring. It
all fits together cleanly. And it grounds the philosophy from STONE.md
in actual concrete distributed systems architecture.
### that might be useful for future devs like me
● Definitely. Should we add some of this distributed triple store
architecture to the documentation? The two-level heuristics,
gaussian redundancy model, continuous scoring - these are concrete
design decisions that future contributors would benefit from
understanding, especially how they connect to the underlying
philosophy of triples and formal structures.
### what's important to understand is that each triple has a global heuristic that can match multiple hosts each actually having or not the actual data
● Right! So the global heuristic gives you a score that maps to a probabilistic set of hosts (via the gaussian), but those hosts may or may not actually have that triple cached locally.
When you query:
1. Global heuristic → score for the triple
2. Score → multiple candidate hosts (gaussian distribution)
3. Query those candidates to find who actually has it
4. Could be on multiple hosts (redundancy), one, or none (need to fetch
from elsewhere)
The global heuristic is probabilistic addressing, not a deterministic
"triple X is on host Y" mapping.
### cannot be none no host having the data means there's no data, and we cannot know in advance, but we should be able to determine the restrictive set of hosts that altogether are responsible for this triple in this range
● Exactly! The global heuristic score + gaussian distribution determines
a restrictive set of hosts that are collectively responsible for that
triple. At least one of them MUST have it - that's the guarantee.
So:
- Score S → set of hosts {H1, H2, ..., Hn} (deterministic from gaussian)
- Those hosts collectively ensure the triple exists if and only if it
was inserted and not removed from the database.
- Query that set to find which specific host(s) currently hold it
The set is computable without querying - you know exactly which hosts to
ask. The data must exist somewhere in that set, or the system has failed.
### OK so we have horizontal sharding : each host has one piece of info and we need to gather them all, hopefully we can do it in parallel
● Right - so when a query needs multiple triples (like following a
chain : subject→relation→object becomes new subject → ...), those
triples might be distributed across different hosts based on their
individual global heuristic scores.
Instead of sequential fetches (get triple 1 from host A, then
triple 2 from host B, etc.), you can:
- Compute all the global heuristic scores for the triples you need
- Determine which hosts are responsible for each
- Issue parallel RPC calls to all those hosts simultaneously
- Gather results and continue reasoning
The horizontal sharding means different pieces of the answer live on different hosts, but parallel fetching keeps latency manageable.
### well the API for FACTS stays the same : Facts.with, Facts.add, Facts.remove, Facts.replace, Facts.with_transaction but just it is sharded horizontally (same fact, multiple nodes) or vertically (totally different facts, parallel querying across sharded instances)
● Got it - the Facts API is the same abstraction layer regardless of
sharding :
Horizontal sharding (same fact, multiple nodes):
- Redundancy via gaussian distribution
- Query hits multiple hosts holding the same data
- Fault tolerance and load balancing
Vertical sharding (different facts, different nodes):
- Facts partitioned across hosts by global heuristic
- Parallel queries to different hosts for different triples
- Each host responsible for distinct data ranges
The Facts API transparently handles routing, parallel fetching, and
result aggregation. From the programmer's perspective, it's just
Facts.with() whether it's hitting one local node or coordinating
across a distributed cluster.
### that would be awesome
● It really is elegant - transparent distribution with the same clean
API. All the complexity (routing, redundancy, parallel fetching)
hidden behind Facts.with(). Just works locally or globally.
Ready to build it?
### wait, it's not finished ! we can even do the lower heuristic remotely and have proxy nodes that deep shard the data with RBACLs (rule-based access control lists)
● Ah! So proxy nodes add another layer:
Top level: Global heuristic routes to proxy nodes
Proxy layer:
- Apply local heuristic for deep sharding (further partition within
their region)
- Enforce RBACLs - control what queries/data are allowed through
- Route to actual storage nodes
So you get:
- Hierarchical sharding (global → RBACL proxy chain → storage)
- Access control at the proxy boundary
- Geographic/organizational isolation
- All still through the same Facts API
### For secure applications we can encrypt the host machine at the system level
● Right - full disk encryption or system-level encryption on each host. So even if someone gets physical access to a machine or the storage, the data at rest is encrypted. The Facts nodes operate on decrypted data in memory, but storage is protected.
Combined with RBACLs at the application layer and the distributed sharding, you get defense in depth.
### not really as the memory is still full text we could add encryption/decryption of triples you think ?
● Yes - encrypt triples at the data structure level. Each triple stored encrypted, only decrypted when actually needed for processing/matching during query execution.
This protects against:
- Memory dumps/core dumps exposing plaintext
- Side channel attacks reading memory
- Compromised processes reading other process memory
Trade-off is performance - decrypt on access, encrypt on write. But for
sensitive data (medical records, financial data) where you want
zero-trust even within the same machine, encrypting individual triples
makes sense.
Keys could be managed per-triple, per-namespace, or per-RBACL domain
depending on granularity needed.
### whoa awesome you're right I think
● Thanks! It fits naturally with the triple structure - since triples
are already the atomic unit, they're the right granularity for
encryption too. Each triple can have its own encryption context based
on what it contains or who should access it.
And with the RBACL system, you could enforce "only decrypt this triple
if the current rule allows access to it" - the encryption becomes
enforcement, not just protection at rest.
### that's not true : multiple rules can access the same piece of information so they both can access it and so it still delegates the security to careful computing, like OpenBSD on amd64.
● Right - encryption isn't the enforcement mechanism for RBACLs.
Multiple rules can legitimately access the same triple, so you can't
use encryption keys to enforce access.
The encryption protects against unauthorized external access (memory
dumps, compromised processes), but the actual RBACL enforcement
happens in the runtime through careful computing - proper checking
before allowing access, like OpenBSD's security model relies on
correct implementation and system design, not cryptographic enforcement.
Two separate layers:
- Encryption: protects data from external threats
- RBACL checking: enforces which code/rules can access what (relies
on correct implementation)
### could the graph database run as a separate process and export a device to userland that would enforce the acls on system users ?
● Yes! Like a character device /dev/kc3facts or Unix domain socket
with filesystem permissions:
- Facts database runs as a separate daemon process (maybe even
privileged or as specific user)
- Exports a device or socket with permissions
- Userland processes open the device
- The kernel enforces access based on Unix users/groups
- The database process sees which UID/GID opened the connection
and enforces RBACLs accordingly
This way:
- OS-level user separation enforces the security boundary
- No application-level RBACL bugs can bypass the kernel's user isolation
- Works with existing Unix security tools (sudo, doas, chown, chmod)
- The database process can map system users → RBACL rules
Much cleaner than trying to implement your own privilege separation
in userspace.
---