Skip to content

feat: inject a neutral per-project workspace layout#1244

Merged
joshuajbouw merged 20 commits into
mainfrom
agent/neutral-workspace-layout
Jul 15, 2026
Merged

feat: inject a neutral per-project workspace layout#1244
joshuajbouw merged 20 commits into
mainfrom
agent/neutral-workspace-layout

Conversation

@joshuajbouw

@joshuajbouw joshuajbouw commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Linked Issue

Closes #1243

Summary

Introduces one validated neutral project workspace layout while preserving .astrid as standalone Astrid's exact default and leaving existing public Rust entry points compatible.

Changes

  • add a typed WorkspaceSelection and global CLI --workspace-state-dir / ASTRID_WORKSPACE_STATE_DIR selection
  • resolve one canonical workspace root and state directory, reject overlapping/redirected layouts, and bind atomic readiness metadata to the selection through a BLAKE3 fingerprint
  • propagate the selection through daemon children, config, capsule discovery/install/update/remove, hooks, WIT GC, kernel boot, gateway source verification, and capsule-loaded publication
  • validate the complete persistent workspace capsule/hook tree before use, rejecting symlinks, reparse redirects, special files, absolute local-component paths, and traversal
  • keep workspace capsules on their checked discovery branch rather than flattening them into generic extra paths
  • preserve default-layout wrappers for existing public APIs
  • check readiness metadata before and after every workspace-sensitive CLI handshake so a mismatched CLI cannot authenticate against another workspace and a concurrent daemon restart cannot silently retarget a client
  • reconnect long-lived MCP uplinks transactionally: construct and authenticate a checked replacement first, reject named-principal anonymous fallback, and swap only after both selection checks succeed
  • apply the workspace invariant to session, kernel-management, admin, invite-redeem, capsule live-load/unload, MCP, headless, bootstrap, status, doctor, ps, top, and who paths
  • resolve bootstrap's selected workspace once and use it consistently for daemon spawn, client handshake, and model configuration
  • keep daemon stop deliberately global as the recovery path for a daemon serving another project or layout
  • preserve stale/unreachable socket behavior for live-load and unload with an unauthenticated reachability probe while reachable selection mismatches fail closed
  • create the private secrets directory as part of normal home setup and self-heal an existing permissive Unix mode to 0700 without rewriting its contents
  • add workspace selection, descendant redirect, local component, handshake ordering, restart race, reconnect transaction, authentication, explicit bootstrap root, global recovery, stale socket, and private-mode regressions

The filesystem checks reject persistent redirects and special files. As with ordinary path-based filesystem APIs, a malicious process running as the same user could still race an individual path after validation; descriptor-relative traversal is outside this change.

Verification

  • every commit has a valid GPG signature
  • cargo metadata --locked
  • cargo fmt --all -- --check
  • git diff --check
  • cargo test --workspace --locked -- --quiet
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
  • direct connection and filesystem-consumer audits confirm workspace-sensitive clients and descendants remain behind the centralized checks
  • two independent exact-head adversarial reviews reported no correctness or security blocker

No release, tag, or publication is created by this pull request.

Checklist

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

@joshuajbouw
joshuajbouw requested a review from Copilot July 14, 2026 18:02
@joshuajbouw
joshuajbouw marked this pull request as ready for review July 14, 2026 18:06

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

Introduces a validated, typed WorkspaceLayout to support a neutral per-project runtime state directory (defaulting exactly to .astrid) and threads that selection through daemon boot, kernel, capsule lifecycle/discovery, hooks, gateway trust checks, WIT GC, and readiness metadata—while preserving existing public entry points via default-layout wrappers.

Changes:

  • Add astrid_core::dirs::WorkspaceLayout (with validation) plus a stable workspace-selection fingerprint used for daemon/CLI coherency.
  • Propagate workspace layout (and explicit workspace roots where needed) across kernel boot, discovery/install/remove/update paths, hooks discovery, gateway trusted source verification, and WIT GC.
  • Publish atomic readiness metadata bound to {workspace_root, layout} and reject CLI connections to daemons serving a different selection; document behavior in CHANGELOG.md.

