dendropsis — Specification

version 0.1

This document specifies the dendropsis protocol: a structural-decomposition extension over a metatheca vault. dendropsis runs a frozen segmenter pipeline against the content blobs reachable from a metatheca state, organizes the resulting hierarchical decomposition into a tree of content-addressed nodes whose components are themselves deposita on a dedicated dendropsis cella under the same vault root, and exposes recursive structural diff, exact move detection, and partial-tree materialization against any historical tree-state.

The specification is language-agnostic. The reference implementation is in Rust (dendropsis-rs/).

dendropsis is structurally an extension over metatheca, not a parallel store. It introduces no new storage primitive — every byte it writes is a syntheca depositum and every mutable pointer it holds is an apotheca pinax — but it does occupy its own dedicated cella under the metatheca vault root, at the fixed path <vault>/ext/dendropsis/cella/. What it adds is a view — a hierarchical decomposition of metatheca’s content optimized for tree-shaped change analysis. The name reflects this: ὄψις (opsis) means "view, sight," and dendropsis names the tree-shaped view (from δένδρον, "tree") over the underlying vault. The suffix mirrors English synopsis and the sibling extensions logopsis and semopsis.

dendropsis is the structural complement to the search-shaped extensions. Where logopsis builds a lexical view (terms → posting lists) and semopsis builds a semantic view (embeddings → vector index), dendropsis builds a compositional view: each indexed blob is decomposed into a tree of content-addressed sub-blobs, and queries operate on tree structure (walk, diff, move-detect) rather than on tokens or vectors. The three extensions are independent and may be deployed in any combination; cross-extension coordination, when wanted, is the responsibility of an upstairs layer.

For the substrate primitives this document builds on, see metatheca SPEC.md, ../../../syntheca/SPEC.md, and ../../../apotheca/SPEC.md. This document covers what sits above those: the tree-node wire format, the segmenter-pipeline identity contract, the tree-state chain, the tree-head pinax, the query algorithms (walk, diff, move-detect), and the CLI surface.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.

1 Scope and terminology

This document specifies dendropsis Phase 1: a single local tree-decomposition view over a single metatheca vault, with rebuild-only reindex semantics (no incremental tree-node mutation), a single fixed segmenter-pipeline identity per chain, and three core queries (walk, recursive structural diff, exact move detection) operating against any historical tree-state. Phase 1 is the v0.1 release surface; deferred items are listed in §10–§11.

Terminology not redefined here is inherited from metatheca SPEC §1, syntheca SPEC §1, and apotheca SPEC §1. In particular: cella, depositum, pinax, hash (BLAKE3), digest (SHA-256), vault, entry, fact, state, head.

1.1 Segmenter

A segmenter is a deterministic function from input bytes plus a segmentation context to an ordered sequence of child references (§1.5). A segmenter is identified by a tuple (family, version) recorded as part of the pipeline declaration (§1.3). The family is an opaque string (e.g. unicode-sentence, markdown-section, fs-directory); the version is an opaque string (e.g. v1.2).

The segmenter’s contract is to be a deterministic pure function: two invocations of the same segmenter identity on identical input bytes under identical context MUST produce byte-identical output child-reference sequences.

dendropsis does not specify what a segmenter does internally (parser grammar, AST traversal rules, boundary detection, normalization). These are implementation-defined within the segmenter family/version. The protocol guarantees only that segmenter output is reproducible under a recorded segmenter identity.

A segmenter MAY emit one of three structural shapes (§1.2). The shape is declared in the pipeline (§1.3) and is fixed for that segmenter’s role in the pipeline.

1.2 Segmenter shapes

dendropsis recognizes three segmenter shapes; together they cover the decomposition patterns this specification supports.

A pipeline declaration (§1.3) MAY mix shapes: a chained segmenter at the root, a terminal segmenter at the leaves, recursive segmenters in between.

1.3 Pipeline

A pipeline is a directed acyclic graph of segmenter roles. Each node in the graph is a role: a named position in the pipeline bound to a segmenter identity (§1.1) and a segmenter shape (§1.2). Each edge declares "outputs of role A tagged for stage B are consumed by role B." Exactly one role is marked as the entry role: the role invoked on the source content blob.

