Run one Engine per whole store. Production :sync requires an explicitly
validated local Linux filesystem with truthful file and directory sync,
advisory locking, and exclusive rename semantics. validated_filesystem: true
is the operator's attestation, not a filesystem probe or device certification.
Never silently downgrade to :write; production builds reject that mode.
macOS :write is for development and carries no power-loss promise. A release
must be built for its target OS/architecture with Elixir 1.20, OTP 29, and a
C11 compiler at build time; the deployed release runs its included native
helper without Mix or a compiler.
Initialize and inspect
Set a dedicated absolute data_dir in the consuming application's runtime
configuration, then initialize a genuinely new store once before supervising
the Engine:
MIX_ENV=prod mix tay.storage.init --data-dir /srv/tay/jobs --durability sync --validated-filesystem
MIX_ENV=prod mix tay.storage.inspect --data-dir /srv/tay/jobs --durability sync --validated-filesystem
Initialization is not an idempotent “ensure” or repair command. Normal startup
is existing-only; a missing lock, STORE marker, or required history is an error.
Only an explicitly intended empty pre-existing root permits
--bootstrap-existing. Never delete or replace a lock to bypass contention.
Offline inspection acquires the existing lock and performs complete physical
and Event-v1 semantic validation without activation. It returns aggregate
diagnostics, not jobs or a reusable recovery ticket. Use mix help tay.storage.inspect for independent replay budgets.
Local executor access
An Engine normally creates its Unix socket automatically outside data_dir.
The final automatic directory is private (0700), and the socket defaults to
0600. An explicit path or TAY_SOCKET_PATH overrides discovery; prepare the
parent ownership and permissions yourself. Tay refuses a regular file at the
socket path and removes only a verified stale socket. Grant access only to
trusted local processes: a peer can submit, inspect, cancel, and execute tasks.
Bound connection, frame, task, result, and error limits for the workload. See
the protocol contract for the wire format and result lifetime.
Queue and lifecycle controls
Use name: when an Engine has a nondefault name. Pause is volatile and stops
new claims, not already-started effects. Drain closes new insertion/claim
admission and succeeds after active tasks and their outcomes settle; queued
jobs may remain. A timeout leaves the Engine draining. Stop normally drains;
forced stop is an explicit interruption, not successful drain. Restart creates
a fresh ownership, recovery, projection, and execution generation:
:ok = Tay.pause_queue(:default)
:ok = Tay.resume_queue(:default)
:ok = Tay.drain(timeout: 30_000)
:ok = Tay.stop(timeout: 30_000)
:ok = Tay.restart(timeout: 30_000)An unknown outcome after a submitted insert or control call does not prove no change. Reconcile by stable job ID, original definition, status, or a new explicit control decision; do not automatically allocate another job ID. Worker effects are at-least-once. Cancellation fences the durable execution token before best-effort termination but cannot undo an effect already made.
Store-v2 automatic compaction
The per-Engine policy is enabled when compaction: is omitted. It uses finite
terminal retention and conservative sealed-history estimates, not periodic full
replay. Compaction closes admission, settles execution and uses the sole Writer's
qualified candidate / validation / CURRENT / deferred-reclamation path.
It does not run online. All thresholds are conjunctive; a timer alone never
authorizes a rewrite. Cooldown uses the later of the verified manifest capture
time and post-publication recovered activation time. Restart conservatively
extends cooldown rather than shortening it by construction time. Backward clocks
conservatively defer.
Configuration (milliseconds except the explicit retention unit):
compaction: [
enabled: true,
terminal_retention: {:hours, 24},
check_interval: 60_000,
min_interval: 3_600_000,
min_sealed_segments: 1,
min_reclaimable_bytes: 16_777_216,
dead_ratio_threshold: 0.25
]Unknown/duplicate keys, unbounded/nonpositive timers or byte/count limits and
invalid ratios fail configuration. Timers/counts fit unsigned 32 bits; bytes fit
signed durable time/integer range. Bounded hours use the shared manifest rule
1..2_562_047_788_015, not a separate configuration duration syntax. Ratios are
positive finite numbers at most one. Automatic configuration rejects infinity.
Retention is eligibility, not an exact-time deletion guarantee: terminal jobs
expire at terminal_at <= captured_at - duration, but actual work waits for
sealed-history thresholds, cooldown, healthy ownership, successful execution
drain and sufficient additional disk. The estimator retains at most 128 oldest
hour buckets, conservatively counting only fully expired hours (up to one extra
hour of estimation delay). Small/idle stores may retain terminals indefinitely
below thresholds. Live, scheduled, available, executing, retryable and unsettled
jobs are protected. Expired IDs are not remembered: get/retry/cancel return
not-found, and a later submission with that ID is a new job.
compaction: false disables the policy child/timer; reclaimable history may then
grow indefinitely and the operator assumes storage management. Manual work
remains available:
{:ok, stats} = Tay.compact(timeout: 900_000)
{:ok, stats} = Tay.compact(terminal_retention: {:hours, 48})
{:ok, stats} = Tay.compact(terminal_retention: :infinity)Manual/automatic/stop/restart share one fixed operation slot; a concurrent
administrative operation returns capacity/busy rather than queuing or forcing a
switch. Supervisor shutdown cancels the evaluator's timer and asks the owner
to cancel construction before CURRENT. Once CURRENT is in flight, it waits for
verification/reconciliation before retiring Engine and defers reclamation.
No source bytes are deleted to obtain candidate headroom. Payload-free aggregate
events are structured Logger debug metadata under :tay_compaction; enable
debug logging to observe evaluation/gate/defer/start/completion/failure events.
See the checkout's Phase-C implementation report for the
qualification status, finite workload measurements and limitations.
Cold backup and restore
Stop and fence every Engine using the source before copying. The native tools hold its existing exclusive lock throughout inventory, hashing, and copy; they refuse a live owner. A cold copy is whole-store bytes, not a live snapshot, semantic validation, repair, or a proof that the backup contains the latest acknowledged history. Keep the SHA-256 catalog outside both stores. The source, destination parent, and catalog parent must exist; destination and output catalog must not. Do not run source and restored copies concurrently: they share a STORE_ID.
MIX_ENV=prod mix tay.storage.backup \
--source /srv/tay/jobs \
--destination /srv/backups/tay-2026-09-13 \
--catalog /srv/backups/tay-2026-09-13.json \
--durability sync --validated-filesystem
MIX_ENV=prod mix tay.storage.restore \
--source /srv/backups/tay-2026-09-13 \
--destination /srv/tay/restored \
--verify-catalog /srv/backups/tay-2026-09-13.json \
--catalog /srv/backups/tay-restored.json \
--durability sync --validated-filesystem
MIX_ENV=prod mix tay.storage.inspect \
--data-dir /srv/tay/restored --durability sync --validated-filesystem
For development copies use --durability development on backup/restore and
--durability write on inspection. Strict copying is Linux-only on a supported
validated persistent local filesystem. The tools reject symlinks, hard-linked
source files, unsafe aliases, unexpected file types, changed ancestors/source
identities, missing/extra archive files, malformed catalogs, checksum mismatch,
and exhausted file/byte budgets. Defaults are 100,000 inventory entries
(including segments) and 10 GiB of file bytes; override only for a separately
qualified workload.
The destination is a new private 0700 sibling staging directory with 0600
files. STORE is copied last. In strict mode every copied file is fsynced,
followed by the segments and store directories, exclusive no-replace
publication, ancestor syncs, then exclusive external catalog creation and its
syncs. A failure preserves the source and any partial staging or published
destination. A complete-looking destination without a successful catalog is
not acknowledged backup success; never overwrite it or infer a recoverable
prefix. Restore always verifies the selected backup's catalog before creating
destination data. An older valid backup explicitly accepts lost later jobs and
possible repeated external effects. Inspect and activate it in a fresh Engine
generation before admitting work.
Incident boundary
On corruption, incomplete history, unsupported semantics, or an uncertain storage operation, stop admission and preserve the entire store and namespace. Do not truncate, pad, delete, rename-aside, skip records, or reuse sequence space. Distinguish ownership contention and operational budget refusal from bad bytes. Increase a budget only for a new full inspection of unchanged data. There is no automatic repair, retention/compaction, or live backup. Protect plaintext args, stores, catalogs, and backups with deployment access controls.