Reviewed changes

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

Show a summary per file
File Description
crates/astrid-kernel/src/socket.rs Writes atomic readiness metadata including workspace selection fingerprint.
crates/astrid-kernel/src/lib.rs Stores and threads WorkspaceLayout through kernel boot and discovery/config wiring with default wrappers.
crates/astrid-kernel/src/kernel_router/mod.rs Uses injected workspace layout for capsule discovery in router inventory.
crates/astrid-kernel/src/kernel_router/install.rs Installs capsules with explicit workspace root/layout in blocking install paths.
crates/astrid-hooks/src/discovery.rs Discovers/saves workspace hooks under the selected state directory (with layout-aware helpers).
crates/astrid-gateway/src/routes/sessions.rs Routes session endpoints using workspace context to scope trusted capsule source verification.
crates/astrid-gateway/src/routes/models.rs Threads workspace context through registry round-trips for trusted source verification.
crates/astrid-gateway/src/routes/mod.rs Adds WorkspaceContext extension and builds routers with explicit workspace inputs.
crates/astrid-gateway/src/routes/capsule_sources.rs Computes trusted capsule source IDs using explicit workspace root/layout (no implicit .astrid).
crates/astrid-gateway/src/lib.rs Runs/builds gateway with explicit workspace root/layout and preserves defaults.
crates/astrid-daemon/src/lib.rs Reads layout selection, loads config with layout, boots kernel with layout, spawns gateway with explicit workspace inputs, writes readiness metadata.
crates/astrid-core/src/dirs.rs Defines WorkspaceLayout, validation rules, layout-aware workspace paths, and workspace selection fingerprinting.
crates/astrid-core/src/dirs_tests.rs Adds regressions for layout validation, fingerprint identity, detection, and accessors.
crates/astrid-config/src/show.rs Exposes config path reporting with explicit workspace layout.
crates/astrid-config/src/merge/types.rs Updates workspace layer labeling to match selectable workspace state directory.
crates/astrid-config/src/loader.rs Loads workspace config using injected layout (and tests to ensure no dual-scan).
crates/astrid-config/src/lib.rs Adds public config loaders accepting an explicit WorkspaceLayout.
crates/astrid-config/src/defaults.toml Updates documentation comment to reflect selectable workspace config.
crates/astrid-config/Cargo.toml Adds astrid-core dependency for WorkspaceLayout.
crates/astrid-cli/src/workspace_layout.rs Adds process-wide WorkspaceLayout storage (OnceLock) for CLI-wide access.
crates/astrid-cli/src/socket_client.rs Adds connect_for_workspace helper ensuring daemon workspace selection coherency.
crates/astrid-cli/src/main.rs Initializes workspace layout once from CLI parsing.
crates/astrid-cli/src/commands/wit.rs Scopes WIT GC mark collection to selected workspace root/layout (no dual-scan).
crates/astrid-cli/src/commands/mcp/watch.rs Connects MCP watch uplink via workspace-aware daemon validation.
crates/astrid-cli/src/commands/mcp/session_guard.rs Connects MCP guard uplink via workspace-aware daemon validation.
crates/astrid-cli/src/commands/mcp/mod.rs Connects MCP serve uplink via workspace-aware daemon validation.
crates/astrid-cli/src/commands/init.rs Creates workspace dir using injected layout rather than hard-coded .astrid.
crates/astrid-cli/src/commands/headless.rs Connects headless/snapshot flows using workspace-aware daemon validation.
crates/astrid-cli/src/commands/daemon.rs Propagates workspace layout to spawned daemons and validates readiness metadata matches selection.
crates/astrid-cli/src/commands/config.rs Loads/prints config using injected workspace layout.
crates/astrid-cli/src/commands/capsule/remove.rs Resolves removal targets using injected workspace layout.
crates/astrid-cli/src/commands/capsule/meta.rs Re-exports layout-aware capsule meta scanning helpers.
crates/astrid-cli/src/commands/capsule/live_load.rs Avoids live reload/unload against mismatched daemon workspace selection.
crates/astrid-cli/src/commands/capsule/list.rs Lists installed capsules using layout-aware scan.
crates/astrid-cli/src/commands/capsule/install.rs Installs/unpacks and resolves targets using injected workspace layout.
crates/astrid-cli/src/commands/capsule/install_update.rs Updates capsules without mixing user/workspace scopes and uses layout-aware scanning/target resolution.
crates/astrid-cli/src/commands/capsule/deps.rs Builds dependency view from layout-aware installed capsule scan.
crates/astrid-cli/src/commands/capsule_verb.rs Connects to daemon for capsule verb registry/execution via workspace-aware validation.
crates/astrid-cli/src/cli.rs Adds global --workspace-state-dir / ASTRID_WORKSPACE_STATE_DIR typed option and tests.
crates/astrid-cli/src/bootstrap.rs Loads config and connects to daemon using injected workspace layout for interactive flows.
crates/astrid-capsule/src/discovery.rs Discovers manifests in an explicit workspace root/layout (no implicit .astrid).
crates/astrid-capsule-install/src/wit.rs Updates docs to reflect workspace installs under selected project state dir.
crates/astrid-capsule-install/src/paths.rs Resolves install targets using explicit workspace root/layout; adds tests.
crates/astrid-capsule-install/src/meta.rs Scans installed capsules using explicit workspace root/layout; adds tests.
crates/astrid-capsule-install/src/manifest_check.rs Checks imports/exports using explicit workspace root/layout (no implicit scan).
crates/astrid-capsule-install/src/local.rs Installs from local path using explicit workspace root/layout and layout-aware checks/paths.
crates/astrid-capsule-install/src/lib.rs Re-exports new layout-aware install/discovery APIs.
crates/astrid-capsule-install/src/contracts.rs Updates comments to reflect “no project state” reads on daemon fail-closed path.
crates/astrid-capsule-install/src/archive.rs Unpacks/installs using explicit workspace root/layout and layout-aware local install.
CHANGELOG.md Documents new workspace layout selection and coherency behavior under [Unreleased].
Cargo.lock Records astrid-config’s new dependency on astrid-core.

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

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 52 out of 53 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 52 out of 53 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 52 out of 53 changed files in this pull request and generated 1 comment.

