Skip to content

feat(sessions): tighten daemon composer pending/ready routing - #602

Merged
evanspearman merged 2 commits into
mainfrom
evan/createsession-wire2
Jul 3, 2026
Merged

feat(sessions): tighten daemon composer pending/ready routing#602
evanspearman merged 2 commits into
mainfrom
evan/createsession-wire2

Conversation

@evanspearman

@evanspearman evanspearman commented Jun 30, 2026

Copy link
Copy Markdown
Member

Depends on #600

  • Wires up daemon-side Phase 2 composition routing: SessionComposer::compose now returns a ComposeOutcome::{Ready, Pending} instead of a finalized Composition, and the manager handler in minimald consumes the new shape.
  • Daemon-collected vars and patches route back to the client as a ContributionResponse so the client's policy and PolicyHooks gate them — the daemon never runs user policy. Daemon-collected packages and lifecycle hooks have no per-item verdict slot in the wire schema, so they pass through into the assembled Composition on the all-decided path.
  • Today every internal CreateSession caller still sends an empty WireContribution, so the manager hits the Ready fast path. The Pending branch is guarded with a defensive InvalidInput error in the manager until the SubmitVerdict handler lands (next step).
  • New PendingComposeState retains the daemon-side packages, hooks, and the client's wire contribution across a Pending outcome so Phase 4 can finalize after the client's verdict comes back.
  • Doc + test coverage: crates/sessions/docs/COMPOSITION.md describes the new 4-phase flow; tests cover the empty-input fast path, the packages/hooks-only fast path, the var → Pending routing, and that the client's wire contribution merges into the composition byte-for-byte on the all-decided path.

Summary by CodeRabbit

  • Bug Fixes
    • Improved session creation handling for client-supplied contributions, preventing submitted details from being silently ignored.
    • Clearer rejection of unsupported in-progress/pending session outcomes with an explicit input error.
    • Reduced session name collisions in repeated end-to-end runs by generating a unique session name each time.
  • Documentation
    • Updated session composition documentation to clarify phase responsibilities and the current on-wire behavior for session responses (with Pending not reachable yet).

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8e1ea8e-78f7-4e98-8f72-e819073fb9b7

📥 Commits

Reviewing files that changed from the base of the PR and between fd473df and f970142.

📒 Files selected for processing (6)
  • crates/minimald-rpc/src/lib.rs
  • crates/minimald/src/sessions.rs
  • crates/minvmd/tests/minimald_session_e2e.rs
  • crates/sessions/docs/COMPOSITION.md
  • crates/sessions/src/core/compose.rs
  • crates/sessions/src/daemon/composer.rs
✅ Files skipped from review due to trivial changes (2)
  • crates/minimald-rpc/src/lib.rs
  • crates/sessions/docs/COMPOSITION.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/minvmd/tests/minimald_session_e2e.rs
  • crates/minimald/src/sessions.rs
  • crates/sessions/src/daemon/composer.rs
  • crates/sessions/src/core/compose.rs

📝 Walkthrough

Walkthrough

This PR reworks Phase 2 session composition so SessionComposer::compose returns ComposeOutcome::{Ready, Pending}. It adds wire-pending conversion helpers, updates the minimald manager to reject pending or non-empty ready compositions, and refreshes docs and the e2e session name.

Changes

Compose Outcome Routing

Layer / File(s) Summary
Wire pending conversion helpers
crates/sessions/src/core/compose.rs
Adds Contribution::is_empty, Composition::from_daemon_passthrough, WirePending, and contribution_to_pending for daemon-to-wire pending conversion.
Daemon composer routing
crates/sessions/src/daemon/composer.rs
SessionComposer::compose now returns ComposeOutcome::{Ready, Pending} and retains daemon state for pending results; tests cover both branches.
Manager CreateSession handling and docs
crates/minimald/src/sessions.rs, crates/minimald-rpc/src/lib.rs, crates/sessions/docs/COMPOSITION.md, crates/minvmd/tests/minimald_session_e2e.rs
The manager consumes ComposeOutcome, rejects pending or non-empty ready compositions, persists successful sessions, and returns CreateSessionResponse::Ready; docs and the e2e session name are updated.

