Skip to content

feat(cli): grant installed distro capsules to the target principal#1196

Merged
joshuajbouw merged 9 commits into
mainfrom
feat/distro-grant-capsules
Jul 15, 2026
Merged

feat(cli): grant installed distro capsules to the target principal#1196
joshuajbouw merged 9 commits into
mainfrom
feat/distro-grant-capsules

Conversation

@joshuajbouw

@joshuajbouw joshuajbouw commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Linked Issue

Closes #1195

Summary

astrid init could install a distro into one principal's home without granting that principal access to invoke the installed capsules. The original implementation also used the process principal as both the authenticated operator and the provisioning target, which made non-default provisioning fail at the final agent.modify step.

This change adds an explicit, opt-in provisioning contract:

  • global --principal is the authenticated operator;
  • init-only --target-principal is the principal whose home, environment, lock, capsules, and grants are provisioned;
  • --grant-capsules grants exactly the transactionally verified installed set through the existing admin.agent.modify path.

Standalone runtime requires an explicitly selected distro. The optional CLI shape remains available for a later operator-enforced source supplied by an embedding layer. No principal name is treated as privileged.

Changes

  • Add init --target-principal <PRINCIPAL> while preserving the process principal as the operator. The target defaults to the process principal when omitted.
  • Ensure the neutral runtime daemon is ready, then use an empty-delta AgentModify request as a read-only authorization and target-existence preflight before target/workspace provisioning. This preserves the existing public enum, wire method, topic, capability, and audit shape.
  • Serialize distro provisioning with an owner-private, non-blocking lock per runtime home and target principal.
  • Add additive checked installer entry points that validate the expected capsule ID and optional declared version immediately after reading Capsule.toml, before target-directory creation, backup, or replacement.
  • Route GitHub release downloads, clone/build, local archives, local Cargo builds, unpacked directories, and signed .shuttle installation through the checked sink. Existing manual installer APIs and InstallOptions remain unchanged.
  • Require every distro capsule to select a concrete released semver version or non-empty tag; branch/revision selectors remain rejected.
  • Record the actual installed manifest/meta version, actual content-addressed WASM hash, and actually resolved ref in Distro.lock.
  • On fresh-lock recovery, validate typed capsule identity, manifest/meta version agreement, canonical blake3:<hex>, WASM/hash presence equivalence, blob existence, and the BLAKE3 hash of the real bin/<hash>.wasm bytes before issuing grants.
  • Reuse the existing idempotent apply_agent_modify helper for the final atomic grant. A no-flag run prints the exact operator-aware recovery command; a failed grant leaves the verified install and lock for an idempotent retry.
  • Preserve honest partial-install behavior: no lock, no grant, and a non-zero exit until every selected capsule installs. .shuttle installs are identity/version checked but still reject --grant-capsules until their grant composition is explicitly implemented.

Verification

  • cargo fmt --all -- --check
  • cargo metadata --locked --offline --no-deps --format-version 1
  • bash -n scripts/e2e/runtime-cli-smoke.sh
  • git diff --check
  • Exact head 34a3a02a7ad1ab266586c3dd548d6d2cac8f188e: all 22 GitHub checks passed, including Ubuntu and macOS tests, Clippy, MSRV, public API compatibility/diff, CodeQL, Gateway OpenAPI, and the real runtime CLI + HTTP sweep.
  • Repository file limits: all changed Rust source files remain below 1,000 lines; test files remain below 2,000 lines.
  • Added regressions for operator/target parsing, daemon/preflight failure ordering, empty-delta authorization, per-target lock contention and permissions, concrete release selectors, pre-mutation ID/version rejection with preservation of prior installs, actual lock provenance, fresh-lock manifest/meta validation, real WASM blob rehashing, non-WASM hash rejection, partial installs, and operator-aware recovery hints.

Focused Rust compile/test attempts on the local macOS host stopped during cold Wasmtime dependency compilation without reaching workspace code or emitting a Rust diagnostic; no local Rust pass is claimed. The exact pushed head passed the complete GitHub matrix.

Checklist

  • Linked to an issue
  • CHANGELOG.md updated under [Unreleased]
  • Public request enum and wire shape preserved
  • Commits are GPG-signed

Copilot AI review requested due to automatic review settings July 10, 2026 20:59
@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 addresses an issue where installing a distro via 'astrid init' placed capsule files but failed to grant the necessary permissions for non-default principals to invoke them. By introducing an opt-in '--grant-capsules' flag, users can now ensure that installed capsules are immediately usable. The changes include robust error handling, idempotent grant application, and clear guidance for manual configuration when the flag is not used, ensuring a smoother onboarding experience for non-default principals.

