DreamDB

DreamDB Specification — 0001: Data Model

Status: Draft. Builds on 0000-overview.md. This document defines the entities of the DreamDB data model precisely. Hash schemes, address syntax, and timestamp formats are deferred to 0002, 0003, and 0004.


1. Purpose

0000 introduced the vocabulary informally. This document fixes the meaning of each entity — what it is, what fields it has, what invariants it must satisfy, and how the entities compose into a Space.

The model is described abstractly. Concrete byte-level encodings of each entity are referenced (e.g. "the manifest is content-addressed per 0002") but specified in their own documents.

2. The Five Entities

              ┌──────────────┐
              │    Space     │
              └──────┬───────┘
                     │ contains 1..N
              ┌──────▼───────┐
              │   Timeline   │
              └──────┬───────┘
                     │ has 0..N
              ┌──────▼───────┐
   layered    │    Track     │
   over  ◄────┤  (3 kinds)   │
              └──────┬───────┘
                     │ contains 0..N (constant: exactly 1)
              ┌──────▼───────┐
              │     Item     │  (Frame / Event / Constant,
              └──────────────┘   depending on track kind)

   ┌──────────────┐
   │   Manifest   │  ← immutable snapshot enumerating
   └──────────────┘    Timelines, Tracks, Layers in a Space

The five entities are: Space, Timeline, Track, Item, Manifest. Layer is not a separate entity — it is a role a Track plays relative to other Tracks (see §6).

2.1 DreamDB Object (the storage-layer concept)

The data model entities above are conceptual. When stored on a backend, each is realized as one or more DreamDB Objects — content-addressed byte sequences whose addresses are the BLAKE3-256 hashes of their bytes (per 0002 §2). All DreamDB Objects share three properties:

  1. Immutable: once written, bytes never change. The address IS the hash, so any modification produces a different address.
  2. Content-addressed: the address is a deterministic function of the bytes alone.
  3. Backend-stored: the bytes live at a backend path equal to the DreamDB address (per 0002 §4 — "the address IS the path").

Object kinds defined in v0:

Object kindRealizesDefined in
Genesis ObjectTimeline identity§5
ManifestSpace snapshot§7
Track ObjectTrack metadata + index§4
Index PageB-tree page (paged Track index, paged Manifest tracks)0002 §7.3.2
SpatialIndex ObjectAlgorithm parameters for spatial bucketing0004 §3
Constant ObjectA single Constant Item's payload§4.3
FragmentMedia chunk (Continuous Signal Track, media)§4.1
Spatial BucketVector batch (Continuous Signal Track, feature)§4.1
Time-bucketed batchEvent batch (Discrete Event Track, high-volume)§4.2
Vector-Storage ObjectPer-Track vector pool (reference-mode bucketing)0007 §6.3
ScalarIndex ObjectScalar-field index (categorical / bitmap)0011 §3
VectorCompressor ObjPer-Space compressor parameters (PQ / RaBitQ)0010 §4
ItemManifestChunk list for one large multi-chunk Item0002 §7.5
GraphIndex ObjectVamana / DiskANN parameters + page layout0013 §3
GraphPagePacked Vamana graph nodes (vector + adjacency)0013 §4.2
TombstoneList ObjectSuppressed-anchor list (Item-level deletion)0020 §3

This is an introductory inventory, not a closed wire enum. The current auxiliary Object address registry is 0002 §7.5; later specifications add, for example, paged tombstone and backfill-decided-set Objects. Auxiliary Object kinds must not be confused with the CLOSED Track-content ObjectKind discriminant.

3. Item

An Item is the atomic unit of a Track. It is the smallest object the protocol addresses individually.

Every Item has, at minimum:

  • A time anchor. A point, a half-open interval [t_start, t_end), or the special "all of time" anchor (for Constants). The time anchor's representation is fixed in 0003.
  • A payload. Bytes interpreted according to the containing Track's modality and bound type. The base model treats them abstractly; owning formats such as 0025 define validation and decoding, so “payload” does not mean implementations may ignore their type contract.
  • A modality tag (inherited from the containing Track — not stored per-item). This determines how the payload bytes are to be decoded by clients.

