Operations
This page covers what you need to run Hyphae day to day: who can do what, how to diagnose a directory before assuming corruption, how to move it safely, how to size it for real hardware, and where the receipts behind every performance and correctness claim in this documentation actually live.
Access control
A fresh directory requires no credential. Durable access control starts with a one-time bootstrap that creates the owner principal and its API key — the file is created mode 0600, outside the data directory, and the secret never reaches stdout:
hyphae security --data-dir "$D" bootstrap \
--name owner --label initial-key --key-out ./owner.key
From that moment, every online Native command requires the key through
--native-api-key-file, HYPHAE_NATIVE_API_KEY_FILE,
or --native-api-key-stdin — never as a plain argument. Key
files must be regular restricted files (owner-only on Unix, a protected
DACL on Windows); the CLI validates the opened handle's identity against
substitution.
The complete flow, including the two steps everyone forgets:
# 1. Create the principal (every mutation needs a unique nonzero idempotency token)
hyphae security --data-dir "$D" --native-api-key-file owner.key \
principal create --name analytics --idempotency-token 1001
# 2. ENABLE IT — principals are created disabled
hyphae security ... principal set-enabled --principal-id <ID> --enabled true \
--idempotency-token 1002
# 3. Assign a built-in role
hyphae security ... assignment create-built-in --principal-id <ID> \
--role reader --scope instance --idempotency-token 1003
# 4. Issue its key with the role's permission set
hyphae security ... key issue --principal-id <ID> --label analytics-read \
--role reader \
--permission catalog.read --permission credential.self_manage \
--permission data.read --permission discover \
--permission proof.generate --permission proof.verify \
--permission search.execute \
--scope instance --key-out ./reader.key --idempotency-token 1004
Two verified traps: a disabled principal makes its key
return authorization_denied on everything, including
capabilities — check principal list before
suspecting the key is wrong. And a key issue that fails
midway still consumes its idempotency token in the reservation, so a
retry with the same token returns catalog_conflict
— use a fresh token.
API keys have the fixed shape hyp1_<key_id>_<secret>:
a 32-hex-character key ID (128 random bits) and a 64-hex-character secret
(256 random bits), 102 visible ASCII bytes total. Parsing is strict — no
whitespace, uppercase hex, or alternate alphabet is accepted as v1, and
missing, malformed, unknown, expired, revoked, and disabled credentials
all produce the same public unauthorized result, so a caller cannot
distinguish "wrong key" from "key exists but is disabled" without a
separately authorized principal list.
| Permission | Meaning | Scope |
|---|---|---|
data.read / data.write | read or mutate SQL, structures, search documents, and transactions | instance, subtree, object |
search.execute | execute lexical, vector, ANN, and hybrid retrieval | instance, subtree, object |
catalog.read / catalog.write | list/describe, or create/mutate, catalog definitions | instance, subtree, object |
proof.generate / proof.verify | generate a proof for an otherwise-authorized read; verify proof artifacts offline | instance, subtree, object / instance |
security.manage / security.read | mutate, or read redacted metadata for, principals/roles/assignments/keys | instance |
maintain | checkpoint, doctor, compact, vacuum, retention | instance |
backup.create / backup.verify / restore | create/verify a backup, or restore a verified one | instance |
observe / discover | status/telemetry/explain, or versions/capabilities | instance |
ownership.manage | transfer ownership, authorize offline recovery | instance |
proof.generate is additive: the wrapped operation's own
permissions and scope are still required on top of it. Built-in roles
(owner, admin, operator,
developer, writer, reader,
auditor) bundle fixed permission sets and cannot be
dropped, renamed, or shadowed; custom roles grant permissions directly
with no inheritance, negative grants, or wildcards. Rotation:
key rotate supports an overlap window of 0 to 604,800
seconds so a caller can cut over without a hard cliff; key
revoke and key abort complete the lifecycle. Losing
the owner key has an offline two-phase security owner
recover/resume flow that requires the exclusive
directory lock. No command surface ever prints a secret or verifier.
Full model:
access control v1.
Doctor: diagnose before you assume corruption
hyphae doctor --data-dir "$D" is a bounded, fully offline
diagnosis over format, pages, WAL, manifests, blobs, indexes, and
recovery authority. It is the first command to run after anything
unexpected — an interrupted process, a restore, an environment change —
before assuming the directory is corrupt. It diagnoses; it does not
repair in place. status gives the all-engine live picture
(visible CSN, pages, retained WAL, replayed transactions);
telemetry gives a bounded, redacted process-local snapshot
that enables no exporter.
| Command | What it does | When |
|---|---|---|
status | all-engine state: visible CSN, pages, retained WAL, replayed transactions | monitoring, scripts |
telemetry | bounded, redacted process-local snapshot; enables no exporter | spot diagnosis |
doctor | offline diagnosis: format, pages, WAL, manifests, blobs, indexes, recovery authority | after incidents; around restore |
checkpoint | publishes one synchronized all-engine recovery boundary | before backup; after bulk loads |
compact | compacts one root family (--target structures|search) | scheduled maintenance |
vacuum | rebuilds live roots into a smaller page generation, atomically published | reclaiming space |
Backup and restore
The verified full cycle — every step validates before promising:
hyphae checkpoint --data-dir "$D"
hyphae backup create --data-dir "$D" --out ./backup # → created (verified at creation)
hyphae backup verify --backup ./backup # → verified (without opening live state)
hyphae restore --backup ./backup --data-dir ./restored # → restored (staging + doctor + atomic activation)
hyphae doctor --data-dir ./restored # → healthy, snapshot_verified: true
A Native backup is physical and synchronized, described by
NATIVE_BACKUP.json with an exact inventory. Restore never
merges or overwrites — it rebuilds into a sibling staging directory,
runs mandatory doctor validation, and activates atomically; the
destination must be new. There is no online or incremental backup:
that is a declared non-capability, and media policy is your
application's decision, not Hyphae's. See
Transactions and proofs for
why a checkpoint always precedes a backup.
Hardware discovery and calibration
hyphae hardware discover [--data-dir <PATH>]
hyphae hardware calibrate [--data-dir <PATH>] [--mode <quick|thorough>]
[--cache-dir <PATH> | --no-cache]
hyphae hardware governor-policy [--data-dir <PATH> | --profile <FILE>]
--calibration <RECEIPT.json>
[--mode <latency|bulk|mixed>] discover reads process-visible CPU topology and features,
memory and page configuration, operating system, virtualization status,
and the filesystem/device under the selected path — it performs no
host or database mutation, and missing platform data stays explicit
rather than being silently reported as zero. calibrate
binds the static profile to the exact executable and compiler, then
measures the implemented CPU, memory, engine, storage, WAL,
thread-scaling, and I/O-depth matrix. governor-policy and
execution-topology derive a runtime policy from an exact
discovery receipt passed through --calibration — the
qualification path deliberately consumes a frozen receipt rather than
silently re-discovering volatile available-memory numbers between
evidence steps.
Durability tuning
Every mutating command accepts --durability strict|group|memory
(see Transactions and proofs
for what each class actually guarantees). Choosing a class is a
per-write decision, not a directory-wide setting — a script can run
Strict for a ledger write and Memory for a disposable cache write in
the same session.
The buffer pool: 8,192 frames by default
The shared buffer pool holds at most DEFAULT_BUFFER_POOL_FRAMES
verified 16 KiB page frames — 8,192 frames, a 128 MiB ceiling — across
16 partitions, populated lazily: a process pays only for the pages it
actually touches, and a frame is verified once per
(page_generation, page_id) while it stays resident.
HYPHAE_BUFFER_POOL_FRAMES overrides the bound (never below
the partition count) for an operator sizing the cache to a larger
working set.
The default is evidence-set, not guessed: at 1,000,000 documents a two-term BM25 query plans over 1,500 posting segments, and under the previous 1,024-frame bound those segments were re-read and re-verified on every query — 44% of the scorer's samples were BLAKE3 and CRC32C page verification. 8,192 frames keep them resident and halve the durable scorer stage; 65,536 frames measured no better. This is the same buffer-pool fact behind the search document cap discussion in Search.
Where evidence lives
Every performance or correctness statement in this documentation names
a receipt rather than asserting a number in isolation. Receipts live
under docs/gates/evidence/ in the source repository, and
performance statements additionally carry an explicit environment
class: development observation (warm, concurrency-1,
developer hardware — never quotable externally),
virtualized operational scale (the closed G7 C-60
authority — quotable only with an explicit "virtualized, no latency
certification" qualifier), and dedicated hardware
(bare-metal receipts produced by the pinned baseline harness against
SQLite, DuckDB, Redis, and Tantivy, with byte-identical deterministic
workloads and like-for-like durability — the only class that supports a
comparative statement). The cross-engine commit protocol additionally
has a machine-checked TLA+ model at
docs/formal/HyphaeCommit.tla, and implementation fidelity
is carried by the physical crash-matrix tests referenced from it. See
Evidence for the browsable index.