Getting Started
This chapter publishes and queries a small native collection. It assumes Rust
is installed and you are comfortable running cargo commands.
1. Add dependencies
cargo new tribles-demo
cd tribles-demo
cargo add triblespace ed25519-dalek rand
triblespace supplies the data model, stores, collection operations, and query
macros. ed25519-dalek and rand create the publishing identity used in this
example.
2. Declare attributes
Attributes carry the encoding of their value. Shared attributes should use a stable explicit anchor; the encoding participates in the resulting identity. Omit the anchor only for local prototypes whose identity may follow their name.
mod literature {
use triblespace::prelude::*;
use triblespace::prelude::blobencodings::UTF8String;
use triblespace::prelude::inlineencodings::{GenId, Handle, ShortString};
attributes! {
"A74AA63539354CDA47F387A4C3A8D54C" as pub title: ShortString;
"6A03BAF6CFB822F04DA164ADAAEB53F6" as pub quote: Handle<UTF8String>;
"8F180883F9FD5F787E9E0AF0DF5866B9" as pub author: GenId;
"0DBB530B37B966D137C50B943700EDB2" as pub firstname: ShortString;
"6BAA463FD4EAF45F6A103DB9433E4545" as pub lastname: ShortString;
"D2D1B857AC92CEAA45C0737147CA417E" as pub alias: ShortString;
}
}
Use trible genid when minting a new published anchor. The literal-pinning
"HEX_ID" unsafe as ... spelling is only for preserving an already-published
attribute's exact historical bytes when its old identity cannot be re-derived.
3. Register a collection
A root collection is identified by the content handle of its descriptor. The
descriptor carries its UTF-8 name, member encoding, and independent READ and
WRITE admission policies. The encoding itself owns member validation and
defines one canonical member-join operation; a root needs no mapping. The descriptor
is an ordinary self-contained Fragment, but applications ask the store to
construct and register it rather than assembling its facts manually. Its
canonical content handle is the collection identity:
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
use triblespace::core::collection::{AdmissionPolicy, CollectionPolicy};
use triblespace::prelude::*;
let key = SigningKey::generate(&mut OsRng);
let mut storage = MemoryRepo::default();
let root = key.verifying_key();
let library = storage.collection(
"library",
CollectionPolicy::new(
AdmissionPolicy::direct(root),
AdmissionPolicy::direct(root),
),
)?;
Each policy is either Open or a canonical threshold over capability roots.
AdmissionPolicy::direct(root) records one such root. Each signed proof edge
then says whether its recipient may invoke, delegate, or do both. The two
policies are deliberately
independent: a collection can have many writers but few readers, or public
READ with tightly held WRITE. Both policies participate in the descriptor
handle, so there is no ambient team scope or separate reach flag.
Local publication is unconditional: any process may append a structurally
valid self-signed commit to its own store. Reading applies the descriptor's
WRITE policy. Commits by a policy root are admitted directly; other authors
become visible only when the same store snapshot contains enough valid proof
paths for exact ACTION_WRITE on this descriptor. Invalid resident evidence
grants nothing.
4. Build a self-contained fragment
With no explicit subject prefix, entity! derives the entity ID from the
canonical set of emitted fields. Fragment::root() returns that exported ID,
which can be referenced by another entity. Long strings become blobs and remain
attached to the fragment automatically.
let author = entity! {
literature::firstname: "Frank",
literature::lastname: "Herbert",
};
let author_id = author.root().expect("intrinsic author id");
let book = entity! {
literature::title: "Dune",
literature::author: &author_id,
literature::quote: "I must not fear. Fear is the mind-killer.",
};
let mut import = author;
import += book;
A fragment has four coordinated channels: facts, descriptive metafacts,
exported IDs, and one shared attachment store. += unions all four, so no
parallel manifest or manual blob-staging step is needed.
5. Publish independent commits
let first = storage.commit(library, &key, import)?;
// A later fact about the same entity is another independent member.
let second = storage.commit(library, &key, entity! {
&author_id @ literature::alias: "Francis",
})?;
assert_ne!(first, second);
Publication loads and structurally validates the registered descriptor, then writes fragment
attachments, data, and metadata before inserting the signed COMMIT record.
It performs no permission check and no implicit flush. The commit is the atomic
assertion. There is no mutable head to advance: both records remain members and
the collection value is their union.
Repeating byte-identical input produces the same exact canonical commit and is idempotent. Distinct input produces another coexisting member. Application-level supersession or versioning is represented in the facts when a domain needs it; append order is never an implicit winner.
6. Read one coherent snapshot
let snapshot = storage.snapshot()?;
let admitted = library.admitted(&snapshot)?;
let available = admitted.available(&snapshot)?;
let missing = admitted.difference(&available)?;
assert!(missing.is_empty());
let facts = admitted.materialize::<TribleSet, _>(&snapshot)?;
let title = "Dune";
for (first, last, quote) in find!(
(first: String, last: String, quote),
pattern!(&facts, [
{ _?author @
literature::firstname: ?first,
literature::lastname: ?last
},
{ _?book @
literature::title: title,
literature::author: _?author,
literature::quote: ?quote
}
])
) {
let quote: View<str> = snapshot.get(quote)?;
println!("'{}'\n - from {title} by {first} {last}.", quote.as_ref());
}
storage.snapshot() freezes blobs, collection records, capability proofs, and
backend state at one coherent known prefix, together with one authorization
instant. library.admitted(&snapshot) then
applies the descriptor's WRITE policy in that same observation and returns the
exact semantic payload cover. available returns the greatest subset of those
same semantic members which has a complete resident realization, so equality
with admitted means the full value is local and difference names missing
semantic support. materialize privately selects a support-equivalent physical
decomposition and constructs the logical value through the same immutable
snapshot. library.read(&snapshot) concisely reads the maximal resident
collection view at that same frozen instant. Repeating an observation or
cloning the snapshot does not advance its clock; later authorization decisions
require a new snapshot. Tests may choose the instant with
storage.snapshot_at(instant) without changing the newly observed content.
Provenance for an exact cover is available through cover.commits(&snapshot).
These are strictly verified attestations over its payloads, not necessarily
the authorized claims which admitted them.
Duplicate signed claims for one payload collapse to one cover member; authors,
signatures, and metadata remain provenance rather than payload identity.
7. Choose durability explicitly
store.commit performs no implicit flush. For a memory store that makes
no difference. For a pile or remote backend, choose the durability boundary
that matches the application:
storage.commit(library, &key, batch_a)?;
storage.commit(library, &key, batch_b)?;
storage.flush()?;
Amortizing one flush over several commits does not weaken their logical identity or change merge semantics. Flushing and closing remain operations of the chosen backend rather than collection policy.
What to remember
entity!builds intrinsic entities and carries required blobs.Fragmentis the self-contained publication value.Collection<E>is a descriptor handle statically bound to theCollectionEncodingwhich owns its member bytes, validation, and canonical physical join operation; the store owns all I/O.store.commitpublishes one signed, independent member without conflating local storage with network authorization.store.snapshotfreezes one coherent known-prefix store observation; collection admission and materialization are pure reads over it.- Replicas converge by unioning records; they never elect a branch head.
- Derived indexes are reproducible collection images, not alternate authority.
Continue with Collection Workflows for the native record algebra, exact covers, migration from legacy piles, and derived collection maintenance.