Items are addressed through immutable Objects per 0002. A one-Item Object has its own content hash; a packed Item uses the containing Object's address and an intra-object locator (§3.1). Repacking can therefore change an Item's storage address without changing its logical value. There is no universal per-Item hash formula independent of the containing format.

Three concrete sub-types exist, corresponding to the three Track kinds:

Track kindItem sub-typeTime-anchor shapeCardinality per track
Continuous SignalFramepoint, regular cadencemany, dense
Discrete EventEventpoint or short interval, sparsemany, sparse
Global ConstantConstantthe entire timeline spanexactly one

3.1 Items vs Objects (the bucketing layer)

An Item is the protocol's logical addressable unit. An Object is the backend's physical storage unit. They are distinct concepts:

  • For modalities where item count and item size are well-matched to one-Item-per-Object — small embedding tracks, sparse low-volume events, all Constants — the Item is the Object.

  • For modalities where one-Item-per-Object is uneconomical (per-frame addressing of VBR media; per-vector storage at 1B+ scale; per-event storage at high event rates), Items are grouped into Objects via a per-modality bucketing scheme. The Object kinds are:

    Item typeObject kindIntra-object locatorDefined in
    Frame (media)Fragment (GOP)byte offset within fragment0007
    Frame (vector at scale)Spatial Bucketindex into packed array0004, 0007
    Event (high-volume)Time-bucketed batchtime-ordered position0007

The full Item address decomposes accordingly:

   <object-address>  ·  <intra-object-locator>

Both indexed selection (fetch Objects referenced by the selected Manifest) and lookup by exact Object address plus locator use this decomposition. Storage LIST does not select a snapshot's Items (0005 §5.3.1). Detailed grammar is in 0002.

The bucketing scheme for a given Track is part of the Track's modality definition, not a per-track choice. (E.g. video.h264 always uses GOP-aligned Fragments; embedding.f32.dim=768.bucketed always uses Spatial Buckets at 1B+ scale.)

4. Track

A Track is a typed collection of Items, anchored to one Timeline.

A Track has:

  • A reference to its Timeline.
  • A modality tag, e.g. video.h264, audio.opus, embedding.f32.dim=512, title.text. Modality tags follow a structured grammar fixed in 0002 §5 (<class>.<encoding>[.<param>...], with param allowing key=value form). The modality determines:
    • What the payload bytes mean.
    • Which Track kind the track belongs to (some modalities are inherently continuous, some inherently event-like, some inherently constant).
  • A kind, one of continuous, event, constant. Determined by the modality tag; not freely chosen.
  • A time interval of coverage, [t_min, t_max), which constrains the time-anchors of all Items in the track. (For Constant tracks, the coverage is the time anchor of the single Constant.)
  • Zero or more Items.

Tracks are immutable once published. "Adding an item to a track" is not a protocol operation. To add information at a new timestamp, a writer publishes a new track (or, more commonly, a new version of an existing logical track via the manifest layering mechanism in §6).

4.1 Track Kind: Continuous Signal

A Continuous Signal Track holds Frames densely over its coverage interval.

  • Frame time-anchors lie within the track's coverage interval.
  • The cadence (frames per second, samples per second, vectors per chunk, etc.) is part of the modality's parameterization.
  • Per §3.1, Frames group into Objects via a modality-specific bucketing scheme:
    • Media modalities (video.*, audio.*) — Frames group into Fragments (self-contained, decoder-ready chunks, typically a GOP, ~1–10 s each). VBR is supported because the fragment-index in the Track entry records actual byte sizes per Fragment. Defined in 0007.
    • Feature modalities at scale (embedding.* with large dim and large item count) — Frames (vectors) group into Spatial Buckets keyed by a derived spatial-bucket-key (per 0004). Targets ~1–10 MB per Bucket Object.
    • Small / low-volume feature modalities — no bucketing; each Frame is its own Object.
  • The Manifest references the Track Object, whose Object index selects Fragments or Buckets as appropriate. Large indexes use content-addressed Index Pages; the complete index is not guaranteed to fit in a small in-memory object.
  • Examples of modality tags: video.h264, video.av1, audio.opus, embedding.f32.dim=512.per_frame, embedding.f32.dim=768.bucketed.