The pipeline is consumer-defined: dendropsis does not bless segmenter identities, does not run segmenter code, and does not interpret role types. dendropsis records the pipeline declaration as data, dispatches segmenters per the declaration, and validates that segmenter outputs conform to their declared shapes.

1.4 Pipeline identity

A pipeline identity is the BLAKE3 hash of a content-addressed pipeline-declaration blob (§4.5). The identity is fixed by the genesis tree-state (§1.8) and inherited unchanged by every subsequent tree-state in the chain.

dendropsis MUST refuse to advance the tree-state chain to a tree-state whose pipeline identity differs from the genesis pipeline identity (§5.4). Changing the pipeline — adding a segmenter, upgrading a segmenter version, altering composition edges — is out of Phase 1 scope (§10) and is supported by dnops destroy5.10) followed by re-init.

The freezing rule reflects the design principle that a dendropsis chain’s identity is its pipeline. Trees built under different pipelines are distinct artifacts; the substrate refuses to conflate them. The underlying entry bytes are unaffected by pipeline changes (they live in metatheca’s cella); only the dendropsis-side derived view is rebuilt.

1.5 Child reference

A child reference identifies one child of a tree node. A child reference carries:

The substrate distinguishes leaf from tree at the protocol level so walks can terminate without parsing leaf bytes. The substrate does not record any further type information about what a leaf or subtree means; that interpretation lives in the segmenter pipeline.

1.6 Tree and tree root

A tree is a tree node together with its transitive children resolved through child references. A tree root is the BLAKE3 hash of the topmost tree node for a given entry’s decomposition. Given a tree root and a backing dendropsis cella, the entire tree is reachable via lazy descent (§5.5).

Trees are immutable: a given tree root names a specific tree forever. Changes to an entry’s content produce new tree roots; old tree roots remain valid pointers to historical decompositions of historical bytes.

1.7 Tree manifest

A tree manifest is the dendropsis-side projection of "for each metatheca entry indexed by the current tree-state, here is its tree root." It is materialized as a single content-addressed depositum (§4.3) per tree-state.

The manifest records, per entry: the entry-ID, the BLAKE3 hash of the metatheca content blob from which the tree was derived (so callers can detect when the blob-ref has advanced past this manifest), and the tree root. Entries reachable from the metatheca state but not yet indexed by dendropsis (e.g. because they were added after the most recent reindex) do not appear in the manifest.

1.8 Tree-state

A tree-state is a moment in the extension’s history. Each tree-state is materialized as a content-addressed tree-state blob4.4) capturing the tree manifest, the pipeline identity, the metatheca state it was built against, and a back-pointer to the prior tree-state. The set of tree-states linked through these back-pointers is the tree-state chain.

The genesis tree-state is the unique tree-state with previous = null, created by dnops init5.1).

1.9 Tree-head

The tree-head is the single mutable pointer identifying the current tree-state. It is realized as an apotheca pinax (§6) on the dendropsis cella, holding the BLAKE3 hash of the current tree-state blob.

The tree-head pinax is disjoint from metatheca’s head pinax (metatheca SPEC §6.1) and from any sibling extension’s head: the pinakes live in disjoint cellae and advance independently. dendropsis reindex advances tree-head; metatheca commits advance metatheca’s head; the two cadences are decoupled.

1.10 Tree-stateref

A tree-state reference (or tree-stateref) follows the same grammar as metatheca’s stateref (metatheca SPEC §1.7), resolving against the tree-state chain instead of the metatheca state chain:

A tree-stateref MUST resolve to exactly one tree-state or fail.

1.11 Move detection

Move detection is the dendropsis-specific query of identifying subtrees that appear at different positions in two trees. Because every tree node is content-addressed, a hash present at position P1 in tree T1 and at position P2 in tree T2 is the same subtree by content. Move detection compares the set of (hash, position) pairs across the two trees and reports hashes that appear in both at different positions. The cost is proportional to the size of the differing portions of the trees, not to total tree size. dendropsis treats move detection as a first-class query (§5.7) because the content-addressing makes it free relative to the cost of structural diff.

