@team-agent/installer 0.5.110 → 0.5.112

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 (54) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +231 -812
  4. package/crates/team-agent/src/cli/diagnose.rs +2 -424
  5. package/crates/team-agent/src/cli/emit.rs +493 -451
  6. package/crates/team-agent/src/cli/leader.rs +4 -9
  7. package/crates/team-agent/src/cli/leaders.rs +90 -29
  8. package/crates/team-agent/src/cli/mod.rs +348 -169
  9. package/crates/team-agent/src/cli/models.rs +80 -24
  10. package/crates/team-agent/src/cli/named_address.rs +114 -25
  11. package/crates/team-agent/src/cli/route.rs +10 -5
  12. package/crates/team-agent/src/cli/send/presentation.rs +60 -41
  13. package/crates/team-agent/src/cli/send/resolve.rs +25 -18
  14. package/crates/team-agent/src/cli/send.rs +4 -4
  15. package/crates/team-agent/src/cli/spec.rs +53 -109
  16. package/crates/team-agent/src/cli/tests/base.rs +189 -82
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +1 -2
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +2 -86
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
  21. package/crates/team-agent/src/cli/tests/mod.rs +0 -2
  22. package/crates/team-agent/src/cli/tests/named_address.rs +19 -22
  23. package/crates/team-agent/src/cli/tests/status_send.rs +15 -10
  24. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +2 -2
  25. package/crates/team-agent/src/cli/types.rs +26 -121
  26. package/crates/team-agent/src/coordinator/health.rs +3 -283
  27. package/crates/team-agent/src/coordinator/mod.rs +0 -5
  28. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
  29. package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
  30. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
  31. package/crates/team-agent/src/coordinator/types.rs +1 -21
  32. package/crates/team-agent/src/db/message_store.rs +0 -42
  33. package/crates/team-agent/src/db/migration.rs +49 -9
  34. package/crates/team-agent/src/db/schema.rs +27 -20
  35. package/crates/team-agent/src/leader/owner_bind.rs +0 -24
  36. package/crates/team-agent/src/leader/registry.rs +3 -3
  37. package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
  38. package/crates/team-agent/src/leader/tests/identity.rs +0 -32
  39. package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
  40. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +4 -9
  41. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -51
  42. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -1
  43. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
  44. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
  45. package/crates/team-agent/src/messaging/mod.rs +0 -2
  46. package/crates/team-agent/src/messaging/results.rs +0 -58
  47. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
  48. package/crates/team-agent/src/state/repository.rs +1 -5
  49. package/package.json +4 -4
  50. package/templates/team_state.md +1 -1
  51. package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
  52. package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
  53. package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
  54. package/crates/team-agent/src/messaging/peers.rs +0 -34
@@ -20,9 +20,8 @@
20
20
  //! ---
21
21
  //!
22
22
  //! coordinator 共享数据面:常量 / newtype / 穷尽 enum / metadata / schema health /
23
- //! report 结构 / abnormal-track 数据型 / WatchCursor / cross-dep 占位。
23
+ //! report 结构 / abnormal-track 数据型 / cross-dep 占位。
24
24
 
25
- use std::collections::BTreeSet;
26
25
  use std::path::{Path, PathBuf};
27
26
 
28
27
  use serde::{Deserialize, Serialize};
@@ -49,10 +48,6 @@ pub const DEFAULT_TICK_INTERVAL_SEC: f64 = 5.0;
49
48
  /// 指数退避封顶(`__main__.py:65` `min(.., 60.0)`)。bug-084 崩溃循环防护。
50
49
  pub const BACKOFF_MAX_SEC: f64 = 60.0;
51
50
 
52
- /// watch log rotation marker(`watch.py:22`,字节级一致 —— 测试钉死)。
53
- pub const ROTATION_MARKER: &str =
54
- "[watch] log rotated; archived segment events.jsonl.1 not replayed — historical replay deferred to a future --replay flag";
55
-
56
51
  // ===========================================================================
57
52
  // NEWTYPES(§3:Pid / WorkspacePath 不与裸 int/PathBuf 混传)
58
53
  // ===========================================================================
@@ -445,21 +440,6 @@ pub struct TeamPresenceSnapshot {
445
440
  pub restart_in_progress: bool,
446
441
  }
447
442
 
448
- // ===========================================================================
449
- // WatchCursor(watch/__init__.py)
450
- // ===========================================================================
451
-
452
- /// watch tail 游标(`watch.py:14-19`)。rotation 检测靠 `archive_signature` 变化 + `offset > size`
453
- /// (`watch.py:73-83`)。`archive_signature = Some((size, mtime_ns))`。
454
- #[derive(Debug, Clone, Default, PartialEq, Eq)]
455
- pub struct WatchCursor {
456
- pub event_offset: u64,
457
- pub seen_result_ids: BTreeSet<String>,
458
- pub initialized: bool,
459
- /// `(size, mtime_ns)`;`mtime_ns: i128`(防溢出)。
460
- pub archive_signature: Option<(u64, i128)>,
461
- }
462
-
463
443
  // ===========================================================================
464
444
  // CROSS-DEP placeholders(其他并行 lane 拥有真名;leader 集成时 reconcile)
465
445
  // ===========================================================================