4.2 Track Kind: Discrete Event

A Discrete Event Track holds Events sparsely over its coverage interval.

  • Event time-anchors lie within the track's coverage interval but are unconstrained in spacing.
  • An Event may have a duration (interval anchor) or be instantaneous (point anchor).
  • The track has no expected cadence; the count of events ranges from 1 to many millions, depending on the application.
  • Per §3.1, Events group into Objects according to track volume:
    • Low-volume Event Tracks (up to a few thousand Events total) — no bucketing; each Event is its own Object.
    • High-volume Event Tracks — Events group into Time-bucketed batch Objects (each batch covers a fixed time slab; intra-batch ordering is by time anchor). Defined in 0007.
  • Examples of modality tags: annotation.json, transcript.turn, scene.boundary, sensor.gps.

4.3 Track Kind: Global Constant

A Global Constant Track holds exactly one Constant, in effect for all time.

  • The Constant has no per-item time anchor. Its address carries no spatiotemporal segment (0002 §6.3), and it is in effect for every valid query time — a range query that overlaps no part of the track's stored coverage still returns it.
  • The track's stored coverage does not participate in query semantics. Readers MUST NOT use it to prune a Constant from a result, and MUST NOT treat a query window outside it as a miss.
  • The canonical coverage a writer MUST emit is the sentinel [0, 2^64-1) — "all of time" — written identically in the TrackObject and in the Manifest's TrackEntry. It is present only because §4.4 makes coverage part of every Track's content hash.
  • A non-canonical finite coverage, if encountered, is valid. Readers MUST interpret it as all-time exactly as they do the sentinel. It requires no migration and MUST NOT be rewritten by an ordinary append.
  • The payload represents a single value: a string, a JSON document, a small binary blob, etc.
  • There is no "name within the track" key. The track's modality tag identifies what the constant represents (e.g. title.text, license.spdx, author.json). Modeling several attributes therefore requires several Tracks, not one Track with several keys. This keeps a Track free of an internal key namespace, consistent with time as the primary index axis (0000 §5.1); logical identity, when an Item has one, is a separate scalar id field, not an intra-Track key (§5.4).
  • Storage layout is a single small immutable object.
  • If the constant's value needs to change later, a new Constant Track of the same modality is published as a higher Layer (§6); the original remains addressable.

4.4 Constraints Common to All Track Kinds

  • A Track is immutable. Its bytes — including the enumeration of its Items — are fixed at publication.
  • A Track's content hash is computed from its canonical encoded Track Object (0002), including its metadata and index references; it cannot be derived from Timeline and modality alone. For a newly written Global Constant Track, §4.3's sentinel avoids changing its coverage with the Timeline's extent. Existing finite-coverage Constants remain valid and are not rewritten by an ordinary append.
  • A Track belongs to exactly one Timeline. Cross-timeline queries are performed by joining at the manifest level (§7), not by sharing tracks.

4.5 Empty / degenerate Track rules

Empty-state rules per Track kind (conformant readers MUST validate; conformant writers MUST honor):

  • Continuous Signal Track: MAY have zero Frames. Valid as a placeholder track committing to a modality without yet having data. Its object_index is the empty inline list (or a paged form with total_items = 0).
  • Discrete Event Track: MAY have zero Events. Valid as a placeholder for future events. Same encoding as above.
  • Global Constant Track: MUST have exactly one Constant. Tracks with zero or two-or-more Constants are malformed; readers MUST reject them.

Manifest-level validation: a Constant Track entry that does not satisfy the "exactly one" rule causes the entire Manifest to be invalid. Other Track kinds with empty object_index are valid.

Append operations (per 0006 §5.1) with zero Items SHOULD be a no-op — the writer SHOULD NOT publish a new Manifest with no track changes. (Writers MAY publish empty-Append Manifests for forensic reasons; readers MUST accept them.)