2 Relationship to metatheca and siblings

dendropsis is an extension over metatheca: it consumes the content made addressable by the metatheca chain and adds a derived, queryable view. It introduces no new storage primitive — every byte is a syntheca depositum and every mutable pointer is an apotheca pinax — but it does add a dedicated cella. All dendropsis durable state lives in a syntheca cella at the fixed vault-relative path <vault>/ext/dendropsis/cella/, separate from metatheca’s source-of-truth cella at <vault>/cella/ (metatheca SPEC §7). The split reflects the layers’ differing status: metatheca’s cella is the source of truth and is never wholesale-deletable; dendropsis’s cella is wholly derived and MAY be discarded and rebuilt at any time (§5.10).

The host/extension relationship has three concrete consequences:

  1. Content authority lives in metatheca. dendropsis MUST NOT mutate any metatheca-emitted fact, state blob, or the metatheca head pinax. dendropsis reads from metatheca’s cella to determine the set of content blobs to decompose; it does not write back.

  2. dendropsis writes are confined to dendropsis’s cella. dendropsis writes only deposita carrying dendropsis’s wire formats (§4) and the single tree-head pinax (§6) on its own cella. It MUST NOT write to any other cella in the vault.

  3. The chains advance independently. A metatheca commit does not trigger a dendropsis reindex, and a dendropsis reindex does not require a metatheca commit. dnops reindex may be invoked against any metatheca state in the chain; dendropsis records which metatheca state it was built against (§4.4 metatheca) so that any tree-state can be traced back to a precise point in the host’s history.

Where logopsis or semopsis is also deployed, its state lives in yet another dedicated cella under <vault>/ext/. dendropsis MUST NOT read from or write to any sibling extension’s cella. Cross-extension non-interference is enforced naturally by cella separation.

A vault MAY contain a metatheca chain without a dendropsis chain. A dendropsis chain MUST NOT exist without a metatheca chain on the same vault; dnops init5.1) requires metatheca’s head pinax to be present at <vault>/cella/.

3 Identifiers and time

3.1 Hashes

Content-addressing throughout dendropsis uses syntheca’s BLAKE3 hash (syntheca SPEC §1.1, §1.2). Wire form: 64 lowercase hex digits.

3.2 Time

All timestamps follow metatheca SPEC §2.3: nanoseconds since the Unix epoch as a signed 64-bit integer, monotonically non-decreasing within a single vault process.

3.3 Strings

All UTF-8 strings on the wire (segmenter family/version identifiers, role names, child-reference keys) are valid UTF-8. Implementations MUST reject any wire blob containing invalid UTF-8 with Malformed. Strings are compared byte-wise in UTF-8 lexicographic order wherever sorting is required.

4 Wire formats

4.1 Canonical JSON

All JSON blobs defined by this specification (tree node §4.2, tree manifest §4.3, tree-state §4.4, pipeline declaration §4.5) are serialized as canonical JSON per RFC 8785, identical to metatheca SPEC §3.1. Phase 1 uses JSON throughout; a future phase may introduce a binary tree-node format for dense trees (§11).

4.2 Tree-node blob

A tree-node blob is a canonical-JSON object:

{
  "type":      "dendropsis/node",
  "version":   1,
  "keyed":     <bool>,
  "children": [
    { "key": "<utf8>"?, "kind": "leaf" | "tree", "hash": "<blake3-hex>" }
  ]
}

Field constraints:

A tree-node’s identity is the BLAKE3 hash of its canonical-JSON bytes.

The canonical JSON encoding gives two important properties: deterministic identity (same logical content → same bytes → same hash) and trivial recursive comparison (two nodes are equal iff their hashes are equal; otherwise children are compared positionally or keywise).

4.3 Tree-manifest blob

A tree-manifest blob is a canonical-JSON object:

{
  "type":     "dendropsis/manifest",
  "version":  1,
  "entries": [
    { "entry": "<uuid-v7>", "blob": "<blake3-hex>", "tree": "<blake3-hex>" }
  ]
}

Field constraints:

