mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-27 07:45:08 +08:00
Prevent cross-worktree session bleed during managed session resume/load
ROADMAP #41 was still leaving a phantom-completion class open: managed sessions could be resumed from the wrong workspace, and the CLI/runtime paths were split between partially isolated storage and older helper flows. This squashes the verified team work into one deliverable that routes managed session operations through the per-worktree SessionStore, rejects workspace mismatches explicitly, extends lane-event taxonomy for workspace mismatch reporting, and updates the affected CLI regression fixtures/docs so the new contract is enforced without losing same- workspace legacy coverage. Constraint: Keep same-workspace legacy flat sessions readable while blocking cross-worktree misuse Constraint: No new dependencies; stay within the ROADMAP #41 changed-file scope Rejected: Leave team auto-checkpoint history as final branch state | noisy/non-lore history for a single roadmap fix Confidence: high Scope-risk: moderate Reversibility: clean Directive: Preserve workspace_root validation on future resume/load helpers; do not reintroduce path-only fallback without equivalent mismatch checks Tested: cargo test -p runtime session_control -- --nocapture; cargo test -p rusty-claude-cli resume -- --nocapture; cargo test -p rusty-claude-cli --test cli_flags_and_config_defaults; cargo test -p rusty-claude-cli --test output_format_contract; cargo test -p rusty-claude-cli --test resume_slash_commands; cargo test --workspace --exclude compat-harness; cargo check --workspace --all-targets; git diff --check Not-tested: cargo clippy --workspace --all-targets -- -D warnings (pre-existing failures in unchanged rust/crates/rusty-claude-cli/build.rs) Related: ROADMAP #41
This commit is contained in:
@@ -36,6 +36,8 @@ pub enum LaneEventName {
|
||||
Closed,
|
||||
#[serde(rename = "branch.stale_against_main")]
|
||||
BranchStaleAgainstMain,
|
||||
#[serde(rename = "branch.workspace_mismatch")]
|
||||
BranchWorkspaceMismatch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -67,6 +69,7 @@ pub enum LaneFailureClass {
|
||||
McpHandshake,
|
||||
GatewayRouting,
|
||||
ToolRuntime,
|
||||
WorkspaceMismatch,
|
||||
Infra,
|
||||
}
|
||||
|
||||
@@ -277,6 +280,10 @@ mod tests {
|
||||
LaneEventName::BranchStaleAgainstMain,
|
||||
"branch.stale_against_main",
|
||||
),
|
||||
(
|
||||
LaneEventName::BranchWorkspaceMismatch,
|
||||
"branch.workspace_mismatch",
|
||||
),
|
||||
];
|
||||
|
||||
for (event, expected) in cases {
|
||||
@@ -300,6 +307,7 @@ mod tests {
|
||||
(LaneFailureClass::McpHandshake, "mcp_handshake"),
|
||||
(LaneFailureClass::GatewayRouting, "gateway_routing"),
|
||||
(LaneFailureClass::ToolRuntime, "tool_runtime"),
|
||||
(LaneFailureClass::WorkspaceMismatch, "workspace_mismatch"),
|
||||
(LaneFailureClass::Infra, "infra"),
|
||||
];
|
||||
|
||||
@@ -329,6 +337,38 @@ mod tests {
|
||||
assert_eq!(failed.detail.as_deref(), Some("broken server"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_mismatch_failure_class_round_trips_in_branch_event_payloads() {
|
||||
let mismatch = LaneEvent::new(
|
||||
LaneEventName::BranchWorkspaceMismatch,
|
||||
LaneEventStatus::Blocked,
|
||||
"2026-04-04T00:00:02Z",
|
||||
)
|
||||
.with_failure_class(LaneFailureClass::WorkspaceMismatch)
|
||||
.with_detail("session belongs to /tmp/repo-a but current workspace is /tmp/repo-b")
|
||||
.with_data(json!({
|
||||
"expectedWorkspaceRoot": "/tmp/repo-a",
|
||||
"actualWorkspaceRoot": "/tmp/repo-b",
|
||||
"sessionId": "sess-123",
|
||||
}));
|
||||
|
||||
let mismatch_json = serde_json::to_value(&mismatch).expect("lane event should serialize");
|
||||
assert_eq!(mismatch_json["event"], "branch.workspace_mismatch");
|
||||
assert_eq!(mismatch_json["failureClass"], "workspace_mismatch");
|
||||
assert_eq!(
|
||||
mismatch_json["data"]["expectedWorkspaceRoot"],
|
||||
"/tmp/repo-a"
|
||||
);
|
||||
|
||||
let round_trip: LaneEvent =
|
||||
serde_json::from_value(mismatch_json).expect("lane event should deserialize");
|
||||
assert_eq!(round_trip.event, LaneEventName::BranchWorkspaceMismatch);
|
||||
assert_eq!(
|
||||
round_trip.failure_class,
|
||||
Some(LaneFailureClass::WorkspaceMismatch)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_events_can_carry_worktree_and_supersession_metadata() {
|
||||
let event = LaneEvent::commit_created(
|
||||
|
||||
Reference in New Issue
Block a user