Numeric values and RAM
Exact integer key-values, expiry, ownership and the ephemeral RAM boundary.
rural.numeric_kv reads and lowers signed i64 and unsigned u64 values over the existing Engine. It adds no engine, authentication, journal, query language or performance guarantee. Counts above 2^53 and both integer extrema remain exact; do not convert them through floating point or JSON numbers in consumers.
encode(Value) returns ten bytes: version 1, signed tag 1 or unsigned tag 2, then eight little-endian payload bytes. Signed payloads preserve their two's-complement bits. decode(bytes) rejects wrong lengths, unknown versions and tags. Signed and unsigned values are distinct even when their magnitudes match.
get(&snapshot, literal_key) returns an optional Value. lower(allocator, &snapshot, expected_head, operations, state_limits) returns an owned Plan with an Engine command and its hash. Keys are literal byte strings, including slashes and zero bytes; they are not paths or schema field names. The namespace __rural/numeric-kv/1/ is separate from document, catalog and index records. Branch 1 and ordinary branches >=3 are admitted; branches 0 and 2 refuse. Raw Engine writers can overwrite this namespace. Malformed raw values refuse typed reads and decisions; this facade provides no raw-writer protection or authorization.
Each batch has at most 32 distinct keys, each 1–256 bytes. Duplicate keys refuse instead of introducing ordered overlays. Create requires absence. Replace and delete require an exact typed expected value. Add additionally requires a delta of the same kind and checked arithmetic; overflow refuses the entire plan. Unsigned decrement is not an add operation; use an explicit compare-and-replace or a signed quantity. Replacement can explicitly change kinds. Every decision reads the same immutable root, and expected head must match it.
The emitted mutation keys and values total at most 16 KiB. Supplied Engine mutation, key, value and request limits further restrict admission. This payload bound excludes arena metadata and allocator overhead. The existing publisher independently checks full encoded request, records, receipts, branch limits, current context and applicable authorization; passing lowering does not grant publication rights.
The Plan owns all command bytes, so source keys and the snapshot may be released after lowering. Treat Plan as move-only and immutable. Its backing allocator must outlive Plan.deinit. Snapshot ownership follows the existing Engine contract. A serialized owner publishes session.transact(original_id, plan.command, null) for owner-local RAM, or submits the command/hash through the existing trusted Database request interface for durable storage. Durable consumers must retain their existing admission and authorization checks.
Retry the identical retained command with the original request ID. Never re-lower an add after commit: doing so changes the root or command and cannot express the original retry. The Engine receipt catalog alone decides replay/conflict. Plan.hash identifies the Engine command payload, not logical operation intent or authority: add zero and replace with the existing value can emit identical commands and hashes.
Errors include ForbiddenBranch, HeadConflict, MutationLimit, KeyTooLarge, ValueTooLarge, RequestTooLarge, DuplicateKey, RecordExists, NotFound, CompareConflict, KindMismatch, Overflow, InvalidNumericValue, UnsupportedVersion and allocation failure. Lowering failures publish nothing. Publisher failures and receipt lifetimes remain the existing Engine contracts.
The consumer fixture covers independent byte vectors, immutable plan lifetimes, exact add replay, typed CAS and arithmetic refusal, raw corruption, exact 32-key/256-byte boundaries, allocation rollback, durable reopen and receipt parity, and fork/image preservation of full unsigned values and signed extrema. Qualification is local prototype evidence; no economic, cryptographic or deployment acceptance is implied.
A minimal owner-local consumer:
var root = try session.snapshot(1);
defer root.deinit();
var plan = try rural.numeric_kv.lower(allocator, &root, root.head, &.{
.{ .create = .{ .key = "contribution_count", .value = .{ .unsigned = 0 } } },
}, .{});
defer plan.deinit();
const result = try session.transact(original_request_id, plan.command, null);
// Retain the same plan and ID if delivery requires a retry.
This records a synthetic exact count; it grants no contribution approval or reward.
Expiring owner-local RAM quantities#
The same numeric owner also provides ClockStamp, ExpiringRecord, getExpiring and lowerExpiring. This subset is for a serialized owner-local RAM Session. It does not admit a native durable TTL profile, public service, WASM ABI, background clock or automatic sweep.
The caller owns and serializes accepted clock state. admitClock(prior, next) is pure: nonzero 16-byte domain, unchanged domain and nondecreasing logical milliseconds are required. Reads and lowering do not advance that state implicitly. A read combines one retained snapshot with one admitted stamp, returning missing, expired or live. Expiration is inclusive: live means now_ms < expires_at_ms. Expired does not mean absent, zero or reclaimed memory.
The separate namespace __rural/numeric-kv-expiring/1/ stores exactly 35 bytes: version 1, clock domain, little-endian absolute deadline and the unchanged ten-byte numeric codec. Wrong lengths, versions, domains or numeric tags refuse without fallback. Ordinary numeric APIs address their original namespace. A foreign/imported clock domain refuses typed expiry access; no automatic rebasing or import qualification is provided.
lowerExpiring(allocator, snapshot, expected_head, prior_clock, next_clock, operations, limits) returns the existing owned Plan. Create requires physical absence and a future deadline; an expired record must first be explicitly collected. Add requires a live full expected record and preserves its deadline. Replace requires a live full expected record and an explicit future replacement deadline. Delete accepts an exact live or expired record. Collect accepts only an exact expired record. Domain, deadline, numeric kind and value all participate in CAS; expected head fences publication against replacements.
At most 32 operations and 16 explicit collection candidates are admitted; existing key, value, payload and Engine limits still apply. There is no unbounded scan. Collection consumes an ordinary receipt slot and refuses at capacity. Retain the original immutable Plan and ID for retries, including after time advances; do not lower again to refresh TTL. Old roots remain readable at explicit times after collection or Session close, so expiration promises no immediate memory refund or secure deletion.
Five local tests qualified actual RAM create/add/renew/collect, expiry cutoffs, unchanged-deadline replay, stale-head/full-record refusal, retained-root lifetime, independent codec bytes, hostile envelopes, collection-cap refusal, deterministic allocation failure and full receipt-capacity conservation. A successful 16-key create/expired-collect batch and exact receipt replay also qualified. Exhaustive allocation-failure coverage is limited to one-collect lowering over a borrowed root; it does not qualify every operation or Engine allocation path. Fresh composition of the nine ordinary numeric tests also passed, preserving their native reopen, codec, CAS, allocation and RAM/image oracles. These are separate checks from the five RAM expiry tests. These results do not establish native durability, image migration, hardware performance or Redis/Valkey compatibility.