taxopsis — Specification

version 0.1

This document specifies the taxopsis protocol: a typed predicate-retrieval extension over a metatheca vault. taxopsis extracts (entry, predicate, typed value) tuples from the facts reachable from a metatheca state, organizes them into ordered segments and hash-bucketed postings whose components are themselves deposita and pinakes on a dedicated taxopsis cella under the same vault root, and exposes exact filter queries — equality, prefix, range — with optional ordered enumeration, against any historical index-state.

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

taxopsis 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/taxopsis/cella/. What it adds is a view — a particular organization of metatheca’s facts optimized for typed filtering and ordering. The name reflects this: ὄψις (opsis) means "view, sight," and taxopsis names the ordered view (from τάξις, "arrangement, ordering") over the underlying vault. The suffix mirrors English synopsis.

Where the sibling retrieval extensions rank — logopsis by lexical relevance, semopsis by semantic similarity — taxopsis filters: retrieval is exact, results are entry sets (optionally ordered), and no scores are produced. taxopsis is a complete, self-contained specification and MAY be deployed without any sibling.

For motivation, design history, and the dbaiv bridge this member generalizes, see README.md. 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 extractor identity contract, the index wire formats, the index-state chain, the index-head pinax, the query semantics, 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 taxopsis Phase 1: a single local predicate index over a single metatheca vault, with rebuild-only reindex semantics (no incremental postings mutation; unchanged components deduplicate by content address), a single fixed extractor identity per chain, binary string collation only, and conjunctive (AND) / disjunctive (OR) / negation filter operators with optional single-predicate ordering. Phase 1 is the v0.1 release surface; deferred items are listed in §11–§12.

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, projection.

1.1 Predicate

A predicate is a non-empty UTF-8 string naming one queryable attribute of an entry — e.g. audio/field:utf8:title or kind. Predicate names MUST NOT contain NUL (U+0000) and MUST NOT contain the octet 0x3D (=), which delimits the hashed-layout key (§5.6). The predicate name set is open; extractors (§1.4) define it. Predicates SHOULD be namespaced by their host convention (e.g. a fact-kind prefix) to keep multi-consumer vaults collision-free.

1.2 Typed value

A typed value is one of:

Every predicate has exactly one value type, declared in the predicate table (§5.8); a build that derives values of different types for one predicate MUST fail with Malformed.

1.3 Tuple

A tuple is one (entry, predicate, typed value) triple produced by the extractor. Tuples form a set: duplicate triples collapse. An entry MAY carry multiple values for the same predicate (e.g. several artist names); each is its own tuple.

1.4 Extractor

The extractor is the deterministic function that derives the tuple set from the host projection at a metatheca state. An extractor is named by an extractor identity: a pair (family, version) of non-empty UTF-8 strings — e.g. ("metatheca-facts", "1") or ("audiotheca-fields", "1"). Identical projection contents under an identical extractor identity MUST yield the identical tuple set. The identity is fixed at init and inherited by every index-state in the chain (§6.4); changing it is a new chain, never a silent edit. This is the same discipline as logopsis’s analyzer identity (logopsis SPEC §1.2).

The default extractor family metatheca-facts is defined in §4.1. Host-specific extractors (e.g. audiotheca’s effective-metadata extractor) are registered by name in the implementation, exactly as analyzers are in logopsis; an operation naming an extractor the build does not implement fails with ExtractorMismatch.

1.5 Layout

Each predicate is materialized in exactly one of two layouts, declared in the predicate table:

The layout assignment is the extractor’s: each tuple is emitted with its predicate’s declared layout and type, and the assignment MUST be deterministic under the extractor identity.

1.6 Postings

A postings sequence is an ordered list of entry IDs — ascending UUID v7 order — recording which entries carry a given predicate–value combination (hashed layout) or a given value run (ordered layout).

1.7 Segment and fence manifest

