JetDatabaseWriter

Caller-stream recovery contract

This contract applies to the public AccessReader.OpenAsync(Stream), AccessWriter.OpenAsync(Stream) and AccessWriter.CreateDatabaseAsync(Stream) overloads. It separates the current live-stream guarantees from the requirements for any future persistent journal-stream API. Supplying a persistent backing object alone does not enable persistent recovery.

Current support and identity

Supplied object Recovery and discovery Coordination
A FileStream, including a subclass that preserves its file semantics The writer uses FileStream.Name to find the existing file protocol’s journal and ownership evidence. Writer open recovers a transaction journal before reading the database header; reader open refuses pending recovery evidence. The stream writer overload refuses pending encryption replacement; use the path writer overload to recover replacement publication. The supplied handle’s sharing, cooperating lockfile/receipt protocol and supported byte-range locks apply. The caller must still provide exclusive writer access.
Any other Stream, including a wrapper around a FileStream Before-images belong to the live rollback owner. Memory captures and any temporary spill file have no persistent database identity, decision, discovery or reopen API. Reopening the stream does not recover an interrupted operation. The caller provides isolation across all users of the underlying bytes. Lockfile options and the per-store I/O gate do not create cross-process authority for this stream.

The library does not unwrap streams or infer a recovery path from an adapter’s custom name property. Changing leaveOpen, UseTransactionalWrites or the page-cache size does not change this classification. Direct file streams retain the file protocol’s original-path and evidence-retention rules and its unqualified storage assumptions; a file handle is not a power-loss qualification receipt.

Resolver-provided linked sources have a separate capability boundary: even a returned FileStream is consumed as the authorized read-only source without filename-derived sidecar discovery. The caller must supply a committed, isolated source and perform any recovery before registration, as described in linked-source configuration.

Live stream obligations

A reader requires readable, seekable bytes with meaningful Length, Position and absolute seeks. A writer additionally requires writing, extending and SetLength, including restoration of the original length. The capability guards check CanRead, CanSeek and CanWrite; they do not probe every later operation or certify an adapter’s semantics. Database offsets are relative to stream offset zero. Supply an empty stream positioned at zero to create a database; creation writes its initial image at the current position and has a separate failure lifecycle.

Successful reads must return the requested source prefix, possibly over several positive short reads; zero before the requested bytes are acquired is end-of-stream. Successful writes must consume the complete supplied buffer at the selected offset. Successful resize must report the requested length. Exceptions or cancellation may follow partial effects. The stream must preserve completed writes for subsequent reads in the live session, without unrelated mutation or silent redirection to a different object. FlushAsync must honor the adapter’s documented live-buffer semantics. An adapter that acknowledges writes and later loses them while the same session continues cannot satisfy live rollback correspondence.

Seek-based page reads check the requested range against the current stream length while holding the store’s I/O gate. A range beyond the available bytes throws EndOfStreamException before seeking, including malformed table-definition links that exceed a memory stream’s supported seek range. This admission relies on the stream’s meaningful length and the exclusive-access or unchanged-snapshot contract below; it does not certify an adapter’s later read effects.

Give the writer exclusive access to both the stream object and its underlying bytes from before open through transaction resolution and completed disposal. Do not independently seek, read, write, resize, dispose, replace or reopen the backing object while it is in use. A reader requires an unchanged snapshot for its entire lifetime; two library objects do not share an I/O gate. leaveOpen: true retains disposal ownership without granting concurrent access. Dispose the library object before handing the stream or its bytes to another user. Reopen after external changes so schema, constraints, counters and caches are rebuilt.

Ordinary statement and explicit-transaction failure handling can restore original bytes and length while the same rollback owner and underlying stream remain usable. Physical shrinking can also retain raw tail images in a temporary spill file. A spill is a private rollback resource, may use delete-on-close, and is not a recoverable journal that applications can adopt after restart. When restoration fails, the writer faults and rejects further mutations. Dispose it and restore a known-good database through the caller’s own storage protocol before reopening. A new writer cannot infer whether unjournaled surviving custom-stream bytes are original, committed or torn.

For a non-FileStream, a durable flush request still calls that stream’s FlushAsync; there is no separate device-fence callback. Successful CommitAsync therefore establishes only the effects promised by the supplied stream, including its flush contract. It does not manufacture durable undo evidence, namespace persistence or a cross-process commit decision. Process termination can lose the live undo owner even if the backing bytes survive. Power loss, remote-service failures and media corruption require their own contracts and qualification.

Ownership and failure retention

After a successful open, default leaveOpen: false disposes the stream with the reader/writer; true leaves it available after disposal. Disposal resolves open transaction scopes through the normal live rollback path when possible. It is not a substitute for a successful commit or a persistent backup. Once the writer is faulted, disposal releases resources without attempting another database flush or repair. The stream’s own disposal can still flush buffered data; this library control-flow behavior does not freeze storage or retire outstanding I/O.

