Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Changelog tracking starts with 0.2.0. Prior versions were not tracked.

### Added

- **`capabilities::enumerate` — list the calling capsule's own held capability names.** The list dual of `capabilities::check`: returns the capability categories declared in this capsule's `[capabilities]` manifest block (`host_process`, `net_connect`, `fs_read`, …) — the names, not the scoped arguments within them (allowlists, `host:port`, paths). Argument-free (the kernel already knows the caller) and infallible — an empty list is the valid "no capabilities" answer — so a reusable capsule can ground its behaviour in what it can actually do instead of hard-coding it, avoiding code-vs-manifest drift. Backed by unicity-astrid/wit#13's `astrid:sys/host.enumerate-capabilities`; contracts submodule bumped accordingly.
- **`process` persistent-process tier — `Command::spawn_persistent`, `PersistentProcess`, and `process::{attach, list, status_many}`.** Mirrors the host `astrid:process@1.0.0` persistent tier: a background child that **outlives the pooled, stateless instance** that started it (unlike `Process`, whose kernel resource is reaped on instance reset). `Command` gains the persistent-only builder knobs — `label`, `keep_stdin_open`, `overflow`, `log_ring_bytes`, `max_lifetime`, `idle_timeout`, `exit_retention`, `limits` — and a `spawn_persistent()` terminal returning a `PersistentProcess` keyed by an opaque `ProcessId`. `PersistentProcess` exposes `status` / `read_logs` (drain) / `read_since` (non-draining cursor, byte-faithful `LogChunk`) / `write_stdin` / `close_stdin` / `signal` / `wait` (bounded) / `stop` (consumes the handle; SIGTERM→grace→SIGKILL, frees the slot) / `release` (consumes). Persist the `ProcessId` (e.g. in KV) and `process::attach(id)` from a later invocation to reattach — the SDK `attach` is a thin id-wrapper, so it works without the host's deferred `attach` resource fn; the first id-keyed call validates ownership. `process::list` / `status_many` enumerate the capsule+principal's persistent processes. New types: `ProcessId`, `ProcessInfo`, `ProcessPhase`, `LogStream`, `LogCursor`, `LogChunk`, `OverflowPolicy`, `ResourceLimits`; `Signal` gains `Stop` / `Cont`. The host's `watch` / `unwatch` lifecycle-event channel and resource-limit enforcement are not yet wired (poll via `status` + bounded `wait`). Backed by unicity-astrid/wit#12; contracts submodule bumped to the merged `astrid:process@1.0.0` persistent tier.

## [0.7.0] - 2026-05-26
Expand Down
28 changes: 24 additions & 4 deletions astrid-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,11 +467,14 @@ pub mod runtime {
}
}

/// Cross-capsule capability queries.
/// Capability introspection.
///
/// Allows a capsule to check whether another capsule (identified by its
/// IPC session UUID) has a specific manifest capability. Used by the
/// prompt builder to enforce `allow_prompt_injection` gating.
/// [`check`] asks whether a capsule (self or any other, by IPC session UUID)
/// holds a specific manifest capability — used by the prompt builder to
/// enforce `allow_prompt_injection` gating. [`enumerate`] is the list dual for
/// the calling capsule's own set: the names for which a self-`check` returns
/// `true`. Capability posture is structural metadata, not a secret
/// (enforce-don't-conceal), so both are ungated.
Comment thread
joshuajbouw marked this conversation as resolved.
pub mod capabilities {
use super::*;

Expand All @@ -488,6 +491,23 @@ pub mod capabilities {
let response = wit_sys::check_capsule_capability(&request).map_err(host_err)?;
Ok(response.allowed)
}

/// Enumerate the calling capsule's own held capability names.
///
/// Returns the capability categories declared in this capsule's
/// `[capabilities]` manifest block (`host_process`, `net_connect`,
/// `fs_read`, …) — the names, not the scoped arguments within them
/// (allowlists, `host:port`, paths). This is exactly the set of names for
/// which [`check`] against this capsule's own UUID returns `true`.
///
/// Argument-free (the kernel already knows the caller) and infallible: the
/// kernel always knows the caller's own registered set, so there is no
/// error path — an empty list is the valid "no capabilities" answer. Lets
/// a reusable capsule ground its behaviour in what it can actually do
/// instead of hard-coding it, avoiding code-vs-manifest drift.
pub fn enumerate() -> Vec<String> {
wit_sys::enumerate_capabilities()
}
}

pub mod net;
Expand Down
33 changes: 31 additions & 2 deletions astrid-sys/wit-staging/deps/astrid-sys/sys@1.0.0.wit
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,39 @@ interface host {
/// Audit: not recorded (read-only, no side effects).
random-bytes: func(length: u64) -> result<list<u8>, error-code>;

/// Check whether a capsule has a specific manifest capability.
/// Check whether a capsule has a specific manifest capability — self or
/// any other capsule (by UUID). The per-name dual of
/// `enumerate-capabilities`.
///
/// Fail-closed: returns `allowed: false` for unknown UUIDs and
/// unknown capabilities. Returns `registry-unavailable` when the
/// registry itself can't be consulted.
/// registry itself can't be consulted. Ungated: capability posture is
/// structural metadata, not a secret (enforce-don't-conceal) — knowing a
/// capability conveys no ability to use it. Audit: not recorded.
check-capsule-capability: func(request: capability-check-request) -> result<capability-check-response, error-code>;

/// Enumerate the CALLING capsule's OWN held capability NAMES — the
/// categories the kernel enforces at host-call time (e.g. `host_process`,
/// `net_connect`, `fs_read`), NOT the scoped arguments within them (a
/// `host_process` allowlist, `host:port`, paths). It is the list dual of
/// `check-capsule-capability`: the set of names for which a self-`check`
/// returns true.
///
/// Argument-free — the kernel already knows the caller. The set is the
/// capsule's manifest-declared capabilities as the kernel registered them;
/// capsule capabilities are not revoked at runtime (the grant/revoke model
/// is principal-scoped, a separate axis), so "what I declared" and "what I
/// can do" coincide at the capsule level.
///
/// Ungated, like `check-capsule-capability`: a capsule grounds its
/// behaviour in what it can actually do — a reusable supervisor binary
/// deployed under different manifests reads its real set instead of
/// hard-coding it; any capsule avoids code-vs-manifest drift by grounding
/// on the registered set. Audit: not recorded (read-only self-
/// introspection). See RFC: host_abi ("Capability introspection").
///
/// Infallible: the kernel always knows the caller's own registered set, so
/// there is no failure mode (an empty list is the valid "no capabilities"
/// answer) — like `clock-ms` / `clock-monotonic-ns`, no `result` wrapper.
enumerate-capabilities: func() -> list<string>;
}
2 changes: 1 addition & 1 deletion contracts