Comment thread crates/astrid-kernel/src/socket.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 52 out of 53 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 52 out of 53 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 52 out of 53 changed files in this pull request and generated 1 comment.

Comment thread crates/astrid-cli/src/socket_client.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 52 out of 53 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 57 out of 58 changed files in this pull request and generated 1 comment.

Comment thread crates/astrid-cli/src/bootstrap.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 58 out of 59 changed files in this pull request and generated no new comments.

joshuajbouw added a commit that referenced this pull request Jul 15, 2026
## Linked Issue

Closes #1241

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

## Summary

Replaces gateway-side audit authority inference with the kernel's live
principal/device capability evaluator. Historical and streaming audit
views now narrow through the same runtime policy, with no AOS-specific
role, key exposure or caller-selected identity.

## Changes

- add an additive borrowed `CapabilityCheck` constructor while
preserving the existing constructor and owned errors
- expose one doc-hidden boolean kernel evaluator for a principal,
optional device and capability
- inject the in-process evaluator through the gateway runner and an
additive embedded-router builder; the default router remains self-only
- evaluate historical audit authority after the asynchronous log read
and at every pagination record boundary
- re-evaluate `audit:read_all` for every live SSE event
- preserve self visibility after malformed, unknown, revoked, disabled,
group-revoked or scope-denied authority
- keep raw audit cursor positions stable while live principal narrowing
filters same-second batches
- bind new cursors to immutable audit-entry identity and effective
visibility scope so same-second appends cannot shift the page boundary
and widening or a principal change requires a restart
- reject unsafe scope changes before visible, hidden and cursor-skipped
rows instead of returning a partial page or false end-of-results
- accept legacy timestamp/raw-offset cursors only for the unambiguous
default self view
- suppress continuation cursors at true physical EOF while retaining a
scope-bound cursor when hidden rows make a later widening unsafe
- pin the gateway's live `audit:read_all` enforcement constant and
require it to resolve through capability-registry revision 1
- retain the pair-device terminal audit distinctions already present on
`main`
- add a race-free pre-bound listener entrypoint for the real HTTP SSE
regression
- expose the same live evaluator boundary to trusted in-process router
embeddings without exposing the internal probe type
- document the connect-info make-service requirement for both public
router builders so unauthenticated redeem throttling keeps the real peer
address, and make the default builder's self-only audit policy explicit
- document the additive runner/router migration path for co-located
callers that need audit firehose visibility
- accept hyphen-leading opaque invite tokens in both CLI redeem and
revoke paths so every base64url token the runtime issues remains usable

