Repository navigation
0.3.0
Added
-
ConsistentTenantCapture::capture_tenant_deferredreturns one tenant's complete observation
while handing blob content out through a reader that reads and hashes it on the read that hands
it out, rather than reading every bound object before it returns. Every refusal, cap and
ordering iscapture_tenant's; what differs is when content is read, and therefore what an
observation costs a caller that never looks at one binding. File answers it from the committed
records, which already name each binding's object and the hash its content must have. The
default reads every object, as before, so every provider serves the same contract. Bindings are
still decided under the provider's consistency boundary; content read afterwards that is no
longer the content the observation bound is refused, never substituted. Measured on a 7,815-blob
157.0 MB authority, release build, this workstation: a full capture falls from 667/675/706/729 ms
to 61/61/103 ms (crates/eventlog-file/tests/measure_capture.rs,
docs/design/file-provider.md). -
File and SQLite expose existing-only open paths for callers that already hold provider
authority. They refuse absent or incomplete stores without creating a root, lock, database or
owner tables; explicit creation and File journal recovery retain their prior behavior. -
PostgreSQL accepts a caller-owned connection authority for custom and mutual-TLS transports
while retaining the provider's bounded pool, role/schema admission, quarantine and shutdown. -
File, SQLite and PostgreSQL can attach an existing inline projector without writing durable
state and atomically rebuild its complete tenant row sets throughInlineProjectionAdmin.
Rebuild uses the attached instance, complete committed history and active blobs, preserving
unrelated tenants and publishing selected rows and cursor together. -
AtomicBlobEventStorepublishes a group's blob bindings and its events in one native
transaction across File, SQLite and PostgreSQL.BlobAppendGroupcarries the group and its
BlobWritebatch; the request fingerprint binds the actual bytes, an original receipt is
resolved before any callback or binding runs, and a failed append rolls tentative content back.
An append that cannot be classified after its content is published reports
EventLogError::UnknownCommitrather than a success or a plain backend failure. Legacy
fingerprint and storage formats are unchanged. Opt-in: a provider that does not implement the
trait is unaffected. -
AtomicEventStore::append_group_guarded_with_blobscommits a group and the blobs it binds under
the group's one durability barrier, with the guard running before a byte of the batch is
written, so a refused guard publishes neither the group nor a blob. File implements it; the
other providers take the trait's refusal. A batch of any size costs the frame the group already
commits and no barrier besides, whereput_blobcosts one barrier per blob. The committed group
record now carries the sorted digests of the batch it bound, so a retry under a committed
idempotency key is compared against what that commit actually carried. -
InspectHistoryreads one tenant's complete decoded history from a store nobody opened for
writing, granting no writer or recovery authority and creating no root, lock or table.
InspectionLimitsstates explicit source-byte, event-count and envelope-byte caps where zero is
a real limit rather than a default;HistoryInspectionis the decoded result and
InspectionErrorits payload-free refusals.FileHistoryInspectorandSqliteHistoryInspector
implement it. A redacted or malformed envelope refuses rather than being returned. On Linux the
SQLite inspector requires an open-file-description lock before it will admit a source, so a
native writer cannot run underneath an observation in progress.
Changed
-
File verifies the complete committed history and every active blob once, when the store is
opened, instead of on every operation. A later operation takes the process lock, compares
manifest.jsonwith the head it observed, and either reuses the frames and fold it already
verified or reads, chains, folds and blob-verifies only the frames the file gained. A new epoch,
a pending recovery intent, a shorter or unchained file or an unfolded head still falls back to
the complete reread. Before reusing anything, the operation re-reads the committed bytes behind
the head it observed and hashes them against what the opener read, so a committed frame damaged
in place after open still refuses a read and still refuses the next append without altering the
history, and a history whose observed prefix was rewritten under a genuine tail is still refused.
A resumed operation also removes the staging names no durable intent selected. The divergence,
blob-integrity and disposal refusals are unchanged. -
File reuses that verified history for reads as well. A capture on a handle that already observed
the committed head re-reads and hashes the committed bytes behind it, folds only the frames the
file gained since, and decodes, rechains and refolds nothing else; anything it cannot decide that
way falls back to the strict reader, which refuses exactly as before. The reader changes nothing
it observes: unlike a resumed transaction it removes no staging name and synchronizes no
directory, and a reserved recovery entry — including one that cannot be followed — still refuses.
Content is read and hashed on every capture, because every capture hands those bytes to its
caller. Measured on a 3,273,126-byte committed history with 1,113 content objects totalling
11,384,593 bytes, twenty captures of one tenant on one handle: a repeated capture costs 57 ms
where it cost 140 ms, and returns the identical observation.
Fixed
-
Inline rebuild validates complete stored event history before applying projectors. SQL capture
and rebuild also read nonpositive stored positions so malformed rows are refused rather than
silently omitted; failed rebuilds preserve existing projection rows and cursors. -
SQLite selects the exact bundled
rusqlite0.40.2 line so composed consumers use one native
SQLite dependency that satisfies their existing 3.51.3 admission floor. Eventlog schemas,
envelopes and storage semantics are unchanged. -
SQLite and PostgreSQL verify stored blob length, integrity edition and SHA-256 at ordinary,
transactional callback and binding readback boundaries. Populated predecessor tables require an
explicit locked trust-of-observed-bytes migration, with typed acknowledged, unknown-commit and
post-commit cleanup outcomes. -
Production proof binds every required case to its exact Cargo package and test target, refusing
cross-provider masking, incomplete or ambiguous execution while retaining workspace and doc tests. -
SQLite and PostgreSQL now refuse different bytes under an existing tenant/digest blob binding
withEventLogError::Invalid, retaining the original content. Identical retries still succeed;
deletion permits rebinding, and concurrent differing writers have exactly one winner, matching
the file provider's immutable binding contract. -
A refused SQLite inspection leaves every lock the store already held exactly as it found it — a
prior writer lock, a prior reader lock, and the source file itself. An inspection that cannot be
admitted changes nothing it looked at. -
Atomic blob publication writes the same blob-integrity columns the standalone
put_blobport
writes, on SQLite and PostgreSQL, and compares a readback through the same validation. Without
them the binding failed the blobs table's ownintegrity_v1 = 1 AND integrity_sha256 IS NOT NULL
check at publication time.
Known
FileEventStorecarries two public methods namedappend_group_with_blobs: the inherent one
taking(&AppendGroup, &[(String, Vec<u8>)]), andAtomicBlobEventStore's taking
&BlobAppendGroup. Rust resolvesstore.append_group_with_blobs(..)to the inherent one, so the
trait method needsAtomicBlobEventStore::append_group_with_blobs(&store, &request)to be
reached. Both arrived in this release, from two lines of work that met at the merge; the two
capabilities are real and neither is deprecated. Naming is to be settled before either is relied
on by name.