In the ordered layout, a predicate’s tuples are sorted by (value, entry) and chunked into segments — content-addressed binary blobs (§5.4) — with a per-predicate fence manifest5.5) recording each segment’s minimum and maximum value, so a range query touches only the covering segments.

1.8 Bucket

In the hashed layout, predicate–value combinations are routed to buckets by a hash prefix of their key (§5.6); each bucket is a content-addressed binary blob (§5.7) holding the postings of every combination routed to it, sorted by key.

1.9 Predicate table

The predicate table is the canonical-JSON blob (§5.8) that declares every predicate (layout, type, fence-manifest hash) and the hashed layout’s bucket map for one index-state. It is the query planner’s single entry point.

1.10 Index-state

An index-state is one immutable snapshot of the index: a canonical-JSON blob (§5.9) referencing its predecessor, the metatheca state it indexes, the extractor identity, the layout configuration, and the predicate table. Index-states form a chain, exactly as metatheca states and logopsis search-states do.

1.11 Index-head

The index-head is the mutable pointer to the current index-state: an apotheca pinax on the taxopsis cella (§7), advanced by compare-and-swap.

1.12 Index-stateref

An index-stateref names an index-state: the literal current (the index-head’s target), a full 64-hex BLAKE3 hash, or an unambiguous hash prefix of at least 4 hex digits. Resolution follows logopsis SPEC §1.9 with "search-state" read as "index-state"; an ambiguous prefix fails with Ambiguous, an unresolvable reference with NotFound.

1.13 Index

The index of an index-state is the pair (predicate table, the segment/bucket blobs it references) — everything the query algorithm (§6.5) reads. The index is a pure function of the metatheca state and the identity parameters pinned at init; it MAY be deleted and rebuilt at any time (§6.12).

2 Relationship to metatheca and the siblings

taxopsis requires a host vault with a metatheca chain: its init6.1) verifies the metatheca head pinax and every reindex pins the produced index-state to a metatheca state hash. Unlike logopsis and semopsis — whose corpus is the content blobs of projection-visible entries — taxopsis consumes the facts: its extractor reads the host projection (metatheca SPEC §5.5) at the pinned state and derives tuples from fact values. An entry needs no live path and no content blob to be indexed by taxopsis; it needs facts the extractor maps to tuples.

The taxopsis cella at <vault>/ext/taxopsis/cella/ is dedicated: metatheca never writes to it, taxopsis never writes to the host cella at <vault>/cella/. The index-state chain advances on its own cadence, independently of the metatheca head; an index-state may lag the metatheca head arbitrarily, and reindex may target any historical metatheca state.

Sibling extensions are independent: a vault MAY run any subset of {logopsis, semopsis, dendropsis, zetetes, taxopsis}, each on its own cella with its own head. A coordinator that composes taxopsis filters with ranked retrieval (e.g. zetetes consuming a taxopsis entry set as a filter source) is future work (§12).

3 Identifiers and time

3.1 Hashes

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

The routing hash of the hashed layout (§5.6) is configurable per chain (blake3 default, sha256 for dbaiv-profile chains) and affects bucket assignment only — never content addressing.

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 and collation

All predicate names and string values on the wire are valid UTF-8; implementations MUST reject anything else with Malformed. Phase 1 defines a single string collation, binary: lexicographic comparison of UTF-8 bytes. Hosts wanting locale-insensitive ordering SHOULD have their extractor emit a pre-folded value (e.g. audiotheca’s canonical slugs or transliterated variants) rather than expect the index to collate; ICU collations are future work (§12).

4 The extractor contract

Given a metatheca state M and an extractor identity xid, the extractor produces the tuple set tuples(M, xid):

  1. The input is the host projection at M — the current facts per metatheca SPEC §5.5 — plus, at the extractor’s discretion, content blobs reachable from projection-visible entries (a dbaiv-style extractor reads document bytes; a fact extractor does not).

  2. The output is a finite set of tuples (§1.3), each carrying the predicate’s declared layout and value type (§1.5).

  3. Determinism: identical projection contents (and, where read, identical blob bytes) under identical xid MUST produce the identical tuple set, layout assignment, and type assignment.