Estimated code review effort: 4 (Complex) | ~50 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SessionComposer
  participant Manager
  participant SessionStore

  Manager->>SessionComposer: compose(session_id, options)
  SessionComposer->>SessionComposer: inspect vars and patches
  alt all-decided
    SessionComposer-->>Manager: ComposeOutcome::Ready(Composition)
    Manager->>SessionStore: persist Active session record
    Manager-->>Manager: return CreateSessionResponse::Ready { id }
  else pending
    SessionComposer-->>Manager: ComposeOutcome::Pending { response, state }
    Manager-->>Manager: reject as InvalidInput
  end
Loading

Possibly related PRs

  • gominimal/minimal#381: Also changes the CreateSession control path and the ManagerHandle::create_session return handling.
  • gominimal/minimal#443: Adds the SubmitVerdict-style RPCs that would consume the pending path described here.
  • gominimal/minimal#568: Implements client-side handling for the daemon ContributionResponse pending payload introduced by this PR.

Suggested labels: needs-spec

Suggested reviewers: norrietaylor, twitchyliquid64

Poem

A rabbit hops through vars and patches bright,
Pending tucked away, Ready takes flight 🐇
Composer turns, the session path is clear,
Docs and tests now echo what appears.
Hop, hop — the wire is in view!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: daemon composer routing now distinguishes Pending and Ready outcomes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
crates/minimald/src/rpc.rs (1)

628-639: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Assert the SSH-derived username is persisted.

CreateSession now intentionally takes username from the SSH connection context rather than the request. Add an assertion here so this RPC path catches regressions that store None or accidentally reintroduce client-supplied identity.

Suggested assertion
         assert_eq!(
             get_session.record.as_ref().unwrap().project_path,
             HostAbsPath::try_new("/uwu").unwrap()
         );
+        assert_eq!(
+            get_session.record.as_ref().unwrap().username,
+            Some("test".to_string())
+        );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/rpc.rs` around lines 628 - 639, The GetSessionRecord
assertion block in the CreateSession RPC test should also verify that the
SSH-derived username is persisted. Update this test around the existing
get_session.record checks to assert the record’s username matches the SSH
connection context value, so the CreateSession path continues to use
server-derived identity rather than client-supplied data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minimald-rpc/src/lib.rs`:
- Around line 227-231: Update the documentation for the `Pending` variant in
`minimald-rpc::lib` so it reflects the current state of daemon-side Phase 2
routing instead of describing it as purely reserved for a future milestone.
Reword the comment near the `Ready`/`Pending` enum docs to mention that
`Pending` is now part of the Phase 2 flow and that the remaining missing piece
is the manager/`SubmitVerdict` resume path. Keep the wire-shape compatibility
note, but make sure callers understand `Pending` is no longer theoretical.

In `@crates/minimald/src/sessions.rs`:
- Around line 83-86: Refresh the comment on the contribution field in
SessionComposer-related session state so it matches the current behavior: the
manager now passes non-empty WireContribution through this boundary instead of
always rejecting it. Update the documentation around contribution to reflect the
actual decision point—either Ready Composition is applied or non-empty input is
rejected—so the comment no longer says the handler only accepts the default and
defers everything to a future phase.
- Around line 293-346: The CreateSession flow in sessions.rs is discarding the
Ready result from SessionComposer::compose, so any merged WireContribution is
lost while only the out-of-band sessions::Record is persisted. Update the Ready
branch in the CreateSession handler to either apply/plumb the returned
composition into the session persistence path before calling self.store.create,
or reject non-empty compositions until that support exists, using
SessionComposer::new, compose, ComposeOutcome::Ready, and sessions::Record as
the key locations.

In `@crates/minvmd/examples/exec.rs`:
- Line 30: The CreateSession response handling in exec.rs is decoding the wrong
type, so it will miss daemon error messages when the response is an error
wrapper. Update the CreateSession response decode path to use
Errorable<CreateSessionResponse> first, then match on Ready only after
unwrapping the wrapper, following the same pattern used by the e2e helper and
the CreateSession handling around the minimald_rpc types.

In `@crates/minvmd/tests/minimald_session_e2e.rs`:
- Around line 228-236: The CreateSession retry in minimald_session_e2e is
reusing the hardcoded session name, which causes later attempts to collide with
the first created session instead of retrying the flaky exec path. Update the
session name generation in the CreateSession setup so each retry attempt gets a
unique value, following the same per-invocation suffix approach used by exec.rs,
and keep the change localized to the SessionConfig name field in
minimald_session_e2e.