Highlights

  • New --grant-capsules flag: Added an opt-in --grant-capsules flag to 'astrid init' that automatically grants access to all installed distro capsules for the target principal.
  • Shared grant logic: Extracted 'apply_agent_modify' in 'commands/agent/mod.rs' to serve as a unified, idempotent kernel path for both 'agent modify' and the new 'init' grant functionality.
  • Improved discoverability and recovery: If the flag is omitted, the CLI now prints the exact 'astrid agent modify' command required to grant access, and provides clear recovery instructions if a grant fails during initialization.
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.


The capsules land upon the drive, But access rules were not alive. With one new flag, the path is clear, And tools now work for all to hear.

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 the --grant-capsules flag to astrid init, allowing users to automatically grant installed distro capsules to a target principal. To support this, the agent modify logic was refactored into a shared, idempotent helper function apply_agent_modify. The feedback focuses on performance improvements in init.rs to eliminate unnecessary allocations and clones, specifically by consuming collections via into_iter() and using std::fmt::Write to build commands directly.

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-cli/src/commands/init.rs Outdated
Comment thread crates/astrid-cli/src/commands/init.rs Outdated
Comment thread crates/astrid-cli/src/commands/init.rs Outdated

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 fixes a principal-provisioning gap in the CLI init flow by adding an opt-in way to grant capsule-access for the capsules installed by a distro, reusing the existing admin.agent.modify kernel path (shared with astrid agent modify).

Changes:

  • Add --grant-capsules to astrid init and thread it through dispatch into the init implementation.
  • Factor out a shared apply_agent_modify helper so both agent modify and init --grant-capsules provision capsule grants through the same idempotent kernel API.
  • Add unit tests for the new grant decision/formatting helpers and document the feature in CHANGELOG.md.

Reviewed changes

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

Show a summary per file
File Description
crates/astrid-cli/src/dispatch.rs Threads grant_capsules into InitOpts; explicitly disables grants for distro apply.
crates/astrid-cli/src/commands/init.rs Implements post-install grant/hint behavior and grant execution via admin.agent.modify.
crates/astrid-cli/src/commands/init_tests.rs Adds unit tests for grant validation/decision logic and command rendering.
crates/astrid-cli/src/commands/agent/mod.rs Extracts apply_agent_modify and returns a structured outcome used by both commands.
crates/astrid-cli/src/cli.rs Adds the --grant-capsules CLI flag.
CHANGELOG.md Documents the new astrid init --grant-capsules behavior.

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

Comment thread crates/astrid-cli/src/commands/init.rs Outdated
@joshuajbouw
joshuajbouw force-pushed the feat/distro-grant-capsules branch from 672bd08 to 5b40982 Compare July 10, 2026 21:04
astrid init installed a distro's capsules per-principal but attached no
capsule-access grants, so a non-default principal ended up with the
capsule files yet no authority to invoke their tools — provisioning it
required an out-of-band, hand-SYNC'd `agent modify --add-capsule` step.

Add an opt-in --grant-capsules flag (only meaningful with --distro) that,
after the distro's capsules install, grants access for exactly the
installed set through the same admin.agent.modify kernel path as
`agent modify --add-capsule`, now factored into a shared
apply_agent_modify helper. Idempotent across re-runs (kernel apply_set_delta
dedups; a failed first grant recovers on the fresh-lock re-run). No-op for
the default principal, which holds admin *. Without the flag a non-default
install prints the exact agent modify command; on grant failure the capsules
are already installed and init exits non-zero with the recovery command.

Closes #1195

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

Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

crates/astrid-cli/src/dispatch.rs:182

  • astrid init parsing allows --grant-capsules without --distro (see the CLI test asserting this), but the dispatch layer still hard-errors when distro is None. This makes the “operator-enforced distro” scenario described in the PR impossible at runtime, and leaves the CLI surface internally inconsistent (parse succeeds, dispatch fails). Please align either by (a) enforcing --distro at the clap layer and removing the parse-only test/docs, or (b) actually resolving an operator-enforced distro before calling run_init when the flag is omitted.
            let distro = distro.ok_or_else(|| {
                anyhow::anyhow!(
                    "astrid init requires --distro <name, @org/repo, path, or .shuttle>; Astrid Runtime does not choose a product distro"
                )
            })?;

Comment thread crates/astrid-cli/src/commands/init_grant.rs
Comment thread crates/astrid-cli/src/commands/init_grant.rs
Comment thread crates/astrid-cli/src/commands/init_grant.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