The extractor function is otherwise opaque to this specification. Everything the index stores is a pure function of tuples(M, xid) and the layout configuration (§5.9).

4.1 The fact extractor

The extractor family metatheca-facts is defined by this specification as the host-agnostic default. Version 1:

The fact extractor gives any metatheca vault a queryable fact-by-value index with no host code. Its currency rule is exact for kinds shaped as discriminators-plus-value; kinds carrying provenance members alongside value split attributes per provenance under it, and hosts with such kinds — or wanting typed dates, derived values, or effective-value resolution — write their own extractor.

5 Wire formats

5.1 Canonical JSON

All JSON blobs defined by this specification (index-state §5.9, predicate table §5.8, fence manifest §5.5) are serialized as canonical JSON per RFC 8785, identical to metatheca SPEC §3.1 — including its exact-integer rule: int and timestamp values appear as JSON integers, never floats. Tabular payloads (segments, buckets) are encoded in the binary formats defined here and referenced by BLAKE3 hash, not embedded as JSON.

5.2 Integer encoding for binary payloads

Where this specification requires variable-length integer encoding in a binary payload, it uses LEB128 unsigned (ULEB128) for non-negative integers. Fixed-width integers are little-endian; signed 64-bit values are encoded as 8 octets little-endian two’s complement.

5.3 Value encoding

Within binary payloads, a typed value is encoded as:

The lexical form of a value — used in the hashed-layout key (§5.6) and in fence manifests — is: the string itself for string; the minimal decimal ASCII representation (optional leading -, no leading zeros, 0 for zero) for int and timestamp.

5.4 Segment blob (binary, ordered layout)

A segment blob encodes one sorted run of (value, entry) records for a single predicate. The blob header is:

+--------+--------+--------+--------+
| magic: 4 octets   "TXSG"          |
+--------+--------+--------+--------+
| version: u32 LE  = 1              |
+--------+--------+--------+--------+
| value_type: u8  (1=string, 2=int, 3=timestamp) |
+-----------------------------------+
| record_count: ULEB128             |
+-----------------------------------+
| records: <record_count> records   |
+-----------------------------------+

Each record is:

+-----------------------------------+
| value: per value_type (S5.3)      |
+-----------------------------------+
| entry: 16 octets, UUID v7 binary  |
+-----------------------------------+

Field constraints:

A segment’s identity is the BLAKE3 hash of these bytes.

The binary format is chosen over JSON for the same reason as logopsis’s posting lists (logopsis SPEC §4.3): segments dominate the index byte size and are scanned in tight loops.

5.5 Fence manifest blob (ordered layout)

A fence manifest is a canonical-JSON object describing one predicate’s segments:

{
  "type":       "taxopsis/fences",
  "version":    1,
  "predicate":  "<utf8>",
  "value_type": "string" | "int" | "timestamp",
  "segments": [
    { "min": <value>, "max": <value>, "count": <integer>,
      "segment": "<blake3-hex>" }
  ]
}

Field constraints:

A fence manifest’s identity is the BLAKE3 hash of its canonical-JSON bytes. Segment sizing is implementation-defined; implementations SHOULD target segment blobs of roughly 256 KiB to 1 MiB so that remote deployments fetch little more than they scan.

5.6 Hashed-layout key and routing

The key of a predicate–value combination is the UTF-8 string <predicate>=<lexical form of value>5.3). Its bucket is named by the first bucket_prefix lowercase hex characters of route_hash(key), where route_hash is the chain’s configured routing hash (§5.9) — the same construction as dbaiv’s deterministic bucket prefix, so a sha256 chain with matching prefix length produces dbaiv-identical bucket structures. All builders of a chain MUST produce identical bucket assignments.

5.7 Bucket blob (binary, hashed layout)