@@ -830,22 +830,6 @@ impl MessageStore {
830
830
  Ok(rows.into_iter().rev().collect())
831
831
  }
832
832
 
833
- /// Allow direct peer messages in both directions. Golden stores `(a,b)` and
834
- /// `(b,a)` so either sender/recipient lookup can use a single ordered key.
835
- pub fn allow_peer(&self, a: &str, b: &str) -> Result<(), MessageStoreError> {
836
- let conn = crate::db::schema::open_db(&self.path)?;
837
- let now = now_ts();
838
- conn.execute(
839
- "insert or ignore into peer_allowlist(a, b, created_at) values (?1, ?2, ?3)",
840
- params![a, b, now.as_str()],
841
- )?;
842
- conn.execute(
843
- "insert or ignore into peer_allowlist(a, b, created_at) values (?1, ?2, ?3)",
844
- params![b, a, now.as_str()],
845
- )?;
846
- Ok(())
847
- }
848
-
849
833
  /// `claim_leader_notification_delivery` (`leader_notification_log.py:30-101`):
850
834
  /// `INSERT OR IGNORE` on PK `(result_id, owner_team_id, owner_epoch)`. rowcount==1
851
835
  /// → `claimed_by_you`; else read the existing winner row → `already_notified_by`.
@@ -1421,32 +1405,6 @@ mod tests {
1421
1405
  assert_eq!(legacy_epoch_from_uuid(Some("uuid-BBB")), 122688376);
1422
1406
  }
1423
1407
 
1424
- #[test]
1425
- fn allow_peer_inserts_bidirectional_rows_idempotently() {
1426
- let s = store();
1427
- s.allow_peer("alice", "bob").unwrap();
1428
- s.allow_peer("alice", "bob").unwrap();
1429
-
1430
- let c = read(&s);
1431
- let mut rows = c
1432
- .prepare("select a, b from peer_allowlist order by a, b")
1433
- .unwrap()
1434
- .query_map([], |row| {
1435
- Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
1436
- })
1437
- .unwrap()
1438
- .collect::<Result<Vec<_>, _>>()
1439
- .unwrap();
1440
- rows.sort();
1441
- assert_eq!(
1442
- rows,
1443
- vec![
1444
- ("alice".to_string(), "bob".to_string()),
1445
- ("bob".to_string(), "alice".to_string()),
1446
- ]
1447
- );
1448
- }
1449
-
1450
1408
  // ════════════════════════ FIX-LOOP (wave-1) RED test ════════════════════════
1451
1409
  // B1: mark() error column = coalesce(?, error) — a NEW error overwrites, but a
1452
1410
  // mark WITHOUT an error must PRESERVE the existing error (current impl clobbers
@@ -88,7 +88,6 @@ pub const MANAGED_TABLE_LAYOUTS: &[(&str, &[&str])] = &[
88
88
  "updated_at",
89
89
  ],
90
90
  ),