Open-time validation and creation have distinct ownership cuts. Writer capability/options/cancellation checks precede its open cleanup handler, so a preflight rejection does not transfer stream ownership. Once writer header/recovery/construction begins, failed open disposes the stream unless leaveOpen is true. Reader failed-open cleanup also honors leaveOpen. Stream creation can fail while writing or flushing the initial image, before a writer exists and before open cleanup is entered; the caller must dispose that stream and discard or restore any partial image. Callers should retain their own disposal scope around acquisition when failure ownership matters.

For direct file streams, preserve the database together with journal, preparation, replacement and ownership evidence required by the file protocol until library recovery completes. Never delete evidence to make a reader or writer open succeed. For other streams, the library provides no export or retention mechanism for live before-images after their owner is disposed or lost. Applications needing recoverable publication today can work on an isolated complete copy and use an independently justified publication/backup protocol; its recovery and persistence guarantees belong to that protocol.

Required contract for a persistent stream adapter

A future journal-stream API must supply all of the following as explicit capabilities. The current stream overloads cannot accept these capabilities; satisfying them outside the library does not switch on a missing recovery mode.

Capability Required behavior
Stable identity and generations Bind database bytes, journal, original length, format/page size, decisions and lease receipts to the same stable database identity and unique transaction generation. Reject stale, foreign or conflicting evidence before mutation. An open stream reference, mutable path hint or content hash alone does not identify exclusive ownership.
Independent journal and discovery Retain complete framed before-images, integrity/identity data, write/truncate authorization and decisions independently of the process and database stream. Reopening must discover pending preparation and journal generations before parsing database contents. Provisioning, promotion, removal and name loss need a persistence contract. Temporary spill files cannot satisfy this capability.
Exact I/O and fences Specify reads, writes, resize/zero exposure and partial failed effects for both database and evidence. A successful journal fence must precede any destructive database effect; a successful database bytes-and-length fence must precede a commit decision. Decision fencing and replay authorization, including any zero-tail marker, must preserve the selected image across interruption. A general FlushAsync completion is insufficient without this storage-specific contract.
Exclusive, fenced ownership Acquire authority before evidence discovery and hold it through validation, replay, final database fence, decision and cleanup. Coordinate all readers, writers and recovery attempts, across processes and reopen. Crash/reacquisition must invalidate stale owners and prevent successor evidence deletion. A process semaphore or cancellation token is insufficient.
Reopen and crash quiescence Reacquire the same identity and surviving database/evidence after failure without recreating or truncating them. Retire or fence all outstanding old I/O before replay so it cannot later overwrite recovery. Detect identity or lease changes during reopen and refuse safely.
Outcome and evidence lifetime Separate confirmed commit/rollback from uncertain decisions. Failed writes, resize, flush, acquisition and cleanup must retain discoverable evidence and prohibit contradictory repair. Invalid evidence refuses without database mutation. Allow repeated recovery under valid evidence, exclusive authority and eventually successful I/O. Only a confirmed selected-image fence and exact-generation ownership authorize evidence cleanup.
Resource ownership Define who creates, owns, closes and reopens each database/journal/lease capability on successful acquisition, partial acquisition, cancellation and every failure path. leaveOpen governs stream disposal only; it cannot authorize journal deletion or lease release before database users finish.

An implementation must connect each capability to production calls and the formal model’s named assumptions, with interruption tests covering acquisition, partial writes and resizes, failed fences, decisions, replay, repeated reopen and stale cleanup. Test database and evidence identity mismatches, missing/corrupt evidence and refused operations without writes. Report process-kill evidence separately from qualified OS/filesystem/service/device interruptions. Keep unsupported configurations at the live rollback guarantee; no caller-stream persistent-recovery claim follows from the abstract model alone.

Implementation and focused evidence

Contract boundary Production source and existing evidence
Stream classification, open and recovery admission AccessWriter, AccessReader and PageFile; direct-file admission/recovery is exercised by PersistentRollbackJournalTests.
Exact reads, live store capabilities and ownership StreamPageStore; PageStoreTests covers short reads, partial reads, seek isolation and capability differences. LeaveOpenTests covers reader/writer disposal ownership.
Live before-images and temporary spills PhysicalRollback; StatementSpillTests.CustomStreamRollback_RetainsCiphertextAcrossSpill and Rollback_RestoresFirstImageAfterRepeatedSpills in StatementSpillTests.
Write/flush failure outcomes and faulted disposal JetTransactionTests.Commit_WhenReplayWriteFails_RestoresFileAndRollsBack and Commit_WhenDurableFlushFails_RestoresFileAndRollsBack use a custom stream in JetTransactionTests. WriterLifecycleConformanceTests covers faulted current-generation reads and disposal.

These tests cover named live implementation paths. They do not provide the future adapter capabilities or qualify physical persistence.