# Alma Authority Doctrine
Alma distinguishes observation, proposal, authorization, and application.
Observation is bounded access to state. Plugins may observe only state granted
by the host, through handles and paths that are validated at every boundary.
Observation does not imply future write authority.
Proposal is a typed request for an owner to consider. Plugins may propose
effects, but they do not apply effects. Every cross-boundary proposal carries
enough provenance for the owner to reject it safely: source identity,
capability, target, observed revision or generation, and redacted diagnostic
shape.
Authorization is the host decision that a subject may attempt one bounded
action on one resource. Grants start denied. New capabilities must be named,
bounded, documented, tested, and visible before they become part of the plugin
ABI.
Application belongs to owners. Buffers own text mutation. Status owns scoped
status surfaces. Filesystem writes go through filesystem policy. Runtime
imports may enqueue proposals or bounded I/O requests, but owner systems decide
whether anything changes.
Receipts are the durable explanation of accepted or rejected authority-bearing
work. A receipt should answer who proposed the work, what capability was used,
what target and revision were involved, what owner made the decision, and what
result occurred.
Receipt capability names derive from the policy capability atom vocabulary, even
when a narrower proposal-specific Rust type is used at the receipt boundary.
Authority-bearing Rust values have private fields and one validating
constructor. Transition proofs are move-only unless cloning is inert
configuration data. Raw guest strings, generated WIT values, paths, ranges, and
handles are untrusted until converted into Alma-owned validated types.
Plugin-scoped identity plus effective grants are one host authority proof;
contexts and update snapshots reuse that proof rather than reimplementing grant
checks at adapter edges.
Workspace read/write access proofs and filesystem-policy tokens retain the
identity that authorized them, so deferred batches cannot safely mix one
plugin's proof with another plugin's work queue.
Capability sets are composable collections of small authority atoms. Bundles
may become user-curated later, but the runtime boundary still reasons in atoms:
observe, propose, publish, or artifact-write under explicit scope. Alma does
not need a policy DSL until Rust types stop being the clearer policy language.
When config and manifest workspace path grants overlap, the effective grant is
the narrowest overlapping prefix. Either side can reduce authority; neither side
can widen it alone.
Capability atoms also own whether an authorization query is scalar or
workspace-path scoped, so adapters do not maintain a second path-arity table.
Workspace authority is split by threat model:
- `workspace.observe`: read or inspect granted workspace paths through policy.
- `workspace.cache_write`: reserved for plugin-private cache writes.
- `workspace.artifact_write`: write attributed generated output through policy.
- `workspace.propose_patch`: reserved for reviewed project-file patches.
Only implemented capabilities are accepted by config, manifests, WIT, and
tests. Reserved names are doctrine, not authority.
Guest-visible workspace observations are narrower than owner-facing filesystem
reports. A read result may carry bounded bytes or a closed rejection, but not
workspace paths, display paths, raw filesystem causes, or diagnostic text.
Direct byte delivery must carry explicit filesystem policy context at the
host-import session boundary; a workspace capability grant alone is not
filesystem authority. Deferred I/O, direct observations, and task-style
observation reservations spend the same per-update workspace request ledger, so
choosing a delivery path does not bypass request caps.
Workspace admission errors expose closed classes, and cross-plugin proof or
ledger mismatches expose closed sources instead of presentation text.
Task-style observations use host-owned task ids as inert lookup keys behind an
opaque WIT resource. Polling or taking a retained result must pair the id with
the owning plugin identity; revocation removes both pending reads and retained
outcomes for that identity. Poll and take results are closed states: pending,
completed, or unknown. Unknown covers stale, evicted, already-taken, and
cross-plugin ids so a task id never becomes an authority probe. Task read
proofs accepted during a guest update must pair with the same plugin's request
ledger and remain pending work: failed updates discard them, release reserved
queue capacity, burn any already-returned task ids, and only sealed successful
updates can enqueue them for filesystem-policy execution. Task resources
returned by failed updates are tombstoned in the Wasmtime table so their reps
cannot alias later resources before guest cleanup. If resource-table drift
would reuse a tracked task-resource rep, the Wasmtime adapter tombstones the
stale tracker entry, removes the new table entry, and rejects the start import.
The queue may expose the same closed state without exposing retained bytes, so
adapters can branch without turning results into diagnostics or authority.
Task ids and result states are adapter vocabulary, not queue authority; the
queue owner remains responsible for scheduling, retention, and revocation
cleanup. Persistent task resources retain the workspace path that authorized
them and revalidate that path against the current update snapshot before
poll/take can inspect queue state; denied revalidation invalidates the resource
and returns a typed authorization rejection while leaving the table rep
occupied until guest drop. The Wasmtime adapter's revocation cleanup requires a
`PluginRevocationReport`, cancels queue-owned task state for that identity, and
tombstones persistent task resources before the runtime instance is unloaded.
Runtime enqueue requires the sealed batch identity to match the runtime
instance, and enqueue and execution require the matching active
`PluginInstanceState`, so a revoked instance cannot use a caller-held sealed
batch to publish reads after authority has been removed.
The Wasmtime revocation helper first matches runtime and host-state identity,
then returns the lifecycle revocation proof with runtime-owned task cleanup so
shutdown code does not split those steps by convention.
The public ABI for task-style observations exposes start, poll, and take
together. Drift tests reject both the old blocking read import and a partial
task group. Host-import metadata owns the WIT names, authorizing
`workspace.observe` atom, and redacted operational import classes so the runtime
queue owner, generated bindings, and diagnostics move with the ABI.