# Compatibility and limits

Store v2 supports the original `"infinity"` MANIFEST retention value and
canonical one-key Value maps for bounded retention: `{"hours" => N}`, with
`N` in `0..2_562_047_788_015`, or `{"minutes" => N}`, with `N` in
`0..153_722_867_280_912`. Readers accept zero as a full-purge marker and normalize
the runtime policy to the one-hour minimum; new full-purge compactions persist
one hour directly. Framing versions, keys, CRC32C, and CURRENT's manifest
digest are unchanged. Readers that predate bounded retention may fail closed on
the map representation; do not use an older binary to open an epoch published
with bounded retention. Unknown forms are never converted to infinity or
repaired.

Record v1, Segment v1, and the six Event-v1 job lifecycle semantics remain fixed
compatibility contracts within Store v2, as described in
[storage](storage.md).
The committed record, segment, and Event literal fixtures are permanent test
anchors; they are not generated from the current encoder or parsed from public
documentation. A newer binary must read every older supported byte and
transition. An older binary must refuse an unsupported event pair or transition
without publishing partial state. Rollback is safe only if the old binary
understands **all** history written by the newer one.

Event v2 is the production semantic implementation. It contains the complete
frozen Event-v1 behavior, so old histories remain byte-for-byte readable
without a migration. New persisted semantics are added through V2-only,
explicitly versioned type/schema pairs; they never add optional fields or new
meanings to an Event-v1 schema.

Persisted worker and queue names are stable UTF-8 keys. Keep their trusted
mapping to compatible callbacks when application modules change. Removing a
mapping blocks affected jobs; it does not drop them. Immutable definitions
retain their original arguments, schedule, timeout, attempts, and retry policy.
Reusing an ID with different canonical definition bytes is a conflict;
resubmitting the same ID and definition reconciles it. A new Event field needs
an explicitly versioned schema and literal fixtures, never a silent addition
to schema 1. The local [executor protocol](protocol.md) adds no disk schema:
connection registrations and capacity are volatile; persistent job completion
and successful return values are durable.

## Supported runtime and storage

- Build with Elixir 1.20, Erlang/OTP 29, and a C11 compiler. The consuming
  release must be built for its target OS/architecture and compatible ABI;
  releases are not cross-platform bundles.
- Strict `:sync` durability is Linux-only on an explicitly validated supported
  **local** filesystem with truthful file/directory sync, locking, and
  exclusive publication. The operator must set `validated_filesystem: true`;
  the flag and a filesystem-type check are not hardware power-loss proof.
- macOS supports explicit development `:write` only, without a power-loss
  claim. Windows, network/object storage, distributed store ownership, and
  unvalidated production filesystems are unsupported. Production builds do
  not silently downgrade to `:write`.
- Strict `:sync` journal group commit is optional and defaults to an immediate
  per-event fsync (`journal: [batch: [max_events: 1, interval_ms: 0]]`).
  Batches preserve record order and acknowledge a batch only after its fsync;
  they do not change the on-disk format or recovery compatibility.
- External effects are at-least-once, not exactly-once. There is no live
  backup, automatic torn-tail repair, or distributed store ownership. Remote
  producers and workers connect through the mTLS HTTP/JSON API; the Engine and
  its store remain single-node and single-writer. Durable
  successful results are bounded JSON values retained with completed jobs, not
  an authoritative business ledger. Store-v2 compaction is online during candidate
  preparation and uses a fenced publication switch. The local Unix socket is a
  same-host trust boundary.

## Historical finite reference profile

The measured reference was Elixir 1.20.2 / OTP 29 on a validated local Linux
Btrfs test volume: 10,000 retained jobs with small encoded arguments, eight
offered callers, two queues with two credits each, 30,000 Events, and
19,385,044 canonical history bytes. This is a finite example, not a throughput,
recovery-time, arbitrary-device, or power-loss guarantee. Validate the actual
target and workload before deployment. That historical run used the following
workload values and limits. Legacy job/node counts are not enforced as limits
by Tay 1.8:

| Boundary | Value |
| --- | ---: |
| Encoded new-job arguments / payload | 1,105 / 2,048 bytes |
| Retained jobs / charged state bytes / nodes | 10,000 / 80,000,000 / 1,000,000 |
| Canonical history / segment allowance | 19,390,000 bytes / 5 coordinates |
| Client admission | 64 permits and 67,108,864 reserved request bytes |
| Measured wake interval | 50 ms |

These values are explicit configuration policy, not source defaults or a
Cartesian combination of independent stress tests. The history ceiling is
only 4,956 bytes above the completed reference cohort before additional
retries and interruptions; it is not a sustainable unbounded workload. No
terminal job or ID was evicted in that historical reference workload. Current
bounded compaction can expire terminal jobs/IDs; see [operations](operations.md).
Monitor admission, free disk, backup space,
restart resources, and retained staging. A larger or different workload needs
new target-specific measurement, not deletion of individual segments.

## Upgrade and restore

Drain and stop the old Engine, make a complete [cold backup](operations.md),
then inspect unchanged history with the target binary and sufficient budgets.
Start a fresh generation; do not carry forward ETS indexes, mutation revisions,
or pending RPCs. A complete event is replayed even if its caller never saw the
acknowledgement. If an old binary cannot understand all written history, it
must refuse rollback. Restoring an older backup is an explicit recovery-point
decision: later acknowledged jobs may be absent and effects may repeat. There
is no anti-rollback watermark or proof that a selected backup is the newest.

## Tay 1.8 schedule events

The 1.8 reader accepts all preceding Store-v2 and Event formats. The new schedule
events were introduced in 1.8.0-rc3. Schedule delivery writes semantic pair
`(11,2)`; current registration, cancellation, explicit advancement and compaction
write `(13,2)`. Historical `(12,2)` checkpoints remain readable. These events
preserve logical revisions and active occurrence membership. Existing schedules
start enforcing their stored overlap policy (`skip` by default) on upgrade.
Earlier binaries reject the new pairs without writable activation. After these
pairs have been written, downgrade requires restoring a cold backup made before
the upgrade. Never replace only CURRENT or individual segment files.

Overlap governs job admission. `skip` keeps the preceding nonterminal job link
when several due occurrences are skipped. `queue` leaves the due time unchanged
until the preceding job and locally tracked execution settlement finish.
Cancellation does not cancel jobs already admitted by a schedule. Application
side effects retain Tay's at-least-once contract.

## Shared budget and occurrence membership

The existing `max_state_bytes` now includes in-memory auxiliary data and admitted
payloads, with no new required configuration. Stores accepted under older job-only
accounting may require a larger value at recovery; refusal preserves storage and
reports the required/configured bytes. This is an operational budget, not RSS.
New schema-2 type 13 preserves complete occurrence membership. Types 7..12 keep
their frozen payloads. Older binaries refuse type 13; downgrade requires a cold
backup made before writing new events. Historical allow checkpoints whose full
membership was already lost remain readable but cannot be tightened in place.
