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§
- Plugin
Store - 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
handleforflush_all.
Type Aliases§
- Plugin
Store Handle - A shared handle to one plugin’s store. Cloned into every
PluginStatebuilt for that manifest id, which is what makes one writer visible to N readers across separatewasmtime::Stores.