Skip to content

feat(sys): implement enumerate-capabilities, de-stub capability check#869

Merged
joshuajbouw merged 1 commit into
mainfrom
feat/sys-enumerate-capabilities
Jun 7, 2026
Merged

feat(sys): implement enumerate-capabilities, de-stub capability check#869
joshuajbouw merged 1 commit into
mainfrom
feat/sys-enumerate-capabilities

Conversation

@joshuajbouw

@joshuajbouw joshuajbouw commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Linked Issue

Closes #868

Summary

Implements astrid:sys/host.enumerate-capabilities (the infallible () -> list<string> added by astrid-runtime/wit#13, merged) and completes the previously-stubbed check-capsule-capability onto the same capability namespace, so a capsule can introspect its own — or any capsule's — manifest capability posture. Motivation is Claude-on-Astrid: a reusable supervisor binary deployed under different manifests can ground its behaviour in what it can actually do instead of hard-coding it.

Changes

  • enumerate_capabilities() returns the calling capsule's held capability NAMES — the categories declared in its [capabilities] manifest block (host_process, net_connect, fs_read, …), not the scoped arguments within them. Infallible per the WIT (a bare list<string>, no result): it reads an owned, lock-free snapshot taken once at load (CapabilitiesDef::held_names) stored on HostState, never the capsule_registry — so there is no registry-unavailable failure mode, and an empty list is the valid "no capabilities" answer. Capsule capabilities are fixed at load (grant/revoke is principal-scoped, a separate axis), so the snapshot is correct for the capsule's lifetime and across the pool.
  • check_capsule_capability de-stubbed: it answered only allow_prompt_injection and returned false for everything else. Both host fns are now derived from CapabilitiesDef's serialized fields rather than a hand-maintained list — held_names() (the list) and has(name) (the per-name dual) read the same struct, so a capability added to it flows through both automatically and the two cannot drift (n ∈ held_names() iff has(n)); unknown names fail closed.
  • Bumps the wit submodule to the merged astrid:[email protected] enumerate commit (9742f80) and restages wit-staging.
  • Lifecycle-hook and astrid-hooks host states (which run outside the capsule manifest/security-gate lifecycle) report an empty set, fail-closed.

Both host fns are ungated, read-only, and audit-not-recorded per the WIT: capability posture is structural metadata, not a secret (enforce-don't-conceal).

Test Plan

Automated

  • cargo test --workspace passes (365 in astrid-capsule incl. the new tests; full workspace green — the gateway_tls integration tests only fail under the dev sandbox, which blocks the ephemeral TCP bind)
  • No new clippy warnings (cargo clippy -p astrid-capsule -p astrid-hooks --all-targets clean)

New coverage: CapabilitiesDef unit tests assert held_names/has agree across the full namespace, that held_names covers every serialized field (derivation guard against a future hand-picked subset), the default holds nothing, and unknown names fail closed; a host-fn test asserts enumerate-capabilities returns the load-time snapshot and the empty default.

Checklist

  • Linked to an issue
  • CHANGELOG.md updated under [Unreleased]

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new capability introspection mechanism for capsules, enabling them to query their own manifest-declared capabilities at runtime. By centralizing capability logic into CapabilitiesDef and exposing it through both a new enumeration host function and an updated capability check function, the system ensures consistent security posture reporting. These changes are designed to be infallible and performant by utilizing a lock-free snapshot taken at capsule load time.

Highlights

  • New Capability Introspection API: Implemented astrid:sys/host.enumerate-capabilities to allow capsules to programmatically query their own declared capabilities.
  • Capability Check Refactoring: Updated check-capsule-capability to use the new CapabilitiesDef::has logic, ensuring consistency between the enumeration list and capability checks.
  • Infrastructure Updates: Updated the wit submodule to version 9742f80 and added necessary state management to HostState to track capability snapshots at load time.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


Capabilities defined at the start, Each capsule knows its own heart. With a list now in hand, It obeys the command, And keeps all the logic apart.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces capability introspection for capsules by adding the enumerate-capabilities function to the host interface. It precomputes and stores the capsule's held capability names at load time within HostState and implements held_names and has helper methods on CapabilitiesDef to query these capabilities. Feedback suggests optimizing held_names to return Vec<&str> instead of Vec<String> to avoid unnecessary allocations of static string literals.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/astrid-capsule/src/manifest/capabilities.rs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements capability introspection in the astrid:sys/[email protected] ABI by adding an infallible enumerate-capabilities() -> list<string> host function and completing check-capsule-capability to consult the full manifest capability namespace (instead of a stubbed single-capability check). It fits into the kernel/wasm host layer by exposing read-only, fail-closed capability posture as structural metadata derived from the capsule’s manifest.

Changes:

  • Add CapabilitiesDef::held_names() and CapabilitiesDef::has(name) as the single source of truth for capability introspection.
  • Plumb a load-time snapshot of capability names into HostState and implement enumerate_capabilities() using that snapshot.
  • Update the staged astrid:sys/[email protected] WIT to document and include enumerate-capabilities, and add unit tests covering the new behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
crates/astrid-hooks/src/handler/wasm.rs Initializes hooks’ HostState with an empty capability_names set (fail-closed) since hooks run outside the manifest lifecycle.
crates/astrid-capsule/wit-staging/deps/astrid-sys/[email protected] Adds/updates WIT surface + documentation for enumerate-capabilities and aligns check-capsule-capability docs with the intended namespace.
crates/astrid-capsule/src/manifest/capabilities.rs Implements held_names() and has() on CapabilitiesDef and adds unit tests ensuring consistency + fail-closed semantics.
crates/astrid-capsule/src/engine/wasm/test_fixtures.rs Updates minimal_host_state fixture to include capability_names.
crates/astrid-capsule/src/engine/wasm/mod.rs Captures capability name snapshot at load and stores it in each pooled HostState; sets empty snapshot for lifecycle hooks.
crates/astrid-capsule/src/engine/wasm/host/sys.rs Completes check_capsule_capability using CapabilitiesDef::has and implements enumerate_capabilities; adds a host-fn unit test.
crates/astrid-capsule/src/engine/wasm/host_state.rs Adds capability_names to HostState with documentation explaining why it’s an owned load-time snapshot.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…heck

Bumps the wit submodule to pick up astrid:[email protected]'s new infallible
enumerate-capabilities() -> list<string>, and implements it on the kernel
side. The host fn returns the CALLING capsule's own held capability NAMES —
the categories declared in its [capabilities] manifest block (host_process,
net_connect, fs_read, …), not the scoped arguments within them.

Because the WIT is infallible (a bare list<string>, no result), it reads an
owned, lock-free snapshot taken once at load and stored on HostState rather
than the capsule_registry — there is no registry-unavailable failure mode to
surface, and an empty list is the valid no-capabilities answer. Capsule
capabilities are fixed at load (the grant/revoke model is principal-scoped, a
separate axis), so the snapshot is correct for the capsule's whole lifetime
and across the pooled instances.

check-capsule-capability is de-stubbed off the same namespace: it previously
answered only allow_prompt_injection and returned false for every other
capability. Both host fns now consult CapabilitiesDef, kept in lockstep by a
single source of truth — held_names() (the list) and has(name) (the per-name
dual), where n appears in held_names() iff has(n) is true. Unknown names fail
closed.

Both are ungated, read-only, and audit-not-recorded per the WIT contract:
capability posture is structural metadata, not a secret (enforce-don't-
conceal) — knowing a capability conveys no ability to use it.
@joshuajbouw joshuajbouw force-pushed the feat/sys-enumerate-capabilities branch from 51f49fc to 0f8d27e Compare June 7, 2026 00:02
@joshuajbouw joshuajbouw merged commit 177f838 into main Jun 7, 2026
18 checks passed
@joshuajbouw joshuajbouw deleted the feat/sys-enumerate-capabilities branch June 7, 2026 00:48
@joshuajbouw joshuajbouw mentioned this pull request Jun 10, 2026
joshuajbouw added a commit that referenced this pull request Jun 10, 2026
## Linked Issue

Closes #853

## Summary

`chore: release` — bumps all workspace crates **0.7.0 → 0.8.0** and
rolls `CHANGELOG [Unreleased]` into `[0.8.0] - 2026-06-10`. Consolidates
the ~50 PRs landed since v0.7.0:

- **HTTP admin gateway** (#756 + follow-ups) — `astrid-gateway`:
principals/caps/quotas/groups/invites/env over HTTP, audit SSE +
historical queries, agent-prompt SSE, OpenAPI emission, bus-direct admin
path (285× throughput), rustls TLS, CORS, metrics, invite + keypair CLI
verbs.
- **Runtime concurrency overhaul** (#813/#816/#819/#820) — routed
per-(capsule, topic, principal) IPC with DRR fairness, async Wasmtime,
dynamic instance pools, split blocking/IO semaphores, per-principal fuel
+ memory ledgers with enforcement and usage reporting.
- **Host process + introspection surface** — `astrid:process` persistent
tier (#866/#867/#871/#873), capability introspection (#868/#869),
`astrid mcp serve` (#879/#880), `astrid-emit` (#814/#842).
- **Security** — macOS 15+ sandbox no longer silently disabled
(#855/#857), audit-feed principal scoping (#850/#851), failed-redeem
audit rows (#846), `self:agent:list` roster leak (#848), pair-device
redeem rate-limit (#844), bearer revocation + wire format v2 (#772).

Breaking: `Capsule.toml` `[publish]`/`[subscribe]`-only IPC surface
(#858/#864/#865, `[[interceptor]]` / `ipc_*` arrays / `[[topic]]`
removed), bearer v2, MSRV 1.95, `astrid-openclaw` removed (#833).

## Changes

- `[workspace.package].version` `0.7.0` → `0.8.0` + the 23 in-workspace
path-dependency pins (now including `astrid-emit`; `axum`/`axum-server`
stay on their external 0.7).
- CHANGELOG: `[Unreleased]` → `[0.8.0] - 2026-06-10` with a release
**synopsis** (extracted into the GitHub release body by release.yml on
tag). Keeps the earlier roll's curation (canonical section order,
bullets verbatim) and merges the 13 entries added to `[Unreleased]` by
the 12 PRs that landed after the branch was first cut (#851#885) —
verified both directions that no bullet was lost.

Branch history note: rebuilt on current `main` (was `CONFLICTING` and 12
commits behind); previous tip was `54c7294`.

## Release steps (maintainer)

1. Merge this PR.
2. Tag `v0.8.0` on the merge commit → release.yml creates the GitHub
release from the CHANGELOG block.
3. Publish/distribute the new `astrid` binaries as usual.

## Test Plan

Release PR — no code changes. `cargo check --workspace` passes on the
rebuilt branch; full test/clippy/MSRV matrix runs in CI. The changelog
merge was verified bullet-by-bullet in both directions: every
`[Unreleased]` bullet on `main` appears in the rolled `[0.8.0]` section,
and every bullet from the earlier curated roll is preserved verbatim.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(sys): implement enumerate-capabilities + complete check-capsule-capability

2 participants