Skip to content

feat(capsule): add allow_persistent operator sub-grant gating spawn-persistent - #873

Merged
joshuajbouw merged 1 commit into
mainfrom
feat/allow-persistent-grant
Jun 8, 2026
Merged

feat(capsule): add allow_persistent operator sub-grant gating spawn-persistent#873
joshuajbouw merged 1 commit into
mainfrom
feat/allow-persistent-grant

Conversation

@joshuajbouw

@joshuajbouw joshuajbouw commented Jun 7, 2026

Copy link
Copy Markdown
Member

Linked Issue

Closes #872

Summary

astrid:process.spawn-persistent is documented ([email protected]) as gated on host_process and an operator allow_persistent sub-grant, but the host only checked host_process + an authenticated principal — the sub-grant did not exist, so the gate the contract promised was never enforced. This makes it real, with no WIT change.

Changes

  • Add CapabilitiesDef.allow_persistent (bool, #[serde(default)], fail-closed) — an operator-reviewed sub-grant of host_process (the "Airlock Override" precedent). host_process alone grants the ephemeral spawn / spawn-background tiers; a persistent child (outlives the pooled instance; on macOS lacks die-with-parent) additionally requires this opt-in.
  • Gate spawn-persistent: returns capability-denied (audited) when the capsule has host_process but not allow_persistent. Checked off the manifest-derived capability_names; ephemeral tiers unaffected.
  • enumerate-capabilities reports it automatically via the serde-derived held_names / has — no parallel list to drift.
  • CHANGELOG updated under [Unreleased].

No WIT change — the contract already specified this gate; this makes the host conform. Capsules that spawn persistent processes (e.g. astrid-capsule-shell) must add allow_persistent to their manifest [capabilities].

Test Plan

Automated

  • cargo test --workspace passes — incl. the four integration-test CapabilitiesDef literals updated for the new field (the original CI failure)
  • No new clippy warnings (clippy --all-targets clean)

Unit-tested: the manifest → capability_names link via the held_names / has serde-derivation tests (all-held, default-holds-nothing, unknown-name). A host-fn integration test for the denial path would need security-gate scaffolding (no existing pattern) — noted as a follow-up.

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 enforces a stricter security boundary for persistent process spawning within the capsule environment. By requiring an explicit 'allow_persistent' sub-grant in addition to the existing 'host_process' capability, the host now correctly conforms to the documented WIT contract, preventing unauthorized persistent background processes from outliving their parent instances.

Highlights

  • Capability Gating: Introduced a new 'allow_persistent' operator sub-grant that is now required alongside 'host_process' to enable persistent process spawning.
  • Security Hardening: Implemented a fail-closed check in the host process logic to ensure that persistent execution capabilities are explicitly granted by the operator.
  • Manifest Updates: Updated the 'CapabilitiesDef' struct to include the 'allow_persistent' field, ensuring it is correctly serialized and available for capability enumeration.
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.


A process that lives past its day, Needs permission to go on its way. With a gate now in place, It won't leave a trace, Unless the operator says 'okay'.

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 a new capability, allow_persistent, which acts as an operator sub-grant on top of host_process to allow capsules to spawn persistent background processes that outlive the instance. The capability is disabled by default (fail-closed) and is verified during process spawning, returning CapabilityDenied if missing. Tests and manifest structures have been updated accordingly to include this new capability. There are no review comments, and I have no feedback to provide.

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.

…ersistent

The astrid:process WIT documents spawn-persistent as gated on host_process AND
an operator allow_persistent sub-grant, but the host only checked host_process
+ an authenticated principal — the sub-grant did not exist. Adds it:

- CapabilitiesDef gains allow_persistent (bool, #[serde(default)] fail-closed),
  an operator-reviewed sub-grant of host_process. host_process alone grants
  ephemeral spawn/spawn-background; persistent exec (a child that outlives the
  pooled instance and, on macOS, lacks die-with-parent) additionally requires
  this opt-in. The serde-derived held_names/has surface it automatically, so
  enumerate-capabilities reports it.
- spawn-persistent now returns capability-denied (audited) when the capsule has
  host_process but not allow_persistent; the ephemeral tier is unaffected.

No WIT change — the contract already specified this gate; this makes the host
match it. Capsules that spawn persistent (e.g. capsule-shell) must add
allow_persistent to their manifest.

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 makes the host runtime conform to the astrid:[email protected] contract by introducing and enforcing an operator sub-grant (allow_persistent) required for astrid:process.spawn-persistent, in addition to the existing host_process capability.

Changes:

  • Add CapabilitiesDef.allow_persistent as a fail-closed (#[serde(default)]) operator sub-grant layered on top of host_process.
  • Gate spawn-persistent to return capability-denied (audited) unless allow_persistent is held.
  • Update capability introspection tests and test manifests to include the new field.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

File Description
crates/astrid-capsule/src/security/manifest_gate.rs Updates a test manifest literal to include the new fail-closed capability field.
crates/astrid-capsule/src/manifest/capabilities.rs Introduces allow_persistent and updates held_names/has tests for the new capability name.
crates/astrid-capsule/src/engine/wasm/host/process/mod.rs Enforces the new allow_persistent gate in the spawn_persistent host implementation with auditing.
crates/astrid-capsule/src/engine/mcp_tests.rs Updates a test manifest literal to include allow_persistent: false.

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

Comment thread crates/astrid-capsule/src/manifest/capabilities.rs
@joshuajbouw
joshuajbouw merged commit 1ffdd03 into main Jun 8, 2026
13 checks passed
@joshuajbouw
joshuajbouw deleted the feat/allow-persistent-grant branch June 8, 2026 10:56
@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.

spawn-persistent: implement the allow_persistent operator sub-grant

2 participants