A bucket blob encodes the postings of every key routed to one bucket. The blob header is:

+--------+--------+--------+--------+
| magic: 4 octets   "TXBK"          |
+--------+--------+--------+--------+
| version: u32 LE  = 1              |
+--------+--------+--------+--------+
| group_count: ULEB128              |
+-----------------------------------+
| groups: <group_count> groups      |
+-----------------------------------+

Each group is:

+-----------------------------------+
| key_len: ULEB128                  |
+-----------------------------------+
| key: <key_len> octets UTF-8       |
+-----------------------------------+
| entry_count: ULEB128              |
+-----------------------------------+
| entries: <entry_count> x 16 octets|
+-----------------------------------+

Field constraints:

A bucket’s identity is the BLAKE3 hash of these bytes. The literal key stored in each group disambiguates routing-hash collisions: lookups match on the key bytes, never on the routing hash.

5.8 Predicate table blob

The predicate table is a canonical-JSON object:

{
  "type":      "taxopsis/predicates",
  "version":   1,
  "extractor": { "family": "<utf8>", "version": "<utf8>" },
  "ordered": [
    { "predicate": "<utf8>", "value_type": "string" | "int" | "timestamp",
      "collation": "binary", "tuple_count": <integer>,
      "fences": "<blake3-hex>" }
  ],
  "hashed": {
    "key_count": <integer>,
    "buckets": [ { "prefix": "<hex>", "bucket": "<blake3-hex>" } ]
  }
}

Field constraints:

A predicate table’s identity is the BLAKE3 hash of its canonical-JSON bytes. The table is separated from the index-state blob so that adjacent reindexes over unchanged tuple sets deduplicate on the cella.

5.9 Index-state blob

An index-state blob is a canonical-JSON object:

{
  "type":          "taxopsis/index-state",
  "version":       1,
  "previous":      "<blake3-hex>" | null,
  "metatheca":     "<blake3-hex>",
  "extractor":     { "family": "<utf8>", "version": "<utf8>" },
  "routing":       { "hash": "blake3" | "sha256", "bucket_prefix": <integer> },
  "predicates":    "<blake3-hex>",
  "created_at_ns": <integer>
}

Field constraints:

An index-state’s identity is the BLAKE3 hash of its canonical-JSON bytes.

6 Operations

A taxopsis-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 taxopsis blobs (index-states, predicate tables, fence manifests, segments, buckets) refer to operations on the taxopsis cella at <vault>/ext/taxopsis/cella/. Operations that read metatheca states, facts, or content blobs target the host cella at <vault>/cella/.

6.1 init

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

  1. Verify metatheca’s head pinax is present on the host cella (§2); fail with NotFound otherwise.

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

  3. Verify the caller-supplied extractor identity is implemented by this build; fail with ExtractorMismatch otherwise.

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

  5. Construct the empty predicate table (ordered = [], hashed = {key_count: 0, buckets: []}) for the supplied extractor identity. Deposit it; let pt be the returned hash.

  6. Construct the genesis index-state blob: previous = null, metatheca = <current metatheca head>, extractor = <as supplied>, routing = <as supplied or {blake3, 2}>, predicates = pt, created_at_ns = now(). Deposit it; let g be the returned hash.

  7. Set the index-head pinax (§7) on the taxopsis cella to g with expected = None.

The genesis index-state contains no tuples; it exists so that reindex has a chain to advance from and so that the extractor identity and routing configuration are fixed at init time. On any failure prior to step 7, the implementation MUST leave no partially-initialized taxopsis cella; if the cella was created in step 4, it MUST be removed before returning the failure.

6.2 Extract

Given a metatheca state M and the chain’s extractor identity xid, compute tuples(M, xid) per §4. Tuple sets are not themselves deposited; they are consumed in-process by §6.3.

6.3 Build the index