A tree-manifest’s identity is the BLAKE3 hash of its canonical-JSON bytes.

The manifest is separated from the tree-state blob so that two tree-states sharing the same manifest deduplicate naturally on the cella (e.g. a reindex against the same metatheca state under the same pipeline).

4.4 Tree-state blob

A tree-state blob is a canonical-JSON object:

{
  "type":          "dendropsis/tree-state",
  "version":       1,
  "previous":      "<blake3-hex>" | null,
  "metatheca":     "<blake3-hex>",
  "pipeline":      "<blake3-hex>",
  "manifest":      "<blake3-hex>",
  "created_at_ns": <integer>
}

Field constraints:

A tree-state’s identity is the BLAKE3 hash of its canonical-JSON bytes.

4.5 Pipeline-declaration blob

A pipeline-declaration blob is a canonical-JSON object:

{
  "type":     "dendropsis/pipeline",
  "version":  1,
  "entry":    "<role-name>",
  "roles": [
    {
      "name":      "<role-name>",
      "segmenter": { "family": "<utf8>", "version": "<utf8>" },
      "shape":     "terminal" | "recursive" | "chained",
      "edges":     { "<output-stage-tag>": "<role-name>" }
    }
  ]
}

Field constraints:

A pipeline-declaration’s identity is the BLAKE3 hash of its canonical-JSON bytes. This identity is what is recorded as pipeline in every tree-state blob (§4.4) and what is fixed at genesis (§1.4).

5 Operations

A dendropsis-equipped vault exposes the following protocol operations. Each is total: it terminates with one of the outcomes listed for it.

Throughout this section, deposit and fetch of dendropsis blobs (tree nodes, manifests, tree-states, pipeline declarations) refer to operations on the dendropsis cella at <vault>/ext/dendropsis/cella/. Operations that fetch metatheca state blobs or content blobs target metatheca’s cella at <vault>/cella/. Step-level cella targets are noted only where the destination is non-obvious from context.

5.1 init

Creates a genesis tree-state on a vault that already has a metatheca chain.

  1. Verify metatheca’s head pinax is present on metatheca’s cella at <vault>/cella/2); fail with NotFound otherwise.

  2. Verify no dendropsis cella exists at <vault>/ext/dendropsis/cella/; fail with Conflict otherwise.

  3. Open a fresh syntheca cella at <vault>/ext/dendropsis/cella/, creating any missing parent directories. All subsequent deposits and the tree-head pinax target this cella.

  4. Construct a pipeline-declaration blob (§4.5) from a caller-supplied pipeline description. Validate that the graph is well-formed per §4.5. Deposit it; let p be the returned hash.

  5. Construct an empty tree-manifest blob (entries = []). Deposit it; let m be the returned hash.

  6. Construct the genesis tree-state blob: previous = null, metatheca = <current metatheca head>, pipeline = p, manifest = m, created_at_ns = now(). Deposit it; let g be the returned hash.

  7. Set the tree-head pinax (§6) on the dendropsis cella to g with expected = None.

The genesis tree-state contains no indexed entries; it exists so that reindex5.4) has a chain to advance from and so that the pipeline identity is fixed at init time.

On any failure prior to step 7, the implementation MUST leave no partially-initialized dendropsis cella. If the cella was created in step 3, it MUST be removed before returning the failure.

5.2 Decompose a blob

Given a content blob b and a pipeline P, produce a tree root by recursive segmenter application:

  1. Fetch bytes = syntheca.get(b) from metatheca’s cella.

  2. Invoke the entry-role segmenter on bytes per §1.1; obtain an ordered sequence of child-reference outputs per §1.5.

  3. For each output child reference of kind = leaf: the child is a syntheca-depositum reference. Verify the referenced bytes are present in metatheca’s cella or, if produced by the segmenter and not already present, deposit them; let the resulting hash be the child’s hash.

  4. For each output child reference of kind = tree: identify the downstream role per the pipeline’s edges (§4.5) — for a terminal segmenter this case is empty; for a recursive segmenter the same role is invoked on the referenced bytes; for a chained segmenter the role named in edges[<output-stage-tag>] is invoked. Recursively apply §5.2 with the downstream role as entry; let the resulting tree root be the child’s hash.

  5. Construct a tree-node blob (§4.2) from the resulting child-reference sequence. Deposit it; let t be the returned hash. Return t.

