@team-agent/installer 0.5.49 → 0.5.51
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 +6 -4
- package/crates/team-agent/src/cli/emit.rs +91 -39
- package/crates/team-agent/src/cli/mod.rs +33 -15
- package/crates/team-agent/src/cli/named_address.rs +82 -53
- package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
- package/crates/team-agent/src/cli/send/persist.rs +154 -0
- package/crates/team-agent/src/cli/send/presentation.rs +333 -0
- package/crates/team-agent/src/cli/send/resolve.rs +361 -0
- package/crates/team-agent/src/cli/send.rs +103 -1308
- package/crates/team-agent/src/cli/spec.rs +2 -2
- package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
- package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
- package/crates/team-agent/src/cli/status_port/format.rs +145 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
- package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
- package/crates/team-agent/src/cli/status_port/store.rs +412 -0
- package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
- package/crates/team-agent/src/cli/status_port.rs +47 -1548
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +9 -7
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +17 -33
- package/crates/team-agent/src/cli/types.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
- package/crates/team-agent/src/coordinator/tick.rs +37 -0
- package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
- package/crates/team-agent/src/db/message_store.rs +154 -44
- package/crates/team-agent/src/event_log.rs +73 -0
- package/crates/team-agent/src/leader/start.rs +28 -4
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
- package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
- package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
- package/crates/team-agent/src/lifecycle/restart/agent.rs +44 -26
- package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +67 -26
- package/crates/team-agent/src/lifecycle/restart/remove.rs +435 -72
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +575 -17
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +55 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
- package/crates/team-agent/src/mcp_server/mod.rs +2 -2
- package/crates/team-agent/src/mcp_server/tests/send.rs +22 -15
- package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -0
- package/crates/team-agent/src/mcp_server/tools.rs +26 -15
- package/crates/team-agent/src/mcp_server/wire.rs +2 -18
- package/crates/team-agent/src/messaging/activity.rs +4 -2
- package/crates/team-agent/src/messaging/address.rs +86 -0
- package/crates/team-agent/src/messaging/delivery.rs +165 -39
- package/crates/team-agent/src/messaging/helpers.rs +17 -13
- package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
- package/crates/team-agent/src/messaging/mod.rs +11 -2
- package/crates/team-agent/src/messaging/persist.rs +309 -0
- package/crates/team-agent/src/messaging/results.rs +16 -24
- package/crates/team-agent/src/messaging/scheduler.rs +4 -2
- package/crates/team-agent/src/messaging/selftest.rs +19 -12
- package/crates/team-agent/src/messaging/send.rs +133 -58
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
- package/crates/team-agent/src/messaging/watchers.rs +13 -3
- package/crates/team-agent/src/redaction.rs +72 -2
- package/crates/team-agent/src/state/persist.rs +2 -1
- package/crates/team-agent/src/state/repository/tests.rs +47 -0
- package/crates/team-agent/src/state/repository.rs +59 -16
- package/package.json +4 -4
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
//! Durable boundary between resolved logical addressing and delivery/recovery.
|
|
2
|
+
|
|
3
|
+
use std::path::{Path, PathBuf};
|
|
4
|
+
|
|
5
|
+
use crate::db::message_store::{MessageRowStatus, MessageStore, PersistMessageInput};
|
|
6
|
+
use crate::model::ids::{AgentId, TaskId, TeamKey};
|
|
7
|
+
|
|
8
|
+
use super::send::TrustedSender;
|
|
9
|
+
use super::MessagingError;
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
12
|
+
pub enum InternalSendKind {
|
|
13
|
+
Delivery,
|
|
14
|
+
LeaderNotification,
|
|
15
|
+
OfflineMailbox,
|
|
16
|
+
Selftest,
|
|
17
|
+
Watcher,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
21
|
+
pub enum SendOrigin {
|
|
22
|
+
Cli,
|
|
23
|
+
Mcp,
|
|
24
|
+
Internal(InternalSendKind),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
28
|
+
pub enum LogicalRecipient {
|
|
29
|
+
Worker(AgentId),
|
|
30
|
+
Leader,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl LogicalRecipient {
|
|
34
|
+
pub fn from_resolved(value: impl Into<String>) -> Self {
|
|
35
|
+
let value = value.into();
|
|
36
|
+
if value == "leader" {
|
|
37
|
+
Self::Leader
|
|
38
|
+
} else {
|
|
39
|
+
Self::Worker(AgentId::new(value))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub fn as_str(&self) -> &str {
|
|
44
|
+
match self {
|
|
45
|
+
Self::Worker(agent) => agent.as_str(),
|
|
46
|
+
Self::Leader => "leader",
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
52
|
+
pub enum DeliveryBlocker {
|
|
53
|
+
CoordinatorUnavailable,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl DeliveryBlocker {
|
|
57
|
+
pub const fn as_str(self) -> &'static str {
|
|
58
|
+
match self {
|
|
59
|
+
Self::CoordinatorUnavailable => "coordinator_unavailable",
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
65
|
+
pub enum InitialDisposition {
|
|
66
|
+
Accepted,
|
|
67
|
+
QueuedUntilLeaderAttach,
|
|
68
|
+
Blocked(DeliveryBlocker),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl InitialDisposition {
|
|
72
|
+
fn row_status(self) -> MessageRowStatus {
|
|
73
|
+
match self {
|
|
74
|
+
Self::Accepted => MessageRowStatus::Accepted,
|
|
75
|
+
Self::QueuedUntilLeaderAttach => MessageRowStatus::QueuedUntilLeaderAttach,
|
|
76
|
+
Self::Blocked(DeliveryBlocker::CoordinatorUnavailable) => {
|
|
77
|
+
MessageRowStatus::QueuedCoordinatorUnavailable
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn error(self) -> Option<&'static str> {
|
|
83
|
+
match self {
|
|
84
|
+
Self::Blocked(blocker) => Some(blocker.as_str()),
|
|
85
|
+
Self::Accepted | Self::QueuedUntilLeaderAttach => None,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[derive(Debug, Clone)]
|
|
91
|
+
pub struct ResolvedSendIntent {
|
|
92
|
+
pub origin: SendOrigin,
|
|
93
|
+
pub target_workspace: PathBuf,
|
|
94
|
+
pub owner_team_id: Option<TeamKey>,
|
|
95
|
+
pub recipient: LogicalRecipient,
|
|
96
|
+
pub sender: TrustedSender,
|
|
97
|
+
pub task_id: Option<TaskId>,
|
|
98
|
+
pub content: String,
|
|
99
|
+
pub reply_to: Option<String>,
|
|
100
|
+
pub requires_ack: bool,
|
|
101
|
+
pub requested_message_id: Option<String>,
|
|
102
|
+
pub initial_disposition: InitialDisposition,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
impl ResolvedSendIntent {
|
|
106
|
+
#[allow(clippy::too_many_arguments)]
|
|
107
|
+
pub fn accepted(
|
|
108
|
+
origin: SendOrigin,
|
|
109
|
+
workspace: &Path,
|
|
110
|
+
owner_team_id: Option<TeamKey>,
|
|
111
|
+
recipient: LogicalRecipient,
|
|
112
|
+
sender: TrustedSender,
|
|
113
|
+
task_id: Option<TaskId>,
|
|
114
|
+
content: impl Into<String>,
|
|
115
|
+
reply_to: Option<String>,
|
|
116
|
+
requires_ack: bool,
|
|
117
|
+
requested_message_id: Option<String>,
|
|
118
|
+
) -> Self {
|
|
119
|
+
Self {
|
|
120
|
+
origin,
|
|
121
|
+
target_workspace: workspace.to_path_buf(),
|
|
122
|
+
owner_team_id,
|
|
123
|
+
recipient,
|
|
124
|
+
sender,
|
|
125
|
+
task_id,
|
|
126
|
+
content: content.into(),
|
|
127
|
+
reply_to,
|
|
128
|
+
requires_ack,
|
|
129
|
+
requested_message_id,
|
|
130
|
+
initial_disposition: InitialDisposition::Accepted,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
136
|
+
pub struct PersistedSend {
|
|
137
|
+
pub message_id: String,
|
|
138
|
+
pub owner_team_id: Option<TeamKey>,
|
|
139
|
+
pub recipient: LogicalRecipient,
|
|
140
|
+
pub row_status: MessageRowStatus,
|
|
141
|
+
pub blocker: Option<DeliveryBlocker>,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
145
|
+
pub enum PersistResolution {
|
|
146
|
+
Persisted(PersistedSend),
|
|
147
|
+
Duplicate(String),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// The only production entry point that creates a durable message row.
|
|
151
|
+
pub fn persist_resolved_send(
|
|
152
|
+
intent: &ResolvedSendIntent,
|
|
153
|
+
) -> Result<PersistResolution, MessagingError> {
|
|
154
|
+
let store = MessageStore::open(&intent.target_workspace)?;
|
|
155
|
+
if let Some(requested) = intent.requested_message_id.as_deref() {
|
|
156
|
+
if store.message_exists(requested)? {
|
|
157
|
+
return Ok(PersistResolution::Duplicate(requested.to_string()));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
let status = intent.initial_disposition.row_status();
|
|
161
|
+
let message_id = store.persist_message(PersistMessageInput {
|
|
162
|
+
message_id: intent.requested_message_id.as_deref(),
|
|
163
|
+
owner_team_id: intent.owner_team_id.as_ref().map(TeamKey::as_str),
|
|
164
|
+
task_id: intent.task_id.as_ref().map(TaskId::as_str),
|
|
165
|
+
sender: intent.sender.as_str(),
|
|
166
|
+
recipient: intent.recipient.as_str(),
|
|
167
|
+
reply_to: intent.reply_to.as_deref(),
|
|
168
|
+
requires_ack: intent.requires_ack,
|
|
169
|
+
status,
|
|
170
|
+
content: &intent.content,
|
|
171
|
+
error: intent.initial_disposition.error(),
|
|
172
|
+
})?;
|
|
173
|
+
Ok(PersistResolution::Persisted(PersistedSend {
|
|
174
|
+
message_id,
|
|
175
|
+
owner_team_id: intent.owner_team_id.clone(),
|
|
176
|
+
recipient: intent.recipient.clone(),
|
|
177
|
+
row_status: status,
|
|
178
|
+
blocker: match intent.initial_disposition {
|
|
179
|
+
InitialDisposition::Blocked(blocker) => Some(blocker),
|
|
180
|
+
InitialDisposition::Accepted | InitialDisposition::QueuedUntilLeaderAttach => None,
|
|
181
|
+
},
|
|
182
|
+
}))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[allow(clippy::too_many_arguments)]
|
|
186
|
+
pub(crate) fn persist_internal_send(
|
|
187
|
+
workspace: &Path,
|
|
188
|
+
kind: InternalSendKind,
|
|
189
|
+
owner_team_id: Option<&str>,
|
|
190
|
+
task_id: Option<&str>,
|
|
191
|
+
sender: &str,
|
|
192
|
+
recipient: &str,
|
|
193
|
+
content: &str,
|
|
194
|
+
reply_to: Option<&str>,
|
|
195
|
+
requires_ack: bool,
|
|
196
|
+
requested_message_id: Option<&str>,
|
|
197
|
+
initial_disposition: InitialDisposition,
|
|
198
|
+
) -> Result<PersistResolution, MessagingError> {
|
|
199
|
+
let mut intent = ResolvedSendIntent::accepted(
|
|
200
|
+
SendOrigin::Internal(kind),
|
|
201
|
+
workspace,
|
|
202
|
+
owner_team_id.map(TeamKey::new),
|
|
203
|
+
LogicalRecipient::from_resolved(recipient),
|
|
204
|
+
TrustedSender::from_runtime_identity(AgentId::new(sender)),
|
|
205
|
+
task_id.map(TaskId::new),
|
|
206
|
+
content,
|
|
207
|
+
reply_to.map(ToOwned::to_owned),
|
|
208
|
+
requires_ack,
|
|
209
|
+
requested_message_id.map(ToOwned::to_owned),
|
|
210
|
+
);
|
|
211
|
+
intent.initial_disposition = initial_disposition;
|
|
212
|
+
persist_resolved_send(&intent)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[cfg(test)]
|
|
216
|
+
mod tests {
|
|
217
|
+
use super::*;
|
|
218
|
+
|
|
219
|
+
fn workspace(tag: &str) -> PathBuf {
|
|
220
|
+
let path =
|
|
221
|
+
std::env::temp_dir().join(format!("ta-send-persist-{tag}-{}", std::process::id()));
|
|
222
|
+
std::fs::create_dir_all(&path).unwrap();
|
|
223
|
+
path
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn coordinator_blocker_is_durable_and_not_delivered() {
|
|
228
|
+
let workspace = workspace("coordinator-blocker");
|
|
229
|
+
let mut intent = ResolvedSendIntent::accepted(
|
|
230
|
+
SendOrigin::Cli,
|
|
231
|
+
&workspace,
|
|
232
|
+
Some(TeamKey::new("carc")),
|
|
233
|
+
LogicalRecipient::from_resolved("w1"),
|
|
234
|
+
TrustedSender::leader(),
|
|
235
|
+
None,
|
|
236
|
+
"probe",
|
|
237
|
+
None,
|
|
238
|
+
true,
|
|
239
|
+
None,
|
|
240
|
+
);
|
|
241
|
+
intent.initial_disposition =
|
|
242
|
+
InitialDisposition::Blocked(DeliveryBlocker::CoordinatorUnavailable);
|
|
243
|
+
let PersistResolution::Persisted(persisted) = persist_resolved_send(&intent).unwrap()
|
|
244
|
+
else {
|
|
245
|
+
panic!("fresh intent must persist")
|
|
246
|
+
};
|
|
247
|
+
assert_eq!(
|
|
248
|
+
persisted.row_status,
|
|
249
|
+
MessageRowStatus::QueuedCoordinatorUnavailable
|
|
250
|
+
);
|
|
251
|
+
assert_eq!(
|
|
252
|
+
persisted.blocker,
|
|
253
|
+
Some(DeliveryBlocker::CoordinatorUnavailable)
|
|
254
|
+
);
|
|
255
|
+
let store = MessageStore::open(&workspace).unwrap();
|
|
256
|
+
let connection = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
257
|
+
let (status, delivered_at, error): (String, Option<String>, Option<String>) = connection
|
|
258
|
+
.query_row(
|
|
259
|
+
"select status, delivered_at, error from messages where message_id = ?1",
|
|
260
|
+
[&persisted.message_id],
|
|
261
|
+
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
|
|
262
|
+
)
|
|
263
|
+
.unwrap();
|
|
264
|
+
assert_eq!(status, "queued_coordinator_unavailable");
|
|
265
|
+
assert_eq!(delivered_at, None);
|
|
266
|
+
assert_eq!(error.as_deref(), Some("coordinator_unavailable"));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[test]
|
|
270
|
+
fn production_row_creation_is_owned_by_persisted_primitive() {
|
|
271
|
+
for (name, source) in [
|
|
272
|
+
("send", include_str!("send.rs")),
|
|
273
|
+
("leader_receiver", include_str!("leader_receiver.rs")),
|
|
274
|
+
("delivery", include_str!("delivery.rs")),
|
|
275
|
+
("watchers", include_str!("watchers.rs")),
|
|
276
|
+
("helpers", include_str!("helpers.rs")),
|
|
277
|
+
("selftest", include_str!("selftest.rs")),
|
|
278
|
+
] {
|
|
279
|
+
assert!(
|
|
280
|
+
!source.contains(".create_message(")
|
|
281
|
+
&& !source.contains(".create_message_with_id("),
|
|
282
|
+
"{name} still creates message rows outside persist_resolved_send"
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#[test]
|
|
288
|
+
fn persisted_truth_and_workspace_identity_have_forward_consumers() {
|
|
289
|
+
for (name, source) in [
|
|
290
|
+
("send", include_str!("send.rs")),
|
|
291
|
+
("delivery", include_str!("delivery.rs")),
|
|
292
|
+
("leader_receiver", include_str!("leader_receiver.rs")),
|
|
293
|
+
] {
|
|
294
|
+
assert!(
|
|
295
|
+
source.contains("persisted.row_status"),
|
|
296
|
+
"{name} rebuilds wire truth instead of consuming PersistedSend"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
let watchers = include_str!("watchers.rs");
|
|
300
|
+
assert!(
|
|
301
|
+
!watchers.contains(".and_then(std::path::Path::parent)"),
|
|
302
|
+
"watcher identity must not be derived from the team.db layout"
|
|
303
|
+
);
|
|
304
|
+
assert!(
|
|
305
|
+
watchers.contains("deliver_primary_watcher(\n workspace,"),
|
|
306
|
+
"watcher must forward the caller's canonical workspace"
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -197,27 +197,15 @@ fn collect_scoped(
|
|
|
197
197
|
collected_results.push(summary);
|
|
198
198
|
}
|
|
199
199
|
if state_dirty {
|
|
200
|
-
|
|
201
|
-
crate::state::
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
)?;
|
|
210
|
-
} else {
|
|
211
|
-
crate::state::persist::save_runtime_state_reapplying_after_conflict(
|
|
212
|
-
&paths.run_workspace,
|
|
213
|
-
&state,
|
|
214
|
-
|latest| {
|
|
215
|
-
for (task_id, result_id) in &task_updates {
|
|
216
|
-
mark_task_done(latest, task_id, result_id);
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
)?;
|
|
220
|
-
}
|
|
200
|
+
crate::state::repository::StateRepository::new(&paths.run_workspace).save_reapplying(
|
|
201
|
+
crate::state::repository::StateWriteIntent::ResultCollection { owner_team_id },
|
|
202
|
+
&state,
|
|
203
|
+
|latest| {
|
|
204
|
+
for (task_id, result_id) in &task_updates {
|
|
205
|
+
mark_task_done(latest, task_id, result_id);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
)?;
|
|
221
209
|
}
|
|
222
210
|
let counts = result_counts(&conn, owner_team_id)?;
|
|
223
211
|
// results.py:157 — ensure_coordinator=true runs the REAL ensure step; the
|
|
@@ -870,7 +858,9 @@ fn report_result_for_owner_team_inner(
|
|
|
870
858
|
}
|
|
871
859
|
}
|
|
872
860
|
if !outcome.ok {
|
|
873
|
-
if
|
|
861
|
+
if outcome.channel.as_deref() != Some("leader_acceptance_pending")
|
|
862
|
+
&& !report_state_has_app_server_receiver(&delivery_state)
|
|
863
|
+
{
|
|
874
864
|
let fallback_error = primary_error.unwrap_or_else(|| {
|
|
875
865
|
format!(
|
|
876
866
|
"leader_notification_primary_failed:{}",
|
|
@@ -892,9 +882,11 @@ fn report_result_for_owner_team_inner(
|
|
|
892
882
|
}
|
|
893
883
|
}
|
|
894
884
|
}
|
|
895
|
-
let leader_notified = outcome.
|
|
896
|
-
let notification_status_wire = if
|
|
885
|
+
let leader_notified = matches!(outcome.status, crate::messaging::DeliveryStatus::Delivered);
|
|
886
|
+
let notification_status_wire = if leader_notified {
|
|
897
887
|
"delivered"
|
|
888
|
+
} else if outcome.channel.as_deref() == Some("leader_acceptance_pending") {
|
|
889
|
+
"submitted_pending_acceptance"
|
|
898
890
|
} else if outcome.channel.as_deref() == Some("rebind_required")
|
|
899
891
|
|| matches!(outcome.status, crate::messaging::DeliveryStatus::Blocked)
|
|
900
892
|
{
|
|
@@ -256,8 +256,10 @@ pub fn stuck_cancel(
|
|
|
256
256
|
},
|
|
257
257
|
);
|
|
258
258
|
}
|
|
259
|
-
crate::state::
|
|
260
|
-
|
|
259
|
+
crate::state::repository::StateRepository::new(workspace).save_reapplying(
|
|
260
|
+
crate::state::repository::StateWriteIntent::SchedulerSuppression {
|
|
261
|
+
owner_team_id: Some(&team),
|
|
262
|
+
},
|
|
261
263
|
&state,
|
|
262
264
|
|latest| {
|
|
263
265
|
for kind in &alert_types {
|
|
@@ -129,18 +129,25 @@ fn run_contract_suite(
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
match scratch_store.as_ref().and_then(|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
match scratch_store.as_ref().and_then(|_| {
|
|
133
|
+
match super::persist::persist_internal_send(
|
|
134
|
+
&scratch,
|
|
135
|
+
super::InternalSendKind::Selftest,
|
|
136
|
+
Some("contract-team"),
|
|
137
|
+
None,
|
|
138
|
+
"doctor",
|
|
139
|
+
"worker",
|
|
140
|
+
"comms contract probe",
|
|
141
|
+
None,
|
|
142
|
+
false,
|
|
143
|
+
None,
|
|
144
|
+
super::InitialDisposition::Accepted,
|
|
145
|
+
)
|
|
146
|
+
.ok()?
|
|
147
|
+
{
|
|
148
|
+
super::PersistResolution::Persisted(persisted) => Some(persisted.message_id),
|
|
149
|
+
super::PersistResolution::Duplicate(_) => None,
|
|
150
|
+
}
|
|
144
151
|
}) {
|
|
145
152
|
Some(message_id) if message_id.starts_with("msg_") && message_id.len() > 4 => {
|
|
146
153
|
let rendered = format!(
|