5. Timeline

A Timeline is a monotonic axis of timestamps over which Tracks are anchored. Every Timeline has a globally unique identity that requires no central registry.

5.1 Timeline Genesis (the identity-bearing Object)

A Timeline is defined by a small immutable Genesis Object:

TimelineGenesis {
   origin:         <timestamp at which t = 0 sits — absolute or
                    relative; encoding fixed in 0003>,
                                                                ;; (resolution implicit at 1 ns in v0; see 0003 §3)
   horizon:        <optional [t_min, t_max); absent for open-ended
                    live streams>,
   nonce:          <128 random bits, writer-supplied at creation>,
   canonical_name: <optional human-readable hint, e.g.
                    "camera-3.match-2026-05-06"; for diagnostics
                    only — never a routing or join key; its bytes
                    still participate in the Genesis hash>,
}

The Timeline's identity is the content hash of the Genesis Object (using the hash function fixed in 0002).

5.2 Why identity is globally unique

  • Probabilistic uniqueness. Independently uniform 128-bit nonces match with probability 2⁻¹²⁸ for one pair; across n creations the nonce-collision birthday bound is approximately n(n−1)/2¹²⁹. Other Genesis fields and the content hash also participate in identity. No central name registry is required, but uniqueness is not a mathematical guarantee and depends on sound randomness.
  • Self-certifying. Given a Timeline identity, anyone can fetch the Genesis Object from any backend that holds it and re-hash to verify the binding.
  • Deterministic per Genesis. Two writers who share the Genesis Object (by exchanging it out-of-band) compute the same identity, so they both write to the same Timeline. Genuine collaboration on one Timeline is fully supported; the only prerequisite is exchanging the Genesis Object first.
  • Independent of Space. A Timeline's identity does not depend on which Space, manifest, or backend references it. The same Timeline may appear in many Spaces, or in none. (This mirrors how Git treats blob hashes as independent of repos.)
  • canonical_name is hashed, not resolved by name. Changing this Genesis field changes Timeline identity. An application may change a display label outside Genesis without renaming the immutable identity; name equality never establishes a join.

5.3 Timeline metadata

Beyond identity-bearing fields, a Timeline carries:

  • An origin, the timestamp t = 0. (Whether origins are absolute Unix nanoseconds or per-Genesis relative is OQ-1 in 0000, resolved in 0003. The choice affects time encoding, not identity.)
  • (Resolution is implicit at 1 ns for v0 per 0003 §3; not stored in the Genesis Object.)
  • An optional horizon, a declared [t_min, t_max) interval beyond which Items are not expected. Useful for bounded recordings; absent for open-ended live streams.

5.4 Role in the data model

A Timeline is the primary index axis for its data. A time-join correlates co-occurring records on that same axis. It requires index traversal and possibly I/O; it is not a zero-cost operation or proof that two records represent one entity.

It is not, however, a statement of identity. "Coincides in time" and "is the same logical entity" are different relations: two distinct samples can share a timestamp, and one logical document can recur at many times. Temporal overlap can support a correlation chosen by an application, but does not imply entity equality, even for time-native data.

For those, model identity explicitly. A scalar id field (e.g. item_id, doc_id, subject_id) indexed through 0011 remains useful for cross-source joins and application-managed ids; 0015's per-record doc_id is one instance. It does not by itself guarantee uniqueness or an atomic lookup-then-mutate operation. The optional entity-key contract in 0026 supplies typed uniqueness, keyed get/create/upsert/supersede/delete, and expected revision tokens with one Ref CAS. The time anchor remains the physical storage position. Historical key revisions and their values remain readable; logical deletion does not imply selective erasure. See the resolution of 0000 OQ-88.

A Space may contain many Timelines. Tracks belong to exactly one Timeline. Cross-Timeline relationships are not part of the v0 data model. Two recordings of the same physical event from different cameras produce two Genesis Objects with different nonces, hence two distinct Timelines — which is correct: they are distinct recordings, however correlated their content. To express "these are the same event from different angles," a higher-level alignment artifact would be needed (a small object mapping time_on_T1 ↔ time_on_T2); alignment is deliberately out of scope for v0 and is mentioned only as a non-goal in §11.