In `@crates/sessions/docs/COMPOSITION.md`:
- Around line 135-144: Update the Response shape paragraph in COMPOSITION.md to
match the current behavior: `SessionComposer::compose` already returns
`ComposeOutcome::{Ready, Pending}`, so remove the stale note that `Pending` is
only future wire-defined daemon-side Phase 2 routing. Instead, describe that
`Pending` is currently produced by composition but rejected by the manager with
`InvalidInput`, while `Ready` remains the only accepted path today.
- Around line 92-100: The COMPOSITION docs currently describe packages as being
included in the pending wire response, but `SessionComposer::compose` only sends
vars, patches, and lifecycle hooks in `ContributionResponse` while packages
remain in `PendingComposeState`. Update the pending path wording to match the
actual flow, and make sure references to `ContributionResponse`,
`PendingComposeState`, and `SessionComposer::compose` reflect that packages stay
daemon-side and are not part of the response payload.

---

Nitpick comments:
In `@crates/minimald/src/rpc.rs`:
- Around line 628-639: The GetSessionRecord assertion block in the CreateSession
RPC test should also verify that the SSH-derived username is persisted. Update
this test around the existing get_session.record checks to assert the record’s
username matches the SSH connection context value, so the CreateSession path
continues to use server-derived identity rather than client-supplied data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8454e329-f109-47f7-90c5-191237c1fe5e

📥 Commits

Reviewing files that changed from the base of the PR and between d15539a and 263e5ad.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • crates/minimal2/src/main.rs
  • crates/minimald-rpc/Cargo.toml
  • crates/minimald-rpc/src/lib.rs
  • crates/minimald/src/exec.rs
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/sessions.rs
  • crates/minimald/src/sftp.rs
  • crates/minimald/src/test_harness.rs
  • crates/minvmd/examples/exec.rs
  • crates/minvmd/tests/minimald_session_e2e.rs
  • crates/sessions/docs/COMPOSITION.md
  • crates/sessions/src/core/compose.rs
  • crates/sessions/src/daemon/composer.rs
  • crates/sessions/src/lib.rs

Comment thread crates/minimald-rpc/src/lib.rs Outdated
Comment thread crates/minimald/src/sessions.rs Outdated
Comment thread crates/minimald/src/sessions.rs
Comment thread crates/minvmd/examples/exec.rs
Comment thread crates/minvmd/tests/minimald_session_e2e.rs
Comment thread crates/sessions/docs/COMPOSITION.md Outdated
Comment thread crates/sessions/docs/COMPOSITION.md Outdated
@twitchyliquid64

Copy link
Copy Markdown
Member

minimal/minimal2 crates were moved, but you can get a clanker to rebase with this prompt:

If you git fetch origin/main and merge it into the current branch, theres a conflict because
i renamed crates/minimal to crates/mip and crates/minimal2 to crates/minimal.

Can you do the fetch + rebase to fix the conflict?

@evanspearman
evanspearman force-pushed the evan/createsession-wire2 branch from 4865519 to fd473df Compare July 2, 2026 15:30
@evanspearman

Copy link
Copy Markdown
Member Author

@twitchyliquid64 Thanks, it's rebased now

// wired in. The defensive guard below catches
// it anyway.
let composer = SessionComposer::new(contribution);
match composer.compose(SessionId::nil(), ComposeOptions::default()) {

@twitchyliquid64 twitchyliquid64 Jul 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm, thoughts on the contribution being passed through to the Session actor and keeping Sessions mostly as a dumb router? We would need to let the Session handle storage of the composition, implying its separate to the session record itself, but that seems doable (we already let the Session actor manage the dirs for the working tree + session "homedir")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh I guess we manage the state of the composition in the session record.. hmmmm

@evanspearman
evanspearman force-pushed the evan/createsession-wire2 branch from fd473df to f970142 Compare July 3, 2026 17:17
@evanspearman
evanspearman merged commit bc78fb7 into main Jul 3, 2026
22 checks passed
@evanspearman
evanspearman deleted the evan/createsession-wire2 branch July 3, 2026 18:32
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.

2 participants