Given the tuple set from §6.2 and the routing configuration, materialize the index:

  1. Partition tuples by layout, per the extractor’s assignment.

  2. Ordered: group by predicate; sort each group by (value, entry); chunk into segments per §5.4 and deposit each; construct and deposit the fence manifest per §5.5.

  3. Hashed: form each tuple’s key (§5.6); group by key, sorting each group’s entries ascending; route keys to buckets; encode and deposit each non-empty bucket per §5.7.

  4. Construct the predicate table per §5.8 and deposit it; let pt be the returned hash. Return pt.

Because every blob is content-addressed, components unchanged since the previous index-state deposit as no-ops: rebuild-only semantics still re-materialize only what changed on the cella.

6.4 Reindex

Atomically advances the index-head from the current index-state to a new index-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 state hash M.

  2. Read the current index-state blob via the index-head pinax; extract its extractor identity xid and routing configuration. Compute the storage digest d of the index-head pinax bytes (SHA-256, per the CAS protocol of §7.3).

  3. Run §6.2 and §6.3 against M and xid, yielding the predicate-table hash pt. An implementation MAY increase routing.bucket_prefix for the new index-state per §5.9.

  4. Construct the new index-state blob with previous = <current index-state hash>, metatheca = M, the inherited extractor, the (possibly prefix-increased) routing, predicates = pt, created_at_ns = now(). Deposit it; let s be the returned hash.

  5. Compare-and-swap the index-head to s with expected = d. On CAS failure, fail with Conflict; the deposited blobs remain (harmless, content-addressed) and the caller MAY retry from step 2.

Reindex MUST NOT change the extractor identity or routing.hash; a caller requesting either fails with ExtractorMismatch. Changing them is a destroy plus a fresh init.

6.5 Query

Evaluates a filter expression against an index-state.

Inputs: a filter expression (§6.6), an index-stateref (default current), an optional order clause (§6.7), an optional limit and cursor (§6.8).

Semantics are set-algebraic. Each atom denotes the set of entries with at least one tuple satisfying it, evaluated against the index-state’s predicate table:

AND is set intersection, OR is set union, NOT is set difference from its enclosing conjunction. A filter expression MUST contain at least one atom at even negation depth; a pure negation fails with Malformed. Evaluation strategy is implementation-defined (merge-joins over the sorted postings are the intended shape), but the result MUST equal the set semantics above.

The result is the (possibly ordered, possibly paginated) sequence of matching entry IDs; with an order clause, each result also carries the ordering value used. taxopsis returns entry identity only — resolving entries to domain objects is the host’s concern.

6.6 Filter expressions

The textual grammar for the CLI and for interchange:

expr    := or
or      := and ( "OR" and )*
and     := unary ( "AND" unary )*
unary   := "NOT" unary | "(" expr ")" | atom
atom    := pred op value | pred "prefix" string
op      := "=" | "<" | "<=" | ">" | ">="
pred    := bare identifier | quoted string
value   := integer | quoted string

Quoted strings are double-quoted with \" and \\ escapes. between is spelled as a conjunction of >= and <=. A malformed expression fails with Malformed. Programmatic surfaces MAY accept a structured equivalent; the semantics are those of §6.5 either way.

6.7 Ordering

An order clause names one ordered-layout predicate and a direction (asc default, or desc). Results order by each entry’s least value for the predicate when ascending, its greatest when descending, under the type’s ordering; ties and entries lacking the predicate order by ascending entry ID, with lacking entries after all valued ones. The ordering MUST be total and deterministic.

6.8 Pagination

With a limit n, the query returns the first n results of the (ordered) result sequence plus a cursor when more remain: the (ordering value, entry) pair of the last returned result ((entry) alone when no order clause is present). Passing the cursor back resumes strictly after that position against the same index-state; implementations MAY encode the cursor opaquely but MUST reject a cursor whose index-state does not match with Malformed. Cursors against current MUST be interpreted against the index-state that was current when the cursor was issued.

6.9 Resolve an index-stateref

Per §1.12. Outcomes: the index-state hash, NotFound, or Ambiguous.