91
- ("peer_allowlist", &["a", "b", "created_at"]),
92
91
  (
93
92
  "result_watchers",
94
93
  &[
@@ -129,7 +128,6 @@ const CREATE_TABLE_TEMPLATES: &[(&str, &str)] = &[
129
128
  ("scheduled_events", "create table if not exists __TABLE__ (\n id integer primary key,\n owner_team_id text,\n due_at text not null,\n target text not null,\n kind text not null,\n payload_json text not null,\n status text not null,\n created_at text not null,\n fired_at text,\n result_json text\n )"),
130
129
  ("delivery_tokens", "create table if not exists __TABLE__ (\n message_id text primary key,\n unique_token text not null,\n injected_at text not null,\n visible_at text,\n consumed_at text,\n failed_at text,\n failure_reason text\n )"),
131
130
  ("agent_health", "create table if not exists __TABLE__ (\n owner_team_id text,\n agent_id text not null,\n status text not null,\n last_output_at text,\n context_usage_pct integer,\n current_task_id text,\n updated_at text not null,\n unique(owner_team_id, agent_id)\n )"),
132
- ("peer_allowlist", "create table if not exists __TABLE__ (\n a text not null,\n b text not null,\n created_at text not null,\n primary key (a, b)\n )"),
133
131
  ("result_watchers", "create table if not exists __TABLE__ (\n watcher_id text primary key,\n owner_team_id text,\n task_id text,\n agent_id text,\n message_id text,\n leader_id text not null,\n recipient text,\n status text not null,\n created_at text not null,\n completed_at text,\n result_id text,\n notified_message_id text,\n error text\n )"),
134
132
  ("leader_notification_log", "create table if not exists __TABLE__ (\n result_id text not null,\n owner_team_id text not null default '',\n owner_epoch integer not null default 0,\n leader_session_uuid text,\n notified_message_id text not null,\n notified_at text not null,\n leader_pane_id_at_notify text,\n envelope_content_hash text,\n primary key (result_id, owner_team_id, owner_epoch)\n )"),
135
133
  ];
@@ -630,12 +628,12 @@ mod tests {
630
628
  let path = temp_db();
631
629
  build_legacy(&path);
632
630
 
633
- // 迁移前:diagnosis = schema_repair_available,全 8 表 diff(4 漂移 + 4 缺失)。
631
+ // 迁移前:diagnosis = schema_repair_available,全 7 表 diff(4 漂移 + 3 缺失)。
634
632
  let before = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
635
633
  assert_eq!(before.status, "schema_repair_available");
636
634
  assert!(!before.ok);
637
635
  assert_eq!(before.user_version, 1);
638
- assert_eq!(before.layout_diffs.len(), 8);
636
+ assert_eq!(before.layout_diffs.len(), MANAGED_TABLE_LAYOUTS.len());
639
637
 
640
638
  // initialize_schema 走 ensure_table_layout(rebuild)+ 建表 + user_version=3。
641
639
  let conn = crate::db::schema::open_db(&path).unwrap();
@@ -697,6 +695,48 @@ mod tests {
697
695
  assert_eq!(d.user_version, SCHEMA_VERSION);
698
696
  }
699
697
 
698
+ // A drifted legacy peer table is inert: no diagnosis, rebuild, backup or row loss.
699
+ #[test]
700
+ fn legacy_peer_allowlist_is_inert_and_preserved() {
701
+ let path = temp_db();
702
+ let conn = crate::db::schema::open_db(&path).unwrap();
703
+ initialize_schema(&conn, Some(&path)).unwrap();
704
+ conn.execute_batch(
705
+ "create table peer_allowlist (created_at text not null, b text not null, a text not null, primary key (a, b));
706
+ insert into peer_allowlist(a, b, created_at) values ('worker', 'reviewer', 'v4');",
707
+ )
708
+ .unwrap();
709
+ assert!(layout_diffs(&conn).unwrap().is_empty());
710
+ initialize_schema(&conn, Some(&path)).unwrap();
711
+ assert_eq!(layout(&conn, "peer_allowlist"), ["created_at", "b", "a"]);
712
+ let row: (String, String, String) = conn
713
+ .query_row("select a, b, created_at from peer_allowlist", [], |r| {
714
+ Ok((r.get(0)?, r.get(1)?, r.get(2)?))
715
+ })
716
+ .unwrap();
717
+ assert_eq!(
718
+ row,
719
+ (
720
+ "worker".to_string(),
721
+ "reviewer".to_string(),
722
+ "v4".to_string()
723
+ )
724
+ );
725
+ drop(conn);
726
+ let diagnosis = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
727
+ assert!(
728
+ diagnosis.ok,
729
+ "legacy peer table must remain inert: {diagnosis:?}"
730
+ );
731
+ assert!(
732
+ !std::fs::read_dir(path.parent().unwrap())
733
+ .unwrap()
734
+ .filter_map(|entry| entry.ok())
735
+ .any(|entry| entry.file_name().to_string_lossy().contains("pre-migration")),
736
+ "legacy peer table must not trigger a migration backup"
737
+ );
738
+ }
739
+
700
740
  #[test]
701
741
  fn diagnosis_missing_db() {
702
742
  let path = temp_db(); // 未创建文件
@@ -713,7 +753,7 @@ mod tests {
713
753
  build_legacy(&path);
714
754
  let conn = crate::db::schema::open_db(&path).unwrap();
715
755
  let events1 = ensure_table_layout(&conn, SCHEMA_VERSION, Some(&path)).unwrap();
716
- assert_eq!(events1.len(), 8); // 4 漂移 rebuild + 4 缺失建表
756
+ assert_eq!(events1.len(), MANAGED_TABLE_LAYOUTS.len()); // 4 漂移 rebuild + 3 缺失建表
717
757
  for e in &events1 {
718
758
  assert_eq!(e.row_count_before, e.row_count_after);
719
759
  }
@@ -759,7 +799,7 @@ mod tests {
759
799
  build_legacy(&path);
760
800
  let conn = crate::db::schema::open_db(&path).unwrap();
761
801
  let events = ensure_table_layout(&conn, SCHEMA_VERSION, None).unwrap(); // None 不再 Err
762
- assert_eq!(events.len(), 8);
802
+ assert_eq!(events.len(), MANAGED_TABLE_LAYOUTS.len());
763
803
  assert_eq!(
764
804
  table_layout(&conn, "messages").unwrap()[..2],
765
805
  ["message_id", "owner_team_id"]
@@ -871,7 +911,7 @@ mod tests {
871
911
  } => {
872
912
  assert!(diagnosis.ok);
873
913
  assert_eq!(diagnosis.user_version, SCHEMA_VERSION);
874
- assert_eq!(rebuilds.len(), 8);
914
+ assert_eq!(rebuilds.len(), MANAGED_TABLE_LAYOUTS.len());
875
915
  }
876
916
  other => panic!("expected Fixed, got {other:?}"),
877
917
  }
@@ -921,8 +961,8 @@ mod tests {
921
961
  .collect();
922
962
  assert_eq!(
923
963
  rebuilds.len(),
924
- 8,
925
- "8 张 managed 表各一条 schema.layout_rebuild"
964
+ MANAGED_TABLE_LAYOUTS.len(),
965
+ "每张 managed 表各一条 schema.layout_rebuild"
926
966
  );
927
967
  for e in &rebuilds {
928
968
  assert_eq!(
@@ -1,7 +1,7 @@
1
1
  //!
2
2
  //! `team.db` schema 初始化(真相源 `message_store/schema.py` + `schema_migration.py`)。
3
3
  //!
4
- //! slice 1:fresh DB schema —— 8 表 DDL、6 索引、ALTER 式列迁移、agent_health 重建、
4
+ //! slice 1:fresh DB schema —— 7 表 DDL、6 索引、ALTER 式列迁移、agent_health 重建、
5
5
  //! WAL/busy_timeout pragmas、`user_version = SCHEMA_VERSION`。
6
6
  //! slice 2(待做):列**顺序**漂移的整表 rebuild(`ensure_table_layout`/`_rebuild_tables`)
7
7
  //! 与 `schema_diagnosis`,验 legacy_team_db_fixture + schema_migration 契约。
@@ -15,14 +15,13 @@ use crate::db::DbError;
15
15
  /// `schema.py:90`。
16
16
  pub const SCHEMA_VERSION: i64 = 5;
17
17
 
18
- /// 8 张表的 DDL(逐字照搬 `schema.py:initialize_schema` 的内联建表;含 `if not exists`)。
18
+ /// 7 张表的 DDL(逐字照搬 `schema.py:initialize_schema` 的内联建表;含 `if not exists`)。
19
19
  /// 顺序与 Python 一致(leader_notification_log 在 ensure 块后创建)。
20
20
  const CREATE_MESSAGES: &str = "create table if not exists messages (\n message_id text primary key,\n owner_team_id text,\n task_id text,\n sender text,\n recipient text,\n reply_to text,\n requires_ack integer,\n status text,\n content text,\n presentation text not null default '{\"sink\":\"leader\",\"class\":\"message\"}',\n artifact_refs text,\n created_at text,\n updated_at text,\n delivered_at text,\n acknowledged_at text,\n error text,\n delivery_attempts integer not null default 0\n )";
21
21
  const CREATE_RESULTS: &str = "create table if not exists results (\n result_id text primary key,\n owner_team_id text,\n task_id text not null,\n agent_id text not null,\n envelope text not null,\n status text not null,\n created_at text not null\n )";
22
22
  const CREATE_SCHEDULED_EVENTS: &str = "create table if not exists scheduled_events (\n id integer primary key,\n owner_team_id text,\n due_at text not null,\n target text not null,\n kind text not null,\n payload_json text not null,\n status text not null,\n created_at text not null,\n fired_at text,\n result_json text\n )";
23
23
  const CREATE_DELIVERY_TOKENS: &str = "create table if not exists delivery_tokens (\n message_id text primary key,\n unique_token text not null,\n injected_at text not null,\n visible_at text,\n consumed_at text,\n failed_at text,\n failure_reason text\n )";
24
24
  const CREATE_AGENT_HEALTH: &str = "create table if not exists agent_health (\n owner_team_id text,\n agent_id text not null,\n status text not null,\n last_output_at text,\n context_usage_pct integer,\n current_task_id text,\n updated_at text not null,\n unique(owner_team_id, agent_id)\n )";
25
- const CREATE_PEER_ALLOWLIST: &str = "create table if not exists peer_allowlist (\n a text not null,\n b text not null,\n created_at text not null,\n primary key (a, b)\n )";
26
25
  const CREATE_RESULT_WATCHERS: &str = "create table if not exists result_watchers (\n watcher_id text primary key,\n owner_team_id text,\n task_id text,\n agent_id text,\n message_id text,\n leader_id text not null,\n recipient text,\n status text not null,\n created_at text not null,\n completed_at text,\n result_id text,\n notified_message_id text,\n error text\n )";
27
26
  const CREATE_LEADER_NOTIFICATION_LOG: &str = "create table if not exists leader_notification_log (\n result_id text not null,\n owner_team_id text not null default '',\n owner_epoch integer not null default 0,\n leader_session_uuid text,\n notified_message_id text not null,\n notified_at text not null,\n leader_pane_id_at_notify text,\n envelope_content_hash text,\n primary key (result_id, owner_team_id, owner_epoch)\n )";
28
27
  const CREATE_AGENT_HEALTH_NEW: &str = "create table agent_health_new (\n owner_team_id text,\n agent_id text not null,\n status text not null,\n last_output_at text,\n context_usage_pct integer,\n current_task_id text,\n updated_at text not null,\n unique(owner_team_id, agent_id)\n )";
@@ -96,7 +95,6 @@ const AGENT_HEALTH_COLUMNS: &[&str] = &[
96
95
  "current_task_id",
97
96
  "updated_at",
98
97
  ];
99
- const PEER_ALLOWLIST_COLUMNS: &[&str] = &["a", "b", "created_at"];
100
98
  const RESULT_WATCHER_COLUMNS: &[&str] = &[
101
99
  "owner_team_id",
102
100
  "watcher_id",
@@ -249,7 +247,6 @@ pub fn initialize_schema(
249
247
  CREATE_SCHEDULED_EVENTS,
250
248
  CREATE_DELIVERY_TOKENS,
251
249
  CREATE_AGENT_HEALTH,
252
- CREATE_PEER_ALLOWLIST,
253
250
  CREATE_RESULT_WATCHERS,
254
251
  ] {
255
252
  tx.execute(ddl, [])?;
@@ -293,7 +290,6 @@ pub fn initialize_schema(
293
290
  )?;
294
291
  ensure_table_columns(&tx, "delivery_tokens", DELIVERY_TOKEN_COLUMNS, &[])?;
295
292
  migrate_agent_health_owner_team_id(&tx)?;
296
- ensure_table_columns(&tx, "peer_allowlist", PEER_ALLOWLIST_COLUMNS, &[])?;
297
293
  ensure_table_columns(
298
294
  &tx,
299
295
  "result_watchers",
@@ -333,7 +329,28 @@ mod tests {
333
329
  conn
334
330
  }
335
331
 
336
- // golden 由 Python initialize_schema 新建 DB 取(team-agent-public@439bef8)。
332
+ #[test]
333
+ fn retired_peer_allowlist_is_neither_created_nor_managed() {
334
+ let conn = fresh();
335
+ assert!(table_layout(&conn, "peer_allowlist").unwrap().is_empty());
336
+ // Legacy residue may have an obsolete layout. Initialization must leave
337
+ // its data alone, not rebuild/drop it or require it for schema health.
338
+ conn.execute_batch(
339
+ "create table peer_allowlist (legacy_payload text);\n insert into peer_allowlist values ('preserve-this-row');",
340
+ )
341
+ .unwrap();
342
+ initialize_schema(&conn, None).unwrap();
343
+ assert!(crate::db::migration::ensure_table_layout(&conn, SCHEMA_VERSION, None)
344
+ .unwrap()
345
+ .is_empty());
346
+ assert_eq!(table_layout(&conn, "peer_allowlist").unwrap(), ["legacy_payload"]);
347
+ let value: String = conn.query_row("select legacy_payload from peer_allowlist", [], |row| row.get(0)).unwrap();
348
+ assert_eq!(value, "preserve-this-row");
349
+ let version: i64 = conn.query_row("pragma user_version", [], |row| row.get(0)).unwrap();
350
+ assert_eq!(version, 5);
351
+ }
352
+
353
+ // Fresh managed schema, excluding retired legacy tables.
337
354
  #[test]
338
355
  fn fresh_schema_matches_python_sqlite_master() {
339
356
  let conn = fresh();
@@ -356,7 +373,6 @@ mod tests {
356
373
  ("idx_scheduled_events_owner_team_id", "index"),
357
374
  ("leader_notification_log", "table"),
358
375
  ("messages", "table"),
359
- ("peer_allowlist", "table"),
360
376
  ("result_watchers", "table"),
361
377
  ("results", "table"),
362
378
  ("scheduled_events", "table"),
@@ -419,9 +435,9 @@ mod tests {
419
435
  assert_eq!(idx, 6, "二次 init 后 6 个 named index 仍在");
420
436
  }
421
437
 
422
- // 全 8 表逐列 (name, type, notnull, dflt, pk) golden(Python initialize_schema table_info)。
438
+ // Managed tables: (name, type, notnull, dflt, pk).
423
439
  #[test]
424
- fn per_column_table_info_all_eight_tables() {
440
+ fn per_column_table_info_managed_tables() {
425
441
  type Col = (&'static str, &'static str, i64, Option<&'static str>, i64);
426
442
  let golden: &[(&str, &[Col])] = &[
427
443
  (
@@ -503,14 +519,6 @@ mod tests {
503
519
  ("updated_at", "TEXT", 1, None, 0),
504
520
  ],
505
521
  ),
506
- (
507
- "peer_allowlist",
508
- &[
509
- ("a", "TEXT", 1, None, 1),
510
- ("b", "TEXT", 1, None, 2),
511
- ("created_at", "TEXT", 1, None, 0),
512
- ],
513
- ),
514
522
  (
515
523
  "result_watchers",
516
524
  &[
@@ -582,7 +590,7 @@ mod tests {
582
590
  ];
583
591
  let got: Vec<(&str, &str)> = idx.iter().map(|(n, s)| (n.as_str(), s.as_str())).collect();
584
592
  assert_eq!(got, want);
585
- // 7 个 sqlite_autoindex(每个有 pk/unique 约束的表各一)。
593
+ // 6 个 sqlite_autoindex(每个有 pk/unique 约束的表各一)。
586
594
  let auto: Vec<String> = conn
587
595
  .prepare(
588
596
  "select name from sqlite_master where type='index' and sql is null order by name",
@@ -599,7 +607,6 @@ mod tests {
599
607
  "sqlite_autoindex_delivery_tokens_1",
600
608
  "sqlite_autoindex_leader_notification_log_1",
601
609
  "sqlite_autoindex_messages_1",
602
- "sqlite_autoindex_peer_allowlist_1",
603
610
  "sqlite_autoindex_result_watchers_1",
604
611
  "sqlite_autoindex_results_1",
605
612
  ]
@@ -17,30 +17,6 @@ use super::{
17
17
  OwnerBindResult, TeamOwner,
18
18
  };
19
19
 
20
- // ── leader::identity — leader_identity / 身份上下文 ──
21
-
22
- /// `leader_identity`(card §47;`__init__.py:355`)。`team-agent identity` 入口。
23
- /// 返回 uuid_prefix + 身份字段(JSON dict,CLI 直出)。
24
- pub fn leader_identity(workspace: &Path, team: Option<&str>) -> Result<Value, LeaderError> {
25
- let state = crate::state::persist::load_runtime_state(workspace)?;
26
- let identity = leader_identity_context(workspace, team, Some(&state))?;
27
- Ok(json!({
28
- "ok": true,
29
- "uuid_prefix": prefix(identity.leader_session_uuid.as_str(), 12),
30
- "machine_fingerprint": identity.machine_fingerprint,
31
- "workspace_abspath": identity.workspace_abspath.to_string_lossy(),
32
- "os_user": identity.os_user,
33
- "team_id": identity.team_id.as_str(),
34
- "current_pane_id": std::env::var("TEAM_AGENT_LEADER_PANE_ID")
35
- .ok()
36
- .filter(|v| !v.is_empty())
37
- .or_else(|| std::env::var("TMUX_PANE").ok().filter(|v| !v.is_empty())),
38
- "last_seen_at": get_path_str(&state, &["leader_receiver", "attached_at"])
39
- .or_else(|| get_path_str(&state, &["leader_receiver", "last_seen_at"])),
40
- "source": serde_json::to_value(identity.leader_session_uuid_source)?,
41
- }))
42
- }
43
-
44
20
  /// `_leader_identity_context`(`__init__.py:192`)。派生 leader 身份上下文(override / state / derive)。
45
21
  pub fn leader_identity_context(
46
22
  workspace: &Path,
@@ -816,7 +816,7 @@ mod tests {
816
816
  }
817
817
 
818
818
  #[test]
819
- #[serial]
819
+ #[serial(env)]
820
820
  fn prune_removes_team_proven_terminal() {
821
821
  let (_home_guard, _home, workspace) = test_home("terminal");
822
822
  write_state(
@@ -834,7 +834,7 @@ mod tests {
834
834
  }
835
835
 
836
836
  #[test]
837
- #[serial]
837
+ #[serial(env)]
838
838
  fn prune_removes_missing_key_only_from_valid_teams_map() {
839
839
  let (_home_guard, _home, workspace) = test_home("missing-key");
840
840
  write_state(&workspace, json!({"teams": {"other": {"status": "alive"}}}));
@@ -850,7 +850,7 @@ mod tests {
850
850
  }
851
851
 
852
852
  #[test]
853
- #[serial]
853
+ #[serial(env)]
854
854
  fn prune_keeps_null_nonobject_and_legacy_teams_shapes() {
855
855
  for (label, state) in [
856
856
  ("null", json!({"teams": null})),
@@ -353,23 +353,3 @@ fn attach_leader_to_state_nonfirst_does_not_overwrite_owner_or_advance_epoch() {
353
353
  "non-first-time attach must NOT advance owner_epoch (golden keeps 3); Rust bumps to 4"
354
354
  );
355
355
  }
356
-
357
- // C3 [WARN] — leader_identity.current_pane_id honors TEAM_AGENT_LEADER_PANE_ID priority (golden
358
- // :366 = env(TEAM_AGENT_LEADER_PANE_ID) or env(TMUX_PANE) or None). Rust owner_bind.rs:32 reads
359
- // TMUX_PANE only. RED: with TEAM_AGENT_LEADER_PANE_ID set + TMUX_PANE unset, current_pane_id must be
360
- // the LEADER_PANE_ID. (Sibling gap, noted: last_seen_at = receiver.attached_at OR receiver.last_seen_at;
361
- // Rust reads attached_at only.)
362
- #[test]
363
- #[serial_test::serial(env)]
364
- fn c3_leader_identity_current_pane_honors_leader_pane_id_env() {
365
- let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
366
- let _e = EnvGuard::apply(&[
367
- ("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
368
- ("TEAM_AGENT_LEADER_PANE_ID", Some("%foo")),
369
- ("TMUX_PANE", None),
370
- ]);
371
- let ws = p2_temp_ws("c3_pane_env");
372
- let v = leader_identity(&ws, None).unwrap();
373
- assert_eq!(v["current_pane_id"], serde_json::json!("%foo"),
374
- "golden :366 current_pane_id = TEAM_AGENT_LEADER_PANE_ID or TMUX_PANE or None; Rust reads TMUX_PANE only");
375
- }
@@ -23,38 +23,6 @@ fn leader_identity_context_derives_when_no_override_no_state() {
23
23
  assert_eq!(id.leader_session_uuid.as_str().len(), 32);
24
24
  }
25
25
 
26
- // leader_identity:CLI 直出 dict(__init__.py:355-369)。unimplemented → RED。
27
- // 强化:uuid_prefix 必须 == leader_identity_context 派生 uuid 的前 12 hex(绑到真值,
28
- // 而非任意 12 字符);整个 dict 的 machine_fingerprint/os_user/team_id/source 必须与 context 一致。
29
- #[test]
30
- #[serial_test::serial(env)]
31
- fn leader_identity_dict_ties_prefix_and_fields_to_derived_context() {
32
- let ws = std::env::temp_dir().join(format!("ta_rs_lid_{}", std::process::id()));
33
- std::fs::create_dir_all(&ws).unwrap();
34
- // 空 state → context 走 derive(无 override/无 state uuid)。
35
- let ctx = leader_identity_context(&ws, None, Some(&serde_json::json!({}))).unwrap();
36
- let expected_prefix = &ctx.leader_session_uuid.as_str()[..12];
37
- let v = leader_identity(&ws, None).unwrap();
38
- assert_eq!(v["ok"], serde_json::json!(true));
39
- // uuid_prefix 绑到派生真值的前 12 hex(错的 12 字符串会被抓)。
40
- assert_eq!(v["uuid_prefix"].as_str().unwrap(), expected_prefix);
41
- // 其余身份字段与 context 字节一致。
42
- assert_eq!(
43
- v["machine_fingerprint"].as_str().unwrap(),
44
- ctx.machine_fingerprint
45
- );
46
- assert_eq!(v["os_user"].as_str().unwrap(), ctx.os_user);
47
- assert_eq!(v["team_id"].as_str().unwrap(), ctx.team_id.as_str());
48
- // source == 派生侧 "derived"(无 override → 不是 "override"/"env")。
49
- assert_eq!(v["source"], serde_json::json!("derived"));
50
- assert_eq!(
51
- ctx.leader_session_uuid_source,
52
- LeaderSessionUuidSource::Derived
53
- );
54
- // CLI 直出形态:current_pane_id / last_seen_at 在无 env/无 receiver 时为 null。
55
- assert_eq!(v["last_seen_at"], serde_json::Value::Null);
56
- }
57
-
58
26
  // =====================================================================
59
27
  // 13. leader_start_plan(unimplemented → RED):钉 mode 选择 + leader_env 导出键。
60
28
  // =====================================================================
@@ -398,61 +398,6 @@ fn claim_lease_no_incident_caller_not_live_refused_under_n_rn3_any_live_pane() {
398
398
  assert_eq!(r.reason, Some(LeaseReason::CallerPaneNotLive));
399
399
  }
400
400
 
401
- // ── 14b. leader_identity dict — workspace_abspath key (golden __init__.py:363) ──
402
- //
403
- // LOCK→RED: leader_identity IS implemented (owner_bind.rs:22) but its dict
404
- // OMITS the golden `workspace_abspath` key (probe_lid.py shows it sits between
405
- // machine_fingerprint and os_user). This pins the missing key so the porter
406
- // must add it. Fails today (key absent → null) = RED against the omission.
407
- #[test]
408
- #[serial_test::serial(env)]
409
- fn leader_identity_dict_includes_workspace_abspath_key() {
410
- let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
411
- let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
412
- let ws = p2_temp_ws("lid_wsabs");
413
- let v = leader_identity(&ws, None).unwrap();
414
- let ctx = leader_identity_context(&ws, None, Some(&serde_json::json!({}))).unwrap();
415
- // golden key present + equals the resolved workspace abspath from context.
416
- assert!(
417
- v.get("workspace_abspath").is_some(),
418
- "golden leader_identity dict carries 'workspace_abspath' (probe_lid.py)"
419
- );
420
- assert_eq!(
421
- v["workspace_abspath"].as_str().unwrap(),
422
- ctx.workspace_abspath.to_string_lossy(),
423
- "workspace_abspath must equal the context's resolved abspath"
424
- );
425
- }
426
-
427
- // LOCK — leader_identity key ORDER & full key set (golden probe_lid.py):
428
- // [ok, uuid_prefix, machine_fingerprint, workspace_abspath, os_user, team_id,
429
- // current_pane_id, last_seen_at, source]. Pins the 9-key surface CLI emits.
430
- #[test]
431
- #[serial_test::serial(env)]
432
- fn leader_identity_dict_has_exact_nine_golden_keys() {
433
- let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
434
- let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
435
- let ws = p2_temp_ws("lid_keys");
436
- let v = leader_identity(&ws, None).unwrap();
437
- let obj = v.as_object().expect("leader_identity → JSON object");
438
- for key in [
439
- "ok",
440
- "uuid_prefix",
441
- "machine_fingerprint",
442
- "workspace_abspath",
443
- "os_user",
444
- "team_id",
445
- "current_pane_id",
446
- "last_seen_at",
447
- "source",
448
- ] {
449
- assert!(
450
- obj.contains_key(key),
451
- "golden leader_identity dict must carry '{key}'"
452
- );
453
- }
454
- }
455
-
456
401
  // ═════════════════════════════════════════════════════════════════════════
457
402
  // WAVE-2 Lane B — leader-lease DIVERGENCE round (adversarial review @ 2cd71ce).
458
403
  // The lease gate is green but NOT byte-parity. Golden: leader/__init__.py
@@ -153,7 +153,8 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
153
153
  );
154
154
  assert!(
155
155
  crate::cli::emit::default_help()
156
- .contains("team-agent codex|claude|copilot|grok|cursor|pi ..."),
156
+ .lines()
157
+ .any(|line| { line.split_whitespace().next() == Some("pi") }),
157
158
  "default help must advertise the dispatchable Pi launcher"
158
159
  );
159
160
  let leader = parse_pi_leader_args(&leader_argv[2..])
@@ -221,14 +222,8 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
221
222
  .expect("write provider-default teammate role");
222
223
  let defaults = compile_role_agent(&role, &Value::Map(Vec::new()), "/workspace")
223
224
  .expect("provider: pi teammate may preserve provider-native defaults");
224
- assert_eq!(
225
- defaults.agent.get("model"),
226
- Some(&Value::Null)
227
- );
228
- assert_eq!(
229
- defaults.agent.get("effort"),
230
- None
231
- );
225
+ assert_eq!(defaults.agent.get("model"), Some(&Value::Null));
226
+ assert_eq!(defaults.agent.get("effort"), None);
232
227
  assert_eq!(
233
228
  defaults.agent.get("dangerously_skip_permissions"),
234
229
  Some(&Value::Bool(false))
@@ -21,7 +21,6 @@ use std::sync::Mutex;
21
21
 
22
22
  use serde_json::{json, Value};
23
23
  use serial_test::serial;
24
- use team_agent::cli::{cmd_wait_ready, CmdOutput, WaitReadyArgs};
25
24
  use team_agent::lifecycle::{
26
25
  classify_restart_plan, restart_with_transport, RestartReport, ResumeDecision, StartMode,
27
26
  };
@@ -448,48 +447,7 @@ fn restart_refuses_first_send_at_null_running_resumable_session_incomplete_when_
448
447
  }
449
448
 
450
449
  #[test]
451
- #[ignore = "real-machine: provider session filesystem + restart/shutdown lifecycle convergence"]
452
- #[serial(env)]
453
- fn wait_ready_reports_session_capture_incomplete_for_running_resumable_worker_without_session() {
454
- let fixture = RestartFixture::new("wait-ready-session-incomplete", "codex");
455
- seed_running_state_without_session(&fixture.team, "codex", &fixture.spawn_cwd);
456
- let mut state = load_runtime_state(&fixture.team).unwrap();
457
- let mcp_config = fixture.team.join(".team/runtime/worker_a.mcp.json");
458
- std::fs::create_dir_all(mcp_config.parent().unwrap()).unwrap();
459
- std::fs::write(&mcp_config, "{}").unwrap();
460
- state["agents"]["worker_a"]["pane_id"] = json!("%1");
461
- state["agents"]["worker_a"]["mcp_config"] = json!(mcp_config.to_string_lossy().to_string());
462
- save_runtime_state(&fixture.team, &state).unwrap();
463
-
464
- let out = json_result(
465
- cmd_wait_ready(&WaitReadyArgs {
466
- workspace: fixture.team.clone(),
467
- timeout: 0.0,
468
- json: true,
469
- team: None,
470
- })
471
- .expect("wait-ready should return structured JSON"),
472
- );
473
-
474
- assert_eq!(
475
- out["ok"],
476
- json!(false),
477
- "wait-ready cannot report ready while a running Codex/Claude worker is missing captured session context; out={out}"
478
- );
479
- assert_eq!(
480
- out["reason"],
481
- json!("session_capture_incomplete"),
482
- "readiness reason must distinguish missing resumable session from generic workers_not_ready; out={out}"
483
- );
484
- assert!(
485
- out["readiness"]["session_capture_incomplete"].as_bool() == Some(true)
486
- && out["readiness"]["all_resumable_have_session"].as_bool() == Some(false),
487
- "readiness truth table must include all_resumable_have_session and session_capture_incomplete; out={out}"
488
- );
489
- }
490
-
491
- #[test]
492
- fn status_quick_start_and_wait_ready_surfaces_include_session_capture_completeness() {
450
+ fn status_and_quick_start_surfaces_include_session_capture_completeness() {
493
451
  let diagnose =
494
452
  std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/cli/diagnose.rs"))
495
453
  .unwrap();
@@ -500,7 +458,7 @@ fn status_quick_start_and_wait_ready_surfaces_include_session_capture_completene
500
458
  .unwrap();
501
459
 
502
460
  for (name, source) in [
503
- ("wait-ready", diagnose.as_str()),
461
+ ("status readiness formula", diagnose.as_str()),
504
462
  ("status --json", status.as_str()),
505
463
  ("quick-start readiness", launch.as_str()),
506
464
  ("quick-start --json", adapters.as_str()),
@@ -945,13 +903,6 @@ fn read_events(team: &Path) -> String {
945
903
  std::fs::read_to_string(team.join(".team/logs/events.jsonl")).unwrap_or_default()
946
904
  }
947
905
 
948
- fn json_result(result: team_agent::cli::CmdResult) -> Value {
949
- match result.output {
950
- CmdOutput::Json(value) => value,
951
- other => panic!("expected JSON command output, got {other:?}"),
952
- }
953
- }
954
-
955
906
  fn assert_no_agent_attribution_ambiguous<const N: usize>(
956
907
  state: &Value,
957
908
  expected_agents: [&str; N],
@@ -170,7 +170,9 @@ fn purge_agent_help_and_dispatch_are_consistent() {
170
170
  );
171
171
  } else {
172
172
  assert!(
173
- command.contains("unknown subcommand") || command.contains("invalid choice"),
173
+ command.contains("unknown subcommand")
174
+ || command.contains("invalid choice")
175
+ || command.contains("没有这个操作"),
174
176
  "RED6: if purge-agent is not implemented, help and dispatch must agree it is absent; help={help} command={command}"
175
177
  );
176
178
  }