# Plugin Security
The plugin security model is complete mediation plus small proof types. Every
value crossing a boundary is untrusted input, a proof produced by one owner, or
a proposal to a later owner.
The Wasm sandbox is one layer. The host boundary is the trust boundary.
Protected assets include buffer text, register and undo text, workspace files,
raw host paths, editor configuration, user-visible diagnostics, ECS topology,
UI responsiveness, and host process stability.
Primary adversaries are malicious components, compromised packages, buggy
plugins with accidental unbounded behavior, manifests that overstate identity
or capability needs, stale handles retained after editor state changes, and
crafted guest paths, text, or serialized payloads.
## Invariants
- A plugin never holds editor authority directly. It holds borrowed WIT
resources and configured grants that the host revalidates at each import.
- Workspace authority is not a borrowed ambient resource. It is created per
workspace import from operation kind, normalized path, bounded payload, and
effective grants. Direct byte delivery also requires an explicit filesystem
policy context and spends the per-update workspace request budget before
filesystem access. Guest-visible delivery also needs an enforceable host-side
timeout or cancellation model before it can cross the Wasmtime boundary.
- Capability authorization is necessary but not sufficient for filesystem
access. Workspace I/O must still pass through `fs_utils`.
- Generated WIT types stay at the adapter edge and convert into Alma-owned
types before policy decisions. WIT resources carry authority shape, not raw
host identifiers.
- Pending work is observable only after a successful guest return.
- Queue capacity is spent only on actionable sealed work. Empty successful
update batches are valid commit-boundary evidence, but effect, workspace I/O,
and workspace observe-task schedulers admit them as no-ops rather than
letting empty work exhaust bounded queues.
- Workspace observation task requests are pending work. Failed guest updates
discard them; successful updates seal them before the host task queue can
publish filesystem-policy work. If the guest drops a task resource before
successful return, the pending request is removed and cannot trigger later
filesystem work.
- Production task queue enqueueing accepts sealed successful-return batches
only. Runtime adapters must not push raw read proofs directly into the queue,
because that would bypass the session request ledger and discard boundary.
Wasmtime enqueue first requires the task batch identity to match the runtime
instance, and enqueue, single-task execution, capped batch execution, and
drain-all execution also require the matching active `PluginInstanceState`,
so revocation closes late scheduling even when a caller still holds a sealed
batch.
- The Wasmtime helper that runs an update and schedules observation tasks is an
owner-loop convenience over the same successful-return batch. If task
scheduling fails after guest return, the error retains the unscheduled
owner-work proof plus the retryable task enqueue failure in one private-field
payload, so external callers cannot construct a mismatched post-update
schedule error by convention. Its operational-event projection is derived
from the typed update or enqueue failure, keeping reporting redacted without
requiring callers to inspect unpublished owner work. Retrying that paired
schedule error rebuilds a `WasmtimePluginScheduledUpdate` proof after task
scheduling succeeds; abandoning it consumes both unpublished owner work and
unscheduled task work into one redacted discard report whose debug output is
count-shaped. Public split APIs return named boundary structs rather than
positional tuples. Callers split owner work only when handing it to the
separate owners.
- Owner publication of that proof validates the intent queue and workspace I/O
worker queue into private admission proofs before mutating either queue. Each
paired admission proof retains the exact target queue borrows through
publication, and each owner queue admission owns its sealed batch until
publication, so admitted pushes are not raw convention calls. If one owner
queue is saturated, neither owner observes partial work and the paired proof
remains retryable or explicitly discardable. Successful publication returns a
named report with admitted owner-work counts. The retryable proof stays behind
typed accessors, and its consuming split names the queue rejection and owner
work instead of relying on a single work-only consuming accessor. Debug output
exposes only identity, closed rejection class, and counts.
- Once a scheduled update has admitted observe-task work to the runtime queue,
owner publication returns a named success report carrying both owner
publication evidence and that scheduled-task report, while owner-queue
publication failures keep the task report paired with the retryable
owner-queue failure. Callers cannot accidentally drop the fact that task work
is already scheduled while deciding whether to retry or discard unpublished
editor/workspace I/O owner work. Retrying that owner-publication failure
consumes the paired error and returns either the named success report or the
same paired failure shape; abandoning it returns a named discard report that
retains the already-scheduled task report. Scheduled-task
evidence is named as already admitted at this boundary, and debug reports
expose counts instead of retained batch internals, nested discard reports, or
task handles.
- Failed sealed workspace observation task enqueue preserves the sealed batch
with the closed queue rejection. Explicitly discarding the returned batch
releases reservations and returns a redacted cleanup report; retrying it on
the owning queue preserves task ids and pending capacity. Wasmtime rejects a
batch from another plugin before queue admission and exposes enqueue failures
through a private-field error with a closed public kind and source-specific
accessors, so external callers cannot construct a mismatch between lifecycle
state, runtime identity, queue rejection, and retryable task batch.
Runtime/state and runtime/batch identity mismatch accessors return named
borrowed identity pairs instead of positional tuples.
The low-level queue error split returns named fields, so callers do not
separate rejection evidence from retryable work by tuple position.
Queue admission itself retains the validated pending queue storage through
enqueue, so all-or-nothing reservation validation and publication are one
proof-mediated operation rather than a validate-then-push convention.
- Failed sealed workspace I/O worker enqueue preserves the sealed batch with
the closed queue rejection. Explicitly discarding the returned batch returns
a redacted cleanup report; retrying it after capacity frees preserves the
accepted successful-return work. Its consuming split returns named fields.
- Queue-owned workspace I/O and observation task state can be canceled by one
identity or by all identities for shutdown. All-identity cleanup groups
reports by plugin identity in deterministic cleanup order and exposes counts
only. Retryable sealed batches outside queues remain caller-owned.
- Wasmtime workspace observation task cleanup at the runtime boundary requires
a `PluginRevocationReport` for the same identity, then clears queue-owned
task state and persistent task resources. A report for another identity fails
closed as an instance/state mismatch.
- Wasmtime shutdown cleanup clears only runtime-owned workspace observation
task state. It cancels queued work, removes uncommitted active-update task
reservations, and tombstones persistent resources, while retryable sealed
batches already returned to callers remain caller-owned.
- The Wasmtime helper that revokes host state and clears runtime-owned
workspace observation tasks first proves the runtime and state identities
match. A mismatch returns the closed instance/state rejection before the
provided state is mutated. Its consuming split returns named lifecycle
revocation and runtime-owned task cleanup fields instead of a positional
tuple.
- Persistent Wasmtime task resources retain their authorizing workspace path.
The host-session boundary returns the reserved handle and authorized path as
one proof, so the Wasmtime adapter does not reconstruct resource authority
from parallel local state. Its consuming split names the task handle and
authorizing path before the runtime persists them. Poll and take revalidate
that path against the current update snapshot before resource-table lookup or
queue access; denied revalidation tombstones the resource without freeing its
table rep and returns the typed authorization rejection. The occupied-resource
cap is a non-zero proof derived from the same queue limits as task scheduling.
Later guest or canonical drops of host-invalidated task resources are cleanup
no-ops. If the Wasmtime table and runtime authority tracker diverge, start,
resolve, and drop fail closed with the existing resource-handle rejection
vocabulary instead of relying on debug-only assertions. A reused table rep
that is still tracked tombstones the stale tracker entry and removes the new
table entry before returning. Table lookup/delete errors tombstone the tracked
resource before returning unless drop already freed the table slot.
- Deferred workspace I/O, direct host-session observations, and task-style
observation reservations spend one shared per-update request ledger. No path
may bypass that accounting by choosing a different commit path.
- Deferred workspace I/O does not maintain an independent mutable request
counter. Its charged request count is the pending batch's retained operation
count, while the shared ledger tracks only non-deferred charges.
- Task-style observation reservations pair the pending task batch, authorized
read proof, and shared request ledger by `PluginIdentity` before charging the
ledger or reserving a task id.
- Pending effect commits, workspace I/O commit failures, worker queue
failures, and observe-task queue failures expose closed error classes. Their
field, identity, and sealed-reservation details also carry closed source
vocabularies, so callers can distinguish access-proof, request-ledger,
identity, queue-capacity, and queue reservation failures without parsing
presentation text.
For host queue zero-limit failures, the field vocabulary is part of the error
value rather than a presentation-only projection.
- Workspace read and write execution accept non-zero byte cap proofs, not raw
integers. Validated budgets produce read proofs for deferred reads, direct
host-session observations, and task-style observations, and write proofs for
deferred artifact writes.
- Host queues, handle stores, ledgers, and pending batches consume validated
limit and budget proofs directly. Zero-limit diagnostics stay at the proof
constructors, and constructors that already receive proof types are
infallible.
- Paired successful-return, owner-work, post-update schedule, and scheduled
owner-publication proofs enforce same-plugin identity in release builds
before constructing a proof value. A mismatched pair is an internal invariant
violation, not a representable cross-plugin proof.
- Workspace request-budget, pending-effect, intent queue, proposal lane,
observe-task pending-capacity, workspace worker, and operational queue
saturation diagnostics retain non-zero cap proofs from validated limits, so
rejected capacity diagnostics cannot represent a zero cap by convention.
- Runtime limit, workspace I/O budget, and workspace observation task queue
zero-limit errors carry closed field vocabularies, not arbitrary diagnostic
strings.
- Guest decode errors carry closed field vocabulary, not adapter-supplied
strings.
- Workspace observation task requests retain the producing update's non-zero
read byte cap. Queue execution uses the retained cap, not a later caller
parameter, so scheduling cannot widen guest-visible bytes.
- Runtime task execution APIs make unbounded work explicit: the drain-all path
is named `execute_all_workspace_observe_tasks`, while owner-loop callers can
use `execute_next_workspace_observe_task` or the non-zero capped
`execute_workspace_observe_tasks_up_to` path.
- Task ids returned before an update commits are not reused. A failed update
may burn reserved ids, but it releases reserved queue capacity and must not
publish filesystem work or let a stale id observe a later task.
- Polling and taking workspace observation tasks require an identity-scoped
lookup and return closed state shapes. Polling carries only redacted outcome
shape, and taking is the only task-result path that carries the byte-bearing
guest-delivery outcome. Queue records and retained completions own an
identity-scoped task key, so internal task state cannot carry an inert task
id without its plugin owner. The queue also exposes the same payload-free
state projection for adapters that do not need bytes. Pending tasks cannot
be consumed; completed outcomes can be consumed once; unknown, evicted,
already-taken, or cross-plugin ids are indistinguishable.
- Task reservation accounting must preserve `Send` and `Sync` host-owned batch
shapes and use explicit RAII queue-slot accounting, so future runtime
scheduling cannot depend on thread-local state or raw shared-owner counts.
Reservation counter underflow is an internal invariant violation in all
builds, not a debug-only check.
- Task result vocabulary is not queue authority. Task ids, owned
identity-scoped task handles, queue keys, internal handle-borrowed lookup
matching, payload-free state queries, payload-free poll states, consuming
take states, redacted poll/take result shapes, and
`PluginWorkspaceObserveTaskCompletionShape` may be handed to adapters, but
queue mutation, retention, and revocation cleanup stay behind the host queue
owner. Wasmtime exposes that cleanup only through a matching
`PluginRevocationReport`, so runtime resource invalidation stays tied to
lifecycle revocation. Poll/take resource resolution also carries the original
workspace path provenance and tombstones resources whose path is no longer
authorized by the current update snapshot. Drops of already-invalidated task
resources are idempotent cleanup, and tombstoned reps remain occupied until
guest drop. Drops of uncommitted task resources also remove the matching
pending request before it can become sealed work. Runtime cancellation also
removes matching uncommitted task requests and releases their reservations
before they can become sealed work. Starting a new Wasmtime update tombstones
any task resources still marked as started by a prior abandoned update before
fresh resources are accepted.
- Draining a sealed batch is the only commit point from plugin effects into ECS
messages.
- Drained effect reports split into named owner request batches with the
drained plugin identity retained, so buffer proposal requests and status
requests are not separated by tuple-position convention.
- Failed `PluginIntentQueue` admission preserves the sealed effect batch with
the closed queue rejection. Explicitly discarding the returned batch produces
a redacted cleanup report; retrying it after capacity frees preserves the
successful-return work. Its consuming split returns named fields.
- Owner systems remain the only commit point for authoritative mutation.
- Stale handles, wrong-kind handles, missing ECS targets, denied capabilities,
invalid ranges, malformed strings, and exceeded limits are normal rejected
outcomes with closed reason enums.
- User-facing diagnostics expose stable identity and reason shape, not raw
paths, buffer text, register text, guest payloads, OS strings, or Bevy
entities.
- Runtime state that survives an update has an owner and a generation. The
owner can revoke it, and the generation makes stale values fail closed.
- Wasmtime update-scoped `ResourceTable` reps are typed by resource kind before
they are stored for guest-call borrows or cleanup, so view and buffer resource
authority cannot be swapped by raw table-index convention.
- Host grant authorization is implemented once by a plugin-scoped authority
proof. Live contexts and per-update snapshots may reuse it, but adapters do
not own parallel denial or workspace-token projection rules.
- Host import snapshots pair effective grants with handle stores only when
both carry the same validated plugin identity.
- Workspace read/write access proofs retain the plugin identity that authorized
them. Filesystem-policy tokens stay internal to `workspace_io`, and pending
workspace I/O, direct observations, and observation task batches reject access
proofs from another plugin identity instead of relying on caller convention.
- Pending workspace artifact writes retain a bounded payload proof with the
non-zero write cap that admitted it, so sealed write work cannot carry an
over-budget payload by convention.
- The Wasmtime adapter stores a host-import rejection and its redacted import
class in one envelope. A source error without attribution, or attribution
without a source error, is not representable.
- `PluginHostImportError` owns the closed rejection class for
non-authorization import failures. Authorization failures deliberately have
no rejection class and map to denied-import events.
- Interior mutability is not authority. Tables that use `Mutex`, `RwLock`,
`RefCell`, or atomics still expose APIs that preserve generation checks,
bounds, and revocation ordering.
## Proof Types
Authority-bearing Rust values have private fields and one validating
constructor. Accessors expose only what the next boundary needs.
Important proofs:
- `PluginIdentity`: stable ASCII identity
- `PluginWitWorld`: current supported WIT world proven at the enabled
config/manifest boundary
- `ValidatedPluginConfig`: registry entry after identity validation
- `EnabledPluginConfig<'_>`: enabled registry view with loadable-entry proofs
- `PluginComponentPath`: normalized relative component path shape only
- `ValidatedPluginRuntimeLimits`: non-zero runtime limits bounded by host caps,
with host collection caps retained as host-sized proofs after config
validation
- `PolicyOpenedPluginManifest`: manifest bytes read through `fs_utils` before
parsing
- `AuthorizedPluginManifest`: manifest identity and WIT world matched the
enabled config
- `AuthorizedPluginComponent`: component bytes read through `fs_utils` before
Wasmtime construction
- `PluginProposalPolicy`: static per-plugin proposal review policy with
explicit `auto_apply` and `manual_review` modes
- `WorkspacePathGrant`: normalized grant prefix, with explicit broad grants
- `WorkspacePath` and `WorkspacePathRef`: owned and borrowed validated guest
path vocabulary
- `CapabilityAtom`: stable policy capability spelling, path arity,
authorization-ref construction, and redacted shape projection shared by
config, manifests, WIT adapters, and host authorization
- `GuestDecodeField`: stable generated-to-Alma field vocabulary for decode
errors at host-import boundaries
- `GuestDecodeLimits`: non-zero guest string, payload, and scalar count caps
derived from validated runtime policy before adapter decode or return-size
checks
- `WitHostImport`: exposed host-import metadata tying WIT function names to
capability atoms and redacted operational classes
- `WitWorkspaceObserveTaskImport`: start/poll/take metadata for the
guest-visible workspace read task surface, tying WIT function names to
`workspace.observe` and redacted operational classes
- `PluginHostAuthority`: plugin identity and effective capability grants after
policy projection, reused by live host contexts and update snapshots
- `AuthorizedWorkspaceReadAccess` and `AuthorizedWorkspaceWriteAccess`:
identity-bearing capability proofs, not filesystem authority
- `PluginWorkspaceIoBudget`, `PluginWorkspaceReadByteLimit`,
`PluginWorkspaceWriteByteLimit`, and the internal request ledger: non-zero
per-update workspace limits, byte-cap proof projection, and shared request
accounting across deferred I/O, direct observations, and task-style
observation reservations, with deferred request counts derived from pending
batch storage rather than duplicated in the ledger
- `PluginWorkspaceIoCommitErrorKind` and
`PluginWorkspaceIoIdentityMismatchSource`: closed commit-failure class and
identity-pairing source for access-proof and request-ledger mismatches at
workspace admission
- `PluginEffectBatchLimitField` and `PluginEffectCommitErrorKind`: closed
pending-effect batch diagnostics for host-import effect admission failures
- internal `PluginWorkspaceReadToken` and `PluginWorkspaceWriteToken`: typed
bridges into `fs_utils` after capability authorization. Token construction
and filesystem execution stay inside `workspace_io`; public callers retain
access proofs rather than raw policy tokens.
- `PluginWorkspaceWritePayload`: bounded deferred write payload proof, retaining
the non-zero write cap that admitted the bytes and exposing only byte length
in debug shapes.
- `PluginWorkspaceIoWorkerQueueLimitField` and
`PluginWorkspaceIoWorkerQueueErrorKind`: closed worker-queue diagnostics for
sealed workspace I/O admission failures
- `PluginWorkspaceIoWorkerEnqueueError`: retry-safe sealed workspace I/O
admission failure that keeps the closed queue rejection paired with the
still-sealed batch
- `PendingPluginWorkspaceIoBatch::observe_existing`: direct read projection
through the shared request ledger and `fs_utils`, returning only the
guest-delivery outcome while rejecting cross-plugin access proofs before
charging budget or touching filesystem policy. The host-import session
helper that calls it is plugin-internal; public guest reads use the
task/result ABI.
- `PluginWorkspaceIoCompletion`: operation-typed execution result, with read
and write variants that cannot pair one operation with another operation's
success payload
- `PluginWorkspaceReadSuccess`, `PluginWorkspaceWriteSuccess`,
`PluginWorkspaceReadError`, and `PluginWorkspaceWriteError`:
operation-specific filesystem-policy outcomes
- `PluginWorkspaceObserveOutcome` and `PluginWorkspaceObserveBytes`:
guest-delivery projection for `workspace.observe`, carrying only bounded
bytes or one closed rejection and no path metadata
- `PluginWorkspaceObserveOutcomeShape`: redacted diagnostic and adapter shape
for guest-delivery outcomes, carrying only byte length or closed rejection
- `PluginWorkspaceObserveTaskId`, `PluginWorkspaceObserveTaskHandle`, owned
queue task keys, internal handle-borrowed lookup matching,
`PluginWorkspaceObserveTaskState`, `PluginWorkspaceObserveTaskResultShape`,
`PluginWorkspaceObserveTaskQueue`, `PluginWorkspaceObserveTaskPoll`,
`PluginWorkspaceObserveTaskTake`, `PluginWorkspaceObserveTaskCompletion`, and
`PluginWorkspaceObserveTaskCancellationReport`: host-owned task model for
guest-visible workspace reads, with inert task ids, owned identity-scoped
handles and queue records, identity-scoped state, payload-free poll, and
consuming take queries, shared closed pending, completed, and unknown result
state, shared redacted poll/take result shapes, identity-scoped enqueue and
retained completion shapes, pending and retained-result bounds, retained read
byte caps, FIFO filesystem-policy execution,
pending-capacity plus non-reused id reservation before guest-visible return,
explicit RAII queue-slot accounting, redacted saturation events, and
revocation or shutdown cleanup
- `WasmtimePluginInstance::cancel_workspace_observe_tasks_for_revocation`:
runtime-owned cleanup hook that requires a matching revocation proof before
canceling task state and tombstoning persistent task resources
- `WasmtimePluginInstance::cancel_workspace_observe_tasks_for_shutdown`:
runtime-owned shutdown cleanup hook for task state still held by the
instance
- `WasmtimePluginInstance::enqueue_workspace_observe_tasks`: task scheduler
admission for sealed successful-return batches.
- Wasmtime workspace observation task execution hooks:
`execute_next_workspace_observe_task`,
`execute_workspace_observe_tasks_up_to`, and
`execute_all_workspace_observe_tasks` require matching active instance state
before filesystem-policy execution. The drain-all path is explicit by name.
- `PendingPluginWorkspaceObserveTaskBatch`,
`SealedPluginWorkspaceObserveTaskBatch`,
`PluginWorkspaceObserveTaskResourceAuthority`, and
`PluginWorkspaceObserveTaskDiscardReport`: successful-return commit boundary
for task-style workspace reads, carrying reserved identity-scoped handles and
read byte caps while rejecting cross-plugin read proofs or request ledgers
before charging budget, pairing resource handles with their authorized
revalidation paths, and keeping task filesystem work discardable until the
guest update returns successfully.
- `PluginWorkspaceObserveTaskQueueErrorKind` and
`PluginWorkspaceObserveTaskReservationMismatch`: closed task-queue failure
class and sealed-reservation mismatch source for identity and queue ownership
failures before task enqueue
- `PluginRuntimeSpec`: runtime input after policy projection and validation,
with identity derived from `AuthorizedPluginComponent`
- `PluginInstanceState`: active or revoked lifecycle gate for one loaded
runtime instance
- `PluginActiveUpdate`: crate-private proof pairing one import snapshot with
update handles after lifecycle gating
- `WasmtimeUpdateResources`: private adapter proof retaining typed view and
buffer resource-table reps for one update call and its cleanup
- `PluginHostImportSnapshot`: validated update-scoped pairing of effective
grants and live handles for one plugin identity
- `PluginBufferSnapshot`: bounded text and revision observed when a buffer
handle is issued
- `PluginBufferEditProposal`: snapshot-derived edit proposal with identity,
capability, target, and base revision provenance
- `PluginBufferProposalCapability`: proposal-specific wrapper over
`CapabilityAtom`, so proposal receipts do not own independent capability
spellings
- `PluginProposalId`: non-zero host proposal identifier
- `PendingPluginBufferEditProposal`: host-visible proposal retained until
auto-apply, manual apply, or manual rejection
- `PluginBufferEditProposalReceipt`: id-correlated result of proposal-lane or
buffer-owner decision
- `PluginRuntimeTraceEvent`: redacted replay/telemetry event adapted from
owner-boundary reports, identity-scoped workspace observation task
completion shapes, cancellations, and receipts
- `PluginProposalReceiptLog`: bounded in-session receipt retention
- `SealedPluginEffectBatch` and `PluginIntentQueueEnqueueError`: effects from a
successful guest return, plus retry-safe ECS admission failure that keeps the
sealed batch paired with the closed queue rejection
- `PluginHandleStore`: per-plugin view and buffer handle owner and revocation
boundary
- `ResolvedPluginBuffer` and `ResolvedPluginView`: short-lived host proofs
produced by handle resolution
Reusable configuration snapshots may implement `Clone` because they do not
represent live runtime authority by themselves. Guest-visible handles may
implement `Copy` because they are inert identifiers and must be resolved through
the owning `PluginHandleStore` on each use.
Short-lived transition proofs should be move-only unless cloning cannot extend
their lifetime or authority. This includes manifest authorization, effective
plugin projection, runtime specs, host contexts, workspace access proofs,
pending batches, sealed batches, drained reports, edit proposals, resolved
handles, and handle stores.
## Capability Rules
Default deny. Config and manifest are both authority ceilings. Effective
workspace grants are the narrowest overlapping prefixes, so either side can
reduce authority and neither side can widen it alone.
V1 capability atoms:
- `buffer.observe`
- `buffer.propose_edit`
- `workspace.observe`
- `workspace.artifact_write`
- `status.publish`
Reserved but unimplemented:
- `workspace.cache_write`
- `workspace.propose_patch`
Unknown config fields, unknown manifest fields, unknown WIT worlds, unknown
capabilities, unknown handles, malformed paths, and invalid limits fail closed.
## ABI Rules
`CURRENT_WIT_WORLD` is the only supported ABI world for v1.
Generated WIT types are private to the ABI boundary. Use `TryFrom` or narrow
decode functions whenever malformed, oversized, stale, unauthorized, or
unsupported guest data can appear. Use `From` only when conversion is
infallible and carries no new authority.
Decode functions must report one `GuestDecodeField` value, not arbitrary field
strings supplied by adapters.
Every exposed host import must map to:
- one named user authority
- one Rust capability variant
- one typed WIT host-import metadata variant
- one config and manifest spelling
- one authorization check
- one redacted error shape
- one deterministic drift test
`WitHostImport` is the default extension point for exposed imports. It keeps
the WIT function name, authorizing capability atom, and redacted operational
import class together so future imports cannot update one surface while
silently skipping another.
`CapabilityAtom` is the default extension point for capability vocabulary. WIT
capability values, authorization-ref path arity, and redacted capability shapes
should derive from the atom so a new capability cannot silently add a schema or
ABI spelling without an owned policy shape.
View and buffer authorities are host-owned, per-plugin scoped, and redacted in
diagnostics. Public WIT imports use borrowed resources instead of scalar ids.
The Wasmtime adapter maps resources through a store-local `ResourceTable` and
then through a captured `PluginHandleStoreSnapshot`; Wasmtime resource reps are
runtime machinery, not a public handle format. Host handle generations remain
an internal revocation invariant.
Workspace authority is narrower than a resource handle. A workspace host import
must decode and validate the path, check the requested operation against
effective grants, enforce payload and request budgets, then enqueue a typed
deferred workspace request. Internal policy tokens are constructed only inside
`workspace_io`. The host-session observation helper can perform a direct
bounded read through an explicit filesystem policy context, but only through
the pending workspace I/O ledger, and it is not guest-visible. Raw host paths
and filesystem access never enter guest-visible types.
Workspace access proofs are plugin-scoped. If a caller tries to queue a read or
write proof under another plugin identity, the host rejects the batch before
filesystem policy sees the request. Task resources keep the same rule: task ids
are not authority, and polling or taking a result must carry the owning
`PluginIdentity`.
Guest-visible `workspace.observe` is exposed only as a start/poll/take task
resource. It uses the bytes-or-rejected delivery projection before any read
bytes cross back into guest code. Owner-facing display paths and filesystem
error detail do not cross that boundary. Malformed paths and denied capability
remain host-import rejections; filesystem-policy read failures become the
closed guest-domain rejection. Synchronous Rust filesystem reads inside a
Wasmtime host import are not acceptable unless the host can enforce timeout and
cancellation without waiting for the read to return.
The task-based WIT shape moves as one group. A start import without poll/take,
or poll/take without matching start authority, would turn task resources into
an underspecified protocol surface. The WIT metadata owns the function names,
authorizing capability atom, and redacted operational import classes. Tests
prove the group is complete, the old blocking read import is absent, and
diagnostics cannot drift from the exposed import group.
The observation task queue provides bounded pending reads, bounded retained
results, identity-scoped polling, non-reused id reservation, pending-capacity
reservation, and revocation cleanup. The pending/sealed task batch proves that
task filesystem work stays behind successful guest return even when the task
resource and queue slot were reserved earlier in the update.
The queue state, poll, and take APIs use the public state shape: pending,
completed, and unknown. Poll and take also share one redacted result shape;
completed poll results expose only the guest-delivery outcome shape, retained
completion shapes carry the task's owning identity, and take is the only path
that consumes and returns the byte-bearing outcome.
This keeps stale, evicted, already taken, and cross-plugin task ids from
becoming distinguishable authority signals.
Prefer WIT algebraic types for public contracts: variants and enums for closed
operation cases, options for true absence, flags for independent booleans, and
results for expected domain failures where the guest may continue. Traps,
timeouts, fuel exhaustion, cancellation, malformed resource use, and host
failures abort the update and discard the session.
Use WIT worlds or linker construction for static authority profiles when plugin
classes diverge. Keep `PluginHostContext` for dynamic policy over specific
buffers, revisions, workspace grants, and runtime limits.
## Error And Diagnostics
Plugin errors stay concrete until an outer boundary maps them to diagnostics,
status, retry, or telemetry. Internal callers should be able to branch on exact
causes such as denied capability, stale handle, malformed guest data, resource
exhaustion, timeout, queue saturation, or owner rejection.
Host-import source errors expose redacted rejection classifiers for outer
diagnostics that do not need the full source enum. The Wasmtime adapter adds
redacted phase and import attribution, and its lower-level runtime failure
detail also uses a closed class vocabulary while dropping raw Wasmtime and OS
source text. Host-import rejection attribution is kept with the typed source
error until the operational event is built. These classifiers do not replace
typed source errors.
Operational diagnostics may include plugin identity, lifecycle hook, a closed
host-import name, closed rejection shape, closed runtime failure class, closed
revocation reason, closed handle kind, capability shape, queue name, closed
limit field name, and byte counts. They must not include guest payloads,
workspace bytes, raw paths, OS error strings, buffer text, register contents,
or Bevy entities.
Lifecycle hooks, host imports, runtime setup failures, canonical ABI
post-return cleanup failures, revocation reasons, handle kinds, and resource
handle rejection reasons use closed vocabularies before becoming operational
events. Closed queue, revocation-reason, handle-kind, resource-handle
rejection, host-state mismatch, and host-import rejection classes render
through explicit stable text, not Rust debug formatting.
Proposal lane diagnostics may expose proposal ids, source identity, capability
shape, target shape, base revision, redacted edit shape, and redacted diff byte
counts. They must not expose the proposed replacement text.
Proposal review policy is not plugin authority and does not let a guest bypass
the proposal lane. It chooses the post-drain handling for admitted buffer edit
proposals from the producing plugin's validated identity. Unconfigured proposal
sources use the inert compatibility default, `auto_apply`.
Proposal receipts may expose proposal ids, source identity, capability shape,
target shape, base revision, redacted edit shape, owner boundary, result
revision, or closed rejection reason. They must not expose proposed replacement
text or raw ECS ids.
Proposal capability attribution derives from `CapabilityAtom`. Proposal-specific
wrappers are acceptable when they constrain which atoms can appear at a given
proposal boundary.
Workspace I/O success values may expose escaped display paths and bounded bytes
only through explicit accessors for the owner/user surface that requested them.
Debug, operational events, and future replay traces must reduce those successes
to operation kind, byte counts, and closed outcome classes.
Observe-task queue and pending/sealed batch debug output is count-shaped; task
handles and per-task retained shapes stay behind explicit task-result and
enqueue-report accessors.
Successful-return aggregate debug output is also count-shaped. Host-import and
owner-batch proofs expose identity plus work counts rather than formatting the
nested sealed effect, workspace I/O, or observe-task batches by convention.
Guest-visible workspace observations use a narrower projection than owner/user
surfaces: success carries bounded bytes only, and failure carries a closed
rejection without raw paths or filesystem cause.
Runtime trace events are diagnostic artifacts, not authority. They may retain
plugin identity, capability spellings, non-zero host task ids, revisions, byte
counts, closed operational classes, and redacted edit shapes. They must not
retain raw ECS ids, workspace-relative paths, host paths, status text,
replacement text, buffer text, file bytes, OS errors, or guest payloads.