alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
# 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`