Skip to main content

Module plugin_store

Module plugin_store 

Source
Expand description

OR.1 — durable, plugin-scoped key/value storage for guests.

Design: docs/dev/architecture/org-roam.md §4.2. Slice plan: docs/dev/operations/slice-plans/org-roam.md OR.1.

§Why the host holds this at all

There is no single guest instance. spawn_event_plugin, spawn_config_plugin, spawn_help_plugin, spawn_dashboard_sections and instantiate_grammar_plugin are separate paths, each building its own wasmtime::Store with its own linear memory. A plugin that keeps state “in the guest” therefore keeps N copies, drifting — and the drift is invisible, because every instance stays internally consistent while answering a different question from its neighbour. Org-roam’s picker would offer a note that its own <CR> could not open, and nothing anywhere would report an error.

So the sharing point has to be host-side. PluginStore is that point: one per manifest id, handed to every PluginState built for that id, so a put on the event seam is visible to a get on the grammar seam immediately — not after a flush, not after a reload.

§What the host knows about the contents

Nothing. Keys are guest-chosen strings and values are opaque bytes. The key layout (nodes, n/<id>, b/<id>, f/<path> for roam) is the guest’s schema, and the host never parses, validates or interprets it — which is the same test org-mode.md §2 set for the agenda, applied to persistence, where hosts usually start learning schemas.

Because keys are never turned into paths, there is no traversal to defend against. f//etc/passwd is a key like any other; the on-disk format length-prefixes it into one file.

§Failure policy

scan_cache.rs’s, promoted from an agenda special case to a primitive: temp-file-and-rename so a kill mid-write leaves the previous state intact, a schema version that refuses an older shape, a size cap, flush-on-drop, and degradation to empty on any corruption. Never to a partial read — a store serving bytes that failed a schema check is how one starts serving plausible nonsense.

§Why the on-disk format is hand-rolled

The payload is bytes, and every serde format in the tree encodes a Vec<u8> as an array of integers — a ~90 KB roam blob becomes ~110 KB of per-element tags for nothing. Byte fidelity is the entire job here, so a length-prefixed frame (30 lines, no dependency, exact) is the better fit than a dependency plus the configuration to stop it being wasteful. It also makes the corruption tests say what they mean: a truncated frame is a truncated frame.

Structs§

PluginStore
One plugin’s durable key/value store.

Functions§

flush_all
Write out every open store with unflushed changes. Called by the binary as it exits; safe to call at any time.
register
Track handle for flush_all.

Type Aliases§

PluginStoreHandle
A shared handle to one plugin’s store. Cloned into every PluginState built for that manifest id, which is what makes one writer visible to N readers across separate wasmtime::Stores.