6. Layer (a Role, Not an Entity)

A Layer is a Track that derives information from, or supersedes, another Track on the same Timeline.

Layering is not a structural property of the Track itself. It is a relationship recorded in the Manifest (§7). The same physical Track object can be:

  • A "base" track in one Space (no parent).
  • A "layer" in another Space (parent declared by a manifest entry).

This is intentional. It mirrors how Git treats blobs: a blob is a blob; whether it represents a file added in this commit or one carried over from a parent is a property of the commit's tree, not of the blob.

The Manifest declares, for each Track in a Space:

  • Its address (where the bytes live; 0002).
  • Its role: base (no parent) or layer-of: <track-address> (derived from another track).
  • Optionally, supersession semantics: whether this layer extends, overrides, or annotates its parent. Vocabulary fixed in 0008.

Three layering patterns are common; all use the same structural mechanism:

  1. Augmentation. A vector embedding track layered over a video track. Same time interval, different modality. Both remain readable.
  2. Correction. A Constant track of modality title.text published as a higher layer over an earlier Constant track of the same modality. Manifest readers see the higher layer first.
  3. Annotation. An Event track of modality annotation.json layered over a video. Each event references the time interval it pertains to via its own time anchor — not via a pointer to the parent track.

In all three cases, the parent track's bytes are untouched. Layering is additive at the storage level and resolved at query time by walking the manifest.

7. Manifest

A Manifest is an immutable, content-addressed object that enumerates the state of a Space at a moment in its history.

