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.

PermissionMeaningScope
data.read / data.writeread or mutate SQL, structures, search documents, and transactionsinstance, subtree, object
search.executeexecute lexical, vector, ANN, and hybrid retrievalinstance, subtree, object
catalog.read / catalog.writelist/describe, or create/mutate, catalog definitionsinstance, subtree, object
proof.generate / proof.verifygenerate a proof for an otherwise-authorized read; verify proof artifacts offlineinstance, subtree, object / instance
security.manage / security.readmutate, or read redacted metadata for, principals/roles/assignments/keysinstance
maintaincheckpoint, doctor, compact, vacuum, retentioninstance
backup.create / backup.verify / restorecreate/verify a backup, or restore a verified oneinstance
observe / discoverstatus/telemetry/explain, or versions/capabilitiesinstance
ownership.managetransfer ownership, authorize offline recoveryinstance

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.

CommandWhat it doesWhen
statusall-engine state: visible CSN, pages, retained WAL, replayed transactionsmonitoring, scripts
telemetrybounded, redacted process-local snapshot; enables no exporterspot diagnosis
doctoroffline diagnosis: format, pages, WAL, manifests, blobs, indexes, recovery authorityafter incidents; around restore
checkpointpublishes one synchronized all-engine recovery boundarybefore backup; after bulk loads
compactcompacts one root family (--target structures|search)scheduled maintenance
vacuumrebuilds live roots into a smaller page generation, atomically publishedreclaiming 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.