@team-agent/installer 0.5.42 → 0.5.44

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.
Files changed (156) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +129 -54
  4. package/crates/team-agent/src/cli/diagnose.rs +1 -2
  5. package/crates/team-agent/src/cli/emit.rs +1 -7
  6. package/crates/team-agent/src/cli/helpers.rs +3 -1
  7. package/crates/team-agent/src/cli/leader.rs +2 -1
  8. package/crates/team-agent/src/cli/mod.rs +126 -16
  9. package/crates/team-agent/src/cli/named_address.rs +14 -5
  10. package/crates/team-agent/src/cli/profile.rs +19 -7
  11. package/crates/team-agent/src/cli/send.rs +9 -3
  12. package/crates/team-agent/src/cli/status.rs +8 -30
  13. package/crates/team-agent/src/cli/status_port.rs +1341 -1272
  14. package/crates/team-agent/src/cli/tests/base.rs +738 -660
  15. package/crates/team-agent/src/cli/tests/compile.rs +45 -18
  16. package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
  21. package/crates/team-agent/src/cli/tests/mod.rs +17 -7
  22. package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
  23. package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
  25. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
  26. package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
  27. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
  28. package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
  29. package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
  30. package/crates/team-agent/src/codex_app_server.rs +2 -5
  31. package/crates/team-agent/src/compiler/tests.rs +139 -33
  32. package/crates/team-agent/src/compiler.rs +55 -22
  33. package/crates/team-agent/src/conpty/backend.rs +23 -33
  34. package/crates/team-agent/src/coordinator/backoff.rs +2 -7
  35. package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
  36. package/crates/team-agent/src/coordinator/health.rs +46 -31
  37. package/crates/team-agent/src/coordinator/mod.rs +3 -3
  38. package/crates/team-agent/src/coordinator/orphan.rs +22 -10
  39. package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
  40. package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
  41. package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
  42. package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
  43. package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
  44. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
  45. package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
  46. package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
  47. package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
  49. package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
  50. package/crates/team-agent/src/db/message_store.rs +138 -30
  51. package/crates/team-agent/src/db/migration.rs +249 -61
  52. package/crates/team-agent/src/db/schema.rs +303 -82
  53. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  54. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  55. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  56. package/crates/team-agent/src/event_log.rs +70 -16
  57. package/crates/team-agent/src/layout/manager.rs +15 -4
  58. package/crates/team-agent/src/layout/mod.rs +4 -4
  59. package/crates/team-agent/src/layout/overlay.rs +10 -3
  60. package/crates/team-agent/src/layout/placement.rs +5 -1
  61. package/crates/team-agent/src/layout/recovery.rs +4 -2
  62. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  63. package/crates/team-agent/src/layout/sessions.rs +17 -9
  64. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  65. package/crates/team-agent/src/layout/worker_env.rs +87 -19
  66. package/crates/team-agent/src/leader/helpers.rs +7 -1
  67. package/crates/team-agent/src/leader/lease.rs +199 -89
  68. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  69. package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
  70. package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
  71. package/crates/team-agent/src/leader/rediscover.rs +74 -25
  72. package/crates/team-agent/src/leader/registry.rs +1 -1
  73. package/crates/team-agent/src/leader/start.rs +75 -54
  74. package/crates/team-agent/src/leader/takeover.rs +46 -11
  75. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  76. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  77. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  78. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  79. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  80. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  81. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  82. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  83. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  84. package/crates/team-agent/src/lib.rs +4 -4
  85. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  86. package/crates/team-agent/src/lifecycle/launch.rs +55 -15
  87. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  88. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  89. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  90. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  91. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
  92. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  93. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  94. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  95. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  96. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  97. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  98. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  99. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  100. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
  103. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  104. package/crates/team-agent/src/main.rs +4 -4
  105. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  106. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  107. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  108. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  109. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  110. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  112. package/crates/team-agent/src/messaging/mod.rs +2 -3
  113. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  114. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  115. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  116. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  117. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  118. package/crates/team-agent/src/messaging/trust.rs +25 -2
  119. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  120. package/crates/team-agent/src/model/enums.rs +65 -16
  121. package/crates/team-agent/src/model/ids.rs +12 -3
  122. package/crates/team-agent/src/model/paths.rs +28 -7
  123. package/crates/team-agent/src/model/permissions.rs +176 -33
  124. package/crates/team-agent/src/model/routing.rs +66 -20
  125. package/crates/team-agent/src/model/spec.rs +365 -69
  126. package/crates/team-agent/src/model/task_graph.rs +36 -9
  127. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  128. package/crates/team-agent/src/model/yaml.rs +7 -6
  129. package/crates/team-agent/src/packaging/install.rs +23 -9
  130. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  131. package/crates/team-agent/src/packaging/mod.rs +9 -1
  132. package/crates/team-agent/src/packaging/repair.rs +13 -6
  133. package/crates/team-agent/src/packaging/tests.rs +63 -16
  134. package/crates/team-agent/src/packaging/types.rs +22 -7
  135. package/crates/team-agent/src/platform/argv.rs +4 -1
  136. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  137. package/crates/team-agent/src/platform/process.rs +54 -24
  138. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  139. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  140. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  141. package/crates/team-agent/src/provider/classify.rs +127 -42
  142. package/crates/team-agent/src/provider/faults.rs +17 -5
  143. package/crates/team-agent/src/provider/helpers.rs +6 -5
  144. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  145. package/crates/team-agent/src/state/persist.rs +28 -0
  146. package/crates/team-agent/src/state/repository.rs +8 -3
  147. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  148. package/crates/team-agent/src/tmux_backend.rs +80 -44
  149. package/crates/team-agent/src/topology.rs +40 -20
  150. package/crates/team-agent/src/transport/test_support.rs +20 -23
  151. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  152. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  153. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  154. package/crates/team-agent/src/transport.rs +12 -17
  155. package/crates/team-agent/src/transport_factory.rs +29 -14
  156. package/package.json +4 -4