A Manifest has:

  • A parents array of zero or more Manifest addresses: empty at genesis or an explicit history root, one for linear history, and multiple for a merge (0008). It is not a nullable singular parent.
  • Timeline entries describing the current snapshot, not an additions-only log.
  • A list of Track entries. Each entry has:
    • The address of the Track.
    • The Timeline it belongs to.
    • Its modality tag and kind (redundant with the Track's own bytes, but cached here so manifest readers can plan queries without first fetching every track).
    • Its binding and lineage relationships. base / layer-of above are conceptual vocabulary; the wire container and exact binding references are defined in 0002 §7.2, not by adding those prose labels as keys.
    • Its coverage interval.
  • A timestamp of publication, by the manifest's writer. This is metadata, not part of any Item's time anchor.
  • A writer identity tag (optional, opaque to the protocol).

A Manifest is the unit of versioning. Publishing new data produces a complete new snapshot; parent links form a DAG, not necessarily a chain. Current visibility comes from that snapshot's active bindings, while retained lineage and parent history serve distinct purposes (0002 §7.2, 0008). Readers do not replay additions from all parents to invent the current view.

Manifest semantics — branching, merging, conflict-free concurrent publication — are detailed in 0008. For the purposes of this document, only the structure is fixed.

8. Space

A Space is the top-level container of a DreamDB installation. It is identified by:

  • Its root manifest address — the address of the most recent Manifest the reader has chosen to read at. (A Space is therefore implicitly parameterized by which manifest you are reading.)
  • Its backend binding — which backend the Manifests, Tracks, and Items live on. The protocol does not require the binding to be unique: the same set of immutable objects may be hosted on multiple backends, and a Space at "manifest M on backend A" is observationally equivalent to "manifest M on backend B" if both backends contain the closure of objects reachable from M.

There is no global registry of Spaces. A Space exists wherever its manifest chain is reachable.

9. Type System Summary

EntityMutable?Content-addressed?Belongs toContains
Spaceby reference (which manifest you read)n/a—Timelines, Tracks, Manifests (transitively)
ManifestnoyesSpace (transitively)references to Timelines and Tracks
Timelinenoyes (by content hash of Genesis Object — globally unique via 128-bit nonce)nothing (Timeline identity is independent of Space)nothing (it is a coordinate axis)
TracknoyesTimelineItems
Itemnovia Object + locatorTracka payload + a time anchor

"Mutable by reference" for Space means: the bytes never change, but the Space-as-a-user-sees-it advances when the user chooses to read a newer manifest.

10. Worked Examples (Three Track Kinds in One Space)

Suppose a Space contains a single Timeline T representing one 10-minute video recording, with origin at the camera's start-of-recording wall-clock time and nanosecond resolution.

A reasonable populated Space might contain:

Timeline T: origin=2026-05-06T09:00:00Z, horizon=[0, 600s)
            (resolution = 1 ns implicit per v0 spec)

Tracks on T:
  ─ Continuous Signal Tracks
      • modality=video.h264              (the raw video frames)
      • modality=audio.opus              (the raw audio frames)
      • modality=embedding.f32.dim=512   (one vector per video keyframe)
                  layer-of: video.h264

  ─ Discrete Event Tracks
      • modality=scene.boundary          (12 events over 10 min)
                  layer-of: video.h264
      • modality=transcript.turn         (~80 events over 10 min)
                  layer-of: audio.opus
      • modality=annotation.json         (3 events, manually added)

  ─ Global Constant Tracks
      • modality=title.text              "FA Cup Final, 2nd half"
      • modality=author.text             "BBC Sport"
      • modality=license.spdx            "CC-BY-4.0"
      • modality=source.uri              "https://..."

Manifest M_3 ← M_2 ← M_1 ← M_0
  (published in this order; later writers added embeddings, then transcripts, then annotations)

A query like "what was the title of the recording where the goalkeeper saved a penalty?" dispatches to two different lookup paths:

  1. "Goalkeeper saved a penalty" is a feature query against embedding.f32.dim=512. The SDK encodes the text, computes the spatial address region (per 0004), and finds the matching frame's time anchor on T.
  2. "What was the title?" is a Constant lookup. The reader resolves the selected Manifest's Constant binding on T, reads its Track/index as needed, and fetches the referenced payload. Its address includes content that cannot be computed from (T, modality, coverage) alone; I/O count depends on cached metadata.

Both lookups consult the same Manifest to know which Tracks are live at the chosen point in history, but neither involves a scan.

11. Out of Scope for this Document (and for v0)

  • The byte-level encoding of any of these entities. (0002, 0007)
  • The timestamp format and the resolution of OQ-1 from 0000. (0003)
  • The address syntax, including the modality-tag grammar referenced informally above. (0002)
  • The spatial-indexing scheme for feature-bearing tracks. (0004)
  • The manifest chaining and merge semantics, including how concurrent writers reconcile. (0008)
  • Cross-Timeline alignment. Mapping time_on_T1 ↔ time_on_T2 between two distinct Timelines (e.g. two cameras at the same physical event) is a higher-level construct and is deliberately deferred. v0 deals with relationships within a Timeline only.
  • Human-readable Timeline naming as identity. The canonical_name field on a Genesis Object is a diagnostic hint, never a routing or join key. Identity is always the content hash.

12. Open Questions Surfaced by This Document

  • OQ-6 (→ 0002 §5): Modality-tag grammar. Resolved: class.encoding(.param)* with reverse-DNS namespacing for user-defined modalities; built-in classes are reserved.
  • OQ-7 (→ 0008 §6.3, 0007 §8.2): Concurrent constant correction conflicts. Resolved: lexicographically-greatest layer-Track address wins for Constants; non-Constant Tracks union-merge.
  • OQ-8 (→ 0007 §8): Default Time-batch duration. Resolved: per-modality via bucket=<duration> parameter (no fixed default; recommended sizing table in §8.1.2).
  • OQ-9 (→ 0004 §5): Default spatial-bucket-key derivation. Resolved: dreamdb.lsh-cosine ships as v0 default; the algorithm registry supports v0.1+ alternatives (dreamdb.pq-ivf, etc.).
  • OQ-10 (→ 0007 §5.4): Default Fragment duration. Resolved: 2 s default; per-modality tunable via frag_duration= parameter; permitted range 1–30 s.

Next: 0002-content-addressing.md — the hash function, what gets hashed, the modality-tag grammar (OQ-6), and the address syntax.