The segmenter functions are opaque to this specification. Their only contract is determinism (§1.1): identical input bytes under identical pipeline MUST produce identical output sequences.

Tree-node deposits are content-addressed; identical sub-trees deduplicate naturally on the cella. This is the source of cross-tree subtree identity (§1.11) and cross-entry citation graph queries (§11).

5.3 Build the tree manifest

Given a metatheca state M and a pipeline P, materialize the tree manifest:

  1. Walk M’s cumulative fact set (metatheca SPEC §3.3) and collect the set of core/blob-ref blobs current in the projection (metatheca SPEC §5.5).

  2. For each such (entry, blob) pair (e, b): a. Compute t = decompose(b, P) per §5.2. b. Append (e, b, t) to the manifest entries.

  3. Sort the manifest entries by entry ascending per §4.3. Construct the tree-manifest blob and deposit it; let m be the returned hash. Return m.

5.4 Reindex

Atomically advances tree-head from the current tree-state to a new tree-state built against a chosen metatheca state.

Inputs: a metatheca stateref (default: metatheca’s current head).

  1. Resolve the metatheca stateref per metatheca SPEC §5.3 to a metatheca state hash M.

  2. Read the current tree-state blob via the tree-head pinax (§6); extract its pipeline identity p. Compute the storage digest d of the tree-head pinax bytes: sha256 over the bytes returned by syntheca.get_pinax, or remembered from the prior successful set_pinax (apotheca SPEC §2.53.5 expected semantics).

  3. Fetch the pipeline-declaration blob at p; reconstruct the pipeline P.

  4. Build the tree manifest per §5.3 against M under P; obtain m.

  5. Construct the new tree-state blob: previous = <current tree-head>, metatheca = M, pipeline = p, manifest = m, created_at_ns = now(). Deposit it; let s be the returned hash.

  6. Call syntheca.set_pinax(name = "head", bytes = s_bytes, expected = Some(d)) against the dendropsis cella.

The commit point is step 6. Steps 1–5 are pre-commit and produce only content-addressed deposita that are safe under interruption.

On Conflict from step 6, another writer advanced tree-head concurrently. The caller MAY retry from step 2.

The pipeline identity is fixed by the genesis tree-state and inherited unchanged by every subsequent tree-state. reindex MUST refuse to write a tree-state with a pipeline field that differs from the current tree-state’s. Changing the pipeline is out of Phase 1 scope (§10) and is supported by destroy + re-init (§5.10).

5.5 Walk a tree

Given a tree root and an optional depth limit, return the tree’s structure lazily:

  1. Fetch the tree-node blob at the given root hash from the dendropsis cella; parse per §4.2. Let its child-reference list be cs.

  2. Yield (root, cs) to the caller.

  3. For each child c in cs: a. If c.kind = leaf: yield (c.hash, leaf) to the caller. b. If c.kind = tree and the depth limit has not been reached: recurse with c.hash as root and the depth limit decremented.

Walk is read-only. Implementations MAY add a local node-resolution cache; the cache is not part of the protocol. A walk with depth limit 0 returns the root node and its immediate children’s references without resolving any subtree — useful for shallow inspection.

5.6 Recursive structural diff

Given two tree roots r1 and r2, classify their difference:

  1. If r1 == r2: return identical. No reads required; no recursion.

  2. Otherwise: fetch both tree-node blobs; parse per §4.2.

  3. If the two nodes have different keyed values: return shape-mismatch. (A keyed node is structurally incompatible with an unkeyed one at the same position.)

  4. If keyed = true: for each key present in one node and not the other, record an added or removed difference. For each key present in both, if the child hashes differ, recurse with the two child hashes; the recursion’s result is recorded as a nested difference at this key.

  5. If keyed = false: compute the longest common subsequence of the two child-reference lists by hash equality. Children present in one list but not the other are recorded as inserted or deleted differences. Children at corresponding positions whose hashes differ are recorded as nested differences via recursion. (Phase 1 uses LCS; future phases may add specialized algorithms for large lists.)

  6. Return the resulting difference tree.

