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:
string— a valid UTF-8 string. Ordering: lexicographic over UTF-8 bytes (thebinarycollation, §3.3).int— a signed 64-bit integer. Ordering: signed numeric.timestamp— nanoseconds since the Unix epoch as a signed 64-bit integer (metatheca SPEC §2.3). Ordering: signed numeric. The type is distinguished frominton the wire so consumers can render it; its ordering and encoding are those ofint.
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:
ordered— per-predicate sorted segments (§5.4) with a fence manifest (§5.5). Supports equality, prefix, range, and ordering. Intended for bounded predicate-name sets with typed values (the faceted-browse regime).hashed— global hash-bucketed postings (§5.7). Supports equality only. Intended for unbounded predicate-name sets (the dbaiv regime), where per-predicate manifests would not scale.
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 manifest
(§5.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
init (§6.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):
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).The output is a finite set of tuples (§1.3), each carrying the predicate’s declared layout and value type (§1.5).
Determinism: identical projection contents (and, where read, identical blob bytes) under identical
xidMUST 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:
For every projection-visible entry (at least one live path), walk its facts in forward chain order. A fact is current when no later fact of the same entry and kind carries an identical body with the
valuemember removed — the non-value members are the attribute identity, the kind-agnostic reading of metatheca SPEC §5.5’s per-attribute latest-wins. A later such fact without avaluemember unsets the attribute.For every current fact whose body holds a scalar
value(JSON string or integer): emit one tuple with the fact kind as the predicate and the scalar as the value (stringfor JSON strings,intfor integers), in theorderedlayout. Anything else emits nothing.
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:
string—lenas ULEB128, thenlenoctets of UTF-8.int,timestamp— 8 octets little-endian two’s complement.
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:
magicMUST be the four ASCII octetsTXSG(taxopsis segment).versionMUST be the little-endian u321.value_typeMUST match the predicate’s declared type in the predicate table.record_countMUST equal the number of records that follow.Records MUST appear in strictly ascending
(value, entry)order under the type’s ordering (§1.2) — no duplicate records.
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:
typeMUST be the literal string"taxopsis/fences".versionMUST be the integer1.predicateandvalue_typeMUST match the predicate table entry referencing this manifest.segmentsMUST be sorted ascending bymin; each element’sminMUST be ≤ itsmaxunder the type’s ordering,countMUST equal the referenced segment blob’srecord_count, andsegmentMUST name a segment blob (§5.4) present in the cella whose first record’s value is ≥minand last record’s value is ≤max. Consecutive elements MAY share a boundary value (a value’s postings may span segments); ranges MUST NOT otherwise overlap.minandmaxare JSON strings forstringpredicates and JSON integers forint/timestamppredicates.
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:
magicMUST be the four ASCII octetsTXBK(taxopsis bucket).versionMUST be the little-endian u321.Groups MUST appear in strictly ascending UTF-8 byte order of
key; every group’s key MUST route to this bucket (§5.6).entriesMUST appear in strictly ascending UUID v7 order — no duplicates.
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:
typeMUST be the literal string"taxopsis/predicates".versionMUST be the integer1.extractorMUST equal theextractorfield of every index-state blob referencing this table.orderedMUST be sorted in strictly ascending UTF-8 byte order ofpredicate, one element per ordered-layout predicate with at least one tuple.collationMUST be"binary"in Phase 1 (§3.3) and is present only forstringpredicates.tuple_countMUST equal the sum of the referenced segments’ record counts.fencesMUST name a fence manifest (§5.5) present in the cella.hashed.key_countMUST equal the total number of groups across all buckets;hashed.bucketsMUST be sorted in strictly ascending order ofprefix, one element per non-empty bucket, eachprefixof exactlybucket_prefix(§5.9) lowercase hex characters, eachbucketnaming a bucket blob (§5.7) present in the cella. A chain with no hashed-layout predicates carries"hashed": { "key_count": 0, "buckets": [] }.
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:
typeMUST be the literal string"taxopsis/index-state".versionMUST be the integer1.previousMUST be either a BLAKE3 hash naming an index-state blob present in the cella, ornull(genesis index-state only). Exactly one index-state in any chain hasprevious = null.metathecaMUST be a BLAKE3 hash naming a metatheca state blob present in the host cella (metatheca SPEC §3.3).extractorMUST be the extractor identity per §1.4, fixed at genesis and inherited unchanged by every subsequent index-state.routing.hashMUST be"blake3"or"sha256", fixed at genesis.routing.bucket_prefixMUST be an integer in[1, 16]; it MAY increase between index-states as the key population grows (a full re-materialization of bucket blobs; earlier index-states remain valid against their own bucket sets), and MUST NOT decrease.predicatesMUST be the BLAKE3 hash of a predicate table (§5.8) present in the cella.created_at_nsMUST be an integer per §3.2.
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.
Verify metatheca’s
headpinax is present on the host cella (§2); fail withNotFoundotherwise.Verify no taxopsis cella exists at
<vault>/ext/taxopsis/cella/; fail withConflictotherwise.Verify the caller-supplied extractor identity is implemented by this build; fail with
ExtractorMismatchotherwise.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.Construct the empty predicate table (
ordered = [],hashed = {key_count: 0, buckets: []}) for the supplied extractor identity. Deposit it; letptbe the returned hash.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; letgbe the returned hash.Set the index-head pinax (§7) on the taxopsis cella to
gwithexpected = 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:
Partition tuples by layout, per the extractor’s assignment.
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.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.
Construct the predicate table per §5.8 and deposit it; let
ptbe the returned hash. Returnpt.
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).
Resolve the metatheca stateref per metatheca SPEC §5.3 to a state hash
M.Read the current index-state blob via the index-head pinax; extract its extractor identity
xidand routing configuration. Compute the storage digestdof the index-head pinax bytes (SHA-256, per the CAS protocol of §7.3).Run §6.2 and §6.3 against
Mandxid, yielding the predicate-table hashpt. An implementation MAY increaserouting.bucket_prefixfor the new index-state per §5.9.Construct the new index-state blob with
previous = <current index-state hash>,metatheca = M, the inheritedextractor, the (possibly prefix-increased)routing,predicates = pt,created_at_ns = now(). Deposit it; letsbe the returned hash.Compare-and-swap the index-head to
swithexpected = d. On CAS failure, fail withConflict; 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:
An atom whose predicate appears in the table’s
orderedlist: binary-search the fence manifest for the covering segments; scan their records for values satisfying the atom; the matching records’ entries form the set.prefixapplies tostringpredicates only and denotes values whose UTF-8 bytes start with the given bytes. An atom value that does not match the predicate’s declared type (integers matchintandtimestamp), or aprefixatom on a non-string predicate, fails withLayoutMismatch.An
eqatom whose predicate is not in theorderedlist resolves through the hashed layout: form the key, route to its bucket (§5.6); the group with the exact key bytes yields the entry set — and an absent key (or bucket) is simply the empty set. Hashed-layout predicates are not enumerated anywhere, so absence is indistinguishable from emptiness by design.A range or
prefixatom, or an order clause, whose predicate is not in theorderedlist: fail withLayoutMismatch— those operations exist only in the ordered layout.
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.
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.
Disposability. Auxiliary state MUST be wholly derived: deleting it at any moment MUST NOT affect correctness, only performance.
Containment. Auxiliary state MUST NOT be written into any cella, referenced from any wire-format blob, or otherwise become interchange surface.
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:
NotFound— an index-stateref or referenced blob did not resolve.Ambiguous— a hash-prefix index-stateref matched more than one index-state.Conflict— index-head CAS lost a race; the caller MAY retry (§6.4).Malformed— a wire-format blob did not parse per §5 (including sort-order, count, typing, or routing-membership violations), a filter expression did not parse per §6.6 or was a pure negation, a cursor did not match its index-state, or an argument did not parse per §1.12.ExtractorMismatch— an operation named an extractor identity this build does not implement, or a reindex attempted to change the extractor identity or routing hash fixed at genesis (§6.4).LayoutMismatch— a range orprefixatom, or an order clause, named a predicate outside theorderedlist; an atom’s value did not match an ordered predicate’s declared type; aprefixatom named a non-string predicate (§6.5).IntegrityError— inherited from syntheca / apotheca; surfaces unchanged.
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:
§2 (host/extension relationship)
§3 (identifiers, time, collation)
§4 (the extractor contract), including the
metatheca-factsdefault family (§4.1)§5 (wire formats), both layouts
§6.1–§6.12 (operations), including extractor and routing pinning and inheritance
§6.13 (auxiliary state), applicable only when an implementation maintains such state
§7 (index-head pinax)
§8 (CLI), with the exception that
--as-ofMAY be omitted fromtxops queryif the implementation always queriescurrent§9 (errors)
A Phase 1 implementation MAY omit §11 items.
11 Out of scope (Phase 1)
Non-binary collations. Locale-aware string ordering (ICU identity, pinned like the extractor). Hosts pre-fold meanwhile (§3.3).
Incremental reindex. Phase 1 rebuilds and lets content addressing deduplicate; a delta build that touches only dirty predicates/buckets from the metatheca fact diff is the natural successor (dbaiv’s managed pipeline already works this way service-side).
Float and decimal value types. Exact integers and strings only; hosts encode decimals as scaled integers meanwhile.
Multi-predicate order clauses and order by descending entry ID.
Remote and served deployment mechanics. The wire formats are deliberately range-fetch-friendly (fence manifests, bucket routing); the deployment analysis lives in
SCALE.md, and the serving tier is hyperetes’s concern.
12 Future work (non-normative)
Composite / prefix-pruned predicates — sequenced atoms compiled into composite keys or pruned bucket sets, the pressure recorded in the quarb × dbaiv × metatheca cross-pollination audit.
Cross-state queries — "all index-states in which
Pheld ofE"; gated on metatheca’s hierarchical-state representation.A zetetes filter source — zetetes SPEC §1 consuming a taxopsis entry set to filter fused ranked results (
genre=Jazz AND ~"harbor").A hyperetes endpoint —
POST {base}/taxopsis/querybeside the search family’s, serving bucket and segment reads at zero distance from an R2-backed cella.dbaiv profile notes — a
sha256routing chain with dbaiv’s prefix-length schedule reproduces dbaiv’s.iaivbucket structure; a short binding document on the dbaiv side pins the correspondences.Value-carrying results — returning matched values for non-order predicates (projection pushdown).
Postings compression — delta-encoded entry IDs and prefix-compressed keys, once measurements justify it.