Skip to content

v0.9.2

Choose a tag to compare

@github-actions github-actions released this 03 Jul 04:22
v0.9.2
15d2d76

Fixed

  • First-run consent no longer storms a GrantRequired for every ungranted capsule in the caller's view. The dispatch access gate ran the grant-on-use check — and emitted a GrantRequired signal — for every ungranted capsule in the caller's view before checking whether the capsule's subscription matched the dispatched topic. A single tools/call on a fresh principal therefore fired a grant prompt for every ungranted view capsule, including all the capsules the call never touched, making first-run consent effectively unconvergeable. The gate now evaluates the cheap, manifest-local interceptor topic-match first (using the same crate::topic::topic_matches the delivery push uses) and engages the per-principal access gate only for a capsule that actually provides an interceptor for the requested topic; a non-matching capsule never reaches the gate. A matching ungranted capsule still fail-closed drops and signals exactly once, and behaviour for granted capsules, interceptor ordering, and non-tool topics is unchanged. Closes #1113.
  • The MCP shim now resolves every grant a single tools/call needs, instead of only the first. The shim resolved exactly one grant_required per call — it elicited consent, re-sent the call once, and passed the re-send's reply straight to the result reshaper — so when the re-sent call tripped the access gate on a different ungranted capsule, that second grant_required reply was returned to the client as the tool result (empty content, isError: false): a phantom empty success, while the broker kept a pending marker for a prompt that never surfaced. The shim now loops resolve→re-send→re-classify until the reply is grant-free or a bound (MAX_GRANT_RESOLUTIONS = 8, a distro's worth) trips; a present grant signal is never classified as terminal, so the only exits are a grant-free reply or an honest isError (malformed / denied / bound exceeded) — never a fabricated empty success. Ingress, approval-elicitation, grant dedup/marker, and fail-secure deny paths are preserved. Closes #1117.
  • astrid mcp serve now actually pushes notifications/tools/list_changed when a capsule is installed/loaded/unloaded during a live session, so newly hot-loaded tools light up without a session restart. The hot-reload watcher advertised tools.listChanged but never delivered a notification: (1) it seeded its baseline on a separate, short-lived uplink and then only ever read, so its own watch uplink was never bound to a principal — and the cli-proxy delivers a principal-stamped astrid.v1.capsules_loaded broadcast only to bound uplinks, so the watcher was silently starved of every reload signal; and (2) on a reload it re-enumerated the tool set over a fresh tools/list round trip that timed out precisely when a reload was in flight (the broker busy with its own describe fan-out), swallowing the push. The watcher now seeds its baseline on the watch uplink itself (that first request binds the connection) and reads the new tool surface directly from the capsules_loaded payload (capsules[].meta.tools[].name, principal-filtered) — the signal's documented intended contract — so the notification fires ~2 s after an install with no dependency on a follow-up request. Refs #1118.

Install

From source (requires Rust 1.95+):

cargo install astrid

Pre-built binaries:
Download the archive for your platform, extract, and add to PATH:

tar xzf astrid-*-$(uname -m)-*.tar.gz
sudo mv astrid-*/astrid astrid-*/astrid-daemon astrid-*/astrid-build astrid-*/astrid-emit /usr/local/bin/

Then run astrid init to set up capsules.


With many thanks from the following Astrinauts 🚀

  • Joshua J. Bouw