The cost is proportional to the size of the differing portions of the trees, not to total tree size. Identical subtrees are detected by hash equality at step 1 and skipped without recursion. This is the source of the O(differing nodes) cost claim (§1.11).

Diff is read-only.

5.7 Move detection

Given two tree roots r1 and r2, identify subtrees present in both trees at different positions:

  1. Walk r15.5) without depth limit. Build a map m1: hash → list of positions, where a position is the path of (key | index) steps from the root to the subtree.

  2. Walk r2 similarly to build m2.

  3. For each hash h present in both m1 and m2: if the position sets differ, report h together with the set difference. (A subtree that appears at the same position in both is not a move; a subtree that appears at one new position and remains at another is a copy; a subtree that appears at exactly one position in each, with the positions different, is a move.)

  4. Return the resulting move list.

Move detection is read-only. The cost is proportional to the sum of node counts in the two trees. Implementations MAY share walk state with §5.6 when both queries are requested over the same pair.

Move detection is symmetric on hash; it does not distinguish "the original" from "the copy" because content-addressed identity makes that distinction meaningless. Caller-side heuristics (e.g. "the position with an earlier timestamp is the original") are outside the scope of this protocol.

5.8 Resolve a tree-state reference

Given a tree-stateref (§1.10) and the current tree-head, return the unique tree-state hash it denotes. Algorithmically identical to metatheca SPEC §5.3 with the tree-state chain substituted for the metatheca state chain.

5.9 Walk the tree-state chain

Algorithmically identical to metatheca SPEC §5.4 with the tree-state chain substituted.

5.10 Destroy

Removes the dendropsis cella and all dendropsis state from the vault.

Inputs: none.

  1. If <vault>/ext/dendropsis/cella/ is absent, fail with NotFound.

  2. Recursively remove the directory <vault>/ext/dendropsis/ (including the cella and any extension-private auxiliary state under it).

Destroy is total: on completion, no dendropsis state remains on the vault. metatheca state and any sibling extension state are unaffected. A subsequent dnops init5.1) succeeds if and only if destroy completed.

Destroy is the supported mechanism for changing the segmenter pipeline (§10): destroy the chain, then re-run dnops init with the desired pipeline. It is also the recovery mechanism for a corrupted dendropsis cella, since the chain is wholly derivable from metatheca’s cella by reindexing.

Destroy MUST NOT touch <vault>/cella/, <vault>/index.db, or any other entry in <vault>/ext/.

6 Tree-head pinax

6.1 Name

The tree-head pinax MUST be stored on the dendropsis cella under the apotheca pinax name head (four ASCII octets: h, e, a, d). This name is fixed by this specification.

The name is a valid apotheca name (apotheca SPEC §4.1): non-empty, length 4, no /, no NUL. dendropsis therefore imposes no multi-segment-name requirement on the underlying apotheca implementation.

The pinax shares only its name with metatheca’s head (metatheca SPEC §6.1) and with any sibling extension’s head; the pinakes live in disjoint cellae and never collide. The same naming convention is used by logopsis and semopsis.

6.2 Bytes

The tree-head pinax bytes follow metatheca SPEC §6.2 verbatim: the 64-octet ASCII encoding of the current tree-state’s BLAKE3 hash as 64 lowercase hex digits, with no trailing newline or whitespace.

6.3 Compare-and-swap protocol

All updates to tree-head MUST go through syntheca.set_pinax with expected set to the SHA-256 digest of the current tree-head bytes, identical in mechanism to metatheca SPEC §6.3. The genesis case (init, §5.1) sets tree-head with expected = None.

7 CLI surface

The reference CLI binary is dendropsis with the recommended alias dnops. It exposes the protocol against the vault rooted at the current working directory unless overridden with --vault <PATH>.

This section specifies the protocol-bearing subcommands. Diagnostic output formatting is implementation-defined unless otherwise noted.

7.1 dnops init