Copilot reviewed 23 out of 24 changed files in this pull request and generated 2 comments.

Comment thread crates/astrid-cli/src/cli.rs
Comment thread crates/astrid-cli/src/cli.rs Outdated

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

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

Comment thread crates/astrid-cli/src/commands/init.rs Outdated

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

Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.

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

Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.

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

Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.

@joshuajbouw
joshuajbouw merged commit cb2a988 into main Jul 15, 2026
29 checks passed
@joshuajbouw
joshuajbouw deleted the feat/distro-grant-capsules branch July 15, 2026 12:17
joshuajbouw added a commit that referenced this pull request Jul 15, 2026
## Linked Issue

Closes #1256

## Summary

Closes the remaining first-release bootstrap authority fallbacks without
activating the deferred RFC #36 persistence model or adding any
product-specific privilege path.

## Changes

- reject kernel and admin management requests whose IPC envelope has no
valid principal instead of inheriting the `default` administrator
- audit missing and malformed admin callers exactly once as reserved
no-capability `anonymous` before returning the uniform denial
- preserve the local CLI active-principal behavior before requests cross
the kernel boundary
- attribute principal-less connection lifecycle signals to `anonymous`
and ignore malformed identities
- abort kernel construction when default administrator profile/key
seeding or legacy-profile migration fails
- reject `AgentModify` results that remove `default` from the built-in
`admin` group while preserving idempotent remove-plus-add
- keep caller resolution in a focused private module so the management
router remains below the source-file cap
- retain the merged device-attenuation, pair-device input-validation,
issuance-preflight, cache-refresh, and terminal-audit behavior
- add live-router, lifecycle, bootstrap, persistence, administrative,
and pair-authority regression coverage

## Verification

- restacked onto `main` after #1196 and #1238 merged
- signed head: `916238ae54db4e1ea7409a4a82432cc5fe8603a4`
- `cargo fmt --all -- --check`
- `git diff --check origin/main...HEAD`
- `cargo test --locked -p astrid-kernel
admin_router_denies_missing_and_invalid_principals_deterministically --
--nocapture`
- `cargo clippy --locked -p astrid-kernel --all-features --tests -- -D
warnings`
- replacement GitHub CI is the complete integration and execution gate

## Checklist

- [x] Linked to an issue
- [x] CHANGELOG.md updated under `[Unreleased]`

No release is created or published by this change.
joshuajbouw added a commit that referenced this pull request Jul 15, 2026
## Linked Issue

Closes #1239

Refs #1237 and astrid-runtime/rfcs#36.

## Summary

Carries the authenticated device into capsule dispatch so execution and
inventory use the same live authority already enforced at the kernel
boundary. The change is generic to Astrid and introduces no
product-specific role or trust path.

## Changes

- resolve one principal/profile/group/device snapshot per stamped event
- reject disabled, malformed, unknown, and revoked identities before
matching or grant signalling
- use device-effective `capsule:list` only for global describe
visibility
- activate exact device-effective `capsule:access:any` for unrestricted
execution and candidate expansion
- preserve exact per-principal capsule grants, no-device/`Full`
behavior, and principal-less lifecycle dispatch
- emit one `GrantRequired` only for a valid in-view denied match
- preserve legacy dispatch when no access resolver is installed
- preserve merged authority-cache refresh, pairing preflight, audit
classification, and caller-boundary audit behavior
- require base `self:auth:pair` authority for pair issuance while
retaining the additional `self:auth:pair:admin` gate and elevated audit
classification for Full, preset-Full, and explicit-universal scopes
- add a dedicated device-scope dispatch regression suite

## Verification

- restacked onto `main` after #1196, #1238, and #1257 merged
- signed head: `57ec31340db1a1749d5f137a25f4352bb6cb401f`
- `cargo fmt --all -- --check`
- `git diff --check origin/main...HEAD`
- `cargo test --locked -p astrid-capsule` — 563 passed
- `cargo test --locked -p astrid-kernel
admin_cap_without_base_pair_capability_cannot_issue_full_token --
--nocapture`
- `cargo clippy --locked -p astrid-capsule -p astrid-kernel
--all-features --tests -- -D warnings`
- replacement GitHub CI is the complete integration and execution gate

## Checklist

- [x] Linked to an issue
- [x] CHANGELOG.md updated under `[Unreleased]`

No release is created or published by this change.
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.

Distro install carries no capsule grants; principal provisioning requires out-of-band agent modify

2 participants