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 @@ -7,6 +7,7 @@ in this file. The format follows [Keep a Changelog](https://keepachangelog.com/e

### Added

- **`capabilities.enumerate` — list the calling capsule's own held capability names.** Mirrors the Rust SDK's `capabilities::enumerate`. 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 array 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 (the `astrid:contracts` events bundle is unchanged).
- **`process` persistent-process tier — `spawnPersistent`, `PersistentProcess`, and `process.{attach, listProcesses, statusMany}`.** Mirrors the Rust SDK and the host `astrid:process@1.0.0` persistent tier: a background child that **outlives the pooled, stateless instance** that started it (unlike `BackgroundProcessHandle`, whose kernel resource is reaped on instance reset). `spawnPersistent(cmd, args, options)` takes the persistent knobs (`label`, `keepStdinOpen`, `overflow`, `logRingBytes`, `maxLifetimeMs`, `idleTimeoutMs`, `exitRetentionMs`, `limits`) and returns a `PersistentProcess` keyed by an opaque id. `PersistentProcess` exposes `status` / `readLogs` (drain) / `readSince` (non-draining cursor → byte-faithful `LogChunkResult`; start with `logCursorStart()`) / `writeStdin` / `closeStdin` / `signal` / `wait` (bounded) / `stop` (SIGTERM→grace→SIGKILL, frees the slot) / `release`. Persist `proc.id` (e.g. in KV) and `process.attach(id)` from a later invocation to reattach — `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.listProcesses` / `statusMany` enumerate the capsule+principal's persistent processes. New types: `PersistentProcessInfo`, `SpawnPersistentOptions`, `LogChunkResult`, `ResourceLimits`, `ProcessPhase`, `LogStream`, `LogCursor`, `OverflowPolicy`; `ProcessSignal` 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 (the `astrid:contracts` events bundle is unchanged).

## [0.1.0] - 2026-05-26
Expand Down
2 changes: 1 addition & 1 deletion contracts
39 changes: 30 additions & 9 deletions packages/astrid-sdk/src/capabilities.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
/**
* Cross-capsule capability queries. Mirrors `astrid_sdk::capabilities`.
* Capability introspection. Mirrors `astrid_sdk::capabilities`.
*
* 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, for example.
* {@link 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, for example. {@link 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.
*
* Fail-closed: returns `false` for unknown UUIDs, unknown capabilities, or
* registry-unavailable conditions (the host returns `registry-unavailable`,
* which `callHost` raises as a SysError — capsule code that wants to swallow
* registry outages should wrap the call).
* Fail-closed: `check` returns `false` for unknown UUIDs, unknown
* capabilities, or registry-unavailable conditions (the host returns
* `registry-unavailable`, which `callHost` raises as a SysError — capsule code
* that wants to swallow registry outages should wrap the call).
*/

import { checkCapsuleCapability } from "astrid:sys/host@1.0.0";
import { checkCapsuleCapability, enumerateCapabilities } from "astrid:sys/host@1.0.0";
import { callHost } from "./errors.js";

export function check(sourceUuid: string, capability: string): boolean {
Expand All @@ -20,3 +23,21 @@ export function check(sourceUuid: string, capability: string): boolean {
);
return resp.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
* {@link check} against this capsule's own UUID returns `true`.
*
* Argument-free (the kernel already knows the caller) and infallible: an empty
* array 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.
*/
export function enumerate(): string[] {
return callHost("capabilities.enumerate", () => enumerateCapabilities());
}
Comment thread
joshuajbouw marked this conversation as resolved.
1 change: 1 addition & 0 deletions packages/astrid-sdk/src/wit-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ declare module "astrid:sys/host@1.0.0" {
export function checkCapsuleCapability(
request: CapabilityCheckRequest,
): CapabilityCheckResponse;
export function enumerateCapabilities(): string[];
}

// ────────────────────────────────────────────────────────────────────
Expand Down