@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +126 -16
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +87 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +199 -89
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/registry.rs +1 -1
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +55 -15
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +54 -24
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/persist.rs +28 -0
- package/crates/team-agent/src/state/repository.rs +8 -3
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +40 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- 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
|
-
(
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
(
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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(
|
|
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 {
|
|
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
|
|
173
|
-
|
|
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(
|
|
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!(
|
|
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)
|
|
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
|
|
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 {
|
|
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(
|
|
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 {
|
|
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(
|
|
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 {
|
|
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!(
|
|
507
|
-
|
|
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
|
|
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)
|
|
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")
|
|
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!(
|
|
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!(
|
|
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(
|
|
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(
|
|
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()
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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())
|
|
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
|
|
723
|
-
|
|
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!(
|
|
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
|
|
730
|
-
|
|
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
|
}
|