## Verification

- exact signed head: `bf3eb251164f1c20e47fb9f3e841677d9efecc02`
- all 20 commits above `d85fb10663c21d08fc43d39d939cfbb5a023cafd` have
good Joshua committer signatures
- `cargo fmt --all -- --check`
- `git diff --check`
- `cargo doc -p astrid-gateway --no-deps`
- `cargo test -p astrid-gateway` — 160 unit, 7 CORS, 10 models and 18
router tests pass
- `cargo test -p astrid-kernel runtime_policy_tests` — 8 tests pass
- focused audit pagination suite — 17 tests pass, including same-second
insertion, missing-anchor and pre-anchor authority-widening regressions
- focused registry enforcement assertion — 1 test passes
- `cargo clippy -p astrid-gateway -p astrid-kernel --all-targets -- -D
warnings`
- all 12 production-root CLI parser tests pass, including redeem and
revoke with a generated-token shape beginning in `-`
- `cargo clippy -p astrid --all-targets -- -D warnings`
- the replacement GitHub matrix is the execution gate for the combined
workspace

## Compatibility

The public HTTP shape, WIT contracts, persisted authority, gateway state
and existing runner entrypoints remain unchanged. The new kernel
evaluator is boolean and doc-hidden. The internal typed gateway probe
remains private; trusted in-process embedders receive an additive
generic callback builder with the same fail-closed policy boundary.

When #1244 is refreshed after this PR, its workspace-aware gateway
runner must retain this live capability probe across normal, TLS and
pre-bound listener paths.

## Checklist

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
@joshuajbouw
joshuajbouw force-pushed the agent/neutral-workspace-layout branch from 1c8ddd3 to 73a8ea0 Compare July 15, 2026 19:48
@joshuajbouw

Copy link
Copy Markdown
Contributor Author

@copilot review

Copilot AI commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@copilot review

Reviewed the current state of the PR. Full test suite passes (0 failures). Key areas checked:

WorkspaceLayout validationnew() correctly rejects empty names, ./.., trailing dots, separators, non-portable characters, Windows device names (CON, COM1COM9, LPT1LPT9), and multi-component paths. Length cap at 64.

WorkspaceSelection filesystem checksresolve() canonicalizes the project root and stores it; all subsequent descendant traversals re-verify against that stored canonical root via verify(). verify_tree uses symlink_metadata (no symlink follow) to detect the type, then canonicalize to confirm no redirect — this correctly catches symlinks, junctions, and hardlinks pointing to device nodes.

Readiness metadata — written atomically via temp-file rename with 0o600 on the file and 0o700 on the parent directory. The daemon writes checked_workspace_selection_fingerprint (the v2: hash domain, always with a real canonicalize) and the CLI reads and compares the same. The unchecked workspace_selection_fingerprint (lexical v1: fallback) is only exercised in unit tests.

Handshake orderingconnect_between_workspace_checks calls check before and after the connection, correctly guarding against a daemon restart mid-handshake. replace_between_workspace_checks validates the replacement client before swapping the held reference.

CapsuleVisibility — anonymous principals are denied at construction; capsule:list capability gates the admin view; non-admin callers see only their own principal's capsules via cloned_values_for.

Minor observation (no change required): the trailing self.resolve_directory(relative)? at the end of verify_tree after the tree walk is intentional — it serves as a post-walk "selection changed during walk" guard, consistent with the documented TOCTOU caveat.

No actionable issues found.

@joshuajbouw
joshuajbouw merged commit 1bdd188 into main Jul 15, 2026
23 checks passed
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.

Inject a neutral per-project workspace layout

3 participants