# Alma Target Architecture
This document expands `docs/arch.md` into the active implementation target.
`docs/arch.md` defines the enduring shape: pure editor semantics first, Bevy
ECS as orchestration, typed events at mutation boundaries, and Wasm plugins as
untrusted guests. This file records the current target deltas without becoming
the detailed plugin manual.
The immediate plugin design problem is the transition from an authorized request
to a real side effect:
- a plugin has an effective capability grant
- the guest calls a host import
- the host validates guest data
- the host resolves borrowed resources through scoped host handles
- the host bounds resource use
- the host either returns safe data or records a proposed effect
- ECS owner systems later perform authoritative editor mutation
That transition is where plugin systems usually become unsafe, stringly, or
impossible to test. Alma should make it boring.
## Status
This is a target design, not a statement of what is fully implemented.
Implemented today:
- static plugin registry configuration
- plugin identity validation
- component path shape validation
- policy-opened manifest and component byte tokens
- manifest parsing and effective grant projection
- capability intersection
- workspace-relative grant validation
- explicit whole-workspace grant spelling
- runtime limit types and validation
- distinct validated runtime-limit proofs for runtime construction
- host-sized non-zero scheduler caps retained after runtime-limit validation
- host queues, handle stores, ledgers, and pending batches consume validated
limit and budget proofs without revalidating raw numeric limits, and their
proof-taking constructors are infallible
- WIT world and capability vocabulary checks
- typed WIT host-import metadata that maps each exposed import to its WIT
function name, authorizing capability atom, and redacted operational class
- task-based `workspace.observe` WIT start/poll/take imports, including an
opaque task resource, redacted operational import classes, and drift coverage
proving the ABI exposes no blocking read import and no partial task group
- optional Wasmtime component bindings behind `plugin-runtime`
- feature-gated Wasmtime component validation, loading, lifecycle calls,
runtime limits, timeout handling, and update discard evidence
- generated `status.publish`, `buffer.observe`, `buffer.propose_edit`,
task-based `workspace.observe`, and `workspace.artifact_write` host import
adapters using borrowed WIT resources for view and buffer authority, opaque
task resources for workspace read delivery, and typed workspace policy tokens
for filesystem authority
- `EditorSet::PluginIntent` placed between `Intent` and `Edit`
- workspace I/O request, typed capability token, and typed filesystem-policy
token shapes that keep plugin authorization separate from `fs_utils`
authority
- shared per-update workspace request accounting for deferred workspace I/O,
direct host-session observations, and task-style observation reservations
- task-style workspace observation reservations reject cross-plugin workspace
I/O ledgers before charging budget or reserving task ids
- workspace I/O and observe-task queue admission diagnostics carry closed error
classes and closed source labels for access proofs, request ledgers,
identity mismatches, and queue ownership mismatches
- workspace request-budget, pending-effect, intent queue, proposal lane,
observe-task pending, workspace worker, and operational queue saturation
diagnostics retain non-zero cap proofs after validation instead of raw count
convention
- capability atom vocabulary that drives config/manifest spellings, WIT
capability adapters, authorization-ref path arity, and redacted shapes
- host-owned typed handle stores with live-handle limits, generation
invalidation, and Wasmtime `ResourceTable` lookup at the adapter edge
- shared host authority proof reused by live plugin contexts and per-update
import snapshots for grant denial and workspace access-token projection
- plugin effect queues
- closed pending-effect batch limit-field and rejection-class diagnostics
- `PluginIntent` ECS queue and drain system for sealed plugin effect batches
- retry-safe `PluginIntentQueue` admission: saturation returns the closed
queue rejection with the still-sealed effect batch, so scheduler code can
retry after capacity frees or explicitly discard with redacted cleanup
evidence
- paired owner-publication admission for sealed editor effects and sealed
workspace I/O, using a private paired admission proof that retains the
validated target queue borrows and per-queue admission proofs until
publication so future runtime owner-loop code can reject without partial
publication when either owner queue is saturated; success returns a named
owner-publication report with admitted work counts
- scheduled-update owner-publication admission that lets owner-loop code hand a
`WasmtimePluginScheduledUpdate` proof to ECS owner queues without splitting
the task scheduling report from retryable editor/workspace owner work by
caller convention; success is represented by a named publication report that
carries both owner-publication evidence and scheduled-task evidence
- scheduled-update owner-publication retry that consumes a paired owner-queue
failure and preserves the already-scheduled task report through repeated
owner queue admission attempts
- scheduled-update owner-publication reports name already-admitted task
evidence as scheduled work, and debug output reports scheduled task counts
rather than task-handle lists
- Wasmtime workspace observation task execution APIs keep drain-all execution
explicit by name and keep single-task or non-zero capped execution as the
owner-loop-friendly paths
- post-update observe-task schedule recovery that consumes a paired schedule
error and rebuilds the scheduled-update proof after task retry succeeds
- named discard reports for both post-update task scheduling failures and
scheduled-update owner-publication failures, preserving paired cleanup
evidence without tuple conventions
- named scheduled-update and scheduled-owner-publication split structs, so
owner work, retryable queue errors, discard evidence, and already-scheduled
task evidence are separated by field name rather than tuple position
- effect, workspace I/O, and workspace observe-task queue admission treats
empty sealed successful-return batches as no-ops, so bounded queue capacity
is spent only on actionable owner or filesystem-policy work
- plugin workspace I/O through `fs_utils`
- deferred synchronous workspace I/O batches for the v1 stepping stone
- revision-guarded plugin buffer edits that reject stale base revisions at the
buffer owner boundary
- host-import session boundary that resolves handles, checks capabilities, and
queues discardable effects before any ECS or filesystem commit point
- host-import snapshots reject host grants and handle stores from different
plugin identities
- Wasmtime host-import rejection retention pairs the first redacted import
class with its typed source error
- host-import source errors own their closed non-authorization operational
rejection projection; capability denial remains a distinct denied-import
event
- operation-typed workspace I/O execution completions, so read and write
reports carry operation-specific success and filesystem-policy error types
- workspace I/O report debug output uses redacted operation and length shapes
instead of success display paths or file bytes
- a bytes-only `workspace.observe` delivery projection for guest-visible reads,
separate from owner-facing workspace read report metadata
- a host-session direct workspace observation path that requires explicit
filesystem policy context, authorizes through captured plugin authority, and
returns only bounded bytes or a closed rejection while the pending workspace
I/O ledger spends the per-update request budget without queuing deferred
workspace I/O. The session helper stays plugin-internal until
guest-visible workspace reads use a task/result ABI.
- workspace read/write access proofs retain the issuing `PluginIdentity`,
filesystem-policy tokens stay internal to `workspace_io`, and pending
workspace I/O rejects access proofs authorized for another plugin before
filesystem policy execution
- a runtime-owned workspace observation task queue with non-zero task
ids, pending and retained-result bounds, handle-scoped poll/take, FIFO
filesystem-policy execution, bounded single-task and capped batch execution,
retention eviction, revocation cleanup, and pending-capacity plus non-reused
id reservation before guest-visible return
- persistent Wasmtime task resources retain the workspace path that authorized
them; poll/take revalidates that path against the current update snapshot
before resource-table lookup or queue access, tombstones denied resources,
keeps tombstoned table reps occupied until guest drop, and rejects any new
task resource whose table rep is still tracked
- closed workspace observation task state, payload-free poll, and consuming
take projections for adapter use: pending, completed, and unknown or
already-consumed ids
- adapter-facing workspace observation task result vocabulary split from queue
mutation, so Wasmtime resource code can encode identity-scoped task
handles, internal handle-borrowed lookup matching, payload-free state
queries, payload-free poll states, consuming take states, shared redacted
poll/take result shapes, and identity-scoped retained completion shapes
without owning scheduling or retention
- a successful-return commit boundary for guest-requested workspace observation
task requests: update sessions hold task reads in a pending batch, failed
updates discard them without rolling task ids back and release reserved
capacity, successful updates seal them, and the task queue enqueues sealed
batches all-or-nothing before any filesystem-policy work can run. The queue
and sealed batch shapes must stay `Send + Sync` so runtime scheduling can
hand them across worker boundaries later. The public start import returns the
reserved handle as an opaque WIT task resource.
- sealed workspace observation task enqueue failures preserve the still-sealed
batch with the closed queue rejection, so future scheduling can retry on the
owning queue instead of losing reserved task ids by accident.
- Wasmtime observe-task enqueue rejects sealed batches whose plugin identity
does not match the runtime instance, and enqueue and execution require the
matching active `PluginInstanceState`, preventing wrong-plugin or revoked
instances from admitting sealed batches or executing queued filesystem reads.
- sealed workspace observation task batches can be explicitly discarded before
queue admission, releasing reserved capacity and returning a redacted discard
report for shutdown, revocation, or deliberate no-retry paths.
- bounded workspace I/O worker queue with FIFO execution and revocation
cancellation
- sealed workspace I/O worker queue saturation preserves the still-sealed batch
with the closed queue rejection, so future scheduling can retry after
capacity frees or explicitly discard the batch with redacted cleanup evidence
- all-identity workspace I/O worker and workspace observation task queue
cancellation for shutdown or broad teardown, returning deterministic
per-identity redacted reports without filesystem work or guest delivery
- Wasmtime instance cleanup for workspace observation tasks requires a
matching `PluginRevocationReport`, then clears queue-owned task state and
persistent task resources before unload
- Wasmtime state revocation can be driven through the runtime instance, which
verifies runtime/state identity before mutation and returns lifecycle
revocation with runtime-owned observe-task cleanup
- closed workspace I/O worker queue limit-field and rejection-class diagnostics
- redacted operational event shapes for load, unload, revocation, denied
imports, timeout, guest trap, runtime setup/fuel failure, intent queue
saturation, workspace I/O queue saturation, workspace observation task queue
saturation, and proposal lane saturation, with explicit stable text for
closed queue, revocation-reason, handle-kind, resource-handle rejection, and
host-import rejection classes
- redacted plugin runtime trace vocabulary for drained effects, discarded
updates, workspace I/O completions, workspace I/O cancellations, workspace
observation task completions, workspace observation task cancellations,
operational events, and proposal receipts
- checked-in runtime trace replay golden coverage over the redacted trace
vocabulary, using one explicit display line per event rather than `Debug`
- real Wasm component fixture coverage for `buffer.observe` and
`buffer.propose_edit`
- real Wasm abuse fixture coverage for fuel exhaustion, timeout, invalid
strings, oversized payloads, invalid resource handles, and stale handles
- real Wasm `buffer.propose_edit` publication through `PluginIntentQueue` and
the buffer owner
- deterministic multi-plugin FIFO coverage for runtime-produced
`buffer.propose_edit` batches
- typed proposal lane model for plugin buffer edits, with default auto-apply
compatibility and manual apply/reject request paths
- closed error-kind vocabularies for plugin intent queue, proposal lane,
proposal decision, and proposal receipt-log diagnostics
- config-backed per-plugin proposal review policy for buffer edit proposals,
with `auto_apply` as the default and `manual_review` as the explicit held
lane mode
- buffer edit proposal provenance and receipts whose capability attribution is
backed by the policy-owned `CapabilityAtom` vocabulary
- first visible proposal-lane chrome summary with redacted edit shape
- redacted one-hunk diff preview, pending proposal status, and retained
proposal receipts
- proposal-lane apply/reject chrome controls that emit typed request messages
Not implemented today:
- additional Wasmtime host import implementations beyond `status.publish`,
`buffer.observe`, `buffer.propose_edit`, task-based `workspace.observe`, and
`workspace.artifact_write`
- synchronous guest-visible `workspace.observe`; public workspace reads use
task resources
- plugin event subscriptions
- background workspace I/O worker threads
- guest-driven `PluginIntent` scheduling and subscriptions
## Editor Input Boundary Review
2026-05-10 review before broader insert-mode work:
Implemented now:
- pure insert-mode vocabulary in `vim::insert`: `InsertState`,
`InsertEntry`, `InsertCommand`, and `InsertExitPolicy`
- `VimMode::Insert(InsertState)` as a first-class mode, not a hidden adapter
flag
- normal grammar entries for Neovim/Vim-shaped `i`, `a`, `I`, `A`, `o`, and
`O`
- a dedicated `adapters::bevy::vim::insert` adapter that converts decoded
text, Enter, Backspace, and Escape into typed edit/cursor requests
- typed cursor request destinations: `CursorCell` for normal/visual cursor
cells and `InsertCaret` for UTF-8 caret boundaries
- bounded per-frame insert payload rejection with redacted diagnostics
The main architectural finding is that byte indices now have two related but
different meanings. Normal and visual mode use a visible cursor cell. Insert
mode uses a caret boundary that may legally sit after the final character of a
line or at EOF. `CursorPosition` still stores the current byte index, but
`CursorMoveRequested` now carries `CursorMoveDestination`. The owner validates
the requested coordinate kind directly instead of inferring clamp policy from
active mode.
Input-mode rules going forward:
- grammar parses entry commands; it does not insert text
- insert-mode text handling stays in a dedicated adapter, not in normal/visual
dispatch
- text mutation still flows through `BufferEditRequested`
- the buffer owner still validates UTF-8 boundaries and emits typed rejections
- insert payloads, paste payloads, and future IME batches must stay bounded and
redacted in diagnostics
- input repeat behavior must become mode-aware before broad insert repeat,
paste, or macro replay support
Gaps and open questions:
- Should `CursorPosition` eventually store a typed coordinate too, or is typed
request intent enough while render/layout still share one view cursor?
- Should insert payload limits move into user configuration, and what caps
should apply separately to keyboard text, paste, and IME composition?
- Should one insert session become a single undo transaction, matching Neovim's
user expectation, or should transaction grouping wait until undo lands?
- Which insert commands are v1: Delete, Ctrl-W, Ctrl-U, Tab, indentation,
completion, replace mode, and arrow-key movement all need typed commands
before being added.
- Should `o`/`O` indentation copy current-line indentation now, or wait for an
indentation policy module?
- How should dot-repeat record insert sessions: semantic entry plus inserted
text, or an explicit transaction envelope?
## Plugin Runtime Records
The detailed plugin design records now live under `docs/plugins/`:
- `docs/plugins/runtime.md`: runtime dataflow, module map, lifecycle,
cancellation, and commit points
- `docs/plugins/security.md`: proof obligations, capability rules, ABI rules,
and redacted error policy
- `docs/plugins/testing.md`: required coverage, fixture inventory, abuse-test
rules, and the capability extension checklist
The top-level plugin target remains:
- every boundary value is untrusted input, an owner-produced proof, or a
proposal to a later owner
- generated WIT values are decoded into Alma-owned types before authorization
- WIT expresses public shape, resources express authority, store/session state
expresses lifetime, and worlds/linkers express available powers
- host imports observe bounded data or queue discardable work, never mutate
authoritative editor state
- guest-visible workspace read delivery must not reuse owner-facing report
metadata. The public shape is bounded bytes or a closed rejection; paths,
display paths, filesystem error detail, and file bytes in diagnostics stay out
of guest-visible diagnostics and trace output. The public WIT surface is a
task resource because Wasmtime guest interruption cannot stop blocking Rust
filesystem work inside a host import. The task queue and successful-return
task batch model the host-owned result resource and commit shapes, including
pending-capacity plus non-reused id reservation before guest-visible return,
closed poll/take result states, shared redacted poll/take result shapes, and
consuming byte delivery through `take`.
- task-style workspace read exposure keeps queue ownership separate from the
WIT resource. ABI metadata keeps the WIT function names,
`workspace.observe` capability atom, generated binding drift checks, and
redacted operational import classes together.
- successful guest return, sealed-batch drain, and owner application remain
separate commit points
- diagnostics disclose stable identity and reason shape, not raw payloads,
paths, buffer text, OS strings, or Bevy entities
Component-model guidance for future plugin API work:
- prefer WIT `variant`, `enum`, `flags`, `option`, and `result` over scalar
tags, sentinels, status-code records, or generic payload envelopes
- use WIT resources for authority-bearing values, and do not expose Wasmtime's
internal integer resource representation as a public contract
- use WIT worlds and linker construction for static capability profiles, then
keep `PluginHostContext` for dynamic policy over specific buffers,
workspaces, revisions, and grants
- keep generated bindings as the normal host/guest surface; manual ABI decode
should exist only for generated values that still need Alma-owned validation
or redaction before policy decisions
- keep plugin-instance state in the Wasmtime `Store`, update-only state in
`PluginHostImportSession`, and global editor authority behind explicit owner
boundaries
- keep WASI deny-by-default. Add filesystem, environment, network, clock,
random, or stdio access only through named capabilities and narrow host
imports
- treat WIT `result` errors as expected plugin-domain outcomes where the guest
may continue, and traps/timeouts/cancellation as failed updates that discard
the current session
- keep effect buffering explicit. Imports append to the update session, and
only successful guest return can produce sealed batches for later ECS owners
Future plugin work should update the split records instead of growing this file.
## Relationship To `docs/arch.md`
`docs/arch.md` is the short architectural constitution. It should stay readable
and stable. This document is implementation-oriented and points to narrower
records when an area needs more detail.
The baseline decisions from `docs/arch.md` remain binding:
- editor behavior belongs in pure Rust modules before ECS wiring
- Bevy provides schedule orchestration and rendering integration
- `ecs::events` are the interpretation-to-mutation contracts
- owner systems in `Edit` or `Layout` mutate authoritative state
- stale ECS targets produce typed rejections
- status output must not leak arbitrary text or raw paths
- Wasm plugins are untrusted guests, not Bevy plugins
- plugins do not receive Bevy `Entity` values
- host capabilities map to user/editor authority
- config is the final authority for plugin grants
- WIT is the ABI contract
- plugin effects run through `PluginIntent` before `Edit`