@team-agent/installer 0.5.60 → 0.5.62
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 +49 -3
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +184 -0
- package/crates/team-agent/src/cli/emit.rs +104 -21
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +14 -2
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +296 -8
- package/crates/team-agent/src/cli/named_address.rs +26 -2
- 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 +3 -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 +5 -1
- 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/status_port/store.rs +7 -3
- package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +36 -2
- package/crates/team-agent/src/communication_mode/mod.rs +54 -0
- package/crates/team-agent/src/compiler/tests.rs +5 -5
- package/crates/team-agent/src/compiler.rs +54 -1
- 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 +6 -5
- 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 +0 -9
- 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 -42
- package/crates/team-agent/src/coordinator/tick.rs +24 -28
- 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 +47 -2
- 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/fake_worker.rs +21 -1
- 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 +1072 -186
- 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 +71 -59
- package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
- 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 +7 -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_agent.rs +1 -1
- 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 +2 -28
- 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 +4 -4
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- 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 -11
- 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/lifecycle/worker_command_context.rs +29 -11
- 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 +5 -3
- package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
- package/crates/team-agent/src/mcp_server/tools.rs +101 -66
- package/crates/team-agent/src/mcp_server/types.rs +6 -18
- package/crates/team-agent/src/mcp_server/wire.rs +32 -26
- 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 +109 -41
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +34 -10
- package/crates/team-agent/src/messaging/leader_receiver.rs +42 -24
- 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 +111 -1
- package/crates/team-agent/src/messaging/results.rs +166 -17
- 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 +135 -89
- package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
- package/crates/team-agent/src/messaging/types.rs +7 -2
- package/crates/team-agent/src/messaging/watchers.rs +24 -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 +2 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +359 -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 +17 -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 +41 -22
- 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 +93 -3
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +24 -7
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +6 -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 -0
- 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/topology.rs +3 -0
- package/crates/team-agent/src/transport.rs +15 -0
- package/package.json +4 -4
- package/skills/team-agent/command-coverage.json +379 -0
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -9,9 +9,10 @@ use crate::model::ids::{AgentId, TaskId, TeamKey};
|
|
|
9
9
|
use crate::transport::{PaneId, Transport};
|
|
10
10
|
|
|
11
11
|
use super::helpers::{status_wire, MessageStatusShadow};
|
|
12
|
-
use super::leader_receiver::
|
|
12
|
+
use super::leader_receiver::send_to_leader_receiver_with_presentation;
|
|
13
13
|
use super::presentation::{
|
|
14
|
-
decide_presentation, PresentationRequest, PresentationSink,
|
|
14
|
+
decide_presentation, PresentationDecision, PresentationRequest, PresentationSink,
|
|
15
|
+
PresentationSource,
|
|
15
16
|
};
|
|
16
17
|
use super::{
|
|
17
18
|
persist_resolved_send, DeliveryBlocker, DeliveryOutcome, DeliveryRefusal, DeliveryStatus,
|
|
@@ -35,21 +36,54 @@ pub enum MessageTarget {
|
|
|
35
36
|
/// Public CLI/MCP inputs never construct this value from a caller-supplied
|
|
36
37
|
/// string. The wrapper keeps that trust boundary visible throughout delivery
|
|
37
38
|
/// instead of degrading the identity back to an untyped option field.
|
|
38
|
-
#[derive(Debug, Clone, PartialEq, Eq
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
40
|
+
pub struct TrustedSender {
|
|
41
|
+
agent_id: AgentId,
|
|
42
|
+
full_name: Option<String>,
|
|
43
|
+
}
|
|
41
44
|
|
|
42
45
|
impl TrustedSender {
|
|
43
46
|
pub fn from_runtime_identity(agent_id: AgentId) -> Self {
|
|
44
|
-
Self
|
|
47
|
+
Self {
|
|
48
|
+
agent_id,
|
|
49
|
+
full_name: None,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn from_runtime_identity_with_source(
|
|
54
|
+
agent_id: AgentId,
|
|
55
|
+
workspace: impl std::fmt::Display,
|
|
56
|
+
team: &str,
|
|
57
|
+
) -> Self {
|
|
58
|
+
let full_name = format!("{}::{}/{}", workspace, team, agent_id.as_str());
|
|
59
|
+
Self {
|
|
60
|
+
agent_id,
|
|
61
|
+
full_name: Some(full_name),
|
|
62
|
+
}
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
pub fn leader() -> Self {
|
|
48
|
-
Self
|
|
66
|
+
Self {
|
|
67
|
+
agent_id: AgentId::new("leader"),
|
|
68
|
+
full_name: None,
|
|
69
|
+
}
|
|
49
70
|
}
|
|
50
71
|
|
|
51
72
|
pub fn as_str(&self) -> &str {
|
|
52
|
-
self.
|
|
73
|
+
self.agent_id.as_str()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn display_name(&self) -> &str {
|
|
77
|
+
self.full_name.as_deref().unwrap_or_else(|| self.as_str())
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl serde::Serialize for TrustedSender {
|
|
82
|
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
83
|
+
where
|
|
84
|
+
S: serde::Serializer,
|
|
85
|
+
{
|
|
86
|
+
serializer.serialize_str(self.display_name())
|
|
53
87
|
}
|
|
54
88
|
}
|
|
55
89
|
|
|
@@ -125,49 +159,14 @@ pub fn send_message(
|
|
|
125
159
|
MessageTarget::Single(target) if target == "leader" => {
|
|
126
160
|
let presentation = decide_presentation(&opts.presentation, PresentationSource::Send);
|
|
127
161
|
if presentation.effective_sink != PresentationSink::Leader {
|
|
128
|
-
|
|
129
|
-
opts.origin,
|
|
162
|
+
return persist_stored_only_send(
|
|
130
163
|
workspace,
|
|
131
|
-
opts.team.clone(),
|
|
132
164
|
LogicalRecipient::Leader,
|
|
133
|
-
opts.sender.clone(),
|
|
134
|
-
opts.task_id.clone(),
|
|
135
165
|
content,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
opts,
|
|
167
|
+
&presentation,
|
|
168
|
+
&event_log,
|
|
139
169
|
);
|
|
140
|
-
intent.initial_disposition = InitialDisposition::StoredOnly;
|
|
141
|
-
intent.presentation = presentation.clone();
|
|
142
|
-
let persisted = match persist_resolved_send(&intent)? {
|
|
143
|
-
PersistResolution::Persisted(persisted) => persisted,
|
|
144
|
-
PersistResolution::Duplicate(message_id) => {
|
|
145
|
-
return Ok(refused_outcome_with_id(
|
|
146
|
-
DeliveryRefusal::Duplicate,
|
|
147
|
-
Some(message_id),
|
|
148
|
-
));
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
event_log.write(
|
|
152
|
-
"presentation.stored_without_live_inject",
|
|
153
|
-
serde_json::json!({
|
|
154
|
-
"message_id": persisted.message_id,
|
|
155
|
-
"requested_sink": presentation.requested_sink,
|
|
156
|
-
"effective_sink": presentation.effective_sink,
|
|
157
|
-
"class": presentation.class,
|
|
158
|
-
"policy_reason": presentation.policy_reason,
|
|
159
|
-
}),
|
|
160
|
-
)?;
|
|
161
|
-
return Ok(DeliveryOutcome {
|
|
162
|
-
ok: true,
|
|
163
|
-
status: DeliveryStatus::StoredOnly,
|
|
164
|
-
message_status: MessageStatusShadow("stored_only".to_string()),
|
|
165
|
-
message_id: Some(persisted.message_id),
|
|
166
|
-
verification: Some("durable_without_live_inject".to_string()),
|
|
167
|
-
stage: None,
|
|
168
|
-
reason: None,
|
|
169
|
-
channel: Some(presentation.effective_sink.as_str().to_string()),
|
|
170
|
-
});
|
|
171
170
|
}
|
|
172
171
|
let outcome = send_to_leader_receiver_with_presentation(
|
|
173
172
|
workspace,
|
|
@@ -175,7 +174,7 @@ pub fn send_message(
|
|
|
175
174
|
"leader",
|
|
176
175
|
content,
|
|
177
176
|
opts.task_id.as_ref(),
|
|
178
|
-
opts.sender.
|
|
177
|
+
opts.sender.display_name(),
|
|
179
178
|
opts.requires_ack,
|
|
180
179
|
None,
|
|
181
180
|
opts.message_id.as_deref(),
|
|
@@ -201,15 +200,7 @@ pub fn send_message(
|
|
|
201
200
|
MessageTarget::Single(target) => target,
|
|
202
201
|
MessageTarget::Broadcast => {
|
|
203
202
|
let recipients = broadcast_recipients(&state, opts.sender.as_str(), opts.team.as_ref());
|
|
204
|
-
return fanout_send(
|
|
205
|
-
workspace,
|
|
206
|
-
&state,
|
|
207
|
-
&recipients,
|
|
208
|
-
content,
|
|
209
|
-
opts,
|
|
210
|
-
&event_log,
|
|
211
|
-
"*",
|
|
212
|
-
);
|
|
203
|
+
return fanout_send(workspace, &recipients, content, opts, "*");
|
|
213
204
|
}
|
|
214
205
|
MessageTarget::Fanout(recipients) if recipients.is_empty() => {
|
|
215
206
|
// swallow batch 3 ②: a failed send carries its reason (Python send error
|
|
@@ -226,9 +217,7 @@ pub fn send_message(
|
|
|
226
217
|
});
|
|
227
218
|
}
|
|
228
219
|
MessageTarget::Fanout(recipients) => {
|
|
229
|
-
return fanout_send(
|
|
230
|
-
workspace, &state, recipients, content, opts, &event_log, "fanout",
|
|
231
|
-
);
|
|
220
|
+
return fanout_send(workspace, recipients, content, opts, "fanout");
|
|
232
221
|
}
|
|
233
222
|
};
|
|
234
223
|
// send.py:259-261 — a non-leader target that is NOT a known team agent is refused
|
|
@@ -240,6 +229,17 @@ pub fn send_message(
|
|
|
240
229
|
if !in_team {
|
|
241
230
|
return Ok(refused_outcome(DeliveryRefusal::TargetNotInTeam));
|
|
242
231
|
}
|
|
232
|
+
let presentation = decide_presentation(&opts.presentation, PresentationSource::Send);
|
|
233
|
+
if presentation.effective_sink != PresentationSink::Leader {
|
|
234
|
+
return persist_stored_only_send(
|
|
235
|
+
workspace,
|
|
236
|
+
LogicalRecipient::from_resolved(recipient),
|
|
237
|
+
content,
|
|
238
|
+
opts,
|
|
239
|
+
&presentation,
|
|
240
|
+
&event_log,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
243
|
if let Some(outcome) = session_drift_refusal(
|
|
244
244
|
&state,
|
|
245
245
|
recipient,
|
|
@@ -354,6 +354,59 @@ pub fn send_message(
|
|
|
354
354
|
})
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
+
fn persist_stored_only_send(
|
|
358
|
+
workspace: &Path,
|
|
359
|
+
recipient: LogicalRecipient,
|
|
360
|
+
content: &str,
|
|
361
|
+
opts: &SendOptions,
|
|
362
|
+
presentation: &PresentationDecision,
|
|
363
|
+
event_log: &EventLog,
|
|
364
|
+
) -> Result<DeliveryOutcome, MessagingError> {
|
|
365
|
+
let mut intent = ResolvedSendIntent::accepted(
|
|
366
|
+
opts.origin,
|
|
367
|
+
workspace,
|
|
368
|
+
opts.team.clone(),
|
|
369
|
+
recipient,
|
|
370
|
+
opts.sender.clone(),
|
|
371
|
+
opts.task_id.clone(),
|
|
372
|
+
content,
|
|
373
|
+
None,
|
|
374
|
+
opts.requires_ack,
|
|
375
|
+
opts.message_id.clone(),
|
|
376
|
+
);
|
|
377
|
+
intent.initial_disposition = InitialDisposition::StoredOnly;
|
|
378
|
+
intent.presentation = presentation.clone();
|
|
379
|
+
let persisted = match persist_resolved_send(&intent)? {
|
|
380
|
+
PersistResolution::Persisted(persisted) => persisted,
|
|
381
|
+
PersistResolution::Duplicate(message_id) => {
|
|
382
|
+
return Ok(refused_outcome_with_id(
|
|
383
|
+
DeliveryRefusal::Duplicate,
|
|
384
|
+
Some(message_id),
|
|
385
|
+
));
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
event_log.write(
|
|
389
|
+
"presentation.stored_without_live_inject",
|
|
390
|
+
serde_json::json!({
|
|
391
|
+
"message_id": persisted.message_id,
|
|
392
|
+
"requested_sink": presentation.requested_sink,
|
|
393
|
+
"effective_sink": presentation.effective_sink,
|
|
394
|
+
"class": presentation.class,
|
|
395
|
+
"policy_reason": presentation.policy_reason,
|
|
396
|
+
}),
|
|
397
|
+
)?;
|
|
398
|
+
Ok(DeliveryOutcome {
|
|
399
|
+
ok: true,
|
|
400
|
+
status: DeliveryStatus::StoredOnly,
|
|
401
|
+
message_status: MessageStatusShadow("stored_only".to_string()),
|
|
402
|
+
message_id: Some(persisted.message_id),
|
|
403
|
+
verification: Some("durable_without_live_inject".to_string()),
|
|
404
|
+
stage: None,
|
|
405
|
+
reason: None,
|
|
406
|
+
channel: Some(presentation.effective_sink.as_str().to_string()),
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
357
410
|
fn stale_worker_target_missing_preflight(
|
|
358
411
|
workspace: &Path,
|
|
359
412
|
state: &serde_json::Value,
|
|
@@ -722,7 +775,7 @@ pub fn apply_worker_sender_bypass(
|
|
|
722
775
|
|
|
723
776
|
/// `session_drift_refusal` (`session_drift.py:69`):send 时检测会话漂移并拒绝。
|
|
724
777
|
/// `None` = 无漂移 (放行);`Some` = 拒绝 [`DeliveryOutcome`] (reason=`SessionDrift`)。
|
|
725
|
-
pub fn session_drift_refusal(
|
|
778
|
+
pub(crate) fn session_drift_refusal(
|
|
726
779
|
state: &serde_json::Value,
|
|
727
780
|
target: &str,
|
|
728
781
|
leader_id: &str,
|
|
@@ -804,11 +857,9 @@ fn broadcast_recipients(
|
|
|
804
857
|
|
|
805
858
|
fn fanout_send(
|
|
806
859
|
workspace: &Path,
|
|
807
|
-
state: &serde_json::Value,
|
|
808
860
|
recipients: &[String],
|
|
809
861
|
content: &str,
|
|
810
862
|
opts: &SendOptions,
|
|
811
|
-
event_log: &EventLog,
|
|
812
863
|
channel_label: &str,
|
|
813
864
|
) -> Result<DeliveryOutcome, MessagingError> {
|
|
814
865
|
let mut last_message_id: Option<String> = None;
|
|
@@ -821,30 +872,17 @@ fn fanout_send(
|
|
|
821
872
|
continue;
|
|
822
873
|
}
|
|
823
874
|
attempted_count = attempted_count.saturating_add(1);
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
)?
|
|
836
|
-
} else {
|
|
837
|
-
// single-recipient re-entry — strip fanout metadata to avoid recursion + ensure
|
|
838
|
-
// each row gets its own caller-supplied message_id (none) so SQLite PK doesn't clash.
|
|
839
|
-
let mut inner_opts = opts.clone();
|
|
840
|
-
inner_opts.message_id = None;
|
|
841
|
-
super::send::send_message(
|
|
842
|
-
workspace,
|
|
843
|
-
&MessageTarget::Single(recipient.clone()),
|
|
844
|
-
content,
|
|
845
|
-
&inner_opts,
|
|
846
|
-
)?
|
|
847
|
-
};
|
|
875
|
+
// Every expanded recipient re-enters the same presentation-aware send
|
|
876
|
+
// funnel. Strip the caller idempotency key so each durable fanout row
|
|
877
|
+
// receives its own stable message id.
|
|
878
|
+
let mut inner_opts = opts.clone();
|
|
879
|
+
inner_opts.message_id = None;
|
|
880
|
+
let outcome = super::send::send_message(
|
|
881
|
+
workspace,
|
|
882
|
+
&MessageTarget::Single(recipient.clone()),
|
|
883
|
+
content,
|
|
884
|
+
&inner_opts,
|
|
885
|
+
)?;
|
|
848
886
|
if outcome.ok {
|
|
849
887
|
delivered_count = delivered_count.saturating_add(1);
|
|
850
888
|
if let Some(mid) = outcome.message_id.clone() {
|
|
@@ -862,8 +900,12 @@ fn fanout_send(
|
|
|
862
900
|
return Ok(outcome);
|
|
863
901
|
}
|
|
864
902
|
}
|
|
903
|
+
let presentation = decide_presentation(&opts.presentation, PresentationSource::Send);
|
|
904
|
+
let stored_only = presentation.effective_sink != PresentationSink::Leader;
|
|
865
905
|
let status = if any_failure {
|
|
866
906
|
DeliveryStatus::FanoutPartial
|
|
907
|
+
} else if delivered_count > 0 && stored_only {
|
|
908
|
+
DeliveryStatus::StoredOnly
|
|
867
909
|
} else if delivered_count > 0 {
|
|
868
910
|
DeliveryStatus::FanoutDelivered
|
|
869
911
|
} else {
|
|
@@ -874,9 +916,13 @@ fn fanout_send(
|
|
|
874
916
|
status,
|
|
875
917
|
message_status: MessageStatusShadow(status_wire(status).to_string()),
|
|
876
918
|
message_id: last_message_id,
|
|
877
|
-
verification:
|
|
919
|
+
verification: stored_only.then(|| "durable_without_live_inject".to_string()),
|
|
878
920
|
stage: None,
|
|
879
921
|
reason: None,
|
|
880
|
-
channel: Some(
|
|
922
|
+
channel: Some(if stored_only {
|
|
923
|
+
presentation.effective_sink.as_str().to_string()
|
|
924
|
+
} else {
|
|
925
|
+
channel_label.to_string()
|
|
926
|
+
}),
|
|
881
927
|
})
|
|
882
928
|
}
|
|
@@ -101,10 +101,10 @@ fn legacy_receiver_without_status_still_rejects_a_foreign_workspace() {
|
|
|
101
101
|
.with_tmux_endpoint(socket)
|
|
102
102
|
.with_targets(vec![observed]);
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
assert!(matches!(
|
|
105
105
|
resolve_live_leader_channel(workspace, &receiver, &transport),
|
|
106
|
-
LeaderChannelResolution::Unbound(LeaderChannelUnbound::PaneWorkspaceMismatch)
|
|
107
|
-
);
|
|
106
|
+
LeaderChannelResolution::Unbound(LeaderChannelUnbound::PaneWorkspaceMismatch(_))
|
|
107
|
+
));
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
#[test]
|
|
@@ -162,8 +162,8 @@ fn same_socket_and_pane_in_a_foreign_workspace_is_not_a_live_leader() {
|
|
|
162
162
|
.with_tmux_endpoint(socket)
|
|
163
163
|
.with_targets(vec![observed]);
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
assert!(matches!(
|
|
166
166
|
resolve_live_leader_channel(workspace, &receiver(socket), &transport),
|
|
167
|
-
LeaderChannelResolution::Unbound(LeaderChannelUnbound::PaneWorkspaceMismatch)
|
|
168
|
-
);
|
|
167
|
+
LeaderChannelResolution::Unbound(LeaderChannelUnbound::PaneWorkspaceMismatch(_))
|
|
168
|
+
));
|
|
169
169
|
}
|
|
@@ -99,6 +99,141 @@ fn attached_leader_mailbox_is_rechecked_by_the_normal_delivery_tick() {
|
|
|
99
99
|
assert!(case.events().contains("leader_receiver.mailbox_requeued"));
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
#[test]
|
|
103
|
+
fn leader_submit_without_receipt_source_cannot_park_forever_without_a_completion_writer() {
|
|
104
|
+
let case = Case::new_without_rollout("missing-receipt-source");
|
|
105
|
+
let transport = RecordingTransport::new("");
|
|
106
|
+
let message_id = case.seed_message("missing receipt source canary");
|
|
107
|
+
|
|
108
|
+
let first = deliver_pending_message(
|
|
109
|
+
&case.workspace,
|
|
110
|
+
&case.store,
|
|
111
|
+
&transport,
|
|
112
|
+
&message_id,
|
|
113
|
+
&case.event_log,
|
|
114
|
+
&case.state,
|
|
115
|
+
)
|
|
116
|
+
.expect("verified leader transport submission");
|
|
117
|
+
|
|
118
|
+
assert_ne!(
|
|
119
|
+
case.message_status(&message_id),
|
|
120
|
+
"submitted_pending_acceptance",
|
|
121
|
+
"F4 acceptance-stall: a verified leader transport submit with no reachable \
|
|
122
|
+
receipt source must not enter submitted_pending_acceptance, because that \
|
|
123
|
+
protected state has no completion writer. The runtime may close the \
|
|
124
|
+
transport-acceptance fact or expose a provider-session receipt writer, \
|
|
125
|
+
but it must not report an indefinitely retryable fact-terminal. outcome={first:?}"
|
|
126
|
+
);
|
|
127
|
+
assert_eq!(
|
|
128
|
+
transport.inject_count(),
|
|
129
|
+
1,
|
|
130
|
+
"the acceptance repair must not obtain progress by physically injecting twice"
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
let _ = deliver_pending_message(
|
|
134
|
+
&case.workspace,
|
|
135
|
+
&case.store,
|
|
136
|
+
&transport,
|
|
137
|
+
&message_id,
|
|
138
|
+
&case.event_log,
|
|
139
|
+
&case.state,
|
|
140
|
+
);
|
|
141
|
+
assert_eq!(
|
|
142
|
+
transport.inject_count(),
|
|
143
|
+
1,
|
|
144
|
+
"a second coordinator observation must not replay the already-submitted leader message"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn nonempty_missing_receipt_source_is_not_token_absent() {
|
|
150
|
+
assert_unavailable_rollout_converges(
|
|
151
|
+
"missing-rollout",
|
|
152
|
+
PathBuf::from("definitely-missing-leader-rollout.jsonl"),
|
|
153
|
+
false,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#[test]
|
|
158
|
+
fn nonempty_unreadable_receipt_source_is_not_token_absent() {
|
|
159
|
+
assert_unavailable_rollout_converges(
|
|
160
|
+
"unreadable-rollout",
|
|
161
|
+
PathBuf::from("leader-rollout-is-a-directory"),
|
|
162
|
+
true,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn assert_unavailable_rollout_converges(tag: &str, rollout: PathBuf, directory: bool) {
|
|
167
|
+
let case = Case::new_with_unavailable_rollout(tag, rollout, directory);
|
|
168
|
+
let transport = RecordingTransport::new("");
|
|
169
|
+
let message_id = case.seed_message("unavailable receipt source canary");
|
|
170
|
+
|
|
171
|
+
let first = deliver_pending_message(
|
|
172
|
+
&case.workspace,
|
|
173
|
+
&case.store,
|
|
174
|
+
&transport,
|
|
175
|
+
&message_id,
|
|
176
|
+
&case.event_log,
|
|
177
|
+
&case.state,
|
|
178
|
+
)
|
|
179
|
+
.expect("verified leader transport submission");
|
|
180
|
+
|
|
181
|
+
assert_eq!(
|
|
182
|
+
case.message_status(&message_id),
|
|
183
|
+
"injected_awaiting_receipt",
|
|
184
|
+
"F4 receipt observation must distinguish an unreadable nonempty rollout_path \
|
|
185
|
+
from a readable rollout that lacks the token. The former is \
|
|
186
|
+
receipt-source-unavailable and must converge to the typed parked state, \
|
|
187
|
+
not remain submitted_pending_acceptance. shape={tag} outcome={first:?}"
|
|
188
|
+
);
|
|
189
|
+
assert_eq!(
|
|
190
|
+
first.verification.as_deref(),
|
|
191
|
+
Some("leader_receipt_source_unavailable"),
|
|
192
|
+
"the outcome must expose read-unavailable rather than TokenAbsent; shape={tag}"
|
|
193
|
+
);
|
|
194
|
+
assert_eq!(transport.inject_count(), 1);
|
|
195
|
+
|
|
196
|
+
let _ = deliver_pending_message(
|
|
197
|
+
&case.workspace,
|
|
198
|
+
&case.store,
|
|
199
|
+
&transport,
|
|
200
|
+
&message_id,
|
|
201
|
+
&case.event_log,
|
|
202
|
+
&case.state,
|
|
203
|
+
);
|
|
204
|
+
assert_eq!(
|
|
205
|
+
transport.inject_count(),
|
|
206
|
+
1,
|
|
207
|
+
"read-unavailable convergence must not obtain progress by a second physical \
|
|
208
|
+
injection; shape={tag}"
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[test]
|
|
213
|
+
fn worker_consumption_writer_remains_a_positive_control() {
|
|
214
|
+
let case = Case::new("worker-positive-control");
|
|
215
|
+
let message_id = case
|
|
216
|
+
.store
|
|
217
|
+
.create_message(
|
|
218
|
+
None,
|
|
219
|
+
"leader",
|
|
220
|
+
"worker",
|
|
221
|
+
"worker consume canary",
|
|
222
|
+
None,
|
|
223
|
+
false,
|
|
224
|
+
None,
|
|
225
|
+
)
|
|
226
|
+
.unwrap();
|
|
227
|
+
|
|
228
|
+
case.store.mark(&message_id, "consumed", None).unwrap();
|
|
229
|
+
|
|
230
|
+
assert_eq!(
|
|
231
|
+
case.message_status(&message_id),
|
|
232
|
+
"consumed",
|
|
233
|
+
"positive control: the independent worker consumption writer must remain reachable"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
102
237
|
struct Case {
|
|
103
238
|
workspace: PathBuf,
|
|
104
239
|
rollout: PathBuf,
|
|
@@ -134,6 +269,58 @@ impl Case {
|
|
|
134
269
|
}
|
|
135
270
|
}
|
|
136
271
|
|
|
272
|
+
fn new_without_rollout(tag: &str) -> Self {
|
|
273
|
+
let workspace = tmp_ws(&format!("leader-inject-acceptance-{tag}"));
|
|
274
|
+
let rollout = workspace.join("absent-leader.jsonl");
|
|
275
|
+
let state = serde_json::json!({
|
|
276
|
+
"active_team_key": "acceptance-team",
|
|
277
|
+
"session_name": "team-acceptance",
|
|
278
|
+
"leader_receiver": {
|
|
279
|
+
"pane_id": "%leader",
|
|
280
|
+
"status": "attached",
|
|
281
|
+
"provider": "claude"
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
|
|
285
|
+
let store = MessageStore::open(&workspace).unwrap();
|
|
286
|
+
let event_log = EventLog::new(&workspace);
|
|
287
|
+
Self {
|
|
288
|
+
workspace,
|
|
289
|
+
rollout,
|
|
290
|
+
store,
|
|
291
|
+
event_log,
|
|
292
|
+
state,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fn new_with_unavailable_rollout(tag: &str, relative: PathBuf, directory: bool) -> Self {
|
|
297
|
+
let workspace = tmp_ws(&format!("leader-inject-acceptance-{tag}"));
|
|
298
|
+
let rollout = workspace.join(relative);
|
|
299
|
+
if directory {
|
|
300
|
+
std::fs::create_dir_all(&rollout).unwrap();
|
|
301
|
+
}
|
|
302
|
+
let state = serde_json::json!({
|
|
303
|
+
"active_team_key": "acceptance-team",
|
|
304
|
+
"session_name": "team-acceptance",
|
|
305
|
+
"leader_receiver": {
|
|
306
|
+
"pane_id": "%leader",
|
|
307
|
+
"status": "attached",
|
|
308
|
+
"provider": "claude",
|
|
309
|
+
"rollout_path": rollout,
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
|
|
313
|
+
let store = MessageStore::open(&workspace).unwrap();
|
|
314
|
+
let event_log = EventLog::new(&workspace);
|
|
315
|
+
Self {
|
|
316
|
+
workspace,
|
|
317
|
+
rollout,
|
|
318
|
+
store,
|
|
319
|
+
event_log,
|
|
320
|
+
state,
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
137
324
|
fn seed_message(&self, content: &str) -> String {
|
|
138
325
|
self.store
|
|
139
326
|
.create_message(None, "worker", "leader", content, None, false, None)
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
2
2
|
use super::*;
|
|
3
|
+
use super::delivery::tmux_pane_width;
|
|
4
|
+
use super::helpers::fail_leader_delivery;
|
|
5
|
+
use super::selftest::evaluate_idle_behavior;
|
|
6
|
+
use super::send::session_drift_refusal;
|
|
7
|
+
use super::watchers::{delivered_result_message, notify_result_watchers};
|
|
3
8
|
use std::collections::BTreeMap;
|
|
4
9
|
use std::path::{Path, PathBuf};
|
|
5
10
|
|
|
@@ -2976,9 +2976,8 @@ fn gate054_attached_status_still_accepts_when_pane_is_live() {
|
|
|
2976
2976
|
// Injection-case revision: transport submit success is NOT a provider
|
|
2977
2977
|
// receipt, so the old `delivered` pin was a false-positive lock
|
|
2978
2978
|
// (MUST-10: physical submit is the framework's fact; acceptance is the
|
|
2979
|
-
// provider's). The un-refused path now
|
|
2980
|
-
//
|
|
2981
|
-
// schedule advances it.
|
|
2979
|
+
// provider's). The un-refused path now records the SAME row in the typed
|
|
2980
|
+
// post-injection fact state until a provider receipt advances it.
|
|
2982
2981
|
let ws = tmp_ws("gate054ok");
|
|
2983
2982
|
let store = store_for(&ws);
|
|
2984
2983
|
let log = EventLog::new(&ws);
|
|
@@ -3012,10 +3011,18 @@ fn gate054_attached_status_still_accepts_when_pane_is_live() {
|
|
|
3012
3011
|
1,
|
|
3013
3012
|
"gate054: exactly one physical inject must have happened (MUST-10 anchor)"
|
|
3014
3013
|
);
|
|
3014
|
+
let injected_awaiting_receipt = crate::db::message_store::MessageRowStatus::ALL
|
|
3015
|
+
.iter()
|
|
3016
|
+
.map(|status| status.as_str())
|
|
3017
|
+
.find(|status| *status == "injected_awaiting_receipt")
|
|
3018
|
+
.expect(
|
|
3019
|
+
"gate054 signed reanchor: typed status directory must expose \
|
|
3020
|
+
injected_awaiting_receipt",
|
|
3021
|
+
);
|
|
3015
3022
|
assert_eq!(
|
|
3016
|
-
out.message_status.0,
|
|
3017
|
-
"gate054
|
|
3018
|
-
|
|
3023
|
+
out.message_status.0, injected_awaiting_receipt,
|
|
3024
|
+
"gate054 signed reanchor: a successful physical inject without a provider receipt \
|
|
3025
|
+
must remain non-delivered as injected_awaiting_receipt, never claim delivered: {out:?}"
|
|
3019
3026
|
);
|
|
3020
3027
|
}
|
|
3021
3028
|
|
|
@@ -53,6 +53,11 @@ pub enum DeliveryRefusal {
|
|
|
53
53
|
TmuxTargetMissing,
|
|
54
54
|
MessageAlreadyClaimed,
|
|
55
55
|
LeaderNotAttached,
|
|
56
|
+
/// The recorded leader pane is live but belongs to another workspace.
|
|
57
|
+
/// Keep this typed cause distinct from an actually unattached leader so
|
|
58
|
+
/// the send surface can return the existing recovery action.
|
|
59
|
+
#[serde(rename = "PaneWorkspaceMismatch")]
|
|
60
|
+
PaneWorkspaceMismatch,
|
|
56
61
|
NoCallerPane,
|
|
57
62
|
TeamOwnerMismatch,
|
|
58
63
|
Ambiguous,
|
|
@@ -402,14 +407,14 @@ pub struct IdleEvaluation {
|
|
|
402
407
|
|
|
403
408
|
/// 抑制快照 (card §50;`scheduler.py:383` `_agent_alert_snapshot`)。清除判据靠 snapshot diff。
|
|
404
409
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
405
|
-
pub struct AlertSnapshot {
|
|
410
|
+
pub(crate) struct AlertSnapshot {
|
|
406
411
|
pub assigned_task_ids: Vec<TaskId>,
|
|
407
412
|
pub delivered_message_ids: Vec<String>,
|
|
408
413
|
}
|
|
409
414
|
|
|
410
415
|
/// 告警抑制条目 (card §50;`scheduler.py:287`)。
|
|
411
416
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
412
|
-
pub struct AlertSuppression {
|
|
417
|
+
pub(crate) struct AlertSuppression {
|
|
413
418
|
pub suppressed_at: String,
|
|
414
419
|
pub suppressed_by: String,
|
|
415
420
|
pub snapshot: AlertSnapshot,
|