dnops init --pipeline <pipeline-path-or-hash> [<path>]

Initialize a dendropsis chain on a vault per §5.1. The vault root is <path> if supplied, otherwise the current directory. The vault MUST already contain a metatheca chain. Exits non-zero if metatheca is absent or a dendropsis chain already exists.

--pipeline accepts either a path to a pipeline-declaration JSON file (which the implementation validates and deposits) or a BLAKE3 hash of a pipeline-declaration blob already present on a cella accessible to the caller (the caller supplies the bytes via the implementation’s configured mechanism). Implementations SHOULD warn if the chosen pipeline identity is new to the cella, since this is now permanent for the chain.

7.2 dnops reindex

dnops reindex [--as-of <metatheca-stateref>]

Build a new tree-state per §5.4 against the named metatheca state (default: metatheca’s current head). On success, prints the new tree-state hash to standard output.

7.3 dnops tree

dnops tree [--as-of <tree-stateref>] <entry-uuid>

Print the tree root for an entry as recorded in the given (or current) tree-state’s manifest. Exits non-zero if the entry is not present in the manifest.

7.4 dnops walk

dnops walk [--depth <int>] <tree-root>

Walk a tree per §5.5 from the given root hash. Default depth is unbounded. Output format: one line per visited node or leaf, prefixed with depth and node hash:

<depth>  <kind>  <hash>  [<key>]

Where <kind> is node or leaf and <key> is the child key (for keyed parents) or the child index (for unkeyed parents).

7.5 dnops diff

dnops diff <tree-root-1> <tree-root-2>

Compute the recursive structural diff per §5.6. Output format is implementation-defined; the reference implementation emits a hierarchical text representation of the difference tree.

7.6 dnops moves

dnops moves <tree-root-1> <tree-root-2>

Compute the move list per §5.7. Output format per move, one line:

<subtree-hash>  <from-path>  <to-path>

Where paths are /-joined sequences of keys or indices from the respective tree roots.

7.7 dnops state

dnops state [<tree-stateref>]

Show metadata for a tree-state: hash, created_at_ns, previous, metatheca, pipeline identity, manifest hash, manifest entry count. Default argument: current.

7.8 dnops log

dnops log [--since <iso8601>] [--until <iso8601>] [-n <count>]

Walk the tree-state chain from current tree-head backward, emitting one line per tree-state with its short hash, ISO-8601 timestamp, and metatheca state short hash.

7.9 dnops fsck

dnops fsck

Verify: the tree-head pinax bytes encode a tree-state present in the cella; every tree-state in the chain parses; the pipeline identity is constant across the chain and references a pipeline-declaration blob present in the cella; every tree-manifest parses, its entries array is sorted ascending in UUID v7 order, and every referenced tree root is a tree-node blob present in the cella; every tree-node blob parses, has consistent keyed/no-key per §4.2, and has sorted child arrays where required; every reachable child of kind = leaf is present in metatheca’s cella; every tree-state’s metatheca field names a metatheca state present in metatheca’s cella. Implementations MAY rely on syntheca and apotheca verification for byte-level integrity.

7.10 dnops destroy

dnops destroy [--force] [<path>]

Destroy the dendropsis cella per §5.10. The vault root is <path> if supplied, otherwise the current directory. Unless --force is supplied, implementations SHOULD prompt for confirmation before proceeding, since destroy is irreversible without a reindex (§5.4) to rebuild the chain — and reinitializing with a different pipeline produces tree hashes that differ from any previously published. Exits non-zero if no dendropsis cella exists at the resolved path.

7.11 Reserved

The verb gc is reserved for future phases.

8 Errors

The error conditions defined by this specification are:

Implementations MAY surface additional implementation-defined errors (I/O failure, segmenter unavailable, …); such errors MUST be distinguishable from the protocol-defined errors above.

9 Conformance

A Phase 1 implementation MUST implement:

A Phase 1 implementation MAY omit:

10 Out of scope (Phase 1)

The following are deliberately out of Phase 1 scope. Their absence is load-bearing for the v0.1 surface and consumers MUST NOT rely on them.

11 Future work (non-normative)