@team-agent/installer 0.5.61 → 0.5.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +5 -2
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +1 -0
- package/crates/team-agent/src/cli/emit.rs +41 -7
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +6 -1
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +33 -3
- package/crates/team-agent/src/cli/named_address.rs +8 -1
- package/crates/team-agent/src/cli/profile.rs +2 -1
- package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
- package/crates/team-agent/src/cli/send/persist.rs +1 -0
- package/crates/team-agent/src/cli/send/presentation.rs +1 -0
- package/crates/team-agent/src/cli/send/resolve.rs +206 -1
- package/crates/team-agent/src/cli/send.rs +2 -1
- package/crates/team-agent/src/cli/spec.rs +1 -0
- package/crates/team-agent/src/cli/status.rs +2 -1
- package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
- package/crates/team-agent/src/cli/status_port/format.rs +1 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +3 -2
- package/crates/team-agent/src/communication_mode/mod.rs +1 -0
- package/crates/team-agent/src/compiler.rs +1 -0
- package/crates/team-agent/src/conpty/backend.rs +1 -0
- package/crates/team-agent/src/conpty/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/backoff.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
- package/crates/team-agent/src/coordinator/health.rs +1 -0
- package/crates/team-agent/src/coordinator/mod.rs +2 -2
- package/crates/team-agent/src/coordinator/orphan.rs +4 -116
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +15 -866
- package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
- package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +139 -375
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
- package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -49
- package/crates/team-agent/src/coordinator/tick.rs +23 -40
- package/crates/team-agent/src/coordinator/types.rs +3 -176
- package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
- package/crates/team-agent/src/db/message_store.rs +2 -1
- package/crates/team-agent/src/db/migration.rs +5 -4
- package/crates/team-agent/src/db/mod.rs +1 -0
- package/crates/team-agent/src/db/schema.rs +1 -0
- package/crates/team-agent/src/diagnose/comms.rs +2 -1
- package/crates/team-agent/src/diagnose/mod.rs +1 -0
- package/crates/team-agent/src/diagnose/orphans.rs +1 -0
- package/crates/team-agent/src/layout/manager.rs +3 -7
- package/crates/team-agent/src/layout/mod.rs +2 -2
- package/crates/team-agent/src/layout/overlay.rs +6 -1
- package/crates/team-agent/src/layout/placement.rs +1 -0
- package/crates/team-agent/src/layout/recovery.rs +3 -0
- package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
- package/crates/team-agent/src/layout/sessions.rs +8 -1
- package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
- package/crates/team-agent/src/layout/worker_env.rs +3 -0
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/helpers.rs +1 -0
- package/crates/team-agent/src/leader/incident.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +27 -22
- package/crates/team-agent/src/leader/mod.rs +2 -3
- package/crates/team-agent/src/leader/owner_bind.rs +13 -3
- package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +4 -17
- package/crates/team-agent/src/leader/registry.rs +3 -15
- package/crates/team-agent/src/leader/start.rs +373 -125
- package/crates/team-agent/src/leader/takeover.rs +10 -232
- package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
- package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
- package/crates/team-agent/src/leader/tests/identity.rs +7 -2
- package/crates/team-agent/src/leader/tests/idle.rs +0 -193
- package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
- package/crates/team-agent/src/leader/tests/mod.rs +4 -41
- package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
- package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
- package/crates/team-agent/src/leader/types.rs +1 -28
- package/crates/team-agent/src/lib.rs +3 -0
- package/crates/team-agent/src/lifecycle/display.rs +7 -1
- package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -27
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch.rs +11 -12
- package/crates/team-agent/src/lifecycle/mod.rs +2 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -66
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
- package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
- package/crates/team-agent/src/lifecycle/restart.rs +13 -15
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
- package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
- package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
- package/crates/team-agent/src/lifecycle/tests.rs +59 -0
- package/crates/team-agent/src/lifecycle/types.rs +0 -9
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
- package/crates/team-agent/src/mcp_server/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +4 -3
- package/crates/team-agent/src/mcp_server/tools.rs +1 -0
- package/crates/team-agent/src/mcp_server/types.rs +1 -18
- package/crates/team-agent/src/mcp_server/wire.rs +5 -5
- package/crates/team-agent/src/messaging/activity.rs +1 -0
- package/crates/team-agent/src/messaging/address.rs +1 -0
- package/crates/team-agent/src/messaging/delivery.rs +4 -3
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
- package/crates/team-agent/src/messaging/mod.rs +10 -14
- package/crates/team-agent/src/messaging/peers.rs +2 -0
- package/crates/team-agent/src/messaging/persist.rs +3 -1
- package/crates/team-agent/src/messaging/presentation.rs +2 -1
- package/crates/team-agent/src/messaging/results.rs +1 -0
- package/crates/team-agent/src/messaging/scheduler.rs +1 -0
- package/crates/team-agent/src/messaging/selftest.rs +1 -1
- package/crates/team-agent/src/messaging/send.rs +41 -8
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/types.rs +2 -2
- package/crates/team-agent/src/messaging/watchers.rs +19 -13
- package/crates/team-agent/src/model/enums.rs +1 -0
- package/crates/team-agent/src/model/errors.rs +1 -0
- package/crates/team-agent/src/model/ids.rs +1 -0
- package/crates/team-agent/src/model/mod.rs +1 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -0
- package/crates/team-agent/src/model/paths.rs +1 -0
- package/crates/team-agent/src/model/permissions.rs +3 -2
- package/crates/team-agent/src/model/routing.rs +1 -0
- package/crates/team-agent/src/model/spec.rs +1 -0
- package/crates/team-agent/src/model/task_graph.rs +1 -0
- package/crates/team-agent/src/model/yaml/tests.rs +1 -0
- package/crates/team-agent/src/model/yaml.rs +1 -0
- package/crates/team-agent/src/packaging/install.rs +9 -147
- package/crates/team-agent/src/packaging/migrate.rs +2 -0
- package/crates/team-agent/src/packaging/mod.rs +1 -0
- package/crates/team-agent/src/packaging/repair.rs +2 -0
- package/crates/team-agent/src/packaging/tests.rs +29 -218
- package/crates/team-agent/src/packaging/types.rs +8 -15
- package/crates/team-agent/src/platform/argv.rs +4 -0
- package/crates/team-agent/src/platform/errors.rs +10 -1
- package/crates/team-agent/src/platform/file_lock.rs +4 -157
- package/crates/team-agent/src/platform/mod.rs +0 -61
- package/crates/team-agent/src/platform/process.rs +1 -0
- package/crates/team-agent/src/provider/adapter.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
- package/crates/team-agent/src/provider/classify.rs +1 -0
- package/crates/team-agent/src/provider/faults.rs +1 -26
- package/crates/team-agent/src/provider/helpers.rs +1 -0
- package/crates/team-agent/src/provider/mod.rs +1 -1
- package/crates/team-agent/src/provider/session/capture.rs +1 -4
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
- package/crates/team-agent/src/provider/session/mod.rs +2 -4
- package/crates/team-agent/src/provider/session/resume.rs +2 -209
- package/crates/team-agent/src/provider/session_scan/claude.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +1 -42
- package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
- package/crates/team-agent/src/provider/types.rs +1 -0
- package/crates/team-agent/src/provider/wire.rs +1 -0
- package/crates/team-agent/src/state/identity.rs +16 -5
- package/crates/team-agent/src/state/identity_keys.rs +1 -0
- package/crates/team-agent/src/state/mod.rs +2 -0
- package/crates/team-agent/src/state/owner_gate.rs +5 -0
- package/crates/team-agent/src/state/ownership.rs +10 -4
- package/crates/team-agent/src/state/paths.rs +12 -0
- package/crates/team-agent/src/state/persist.rs +9 -3
- package/crates/team-agent/src/state/projection.rs +15 -2
- package/crates/team-agent/src/state/repository/intent.rs +1 -0
- package/crates/team-agent/src/state/repository/tests.rs +1 -0
- package/crates/team-agent/src/state/repository.rs +7 -13
- package/crates/team-agent/src/state/selector.rs +1 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
- package/crates/team-agent/src/tmux_backend.rs +63 -0
- package/crates/team-agent/src/transport.rs +15 -0
- package/package.json +4 -4
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
//!
|
|
1
|
+
//!
|
|
2
|
+
//! unit-5 (Stage 2) — closed `ResumeRefusalReason` enum and recovery hints.
|
|
2
3
|
//!
|
|
3
4
|
//! Today the restart resume gate flattens every refusal into one of two
|
|
4
5
|
//! opaque strings:
|
|
@@ -159,110 +160,6 @@ impl ResumeRefusalReason {
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
/// Outcome of `ResumePreflight::check`.
|
|
163
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
164
|
-
pub enum ResumePreflightOutcome {
|
|
165
|
-
/// Worker can resume from the named session.
|
|
166
|
-
Resume { session_id: String },
|
|
167
|
-
/// `--allow-fresh` was set; worker will start fresh.
|
|
168
|
-
FreshStart,
|
|
169
|
-
/// Resume refused. Caller MUST NOT proceed with teardown/spawn.
|
|
170
|
-
Refuse { reason: ResumeRefusalReason },
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/// Information about whether a provider backing file is present on disk.
|
|
174
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
175
|
-
pub struct ProviderBackingCheck {
|
|
176
|
-
pub paths: Vec<PathBuf>,
|
|
177
|
-
pub exists: bool,
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/// Per-decision detail emitted to events / JSON.
|
|
181
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
182
|
-
pub struct ResumeDecisionDetail {
|
|
183
|
-
pub agent_id: String,
|
|
184
|
-
pub session_id: Option<String>,
|
|
185
|
-
pub provider: String,
|
|
186
|
-
pub backing: Option<ProviderBackingCheck>,
|
|
187
|
-
pub outcome: ResumePreflightOutcome,
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/// Pure resume preflight evaluator. No I/O — callers pass facts in.
|
|
191
|
-
pub struct ResumePreflight;
|
|
192
|
-
|
|
193
|
-
impl ResumePreflight {
|
|
194
|
-
/// Decide a single worker's resume outcome from the facts the
|
|
195
|
-
/// restart code already collected. Mirrors the existing
|
|
196
|
-
/// `classify_restart_plan_with_resume_validation` decision tree:
|
|
197
|
-
///
|
|
198
|
-
/// * session_id present + provider can resume + backing exists -> Resume
|
|
199
|
-
/// * session_id present + (no resume OR no backing) + allow_fresh -> FreshStart
|
|
200
|
-
/// * session_id present + (no resume OR no backing) + !allow_fresh -> Refuse
|
|
201
|
-
/// * session_id absent + allow_fresh -> FreshStart
|
|
202
|
-
/// * session_id absent + !allow_fresh -> Refuse(NoSessionId)
|
|
203
|
-
pub fn check(
|
|
204
|
-
session_id: Option<&str>,
|
|
205
|
-
provider_can_resume: bool,
|
|
206
|
-
backing: Option<&ProviderBackingCheck>,
|
|
207
|
-
provider_name: &str,
|
|
208
|
-
allow_fresh: bool,
|
|
209
|
-
) -> ResumePreflightOutcome {
|
|
210
|
-
Self::check_with_hint(
|
|
211
|
-
session_id,
|
|
212
|
-
provider_can_resume,
|
|
213
|
-
backing,
|
|
214
|
-
provider_name,
|
|
215
|
-
allow_fresh,
|
|
216
|
-
None,
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/// Layer 2 self-healing variant: same decision tree as
|
|
221
|
-
/// [`Self::check`], but propagates an optional `RecoveryHint` into
|
|
222
|
-
/// the `SessionBackingStoreMissing` refusal so the CLI / event log
|
|
223
|
-
/// can surface a "look for session named X under cwd Y" pointer to
|
|
224
|
-
/// the operator. The hint is for HUMAN consumption only — no
|
|
225
|
-
/// auto-resume happens off it (Layer 3 follow-up).
|
|
226
|
-
pub fn check_with_hint(
|
|
227
|
-
session_id: Option<&str>,
|
|
228
|
-
provider_can_resume: bool,
|
|
229
|
-
backing: Option<&ProviderBackingCheck>,
|
|
230
|
-
provider_name: &str,
|
|
231
|
-
allow_fresh: bool,
|
|
232
|
-
recovery_hint: Option<RecoveryHint>,
|
|
233
|
-
) -> ResumePreflightOutcome {
|
|
234
|
-
match session_id {
|
|
235
|
-
Some(sid) if provider_can_resume => {
|
|
236
|
-
let backing_present = backing.map(|b| b.exists).unwrap_or(true);
|
|
237
|
-
if backing_present {
|
|
238
|
-
ResumePreflightOutcome::Resume {
|
|
239
|
-
session_id: sid.to_string(),
|
|
240
|
-
}
|
|
241
|
-
} else if allow_fresh {
|
|
242
|
-
ResumePreflightOutcome::FreshStart
|
|
243
|
-
} else {
|
|
244
|
-
ResumePreflightOutcome::Refuse {
|
|
245
|
-
reason: ResumeRefusalReason::SessionBackingStoreMissing {
|
|
246
|
-
checked_paths: backing.map(|b| b.paths.clone()).unwrap_or_default(),
|
|
247
|
-
recovery_hint,
|
|
248
|
-
},
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
Some(_) if allow_fresh => ResumePreflightOutcome::FreshStart,
|
|
253
|
-
Some(_) => ResumePreflightOutcome::Refuse {
|
|
254
|
-
reason: ResumeRefusalReason::ProviderResumeUnsupported {
|
|
255
|
-
provider: provider_name.to_string(),
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
None if allow_fresh => ResumePreflightOutcome::FreshStart,
|
|
259
|
-
None => ResumePreflightOutcome::Refuse {
|
|
260
|
-
reason: ResumeRefusalReason::NoSessionId,
|
|
261
|
-
},
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
163
|
#[cfg(test)]
|
|
267
164
|
mod tests {
|
|
268
165
|
use super::*;
|
|
@@ -287,58 +184,6 @@ mod tests {
|
|
|
287
184
|
assert!(matches!(r, ResumeRefusalReason::Other { .. }));
|
|
288
185
|
}
|
|
289
186
|
|
|
290
|
-
#[test]
|
|
291
|
-
fn preflight_resume_when_session_and_backing_present() {
|
|
292
|
-
let backing = ProviderBackingCheck {
|
|
293
|
-
paths: vec![PathBuf::from("/tmp/x.jsonl")],
|
|
294
|
-
exists: true,
|
|
295
|
-
};
|
|
296
|
-
let out = ResumePreflight::check(Some("sess-1"), true, Some(&backing), "codex", false);
|
|
297
|
-
assert!(matches!(out, ResumePreflightOutcome::Resume { .. }));
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
#[test]
|
|
301
|
-
fn preflight_refuses_when_session_id_missing_without_allow_fresh() {
|
|
302
|
-
let out = ResumePreflight::check(None, true, None, "codex", false);
|
|
303
|
-
assert_eq!(
|
|
304
|
-
out,
|
|
305
|
-
ResumePreflightOutcome::Refuse {
|
|
306
|
-
reason: ResumeRefusalReason::NoSessionId
|
|
307
|
-
}
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
#[test]
|
|
312
|
-
fn preflight_distinguishes_backing_missing_from_no_session_id() {
|
|
313
|
-
let backing = ProviderBackingCheck {
|
|
314
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
315
|
-
exists: false,
|
|
316
|
-
};
|
|
317
|
-
let out = ResumePreflight::check(Some("sess-x"), true, Some(&backing), "codex", false);
|
|
318
|
-
match out {
|
|
319
|
-
ResumePreflightOutcome::Refuse { reason } => {
|
|
320
|
-
assert_eq!(reason.wire(), "session_backing_store_missing");
|
|
321
|
-
}
|
|
322
|
-
other => panic!("expected backing-missing refusal; got {other:?}"),
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
#[test]
|
|
327
|
-
fn preflight_allow_fresh_converts_refusals_to_fresh() {
|
|
328
|
-
let backing = ProviderBackingCheck {
|
|
329
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
330
|
-
exists: false,
|
|
331
|
-
};
|
|
332
|
-
assert_eq!(
|
|
333
|
-
ResumePreflight::check(None, true, None, "codex", true),
|
|
334
|
-
ResumePreflightOutcome::FreshStart
|
|
335
|
-
);
|
|
336
|
-
assert_eq!(
|
|
337
|
-
ResumePreflight::check(Some("sess-x"), true, Some(&backing), "codex", true),
|
|
338
|
-
ResumePreflightOutcome::FreshStart
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
187
|
// Layer 2 self-healing tests ────────────────────────────────────────────
|
|
343
188
|
|
|
344
189
|
#[test]
|
|
@@ -364,56 +209,4 @@ mod tests {
|
|
|
364
209
|
};
|
|
365
210
|
assert_eq!(hint.picker_hint(), "codex session");
|
|
366
211
|
}
|
|
367
|
-
|
|
368
|
-
#[test]
|
|
369
|
-
fn preflight_with_hint_attaches_to_backing_missing_refusal() {
|
|
370
|
-
let backing = ProviderBackingCheck {
|
|
371
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
372
|
-
exists: false,
|
|
373
|
-
};
|
|
374
|
-
let hint = RecoveryHint {
|
|
375
|
-
provider_session_name_hint: Some("coder".to_string()),
|
|
376
|
-
spawn_cwd: Some(PathBuf::from("/repo")),
|
|
377
|
-
provider: "claude".to_string(),
|
|
378
|
-
};
|
|
379
|
-
let out = ResumePreflight::check_with_hint(
|
|
380
|
-
Some("sess-1"),
|
|
381
|
-
true,
|
|
382
|
-
Some(&backing),
|
|
383
|
-
"claude",
|
|
384
|
-
false,
|
|
385
|
-
Some(hint.clone()),
|
|
386
|
-
);
|
|
387
|
-
match out {
|
|
388
|
-
ResumePreflightOutcome::Refuse {
|
|
389
|
-
reason:
|
|
390
|
-
ResumeRefusalReason::SessionBackingStoreMissing {
|
|
391
|
-
recovery_hint: Some(h),
|
|
392
|
-
..
|
|
393
|
-
},
|
|
394
|
-
} => {
|
|
395
|
-
assert_eq!(h, hint);
|
|
396
|
-
}
|
|
397
|
-
other => panic!("expected SessionBackingStoreMissing with hint; got {other:?}"),
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
#[test]
|
|
402
|
-
fn preflight_no_hint_legacy_behavior_unchanged() {
|
|
403
|
-
// Default check() (no hint) still produces None recovery_hint —
|
|
404
|
-
// wire-string-compatible with pre-Layer-2 callers.
|
|
405
|
-
let backing = ProviderBackingCheck {
|
|
406
|
-
paths: vec![PathBuf::from("/missing.jsonl")],
|
|
407
|
-
exists: false,
|
|
408
|
-
};
|
|
409
|
-
let out = ResumePreflight::check(Some("sess-x"), true, Some(&backing), "codex", false);
|
|
410
|
-
match out {
|
|
411
|
-
ResumePreflightOutcome::Refuse {
|
|
412
|
-
reason: ResumeRefusalReason::SessionBackingStoreMissing { recovery_hint, .. },
|
|
413
|
-
} => {
|
|
414
|
-
assert!(recovery_hint.is_none());
|
|
415
|
-
}
|
|
416
|
-
other => panic!("expected backing-missing refusal; got {other:?}"),
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
212
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! Provider session-scan boundary.
|
|
2
3
|
//!
|
|
3
4
|
//! `ProviderAdapter` keeps the public polling facade in `adapter.rs`; this
|
|
@@ -14,27 +15,6 @@ pub(crate) mod codex;
|
|
|
14
15
|
pub(crate) mod common;
|
|
15
16
|
pub(crate) mod copilot;
|
|
16
17
|
|
|
17
|
-
/// Marker label for which scan strategy a provider uses.
|
|
18
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
19
|
-
pub enum SessionScanStrategy {
|
|
20
|
-
CodexJsonl,
|
|
21
|
-
ClaudeTranscript,
|
|
22
|
-
CopilotHome,
|
|
23
|
-
NotRequired,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
impl SessionScanStrategy {
|
|
27
|
-
pub fn for_provider(provider: Provider) -> Self {
|
|
28
|
-
match provider {
|
|
29
|
-
Provider::Codex => Self::CodexJsonl,
|
|
30
|
-
Provider::Claude | Provider::ClaudeCode => Self::ClaudeTranscript,
|
|
31
|
-
Provider::Copilot => Self::CopilotHome,
|
|
32
|
-
Provider::GeminiCli => Self::NotRequired,
|
|
33
|
-
Provider::Fake => Self::NotRequired,
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
18
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
39
19
|
pub struct CaptureSessionContext {
|
|
40
20
|
pub agent_id: String,
|
|
@@ -87,24 +67,3 @@ pub(crate) fn scan_session_candidates_once(
|
|
|
87
67
|
}
|
|
88
68
|
|
|
89
69
|
pub(crate) use claude::rollout_path_has_leader_marker as rollout_path_has_claude_leader_marker;
|
|
90
|
-
|
|
91
|
-
#[cfg(test)]
|
|
92
|
-
mod tests {
|
|
93
|
-
use super::*;
|
|
94
|
-
|
|
95
|
-
#[test]
|
|
96
|
-
fn strategy_per_provider() {
|
|
97
|
-
assert_eq!(
|
|
98
|
-
SessionScanStrategy::for_provider(Provider::Codex),
|
|
99
|
-
SessionScanStrategy::CodexJsonl
|
|
100
|
-
);
|
|
101
|
-
assert_eq!(
|
|
102
|
-
SessionScanStrategy::for_provider(Provider::Claude),
|
|
103
|
-
SessionScanStrategy::ClaudeTranscript
|
|
104
|
-
);
|
|
105
|
-
assert_eq!(
|
|
106
|
-
SessionScanStrategy::for_provider(Provider::Fake),
|
|
107
|
-
SessionScanStrategy::NotRequired
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! identity:leader receiver identity 派生 / 迁移 / first-time binding(真相源 `state.py`)。
|
|
2
3
|
//!
|
|
3
4
|
//! Python 直接读 `os.environ`/`os.getcwd()`/`Path.resolve()`;此处把**环境变量**经
|
|
@@ -44,8 +45,9 @@ fn path_str(p: &Path) -> String {
|
|
|
44
45
|
p.to_string_lossy().into_owned()
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
///
|
|
47
49
|
/// `_identity_os_user`(`state.py:275`):`USER` or `USERNAME` or `""`。
|
|
48
|
-
|
|
50
|
+
fn identity_os_user(env: &dyn IdentityEnv) -> String {
|
|
49
51
|
let user = env_str(env, "USER");
|
|
50
52
|
if !user.is_empty() {
|
|
51
53
|
return user;
|
|
@@ -57,9 +59,10 @@ pub fn identity_os_user(env: &dyn IdentityEnv) -> String {
|
|
|
57
59
|
String::new()
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
///
|
|
60
63
|
/// `_identity_machine_fingerprint`(`state.py:279`):team_owner/leader_receiver 的非空
|
|
61
64
|
/// machine_fingerprint 优先,否则 env `TEAM_AGENT_MACHINE_FINGERPRINT` or `""`。
|
|
62
|
-
|
|
65
|
+
fn identity_machine_fingerprint(state: &Value, env: &dyn IdentityEnv) -> String {
|
|
63
66
|
for key in ["team_owner", "leader_receiver"] {
|
|
64
67
|
if let Some(fp) = state
|
|
65
68
|
.get(key)
|
|
@@ -74,9 +77,10 @@ pub fn identity_machine_fingerprint(state: &Value, env: &dyn IdentityEnv) -> Str
|
|
|
74
77
|
env_str(env, "TEAM_AGENT_MACHINE_FINGERPRINT")
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
///
|
|
77
81
|
/// `_identity_workspace_abspath`(`state.py:264`):按 workspace→team_dir→spec_path→fallback
|
|
78
82
|
/// 顺序派生 workspace 绝对路径(各分支先 `resolve()` 再做 parent 导航)。
|
|
79
|
-
|
|
83
|
+
fn identity_workspace_abspath(
|
|
80
84
|
state: &Value,
|
|
81
85
|
env: &dyn IdentityEnv,
|
|
82
86
|
workspace: Option<&Path>,
|
|
@@ -157,9 +161,10 @@ fn leader_session_uuid_for_state(
|
|
|
157
161
|
Ok(uuid.as_str().to_string())
|
|
158
162
|
}
|
|
159
163
|
|
|
164
|
+
///
|
|
160
165
|
/// `_migrate_team_identity`(`state.py:295`):为缺 `leader_session_uuid` 的 team_owner /
|
|
161
166
|
/// leader_receiver 填入派生 uuid。返回是否有改动。
|
|
162
|
-
|
|
167
|
+
fn migrate_team_identity(
|
|
163
168
|
state: &mut Value,
|
|
164
169
|
env: &dyn IdentityEnv,
|
|
165
170
|
workspace: Option<&Path>,
|
|
@@ -189,6 +194,7 @@ pub fn migrate_team_identity(
|
|
|
189
194
|
Ok(changed)
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
///
|
|
192
198
|
/// `_migrate_state_identity`(`state.py:306`):顶层(有 session_name 时)+ 每个 team 子状态迁移。
|
|
193
199
|
pub fn migrate_state_identity(
|
|
194
200
|
state: &mut Value,
|
|
@@ -220,6 +226,7 @@ pub fn migrate_state_identity(
|
|
|
220
226
|
Ok(changed)
|
|
221
227
|
}
|
|
222
228
|
|
|
229
|
+
///
|
|
223
230
|
/// `_caller_identity_from_env`(`state.py:316`):env override/env/derived → 5 字段 caller 身份。
|
|
224
231
|
pub fn caller_identity_from_env(
|
|
225
232
|
state: Option<&Value>,
|
|
@@ -282,9 +289,10 @@ pub fn caller_identity_from_env(
|
|
|
282
289
|
})
|
|
283
290
|
}
|
|
284
291
|
|
|
292
|
+
///
|
|
285
293
|
/// `populate_team_owner_from_env`(`state.py:414`):已有 team_owner → 仅补迁移 uuid 后返回;
|
|
286
294
|
/// 否则从 env caller 身份 seed team_owner(无 pane_id → `None`)。`now_iso` = claimed_at。
|
|
287
|
-
|
|
295
|
+
fn populate_team_owner_from_env(
|
|
288
296
|
state: &mut Value,
|
|
289
297
|
source: &str,
|
|
290
298
|
env: &dyn IdentityEnv,
|
|
@@ -320,6 +328,7 @@ pub fn populate_team_owner_from_env(
|
|
|
320
328
|
Ok(Some(owner))
|
|
321
329
|
}
|
|
322
330
|
|
|
331
|
+
///
|
|
323
332
|
/// `apply_first_time_leader_binding`(`state.py:434`):命令 + workspace realpath 双门后,
|
|
324
333
|
/// 写入 team_owner / leader_receiver(owner_epoch=0)。拒绝 dict 字节对齐 Python(含 `pane` +
|
|
325
334
|
/// `repr()` 错误串)。`command_looks_usable`(`_leader_command_looks_usable`,step 11)经闭包注入。
|
|
@@ -411,6 +420,7 @@ pub fn apply_first_time_leader_binding(
|
|
|
411
420
|
json!({ "ok": true, "pane": pane_info, "warning": null, "first_time": true })
|
|
412
421
|
}
|
|
413
422
|
|
|
423
|
+
///
|
|
414
424
|
/// `leader_env_exports`(`state.py:469`):receiver + identity → 6 个 leader env 导出(插入序)。
|
|
415
425
|
pub fn leader_env_exports(receiver: &Value, identity: &Value) -> Vec<(String, String)> {
|
|
416
426
|
vec![
|
|
@@ -441,6 +451,7 @@ pub fn leader_env_exports(receiver: &Value, identity: &Value) -> Vec<(String, St
|
|
|
441
451
|
]
|
|
442
452
|
}
|
|
443
453
|
|
|
454
|
+
///
|
|
444
455
|
/// `validate_leader_uuid_from_targets`(`state.py:480`):fake provider 直过;否则核对 targets
|
|
445
456
|
/// 扫描成功 + receiver.pane_id 在 targets 中存在。
|
|
446
457
|
pub fn validate_leader_uuid_from_targets(receiver: &Value, targets: &Value) -> Value {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! step 5 · state — state.json 持久化 / identity / owner-gate / projection(真相源 `state.py`)。
|
|
2
3
|
//!
|
|
3
4
|
//! §6 step 5:原子写/锁/self-heal(**bug-084** 血泪)/ team projection / leader receiver identity /
|
|
@@ -32,6 +33,7 @@ pub mod selector;
|
|
|
32
33
|
use serde_json::Value;
|
|
33
34
|
use thiserror::Error;
|
|
34
35
|
|
|
36
|
+
///
|
|
35
37
|
/// Python 真值语义(`null`/`false`/`0`/`""`/`[]`/`{}` → false),`state` 模块共用。
|
|
36
38
|
pub(crate) fn json_truthy(v: &Value) -> bool {
|
|
37
39
|
match v {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! owner-gate:trust own-vs-foreign(§11 / bug-064 / bug-082 血泪;真相源 `state.py`)。
|
|
2
3
|
//!
|
|
3
4
|
//! 三核心纯判定(集成 send_message/MCP 是 step 11/14):
|
|
@@ -37,6 +38,7 @@ pub struct CallerIdentity {
|
|
|
37
38
|
pub leader_session_uuid_source: String,
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
///
|
|
40
42
|
/// `check_team_owner`(`state.py:366`)的纯判定版:caller 身份 + 是否有 TEAM_AGENT_ID + liveness 注入。
|
|
41
43
|
/// 返回 `None`=允许(own / 无 owner / 死 owner pane 可接管);`Some(dict)`=拒绝(team_owner_mismatch)。
|
|
42
44
|
///
|
|
@@ -123,6 +125,7 @@ fn caller_to_value(c: &CallerIdentity) -> Value {
|
|
|
123
125
|
})
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
///
|
|
126
129
|
/// `worker_sender_bypasses_owner_gate`(`state.py:400`):worker peer-send 绕过 owner 门。
|
|
127
130
|
/// 返回 `Some(agent_id)`=绕过;`None`=不绕过(走 owner 门)。
|
|
128
131
|
pub fn worker_sender_bypasses_owner_gate(
|
|
@@ -160,6 +163,7 @@ pub fn worker_sender_bypasses_owner_gate(
|
|
|
160
163
|
Some(sender.to_string())
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
///
|
|
163
167
|
/// §11 realpath own-vs-foreign(`state.py:449`):**两边 canonicalize** 后全等才判 own。
|
|
164
168
|
/// macOS `/tmp → /private/tmp` 软链不对称由「两边 canonicalize」消除。canonicalize 失败(路径不存在)
|
|
165
169
|
/// → 退化为词法绝对比较(此时无软链可解,等价)。**禁 basename/startswith/子串/反推**。
|
|
@@ -187,6 +191,7 @@ fn lexical_abs(p: &Path) -> std::path::PathBuf {
|
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
|
|
194
|
+
///
|
|
190
195
|
/// `os.path.realpath` 等价(对抗 P0-B):全路径存在 → `canonicalize`;否则对**最长存在前缀**
|
|
191
196
|
/// canonicalize(解析其软链)再拼回缺失末段(规范化 `..`/`.`)。这复刻 Python「解析存在部分的
|
|
192
197
|
/// 软链 + 保留不存在末段」,修掉「父目录是软链 + 末段不存在 → 漏判 own」的 §11 血泪。
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! Stage 2 of identity-boundary unified plan (architect direction 2026-06-23):
|
|
2
3
|
//! `state::ownership` — single read entry point for `team_owner` lookups.
|
|
3
4
|
//!
|
|
@@ -41,7 +42,7 @@ use crate::state::projection::{read_owner as projection_read_owner, team_state_k
|
|
|
41
42
|
/// owner value. CLI status / diagnose use it to surface legacy duplicate
|
|
42
43
|
/// warnings.
|
|
43
44
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
44
|
-
|
|
45
|
+
enum OwnershipSource {
|
|
45
46
|
/// Stage 5 canonical path: `.team/runtime/<team_key>/state.json` (not
|
|
46
47
|
/// yet emitted — placeholder for forward compatibility).
|
|
47
48
|
CanonicalPerTeamState,
|
|
@@ -57,11 +58,12 @@ pub enum OwnershipSource {
|
|
|
57
58
|
/// caller consumes (same shape as `state.get("team_owner")` returned today);
|
|
58
59
|
/// the `source` is diagnostic.
|
|
59
60
|
#[derive(Debug, Clone)]
|
|
60
|
-
|
|
61
|
+
struct OwnershipRead<'a> {
|
|
61
62
|
pub value: &'a Value,
|
|
62
63
|
pub source: OwnershipSource,
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
///
|
|
65
67
|
/// Stage 2 entry point: read the team owner for the given `team_key` from an
|
|
66
68
|
/// in-memory state value, honouring the legacy precedence. Returns `None` if
|
|
67
69
|
/// no source carries a non-empty, valid owner record.
|
|
@@ -73,7 +75,7 @@ pub struct OwnershipRead<'a> {
|
|
|
73
75
|
/// This deliberately reuses `projection::read_owner` so the pane-id validity
|
|
74
76
|
/// check (`%N` or all-digits) stays in one place. The repository's value-add
|
|
75
77
|
/// is the precedence ordering and the `OwnershipSource` tag.
|
|
76
|
-
|
|
78
|
+
fn read_owner_for_team<'a>(state: &'a Value, team_key: &str) -> Option<OwnershipRead<'a>> {
|
|
77
79
|
// Step 1 (Stage 5): canonical per-team state. Stage 2 does not consult
|
|
78
80
|
// a separate file yet — owner truth still lives in the in-memory state.
|
|
79
81
|
// Once Stage 5 wires `.team/runtime/<team_key>/state.json` as canonical,
|
|
@@ -109,6 +111,7 @@ pub fn read_owner_for_team<'a>(state: &'a Value, team_key: &str) -> Option<Owner
|
|
|
109
111
|
None
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
///
|
|
112
115
|
/// Convenience: just the JSON value, no diagnostic source. Returns `None`
|
|
113
116
|
/// when no owner is found. Stable across Stage 5 — only the lookup
|
|
114
117
|
/// implementation will move.
|
|
@@ -116,6 +119,7 @@ pub fn read_owner_value<'a>(state: &'a Value, team_key: &str) -> Option<&'a Valu
|
|
|
116
119
|
read_owner_for_team(state, team_key).map(|read| read.value)
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
///
|
|
119
123
|
/// Stage 3 (identity-boundary unified plan, architect direction 2026-06-23):
|
|
120
124
|
/// single write entry point for owner mutations. Pre-Stage-3, owner writes
|
|
121
125
|
/// were spread across 13 sites (claim/attach/readopt/managed-leader/quick-
|
|
@@ -187,6 +191,7 @@ pub fn write_owner(state: &mut Value, team_key: &str, record: OwnershipWrite) {
|
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
|
|
194
|
+
///
|
|
190
195
|
/// Stage 3 top-level cleanup (architect direction 2026-06-24,
|
|
191
196
|
/// .team/artifacts/stage3-toplevel-cleanup-fix.md): strip the legacy
|
|
192
197
|
/// top-level `team_owner / leader_receiver / owner_epoch` fields from a
|
|
@@ -202,12 +207,13 @@ pub fn write_owner(state: &mut Value, team_key: &str, record: OwnershipWrite) {
|
|
|
202
207
|
/// load-time path — legacy single-team states may still have only
|
|
203
208
|
/// top-level owner and no canonical `teams.<key>` owner, and a blind load
|
|
204
209
|
/// strip would lose ownership before migration.
|
|
205
|
-
|
|
210
|
+
fn strip_top_level_ownership(root: &mut serde_json::Map<String, Value>) {
|
|
206
211
|
for key in ["team_owner", "leader_receiver", "owner_epoch"] {
|
|
207
212
|
root.remove(key);
|
|
208
213
|
}
|
|
209
214
|
}
|
|
210
215
|
|
|
216
|
+
///
|
|
211
217
|
/// Stage 3 save-output canonical-aware strip (architect direction
|
|
212
218
|
/// 2026-06-24, .team/artifacts/stage3-save-strip-fix.md): conditional
|
|
213
219
|
/// version of `strip_top_level_ownership` used by `state::persist`'s
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! Stage 0 of the identity-boundary unified plan (architect direction
|
|
2
3
|
//! 2026-06-23): `TeamScope` + `TeamRuntimePaths` foundation.
|
|
3
4
|
//!
|
|
@@ -42,6 +43,7 @@ pub struct TeamScope {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
impl TeamScope {
|
|
46
|
+
///
|
|
45
47
|
/// Construct a TeamScope from an already-resolved workspace + team_key.
|
|
46
48
|
/// Callers that have a display name must resolve it through the existing
|
|
47
49
|
/// `state::selector` machinery first; this constructor refuses an empty
|
|
@@ -66,6 +68,7 @@ impl TeamScope {
|
|
|
66
68
|
&self.team_key
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
///
|
|
69
72
|
/// Convenience: derive the path helper from this scope.
|
|
70
73
|
pub fn paths(&self) -> TeamRuntimePaths {
|
|
71
74
|
TeamRuntimePaths::for_scope(self)
|
|
@@ -110,6 +113,7 @@ impl TeamRuntimePaths {
|
|
|
110
113
|
&self.team_key
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
///
|
|
113
117
|
/// `.team/runtime/teams/<team_key>/` — the team's future B3 runtime
|
|
114
118
|
/// directory. It is not used as product authority until the B3 migration;
|
|
115
119
|
/// current callers must keep using `runtime_state_path`.
|
|
@@ -119,6 +123,7 @@ impl TeamRuntimePaths {
|
|
|
119
123
|
.join(&self.team_key)
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
///
|
|
122
127
|
/// Transitional runtime spec path. Mirrors the existing
|
|
123
128
|
/// `runtime_spec_path` helper while specs remain at
|
|
124
129
|
/// `.team/runtime/<team_key>/team.spec.yaml`.
|
|
@@ -126,6 +131,7 @@ impl TeamRuntimePaths {
|
|
|
126
131
|
runtime_spec_path(&self.workspace, &self.team_key)
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
///
|
|
129
135
|
/// `.team/runtime/teams/<team_key>/state.json` — the canonical per-team
|
|
130
136
|
/// state path that B3 will start writing. Not used by current product
|
|
131
137
|
/// code; callers must continue using `runtime_state_path` until B3
|
|
@@ -134,11 +140,13 @@ impl TeamRuntimePaths {
|
|
|
134
140
|
self.team_dir().join("state.json")
|
|
135
141
|
}
|
|
136
142
|
|
|
143
|
+
///
|
|
137
144
|
/// `.team/runtime/teams/<team_key>/coordinator.pid` — future sidecar location.
|
|
138
145
|
pub fn coordinator_pid_path(&self) -> PathBuf {
|
|
139
146
|
self.team_dir().join("coordinator.pid")
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
///
|
|
142
150
|
/// `.team/runtime/teams/<team_key>/coordinator.log` — future sidecar location.
|
|
143
151
|
pub fn coordinator_log_path(&self) -> PathBuf {
|
|
144
152
|
self.team_dir().join("coordinator.log")
|
|
@@ -180,6 +188,7 @@ pub enum CommandScope {
|
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
impl CommandScope {
|
|
191
|
+
///
|
|
183
192
|
/// Resolve the CLI's `--team` argument against the workspace state.
|
|
184
193
|
/// On any I/O error the empty case is returned — destructive commands
|
|
185
194
|
/// will run their own selector and surface the real error.
|
|
@@ -202,6 +211,7 @@ impl CommandScope {
|
|
|
202
211
|
}
|
|
203
212
|
}
|
|
204
213
|
|
|
214
|
+
///
|
|
205
215
|
/// Convert to an `Option<String>` for backward compatibility with
|
|
206
216
|
/// existing `args.team.as_deref()` call sites.
|
|
207
217
|
pub fn team_key(&self) -> Option<&str> {
|
|
@@ -211,11 +221,13 @@ impl CommandScope {
|
|
|
211
221
|
}
|
|
212
222
|
}
|
|
213
223
|
|
|
224
|
+
///
|
|
214
225
|
/// True iff there are 2+ alive teams and no explicit `--team`.
|
|
215
226
|
pub fn is_ambiguous(&self) -> bool {
|
|
216
227
|
matches!(self, Self::Ambiguous(_))
|
|
217
228
|
}
|
|
218
229
|
|
|
230
|
+
///
|
|
219
231
|
/// The candidate list when ambiguous; empty otherwise.
|
|
220
232
|
pub fn candidates(&self) -> &[String] {
|
|
221
233
|
match self {
|