In development

Embedding and application workflows

The embedded directory API, owned outcomes and current evidence boundaries.

Evidence and edition boundary#

12 October 2026 reconciliation (T-R105): the implementation opens a parent directory and child name through Database.open; the PF11 contract explicitly calls this directory edition implemented without accepting PF11 performance or the milestone. The signature freeze describes Zig API 1.0, with no C ABI or calling-convention stability across compiler upgrades (PF11 contract).

Qualification status remains contradictory: the retained PF11 evidence says “running” and dependent on PF10, while the 28 September NEXT-GEN review says “deferred” despite substantial code. Neither supplies current registry acceptance here; the backlog B02 leaves PF10's sorted-query numerical gate and PF11 status open. The retained evidence reports a corrected 29/29 PF11 run and 836/836 full-suite run, but explicitly disclaims performance qualification; those historical counts are not a fresh execution result (PF11 evidence). The current PF11 fixture contains lifetime, retry/restart, uncertainty, callback-collision and concurrent drain checks; the 12 October baseline records that native tests never launched because the guard lock was denied.

“Main-file edition is later” is the boundary of this directory API (PF11 contract), not an acceptance statement for later storage work. The public portable-cells design distinguishes sealed export, active embedded storage and a separately qualified active single-file format; it labels itself exploratory. The capability map records later native paged/recovery subsets with surviving whole-profile limits, which do not establish an active-file callable contract for this API. A matching public active-file API/qualification receipt is an explicit gap in this reading set (backlog B02/B13 and ER11); a live main-file copy cannot be treated as a qualified backup on this evidence (portable-cells design).

Related gates remain separate: formal refinement leaves current receipt-repair closure unknown; MCP leaves catalog/context/expand/transact unsupported; collection feeds qualify bounded native polling without durable subscriptions; and the UBI consumer guide retains actual corpus, live recovery and D13/D14 foreign-binding/native-host proof obligations. None is closed by directory embedding (backlog B15/B04/B19/B17/B35). Public replacements for the archived ADR-008 and canonical status/active-file records remain explicit link/evidence gaps; this guide publishes no private archive content (backlog B42).

Handle and outcome contract#

The following usage rules follow the PF11 stage and lifetime contract and implementation; their checks are expressed in the PF11 fixture.

open takes a caller-owned parent std.Io.Dir and a child directory name, the same shape as Database.open. Calls are trusted and local. There is no listener, socket, or dispatch. state.Context checks freshness and grants nothing. This is one of two entry paths into the same engine; the other is the owning local server. Remote clients must not open these directory files over a network filesystem (ADR-008).

The caller owns the allocator and std.Io. Both outlive the handle and every retained snapshot, row, inventory, and pin. The allocator must allow concurrent frees. open fills the caller handle in place. A failed open leaves that slot unpublished. The caller keeps the handle allocated and unmoved until close has returned and every concurrent call on that handle has returned. drain does not end that duty. A zero admitted count means admitted calls have reached their decrement, not that those calls have returned. The entry increment, the closed-flag load, the closed-flag stores in close and drain, and the idle load are sequentially consistent, so a call that still sees the handle open cannot overlap Database.close with an idle load that already saw zero. Do not copy or move the handle.

The handle admits calls, drains them, and completes close once. drain refuses new work and waits until the admitted count is zero. close does that and then calls Database.close once. A concurrent close shares that completion and does not join another thread. close and drain return void, finish the drain, and do not abandon the owned database. Database.close is not a stop signal and takes no new locks here. Snapshots the caller already holds stay usable after close while the allocator lives.

open is only for unpublished storage or a slot whose close has finished. database and service are in-place storage. Calling them directly is outside this API and is not joined with close.

Same-thread reentry of any embedded call from a hook, group prepare, or query checkpoint, including close and drain, is a caller precondition violation. The before-append hook cannot fail, allocate, or perform I/O. Group prepare and query checkpoint remain anyerror. A returned name equal to RecoveryRequired, AppendOutcomeUnknown, or PublicationOutcomeUnknown does not prove journal state.

Reads are success or failure. A null get is success. Mutations are committed, replayed, rejected, or unknown. The first two carry the returned receipt. Backups are published, rejected, or unknown. Every failure and every mutation unknown carries no_effect and attempt_admitted (unproven, yes, or no). Those facts come from the proven stage. An error name does not choose them. A kernel name outside the contract table stays category unknown. The diagnostic keeps the raw name and is not a caller switch.

committed and replayed are both successful receipts for the same request bytes. A later head, a different request id, or any other changed field is a different command and is not a replay. Retry an unknown result with the original identity. rejected means this attempt is absent. It does not clear an earlier unresolved request, and it is not permission to mint a new identity for that request.

Group Outcome.unknown carries ?anyerror. The append-error path passes that error through, so the public diagnostic names it. The last-sequence mismatch passes null, and the diagnostic stays empty. Do not label the mismatch AppendOutcomeUnknown.

Open-time RecoveryRequired, before a live handle, is OpenFailed. The recovery fence on a live call is disposition unknown, operation recovery, with no_effect=yes and attempt_admitted=yes. That attempt did not append. The prior logical request stays unresolved.

query keeps one snapshot and the full bounded result. next borrows one row and does not allocate. The caller serializes next, cancel, and deinit. The first release wins. After release, cursorNext is category unknown with diagnostic CursorReleased.

Callable names are the tracked signature freeze. Stage tables and fact rules are the PF11 contract, which retains the later main-file boundary and disclaims performance qualification.

Example commands#

Build targets are defined in build.zig; the example, adapter, no-listener checker and service-trace checker define the behaviors described below. These commands are reproduction instructions, not a record of execution in this slice (12 October baseline).

Compile through the repository build guard, Zig 0.16.0, ReleaseSafe, one job:

python3 scripts/build_guard.py -- zsh scripts/zig.sh build build-embedded-example -Doptimize=ReleaseSafe -j1
python3 scripts/build_guard.py -- zsh scripts/zig.sh build build-embedded-adapter -Doptimize=ReleaseSafe -j1

The example takes an existing parent directory and a new child name. It writes the child store and prints nothing on success:

zig-out/bin/rural-embedded-example PARENT CHILD

Independent no-listener proof (requires the installed example binary):

python3 scripts/check_embedded_example.py

Embedded versus sole-owner service logical create/get/replay parity (requires rural and rural-embedded-trace):

python3 scripts/build_guard.py -- zsh scripts/zig.sh build build-embedded-trace -Doptimize=ReleaseSafe -j1
python3 scripts/check_embedded_service_trace.py

The adapter takes one disposable directory. It prints one JSON object. signatures is 24 and verified is true only after every signature from checkVersion through backupCreate has run. The *_ns fields are clocks around those calls. They are not a performance result.

zig-out/bin/rural-embedded-adapter --profile DISPOSABLE_DIRECTORY

Packet tests, also one guarded job, and not a load profile:

python3 scripts/build_guard.py -- zsh scripts/zig.sh build test-performance -Dperf-packet=PF11 -Doptimize=ReleaseSafe -j1
python3 scripts/build_guard.py -- zsh scripts/zig.sh build test-performance -Dperf-packet=PF07 -Doptimize=ReleaseSafe -j1