6.10 Walk the index-state chain

From any resolved index-state, follow previous links to genesis, yielding each index-state blob. A missing link fails with NotFound (the chain is corrupt; see §6.11).

6.11 fsck

Verifies, for every index-state reachable from the index-head: blob parse per §5; chain integrity to genesis; extractor and routing.hash constancy; bucket_prefix monotonicity; predicate-table, fence-manifest, segment, and bucket constraints (sort orders, counts, routing membership, type consistency); and presence of every referenced blob. Reports all violations; an empty report is success.

6.12 Destroy

Removes the taxopsis subtree <vault>/ext/taxopsis/ — the cella and any extension-private auxiliary state — and nothing else: never sibling extensions, never the host cella. The host vault is unaffected; a subsequent init starts a fresh chain.

6.13 Auxiliary state

An implementation MAY maintain extension-private auxiliary state — caches, projections, or other derived structures that accelerate its own operation — under <vault>/ext/taxopsis/, outside the cella. §6.12 already provides for the removal of any such state; this section states the contract that makes it legitimate.

  1. Observational neutrality. Auxiliary state MUST NOT change the observable behaviour of any protocol operation: a query answered from auxiliary state MUST return results identical to the same query evaluated from the wire-format blobs (§5) — the same entries, the same order, the same cursors.

  2. Disposability. Auxiliary state MUST be wholly derived: deleting it at any moment MUST NOT affect correctness, only performance.

  3. Containment. Auxiliary state MUST NOT be written into any cella, referenced from any wire-format blob, or otherwise become interchange surface.

  4. Freshness. An implementation SHOULD key auxiliary state to the hash of the index-state it reflects; anything it cannot serve falls back to the wire-format blobs.

Maintaining no auxiliary state is always conformant (§10); these constraints bind only implementations that choose acceleration.

7 Index-head pinax

7.1 Name

The index-head pinax MUST be stored on the taxopsis cella under the apotheca pinax name head, exactly as the sibling extensions store theirs (logopsis SPEC §6.1): the pinakes live in disjoint cellae and never collide.

7.2 Bytes

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

7.3 Compare-and-swap protocol

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

8 CLI surface

The reference CLI binary is taxopsis with the recommended alias txops. 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.

8.1 txops init

txops init --extractor <family>:<version>
           [--routing-hash blake3|sha256]
           [--bucket-prefix <N>] [PATH]

Runs §6.1. Defaults: --routing-hash blake3, --bucket-prefix 2. The extractor identity is permanent for the chain; the CLI SHOULD say so when defaulting anything.

8.2 txops reindex

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

Runs §6.4; prints the new index-state hash.

8.3 txops query

txops query <expr> [--order-by <pred>[:asc|:desc]]
            [--limit <N>] [--cursor <C>]
            [--as-of <index-stateref>]

Runs §6.5 with the expression grammar of §6.6; - reads the expression from standard input. Output: one result per line — the entry ID, followed by the ordering value when --order-by is given — then, when results remain, a final cursor <C> line.

8.4 txops state

txops state [<index-stateref>]

Prints the resolved index-state’s metadata: hash, previous, metatheca state, extractor identity, routing configuration, ordered-predicate count, hashed key count, created time.

8.5 txops log

txops log [--limit <N>] [<index-stateref>]

Walks the chain per §6.10 from the given index-state (default current), printing one line per index-state.

8.6 txops fsck

Runs §6.11; exit status 0 iff no violations.

8.7 txops destroy

Runs §6.12. The CLI MUST require explicit confirmation (interactive prompt or a --force flag).

8.8 Reserved

txops compact, txops verify, and txops serve are reserved for future phases and MUST NOT be given other meanings by implementations.

9 Errors

The error conditions defined by this specification are:

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

10 Conformance

A Phase 1 implementation MUST implement:

A Phase 1 implementation MAY omit §11 items.

11 Out of scope (Phase 1)

12 Future work (non-normative)