Architecture Overview
TribleSpace is an embedded knowledge graph whose storage and distribution model is a small join algebra. Facts, blobs, and publication records are immutable; independent stores combine by union. The architecture follows from that choice rather than wrapping an ordinary mutable database in a replication protocol.
The load-bearing principles
Content addressing
Every blob is named by a hash of its bytes. Identical values deduplicate, readers can validate integrity without trusting storage, and a reference has the same meaning in memory, in a pile, or on an object store. Handles fit in a trible's 32-byte value slot, so descriptions, datasets, metadata, and large attachments all use the same reference primitive.
Monotone evidence
A TribleSet is a set of immutable facts. A CollectionStore is a set of
immutable algebra records. Their merge is set union, which is associative,
commutative, and idempotent. Concatenating independently written piles therefore
cannot create a last-writer-wins conflict or change the meaning of an existing
record.
This is the practical consequence of the CALM principle: monotone conclusions do not need a coordination protocol. Application-level change is represented explicitly as new facts, version links, or successor DAGs rather than by overwriting an ambient current value.
Authority and computation are separate
A signed COMMIT says that an author places one element in a collection.
A signed MERGE or DERIVE endorses a reproducible computation connecting
known elements. The former introduces membership; the latter lets a reader
reuse materialized work from a producer admitted by the target's WRITE policy.
A signature identifies the endorser; it does not prove the equation correct.
Readers do not repeat the encoding join or mapping to check it.
Foreign record bytes are signature-checked once at ingress. Typed local publication and trusted store replay do not repeat that check. Admission still uses the proofs and resident capability definitions of each frozen snapshot: a later grant or newly resident definition may activate an old equation. Generic collection authority does not expire as the clock advances.
That separation is why a materialized index does not become ground truth merely because it is convenient, and why collecting an accelerator does not erase the committed facts from which it can be rebuilt.
Who may make that signed assertion can be proven without making storage a
policy oracle. A descriptor carries independent READ and WRITE policies. Each
is open or a canonical quorum over external trust roots with one semantic
threshold.
Each resident proof is one self-contained, prefix-signed path from one root:
its header binds the exact resource, and every edge carries a capability
definition handle, delegate, and signature over the complete prefix. Definitions
describe independent invocation and delegation action sets; a child's actions
must all be delegated by its parent. The handles may differ. Ordinary collection
operations count independently valid rooted prefixes against ACTION_WRITE
on the descriptor, with roots selected for that action rather than the grant's
exact handle. Sibling paths cannot lend one another delegation authority, and
merely finding an unverified or irrelevant proof in storage grants nothing.
Architectural layers
┌──────────────────────────────────────────────────┐
│ Application │
│ entity! · Fragment · find! · pattern! │
├──────────────────────────────────────────────────┤
│ Typed collections and immutable observations │
│ publish, select exact covers, materialize views │
├──────────────────────────────────────────────────┤
│ Collection algebra │
│ signed COMMIT · stored MERGE · stored DERIVE │
├──────────────────────────────────────────────────┤
│ Storage │
│ CollectionStore · BlobStore · WantStore │
│ CapabilityProofStore │
├──────────────────────────────────────────────────┤
│ Data and representations │
│ TribleSet/PATCH · SimpleArchive · SuccinctArchive│
└──────────────────────────────────────────────────┘
The boundaries are deliberately narrow. Query constraints do not know how bytes were published. A collection encoding or mapping does not decide replication policy. A blob store does not infer authority from a handle it happens to contain.
Tribles, sets, and fragments
A Trible
is a fixed 64-byte entity–attribute–value fact. The attribute determines how
the value's 32 bytes are interpreted. A TribleSet
stores each fact once while maintaining the six entity/attribute/value
permutations needed by the query engine. The underlying persistent adaptive
tries make cloned sets cheap and union, intersection, and difference structural
operations.
A Fragment is the publication unit applications normally construct. It
carries:
- ordinary facts;
- descriptive metafacts;
- exported intrinsic entity IDs; and
- the content-addressed attachments referenced by either fact set.
Fragments compose with +=. The entity! macro derives an ID when no explicit
subject is supplied and inserts encoded blob payloads into the fragment's
attachment store. This keeps provenance and required bytes together without
mixing schema descriptions into ordinary application queries.
Blob storage
BlobStorePut, BlobStoreGet, BlobStoreMeta, and BlobStoreList describe
small independent capabilities rather than one all-or-nothing database trait.
The main backends are:
MemoryRepofor process-local work and tests;Pilefor one append-only, memory-mapped file; andObjectStoreRemotefor S3-compatible storage.
Content addressing makes storage placement a physical concern. A missing local blob is not a semantic retraction: another node may still provide the exact bytes later.
Collections are self-describing lattices
A collection descriptor is an ordinary TribleSet, encoded as a canonical
SimpleArchive. Its content handle is the CollectionHandle. A root descriptor
normally states:
- a human-readable UTF-8 name;
- the canonical member encoding; and
- independent descriptor-local READ and WRITE admission policies.
A derived descriptor replaces the name with its source collection and one concrete mapping entity. Canonical builders derive that entity's id, while readers preserve the substitution rule by validating its algorithm and parameters instead of its minting history. The entity names a mapping algorithm and carries its concrete parameters as ordinary tribles. The target encoding and both policies remain local to the derived descriptor; policy never inherits from the source. Descriptions of the encoding and mapping algorithm travel in the same archive, so a record naming the descriptor remains interpretable without a separate registry entry.
CollectionStore contains three native record kinds:
| Record | Meaning | Dense payload |
|---|---|---|
COMMIT(C, x, metadata, author, signature) | The author asserts x as an independent member of C. | 192 bytes |
MERGE(C, a, b, c, author, signature) | The author endorses a ⊔ b = c under C's join law. | 224 bytes |
DERIVE(T, a, b, author, signature) | The author endorses the mapping named by T from a to b. | 192 bytes |
The exact canonical record value is the semantic object; none of the three has a synthetic entity ID. A repeat insert is a no-op. The network PATCH keys a record by a full-width BLAKE3 fingerprint of the kind and canonical payload; the pile keys it by the collection and member it names and the frame it sits in. Neither key is collection semantics. All three records are signed. A COMMIT or a DERIVE is admitted by WRITE on the collection it names. A MERGE needs no WRITE: a store folds only the MERGEs its own host key signed, each host joining the same foundations into a lattice of its own, and another key's MERGE stays in the store and folds into nothing. Several keys can sign the same DERIVE: these are distinct provenance records but one semantic equation. Inferred equations carry only endpoints, not fabricated signatures or provenance records.
The algebra has no distinguished head. Several commits coexist, and the value of a selected collection view is the join of its admitted members. This makes a commit the atomic publication boundary without inventing a mutable register above it.
Publishing and observing
The collection value is its canonical descriptor handle, and the storage
backend owns I/O and durability. store.collection(name, policy) constructs
and registers a canonical root descriptor. store.derive::<Target>(source, argument, policy) does the same for a target-owned canonical derivation, and
store.attach::<Target>(parent, argument) for an index attached to a root,
whose descriptor carries no policy. Mappings between two foreign encoding
types use the explicit store.derive_with(source, mapping, policy) and
store.attach_with(parent, mapping) coherence seams.
store.commit(collection, signer, fragment) publishes attachments, canonical data, canonical metadata,
and the signed native record in dependency order. Local publication performs
no authorization check and no implicit flush: authorization governs which
resident assertions another operation admits, not what a process may append to
its own store.
Reads are exact about what they observed, not magical about global time:
store.snapshot()freezes blob bytes, collection records, capability proofs, and backend state from one coherent known prefix;collection.admitted(&snapshot)reads the coverage index in that observation -- every record decided once against the descriptor's WRITE policy and the resident capability evidence -- and returns one semanticCover<E>, the union of what the collection's frontier stands for, without fetching member data; andcollection.read(&snapshot)attaches that frontier from the same index and reconstructs the logical value from the nodes whose bytes are resident, descending through theMERGEthat produced a node whose bytes are not, through that same immutable observation.
Snapshots carry no clock. Content-change masks compare stored evidence, and collection authorization does not expire with wall time. An application action such as Secrets key delivery supplies its own evaluation time and interprets its deadline facts before counting each root's proof prefix; that does not expire collection membership.
Cover identity is the collection descriptor plus distinct payload handles. Signer, signature, and metadata attestations currently known to the store remain queryable, but no attestation is required for replay, and another one over the same payload does not change the cover or repeat data work.
Each store snapshot observes one known prefix of an append-only store. A concurrent commit may appear now or on the next call, but one observation never combines records from one prefix with payloads from another. The WRITE-policy roots and every signer with sufficient valid resident support participate; unauthorized commits remain inert.
Derived physical representations
The same logical data can be projected into encodings optimized for a particular task. A canonical SuccinctArchive collection or a regular-path summary is connected to its source by a mapping which is a join homomorphism:
f(a ⊔ b) = f(a) ⊔ f(b)
That law makes equations on either side of the mapping reusable evidence.
Construction and representation maintenance are separate operations. The
foundational support is always Support = Cover<SimpleArchive>: the admitted
committed payloads at the root of the descriptor lineage. Mapping and merging
never change those denotational coordinates. Multi-hop maintenance invokes
each mapping in order over whatever its immediate source already realizes, so
every target ends up standing for what its source's frontier stands on. There
is no way to ask for a narrower support: a support is what a target stands on,
read back from it, never an argument to select by. Two targets derived from one
source and read from one snapshot agree on it by construction.
ensure is a live asynchronous store operation. For a root SimpleArchive
collection it acquires missing blobs for the admitted foundational support
without publishing equations. For a derived target it takes the support already
realized by resident immediate-source members, reuses resident target nodes and
stored equations, acquires exact missing dependencies for the selected work,
and publishes only the missing DERIVE work for its one immediate mapping. It
never creates a MERGE, computes an upstream blob, or emits a durable WANT.
maintain first performs that same realization work and then repeatedly joins
target members in the deterministic dyadic serialized-size tiers. A target join
whose exact immutable
dependency cannot be acquired leaves a finer target cover in place. Different
target covers may denote the same join, and every lattice position uses the
same Cover<E> shape while retaining its own typed member handles. Missing
derived artifacts are cache misses, not missing facts.
Every store-level ensure or maintain operation returns a fresh
StoreSnapshot. The result is the post-operation temporal boundary, including
work concurrently published before that snapshot. Read-only
CollectionSnapshotExt::collection selects a resident target cover from one
such store snapshot and returns a CollectionSnapshot which owns that
immutable observation and the realized target cover; the foundational support
it stands on is a separate support() query over the same observation. A
caller chooses the logical projection later with CollectionSnapshot::view.
Cover carries no route mode. The resolver checks its explicit members first
and, only when needed, widens through stored MERGE equations to a resident
equal-support route. Rank9 acceleration is another ordinary derived collection.
Its members are ordinary
Blob<Rank9AcceleratedSuccinctArchiveBlob> values whose first 32 bytes name the
exact portable SuccinctArchiveBlob child carrying their source rows. The root
and named child together are a complete source-bound accelerated encoding, not
a separate sidecar or runtime artifact.
Both raw Succinct and Rank9-accelerated members own canonical joins. The Rank9
join computes the same union, but its result names the exact raw Succinct union
as an immutable child. It may consume that raw union when the blob is already
resident; it never creates the upstream raw blob or its MERGE record. If the
child is absent, target maintenance leaves the exact finer accelerated cover
in place. A caller which wants both lattices compact invokes ordinary maintenance
on their mapping hops explicitly, in order. Exact requests can instead pin the
same foundational support across both hops. A cover-aware view
follows each embedded handle through its store snapshot, validates the exact
raw/index pair, and only then builds the transient query runtime. A root whose
raw child is absent is not a usable query value.
WANT is operational, not semantic
A WantStore records operational interest in obtaining a blob. Blob(H) is
the only request shape and names no collection. WANTs do not add collection
members, authorize authors, retain all referenced data, or force another node
to perform work. They are durable coordination-free questions which a
reconciler may satisfy by fetching content into the local store.
The network reconciler discovers exact providers under the opaque, domain-separated locator KDF(H). An H-bound endpoint token rejects false directory entries before dialing. The direct stream then performs a provider-first, requester-second proof of H, bound to both authenticated endpoint identities, without ever transmitting H. Returned bytes must hash to H. This path does not load a collection descriptor or consult READ(C).
Keeping WANT orthogonal prevents evidence convergence from becoming involuntary blob mirroring. A peer can repair a READ-authorized collection's record and authorization-evidence overlay, then decide which blobs and derived representations are useful locally.
Routing is soft state, not semantic evidence
First contacts come from the pile, not from configuration: the keys a selected collection's records and grants name, and for a process beside the pile's sync daemon, the addresses that daemon recorded in the pile's configuration. Peerings, candidate orders, DHT referrals, connection liveness, and provider leases are bounded process-local state. Restarting may forget them without changing a collection, and none of them authorize a peer, promise content residency, or retain a blob. Historical PEER and STORE_SCOPE records remain physically decodable for old piles but do not participate in current synchronization or repository snapshots; semantic rewrites drop them.
Storage and synchronization compose by union
Pile stores blobs, native collection records, capability proofs, and WANT
records in one
append-only log. ObjectStoreRemote places immutable collection records under
content-derived object keys. The network layer uses READ(C)-gated Merkle
pushes between peered neighbours, each side pushing its own collection trees
on a timer, to union that collection's records and structurally relevant
native READ(C)/WRITE(C) proof records. Each proof record
is the complete authorization value and has no referenced blob closure.
Independently, every resident blob may
publish an opaque XOR-DHT lease under KDF(H); knowing H is the bearer capability
for its exact bytes regardless of collection policy or collection-repair
direction. Collection READ(C) gates collection anti-entropy, but never exact
GET. Merge/derive questions are answered from the
converged local record index.
In every case convergence means unioning evidence; it does not mean electing a
winner.
Legacy branch and pin records remain decodable only so old piles can be inspected, conservatively retained, and explicitly migrated. They are not part of the current publication or authorization model.