@@ -14,33 +14,109 @@ use crate::db::DbError;
14
14
 
15
15
  /// canonical 列序(`schema_migration.py:MANAGED_TABLE_LAYOUTS`)。
16
16
  pub const MANAGED_TABLE_LAYOUTS: &[(&str, &[&str])] = &[
17
- ("messages", &[
18
- "message_id", "owner_team_id", "task_id", "sender", "recipient", "reply_to", "requires_ack",
19
- "status", "content", "artifact_refs", "created_at", "updated_at", "delivered_at",
20
- "acknowledged_at", "error", "delivery_attempts",
21
- ]),
22
- ("results", &["result_id", "owner_team_id", "task_id", "agent_id", "envelope", "status", "created_at"]),
23
- ("scheduled_events", &[
24
- "id", "owner_team_id", "due_at", "target", "kind", "payload_json", "status", "created_at",
25
- "fired_at", "result_json",
26
- ]),
27
- ("delivery_tokens", &[
28
- "message_id", "unique_token", "injected_at", "visible_at", "consumed_at", "failed_at",
29
- "failure_reason",
30
- ]),
31
- ("agent_health", &[
32
- "owner_team_id", "agent_id", "status", "last_output_at", "context_usage_pct", "current_task_id",
33
- "updated_at",
34
- ]),
17
+ (
18
+ "messages",
19
+ &[
20
+ "message_id",
21
+ "owner_team_id",
22
+ "task_id",
23
+ "sender",
24
+ "recipient",
25
+ "reply_to",
26
+ "requires_ack",
27
+ "status",
28
+ "content",
29
+ "artifact_refs",
30
+ "created_at",
31
+ "updated_at",
32
+ "delivered_at",
33
+ "acknowledged_at",
34
+ "error",
35
+ "delivery_attempts",
36
+ ],
37
+ ),
38
+ (
39
+ "results",
40
+ &[
41
+ "result_id",
42
+ "owner_team_id",
43
+ "task_id",
44
+ "agent_id",
45
+ "envelope",
46
+ "status",
47
+ "created_at",
48
+ ],
49
+ ),
50
+ (
51
+ "scheduled_events",
52
+ &[
53
+ "id",
54
+ "owner_team_id",
55
+ "due_at",
56
+ "target",
57
+ "kind",
58
+ "payload_json",
59
+ "status",
60
+ "created_at",
61
+ "fired_at",
62
+ "result_json",
63
+ ],
64
+ ),
65
+ (
66
+ "delivery_tokens",
67
+ &[
68
+ "message_id",
69
+ "unique_token",
70
+ "injected_at",
71
+ "visible_at",
72
+ "consumed_at",
73
+ "failed_at",
74
+ "failure_reason",
75
+ ],
76
+ ),
77
+ (
78
+ "agent_health",
79
+ &[
80
+ "owner_team_id",
81
+ "agent_id",
82
+ "status",
83
+ "last_output_at",
84
+ "context_usage_pct",
85
+ "current_task_id",
86
+ "updated_at",
87
+ ],
88
+ ),
35
89
  ("peer_allowlist", &["a", "b", "created_at"]),
36
- ("result_watchers", &[
37
- "watcher_id", "owner_team_id", "task_id", "agent_id", "message_id", "leader_id", "status",
38
- "created_at", "completed_at", "result_id", "notified_message_id", "error",
39
- ]),
40
- ("leader_notification_log", &[
41
- "result_id", "owner_team_id", "owner_epoch", "leader_session_uuid", "notified_message_id",
42
- "notified_at", "leader_pane_id_at_notify", "envelope_content_hash",
43
- ]),
90
+ (
91
+ "result_watchers",
92
+ &[
93
+ "watcher_id",
94
+ "owner_team_id",
95
+ "task_id",
96
+ "agent_id",
97
+ "message_id",
98
+ "leader_id",
99
+ "status",
100
+ "created_at",
101
+ "completed_at",
102
+ "result_id",
103
+ "notified_message_id",
104
+ "error",
105
+ ],
106
+ ),
107
+ (
108
+ "leader_notification_log",
109
+ &[
110
+ "result_id",
111
+ "owner_team_id",
112
+ "owner_epoch",
113
+ "leader_session_uuid",
114
+ "notified_message_id",
115
+ "notified_at",
116
+ "leader_pane_id_at_notify",
117
+ "envelope_content_hash",
118
+ ],
119
+ ),
44
120
  ];
45
121
 
46
122
  /// rebuild / 建缺表用的 DDL 模板(`schema_migration.py:CREATE_TABLE_SQL`,`__TABLE__` 占位)。
@@ -134,7 +210,9 @@ fn maybe_test_fault_after_insert() -> Result<(), DbError> {
134
210
  #[cfg(test)]
135
211
  {
136
212
  if FAULT_AFTER_INSERT.with(std::cell::Cell::get) {
137
- return Err(DbError::Schema("injected test fault after insert (atomicity probe)".to_string()));
213
+ return Err(DbError::Schema(
214
+ "injected test fault after insert (atomicity probe)".to_string(),
215
+ ));
138
216
  }
139
217
  }
140
218
  Ok(())
@@ -165,12 +243,26 @@ pub fn layout_diffs(conn: &Connection) -> Result<Vec<Diff>, DbError> {
165
243
  let mut diffs = Vec::new();
166
244
  for (table, expected) in MANAGED_TABLE_LAYOUTS {
167
245
  if !table_exists(conn, table)? {
168
- diffs.push(Diff { table, expected: expected.to_vec(), actual: vec![], missing: true });
246
+ diffs.push(Diff {
247
+ table,
248
+ expected: expected.to_vec(),
249
+ actual: vec![],
250
+ missing: true,
251
+ });
169
252
  continue;
170
253
  }
171
254
  let actual = table_layout(conn, table)?;
172
- if !actual.iter().map(String::as_str).eq(expected.iter().copied()) {
173
- diffs.push(Diff { table, expected: expected.to_vec(), actual, missing: false });
255
+ if !actual
256
+ .iter()
257
+ .map(String::as_str)
258
+ .eq(expected.iter().copied())
259
+ {
260
+ diffs.push(Diff {
261
+ table,
262
+ expected: expected.to_vec(),
263
+ actual,
264
+ missing: false,
265
+ });
174
266
  }
175
267
  }
176
268
  Ok(diffs)
@@ -229,7 +321,10 @@ fn rebuild_tables(
229
321
  .filter(|c| diff.actual.iter().any(|a| a == c))
230
322
  .collect();
231
323
  let column_sql = common.join(", ");
232
- conn.execute(&format!("insert into {temp}({column_sql}) select {column_sql} from {table}"), [])?;
324
+ conn.execute(
325
+ &format!("insert into {temp}({column_sql}) select {column_sql} from {table}"),
326
+ [],
327
+ )?;
233
328
  maybe_test_fault_after_insert()?; // insert 后、rename 前注入点(原子性探针)
234
329
  conn.execute(&format!("alter table {table} rename to {old}"), [])?;
235
330
  conn.execute(&format!("alter table {temp} rename to {table}"), [])?;
@@ -256,7 +351,9 @@ fn rebuild_tables(
256
351
 
257
352
  fn backup_path(db_path: &Path, user_version: i64) -> PathBuf {
258
353
  let stamp = chrono::Utc::now().format("%Y%m%dT%H%M%SZ");
259
- db_path.with_file_name(format!("team.db.pre-migration-{stamp}-from-v{user_version}.bak"))
354
+ db_path.with_file_name(format!(
355
+ "team.db.pre-migration-{stamp}-from-v{user_version}.bak"
356
+ ))
260
357
  }
261
358
 
262
359
  /// `schema_migration.py:ensure_table_layout`:版本迁移 + (有漂移则)备份 + 整表 rebuild,
@@ -288,7 +385,8 @@ pub fn ensure_table_layout(
288
385
  let uv = pragma_user_version(conn)?;
289
386
  let backup = backup_path(&dbp, uv);
290
387
  if let Some(parent) = backup.parent() {
291
- std::fs::create_dir_all(parent).map_err(|e| DbError::Schema(format!("backup mkdir: {e}")))?;
388
+ std::fs::create_dir_all(parent)
389
+ .map_err(|e| DbError::Schema(format!("backup mkdir: {e}")))?;
292
390
  }
293
391
  std::fs::copy(dbp, &backup).map_err(|e| DbError::Schema(format!("backup copy: {e}")))?;
294
392
  rebuild_tables(conn, &diffs, &backup)
@@ -301,7 +399,9 @@ pub fn ensure_table_layout(
301
399
  // Gap 46 契约:每次 rebuild 在 COMMIT 后发射 schema.layout_rebuild 事件(step 3↔4 集成点)。
302
400
  // 回滚的 rebuild 不发(post-commit 语义)。best-effort,不让审计失败拖垮迁移。
303
401
  if !events.is_empty() {
304
- let resolved = db_path.map(Path::to_path_buf).or_else(|| db_path_from_conn(conn));
402
+ let resolved = db_path
403
+ .map(Path::to_path_buf)
404
+ .or_else(|| db_path_from_conn(conn));
305
405
  if let Some(p) = resolved {
306
406
  emit_rebuild_events(&p, &events);
307
407
  }
@@ -330,8 +430,7 @@ pub fn schema_diagnosis(db_path: &Path, schema_version: i64) -> Result<Diagnosis
330
430
  user_version: 0,
331
431
  layout_diffs: vec![],
332
432
  recommended_action:
333
- "No team.db exists yet; initialize_schema will create it on first use."
334
- .to_string(),
433
+ "No team.db exists yet; initialize_schema will create it on first use.".to_string(),
335
434
  });
336
435
  }
337
436
  let conn = Connection::open(db_path)?;
@@ -341,7 +440,11 @@ pub fn schema_diagnosis(db_path: &Path, schema_version: i64) -> Result<Diagnosis
341
440
  let ok = diffs.is_empty() && uv == schema_version;
342
441
  Ok(Diagnosis {
343
442
  ok,
344
- status: if ok { "ok".to_string() } else { "schema_repair_available".to_string() },
443
+ status: if ok {
444
+ "ok".to_string()
445
+ } else {
446
+ "schema_repair_available".to_string()
447
+ },
345
448
  user_version: uv,
346
449
  recommended_action: if diff_tables.is_empty() {
347
450
  "none".to_string()
@@ -354,7 +457,10 @@ pub fn schema_diagnosis(db_path: &Path, schema_version: i64) -> Result<Diagnosis
354
457
 
355
458
  /// 便捷:对一个 workspace 的 `.team/runtime/team.db` 跑 diagnosis(对应 Python 签名)。
356
459
  pub fn schema_diagnosis_workspace(workspace: &Path) -> Result<Diagnosis, DbError> {
357
- schema_diagnosis(&workspace.join(".team").join("runtime").join("team.db"), SCHEMA_VERSION)
460
+ schema_diagnosis(
461
+ &workspace.join(".team").join("runtime").join("team.db"),
462
+ SCHEMA_VERSION,
463
+ )
358
464
  }
359
465
 
360
466
  /// `schema_migration.py:_workspace_from_db_path`:`<ws>/.team/runtime/team.db` → `<ws>`。
@@ -398,7 +504,10 @@ pub enum FixResult {
398
504
  /// 撞活跃锁 → 拒绝且**不写备份**(`status:'blocked', reason:'active_lock'`)。
399
505
  Blocked { reason: String },
400
506
  /// 已修复 → diagnosis(ok)+ rebuild 事件。
401
- Fixed { diagnosis: Diagnosis, rebuilds: Vec<RebuildEvent> },
507
+ Fixed {
508
+ diagnosis: Diagnosis,
509
+ rebuilds: Vec<RebuildEvent>,
510
+ },
402
511
  }
403
512
 
404
513
  /// `schema_migration.py:_db_lock_status`:0-timeout `BEGIN IMMEDIATE` 探锁;locked/busy → `active_lock`。
@@ -426,7 +535,10 @@ fn db_lock_status(db_path: &Path) -> Result<Option<String>, DbError> {
426
535
  pub fn fix_schema_layout(workspace: &Path, schema_version: i64) -> Result<FixResult, DbError> {
427
536
  let db_path = workspace.join(".team").join("runtime").join("team.db");
428
537
  if !db_path.exists() {
429
- return Ok(FixResult::Missing(schema_diagnosis(&db_path, schema_version)?));
538
+ return Ok(FixResult::Missing(schema_diagnosis(
539
+ &db_path,
540
+ schema_version,
541
+ )?));
430
542
  }
431
543
  if let Some(reason) = db_lock_status(&db_path)? {
432
544
  let _ = crate::event_log::EventLog::new(workspace).write(
@@ -441,7 +553,10 @@ pub fn fix_schema_layout(workspace: &Path, schema_version: i64) -> Result<FixRes
441
553
  conn.execute_batch(&format!("pragma user_version = {schema_version}"))?;
442
554
  drop(conn);
443
555
  let diagnosis = schema_diagnosis(&db_path, schema_version)?;
444
- Ok(FixResult::Fixed { diagnosis, rebuilds })
556
+ Ok(FixResult::Fixed {
557
+ diagnosis,
558
+ rebuilds,
559
+ })
445
560
  }
446
561
 
447
562
  #[cfg(test)]
@@ -503,14 +618,26 @@ mod tests {
503
618
  initialize_schema(&conn, Some(&path)).unwrap();
504
619
 
505
620
  // 迁移后:4 表 canonical 列序 + 行数保全。
506
- assert_eq!(layout(&conn, "messages")[..3], ["message_id", "owner_team_id", "task_id"]);
507
- assert_eq!(layout(&conn, "agent_health")[..2], ["owner_team_id", "agent_id"]);
621
+ assert_eq!(
622
+ layout(&conn, "messages")[..3],
623
+ ["message_id", "owner_team_id", "task_id"]
624
+ );
625
+ assert_eq!(
626
+ layout(&conn, "agent_health")[..2],
627
+ ["owner_team_id", "agent_id"]
628
+ );
508
629
  assert_eq!(table_count(&conn, "messages").unwrap(), 2);
509
630
  assert_eq!(table_count(&conn, "results").unwrap(), 2);
510
631
  assert_eq!(table_count(&conn, "scheduled_events").unwrap(), 2);
511
632
  assert_eq!(table_count(&conn, "agent_health").unwrap(), 2);
512
633
  // 行内容保全(owner_team_id 迁移后仍可读)。
513
- let m1: String = conn.query_row("select owner_team_id from messages where message_id='m1'", [], |r| r.get(0)).unwrap();
634
+ let m1: String = conn
635
+ .query_row(
636
+ "select owner_team_id from messages where message_id='m1'",
637
+ [],
638
+ |r| r.get(0),
639
+ )
640
+ .unwrap();
514
641
  assert_eq!(m1, "t");
515
642
  drop(conn);
516
643
 
@@ -522,7 +649,8 @@ mod tests {
522
649
 
523
650
  // 备份文件已写(team.db.pre-migration-*-from-v1.bak)。
524
651
  let runtime = path.parent().unwrap();
525
- let baks: Vec<_> = std::fs::read_dir(runtime).unwrap()
652
+ let baks: Vec<_> = std::fs::read_dir(runtime)
653
+ .unwrap()
526
654
  .filter_map(|e| e.ok())
527
655
  .filter(|e| {
528
656
  let n = e.file_name().to_string_lossy().to_string();
@@ -583,7 +711,10 @@ mod tests {
583
711
  drop(conn);
584
712
  let conn2 = Connection::open(&path).unwrap();
585
713
  assert_eq!(
586
- table_layout(&conn2, "messages").unwrap().last().map(String::as_str),
714
+ table_layout(&conn2, "messages")
715
+ .unwrap()
716
+ .last()
717
+ .map(String::as_str),
587
718
  Some("owner_team_id"),
588
719
  "回滚后 messages 应仍是 legacy 布局(owner_team_id 末列)"
589
720
  );
@@ -605,7 +736,10 @@ mod tests {
605
736
  let conn = crate::db::schema::open_db(&path).unwrap();
606
737
  let events = ensure_table_layout(&conn, SCHEMA_VERSION, None).unwrap(); // None 不再 Err
607
738
  assert_eq!(events.len(), 8);
608
- assert_eq!(table_layout(&conn, "messages").unwrap()[..2], ["message_id", "owner_team_id"]);
739
+ assert_eq!(
740
+ table_layout(&conn, "messages").unwrap()[..2],
741
+ ["message_id", "owner_team_id"]
742
+ );
609
743
  }
610
744
 
611
745
  // agent_health 完全缺 owner_team_id 列 → generic rebuild 补 NULL 列、行保全。
@@ -622,10 +756,17 @@ mod tests {
622
756
  drop(c);
623
757
  let conn = crate::db::schema::open_db(&path).unwrap();
624
758
  initialize_schema(&conn, Some(&path)).unwrap();
625
- assert_eq!(table_layout(&conn, "agent_health").unwrap()[0], "owner_team_id");
759
+ assert_eq!(
760
+ table_layout(&conn, "agent_health").unwrap()[0],
761
+ "owner_team_id"
762
+ );
626
763
  assert_eq!(table_count(&conn, "agent_health").unwrap(), 1);
627
764
  let owner: Option<String> = conn
628
- .query_row("select owner_team_id from agent_health where agent_id='a'", [], |r| r.get(0))
765
+ .query_row(
766
+ "select owner_team_id from agent_health where agent_id='a'",
767
+ [],
768
+ |r| r.get(0),
769
+ )
629
770
  .unwrap();
630
771
  assert_eq!(owner, None, "补的 owner_team_id 列应为 NULL");
631
772
  }
@@ -662,7 +803,11 @@ mod tests {
662
803
  let conn = crate::db::schema::open_db(&path).unwrap();
663
804
  initialize_schema(&conn, Some(&path)).unwrap();
664
805
  let (content, status): (String, Option<String>) = conn
665
- .query_row("select content, status from messages where message_id='u1'", [], |r| Ok((r.get(0)?, r.get(1)?)))
806
+ .query_row(
807
+ "select content, status from messages where message_id='u1'",
808
+ [],
809
+ |r| Ok((r.get(0)?, r.get(1)?)),
810
+ )
666
811
  .unwrap();
667
812
  assert_eq!(content, "héllo🦀\n世界");
668
813
  assert_eq!(status, None);
@@ -671,7 +816,14 @@ mod tests {
671
816
  #[test]
672
817
  fn fix_schema_layout_missing_blocked_fixed() {
673
818
  // missing:无 db。
674
- let ws_missing = temp_db().parent().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
819
+ let ws_missing = temp_db()
820
+ .parent()
821
+ .unwrap()
822
+ .parent()
823
+ .unwrap()
824
+ .parent()
825
+ .unwrap()
826
+ .to_path_buf();
675
827
  match fix_schema_layout(&ws_missing, SCHEMA_VERSION).unwrap() {
676
828
  FixResult::Missing(d) => assert_eq!(d.status, "missing"),
677
829
  other => panic!("expected Missing, got {other:?}"),
@@ -680,9 +832,19 @@ mod tests {
680
832
  // fixed:legacy db。workspace = .../<dir>(db 在 ws/.team/runtime/team.db)。
681
833
  let db = temp_db();
682
834
  build_legacy(&db);
683
- let ws = db.parent().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
835
+ let ws = db
836
+ .parent()
837
+ .unwrap()
838
+ .parent()
839
+ .unwrap()
840
+ .parent()
841
+ .unwrap()
842
+ .to_path_buf();
684
843
  match fix_schema_layout(&ws, SCHEMA_VERSION).unwrap() {
685
- FixResult::Fixed { diagnosis, rebuilds } => {
844
+ FixResult::Fixed {
845
+ diagnosis,
846
+ rebuilds,
847
+ } => {
686
848
  assert!(diagnosis.ok);
687
849
  assert_eq!(diagnosis.user_version, 3);
688
850
  assert_eq!(rebuilds.len(), 8);
@@ -693,16 +855,26 @@ mod tests {
693
855
  // blocked:另一个连接持 BEGIN IMMEDIATE 写锁时 fix 应拒绝(不写备份)。
694
856
  let db2 = temp_db();
695
857
  build_legacy(&db2);
696
- let ws2 = db2.parent().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
858
+ let ws2 = db2
859
+ .parent()
860
+ .unwrap()
861
+ .parent()
862
+ .unwrap()
863
+ .parent()
864
+ .unwrap()
865
+ .to_path_buf();
697
866
  let holder = Connection::open(&db2).unwrap();
698
- holder.busy_timeout(std::time::Duration::from_secs(5)).unwrap();
867
+ holder
868
+ .busy_timeout(std::time::Duration::from_secs(5))
869
+ .unwrap();
699
870
  holder.execute_batch("BEGIN IMMEDIATE").unwrap(); // 占写锁
700
871
  match fix_schema_layout(&ws2, SCHEMA_VERSION).unwrap() {
701
872
  FixResult::Blocked { reason } => assert_eq!(reason, "active_lock"),
702
873
  other => panic!("expected Blocked, got {other:?}"),
703
874
  }
704
875
  // 撞锁时不应写任何备份。
705
- let baks = std::fs::read_dir(db2.parent().unwrap()).unwrap()
876
+ let baks = std::fs::read_dir(db2.parent().unwrap())
877
+ .unwrap()
706
878
  .filter_map(|e| e.ok())
707
879
  .filter(|e| e.file_name().to_string_lossy().contains("pre-migration"))
708
880
  .count();
@@ -719,14 +891,30 @@ mod tests {
719
891
  initialize_schema(&conn, Some(&path)).unwrap();
720
892
  let ws = path.parent().unwrap().parent().unwrap().parent().unwrap();
721
893
  let events = crate::event_log::EventLog::new(ws).tail(50).unwrap();
722
- let rebuilds: Vec<_> = events.iter().filter(|e| e["event"] == serde_json::json!("schema.layout_rebuild")).collect();
723
- assert_eq!(rebuilds.len(), 8, "8 张 managed 表各一条 schema.layout_rebuild");
894
+ let rebuilds: Vec<_> = events
895
+ .iter()
896
+ .filter(|e| e["event"] == serde_json::json!("schema.layout_rebuild"))
897
+ .collect();
898
+ assert_eq!(
899
+ rebuilds.len(),
900
+ 8,
901
+ "8 张 managed 表各一条 schema.layout_rebuild"
902
+ );
724
903
  for e in &rebuilds {
725
- assert_eq!(e["row_count_before"], e["row_count_after"], "事件须 row_count 前后相等");
904
+ assert_eq!(
905
+ e["row_count_before"], e["row_count_after"],
906
+ "事件须 row_count 前后相等"
907
+ );
726
908
  assert!(e["backup_path"].as_str().is_some());
727
909
  }
728
910
  // 含 messages 那条,to_layout_columns 是 canonical。
729
- let m = rebuilds.iter().find(|e| e["table"] == serde_json::json!("messages")).unwrap();
730
- assert_eq!(m["to_layout_columns"][1], serde_json::json!("owner_team_id"));
911
+ let m = rebuilds
912
+ .iter()
913
+ .find(|e| e["table"] == serde_json::json!("messages"))
914
+ .unwrap();
915
+ assert_eq!(
916
+ m["to_layout_columns"][1],
917
+ serde_json::json!("owner_team_id")
918
+ );
731
919
  }
732
920
  }