@team-agent/installer 0.2.10 → 0.3.0
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 +744 -0
- package/Cargo.toml +34 -0
- package/crates/team-agent/Cargo.toml +33 -0
- package/crates/team-agent/src/cli/adapters.rs +1343 -0
- package/crates/team-agent/src/cli/diagnose.rs +554 -0
- package/crates/team-agent/src/cli/emit.rs +1077 -0
- package/crates/team-agent/src/cli/helpers.rs +88 -0
- package/crates/team-agent/src/cli/leader.rs +216 -0
- package/crates/team-agent/src/cli/mod.rs +1141 -0
- package/crates/team-agent/src/cli/profile.rs +306 -0
- package/crates/team-agent/src/cli/send.rs +215 -0
- package/crates/team-agent/src/cli/status.rs +179 -0
- package/crates/team-agent/src/cli/status_port.rs +502 -0
- package/crates/team-agent/src/cli/tests/base.rs +616 -0
- package/crates/team-agent/src/cli/tests/compile.rs +96 -0
- package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
- package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
- package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
- package/crates/team-agent/src/cli/tests/mod.rs +97 -0
- package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
- package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
- package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
- package/crates/team-agent/src/cli/types.rs +605 -0
- package/crates/team-agent/src/compiler/tests.rs +701 -0
- package/crates/team-agent/src/compiler.rs +489 -0
- package/crates/team-agent/src/coordinator/backoff.rs +153 -0
- package/crates/team-agent/src/coordinator/health.rs +436 -0
- package/crates/team-agent/src/coordinator/mod.rs +80 -0
- package/crates/team-agent/src/coordinator/orphan.rs +179 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
- package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
- package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
- package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
- package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
- package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
- package/crates/team-agent/src/coordinator/tick.rs +2032 -0
- package/crates/team-agent/src/coordinator/types.rs +584 -0
- package/crates/team-agent/src/db/migration.rs +716 -0
- package/crates/team-agent/src/db/mod.rs +23 -0
- package/crates/team-agent/src/db/schema.rs +378 -0
- package/crates/team-agent/src/event_log.rs +375 -0
- package/crates/team-agent/src/fake_worker.rs +253 -0
- package/crates/team-agent/src/leader/helpers.rs +190 -0
- package/crates/team-agent/src/leader/inject.rs +33 -0
- package/crates/team-agent/src/leader/lease.rs +1063 -0
- package/crates/team-agent/src/leader/mod.rs +99 -0
- package/crates/team-agent/src/leader/owner_bind.rs +292 -0
- package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
- package/crates/team-agent/src/leader/rediscover.rs +1099 -0
- package/crates/team-agent/src/leader/start.rs +273 -0
- package/crates/team-agent/src/leader/takeover.rs +235 -0
- package/crates/team-agent/src/leader/tests/basics.rs +183 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +271 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
- package/crates/team-agent/src/leader/tests/mod.rs +125 -0
- package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
- package/crates/team-agent/src/leader/types.rs +487 -0
- package/crates/team-agent/src/lib.rs +85 -0
- package/crates/team-agent/src/lifecycle/display.rs +228 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
- package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
- package/crates/team-agent/src/lifecycle/mod.rs +62 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
- package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
- package/crates/team-agent/src/lifecycle/restart.rs +76 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
- package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests.rs +27 -0
- package/crates/team-agent/src/lifecycle/types.rs +685 -0
- package/crates/team-agent/src/main.rs +41 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
- package/crates/team-agent/src/mcp_server/mod.rs +183 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
- package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
- package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
- package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
- package/crates/team-agent/src/mcp_server/tests.rs +38 -0
- package/crates/team-agent/src/mcp_server/tools.rs +603 -0
- package/crates/team-agent/src/mcp_server/types.rs +421 -0
- package/crates/team-agent/src/mcp_server/wire.rs +388 -0
- package/crates/team-agent/src/message_store.rs +767 -0
- package/crates/team-agent/src/messaging/activity.rs +433 -0
- package/crates/team-agent/src/messaging/delivery.rs +542 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
- package/crates/team-agent/src/messaging/mod.rs +147 -0
- package/crates/team-agent/src/messaging/peers.rs +32 -0
- package/crates/team-agent/src/messaging/results.rs +537 -0
- package/crates/team-agent/src/messaging/scheduler.rs +344 -0
- package/crates/team-agent/src/messaging/selftest.rs +100 -0
- package/crates/team-agent/src/messaging/send.rs +582 -0
- package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
- package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
- package/crates/team-agent/src/messaging/trust.rs +192 -0
- package/crates/team-agent/src/messaging/types.rs +355 -0
- package/crates/team-agent/src/messaging/watchers.rs +591 -0
- package/crates/team-agent/src/model/enums.rs +311 -0
- package/crates/team-agent/src/model/errors.rs +17 -0
- package/crates/team-agent/src/model/ids.rs +155 -0
- package/crates/team-agent/src/model/mod.rs +22 -0
- package/crates/team-agent/src/model/paths.rs +228 -0
- package/crates/team-agent/src/model/permissions.rs +567 -0
- package/crates/team-agent/src/model/routing.rs +340 -0
- package/crates/team-agent/src/model/spec.rs +680 -0
- package/crates/team-agent/src/model/task_graph.rs +380 -0
- package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
- package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
- package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
- package/crates/team-agent/src/model/yaml/tests.rs +288 -0
- package/crates/team-agent/src/model/yaml.rs +800 -0
- package/crates/team-agent/src/packaging/install.rs +305 -0
- package/crates/team-agent/src/packaging/migrate.rs +30 -0
- package/crates/team-agent/src/packaging/mod.rs +82 -0
- package/crates/team-agent/src/packaging/repair.rs +24 -0
- package/crates/team-agent/src/packaging/tests.rs +829 -0
- package/crates/team-agent/src/packaging/types.rs +369 -0
- package/crates/team-agent/src/provider/adapter.rs +801 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
- package/crates/team-agent/src/provider/classify.rs +456 -0
- package/crates/team-agent/src/provider/faults.rs +136 -0
- package/crates/team-agent/src/provider/helpers.rs +41 -0
- package/crates/team-agent/src/provider/mod.rs +53 -0
- package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
- package/crates/team-agent/src/provider/tests/classify.rs +240 -0
- package/crates/team-agent/src/provider/tests/faults.rs +120 -0
- package/crates/team-agent/src/provider/tests/idle.rs +208 -0
- package/crates/team-agent/src/provider/tests/wire.rs +213 -0
- package/crates/team-agent/src/provider/tests.rs +31 -0
- package/crates/team-agent/src/provider/types.rs +424 -0
- package/crates/team-agent/src/state/identity.rs +656 -0
- package/crates/team-agent/src/state/mod.rs +58 -0
- package/crates/team-agent/src/state/owner_gate.rs +423 -0
- package/crates/team-agent/src/state/persist.rs +712 -0
- package/crates/team-agent/src/state/projection.rs +657 -0
- package/crates/team-agent/src/state/selector.rs +105 -0
- package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
- package/crates/team-agent/src/tmux_backend.rs +758 -0
- package/crates/team-agent/src/transport/test_support.rs +252 -0
- package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
- package/crates/team-agent/src/transport/tests/mod.rs +199 -0
- package/crates/team-agent/src/transport/tests/wire.rs +527 -0
- package/crates/team-agent/src/transport.rs +774 -0
- package/npm/install.mjs +90 -106
- package/package.json +15 -13
- package/crates/team-agent-core/Cargo.toml +0 -12
- package/crates/team-agent-core/src/lib.rs +0 -332
- package/crates/team-agent-core/src/main.rs +0 -152
- package/pyproject.toml +0 -18
- package/scripts/install.py +0 -88
- package/scripts/run_regression_tests.py +0 -83
- package/src/team_agent/__init__.py +0 -3
- package/src/team_agent/__main__.py +0 -5
- package/src/team_agent/_legacy_pane_discovery.py +0 -186
- package/src/team_agent/abnormal_track.py +0 -253
- package/src/team_agent/approvals/__init__.py +0 -65
- package/src/team_agent/approvals/constants.py +0 -6
- package/src/team_agent/approvals/parsing.py +0 -176
- package/src/team_agent/approvals/runtime_prompts.py +0 -171
- package/src/team_agent/approvals/status.py +0 -176
- package/src/team_agent/cli/__init__.py +0 -137
- package/src/team_agent/cli/commands.py +0 -481
- package/src/team_agent/cli/e2e.py +0 -202
- package/src/team_agent/cli/helpers.py +0 -226
- package/src/team_agent/cli/parser.py +0 -540
- package/src/team_agent/compiler.py +0 -334
- package/src/team_agent/coordinator/__init__.py +0 -53
- package/src/team_agent/coordinator/__main__.py +0 -83
- package/src/team_agent/coordinator/lifecycle.py +0 -363
- package/src/team_agent/coordinator/metadata.py +0 -61
- package/src/team_agent/coordinator/paths.py +0 -17
- package/src/team_agent/diagnose/__init__.py +0 -48
- package/src/team_agent/diagnose/checks.py +0 -101
- package/src/team_agent/diagnose/comms.py +0 -213
- package/src/team_agent/diagnose/health.py +0 -241
- package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
- package/src/team_agent/diagnose/preflight.py +0 -194
- package/src/team_agent/diagnose/quick_start.py +0 -324
- package/src/team_agent/display/__init__.py +0 -92
- package/src/team_agent/display/adaptive.py +0 -511
- package/src/team_agent/display/backend.py +0 -46
- package/src/team_agent/display/close.py +0 -154
- package/src/team_agent/display/ghostty.py +0 -77
- package/src/team_agent/display/rebuild.py +0 -102
- package/src/team_agent/display/tiling.py +0 -156
- package/src/team_agent/display/worker_window.py +0 -114
- package/src/team_agent/display/workspace.py +0 -382
- package/src/team_agent/errors.py +0 -10
- package/src/team_agent/events.py +0 -84
- package/src/team_agent/fake_worker.py +0 -80
- package/src/team_agent/idle_predicate.py +0 -200
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -111
- package/src/team_agent/launch/__init__.py +0 -41
- package/src/team_agent/launch/bootstrap.py +0 -85
- package/src/team_agent/launch/config.py +0 -106
- package/src/team_agent/launch/core.py +0 -301
- package/src/team_agent/launch/requirements.py +0 -57
- package/src/team_agent/leader/__init__.py +0 -926
- package/src/team_agent/leader_binding.py +0 -183
- package/src/team_agent/lifecycle/__init__.py +0 -5
- package/src/team_agent/lifecycle/agents.py +0 -278
- package/src/team_agent/lifecycle/operations.py +0 -411
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
- package/src/team_agent/lifecycle/start.py +0 -363
- package/src/team_agent/mcp_server/__init__.py +0 -42
- package/src/team_agent/mcp_server/__main__.py +0 -7
- package/src/team_agent/mcp_server/contracts.py +0 -148
- package/src/team_agent/mcp_server/normalize.py +0 -257
- package/src/team_agent/mcp_server/server.py +0 -150
- package/src/team_agent/mcp_server/tools.py +0 -352
- package/src/team_agent/message_store/__init__.py +0 -23
- package/src/team_agent/message_store/agent_health.py +0 -113
- package/src/team_agent/message_store/core.py +0 -497
- package/src/team_agent/message_store/leader_notification_log.py +0 -198
- package/src/team_agent/message_store/result_watchers.py +0 -251
- package/src/team_agent/message_store/schema.py +0 -308
- package/src/team_agent/message_store/schema_migration.py +0 -448
- package/src/team_agent/messaging/__init__.py +0 -1
- package/src/team_agent/messaging/activity_detector.py +0 -254
- package/src/team_agent/messaging/delivery.py +0 -473
- package/src/team_agent/messaging/deps.py +0 -247
- package/src/team_agent/messaging/idle_alerts.py +0 -423
- package/src/team_agent/messaging/internal_delivery.py +0 -46
- package/src/team_agent/messaging/leader.py +0 -497
- package/src/team_agent/messaging/leader_api_errors.py +0 -216
- package/src/team_agent/messaging/leader_panes.py +0 -673
- package/src/team_agent/messaging/owner_bypass.py +0 -29
- package/src/team_agent/messaging/result_delivery.py +0 -539
- package/src/team_agent/messaging/results.py +0 -447
- package/src/team_agent/messaging/scheduler.py +0 -450
- package/src/team_agent/messaging/send.py +0 -532
- package/src/team_agent/messaging/session_drift.py +0 -94
- package/src/team_agent/messaging/tmux_io.py +0 -506
- package/src/team_agent/messaging/tmux_prompt.py +0 -338
- package/src/team_agent/messaging/trust_auto_answer.py +0 -52
- package/src/team_agent/orchestrator/__init__.py +0 -376
- package/src/team_agent/orchestrator/plan.py +0 -122
- package/src/team_agent/orchestrator/state.py +0 -128
- package/src/team_agent/paths.py +0 -45
- package/src/team_agent/permissions.py +0 -123
- package/src/team_agent/profiles/__init__.py +0 -82
- package/src/team_agent/profiles/constants.py +0 -19
- package/src/team_agent/profiles/core.py +0 -407
- package/src/team_agent/profiles/helpers.py +0 -69
- package/src/team_agent/profiles/provider_env.py +0 -188
- package/src/team_agent/profiles/smoke.py +0 -201
- package/src/team_agent/provider_cli/__init__.py +0 -43
- package/src/team_agent/provider_cli/adapter.py +0 -172
- package/src/team_agent/provider_cli/base.py +0 -48
- package/src/team_agent/provider_cli/claude.py +0 -457
- package/src/team_agent/provider_cli/codex.py +0 -336
- package/src/team_agent/provider_cli/copilot.py +0 -8
- package/src/team_agent/provider_cli/fake.py +0 -39
- package/src/team_agent/provider_cli/gemini.py +0 -95
- package/src/team_agent/provider_cli/opencode.py +0 -8
- package/src/team_agent/provider_cli/prompt.py +0 -62
- package/src/team_agent/provider_cli/registry.py +0 -18
- package/src/team_agent/provider_cli/unsupported.py +0 -32
- package/src/team_agent/provider_state/README.md +0 -78
- package/src/team_agent/provider_state/__init__.py +0 -86
- package/src/team_agent/provider_state/claude.py +0 -86
- package/src/team_agent/provider_state/codex.py +0 -84
- package/src/team_agent/provider_state/common.py +0 -207
- package/src/team_agent/provider_state/registry.py +0 -118
- package/src/team_agent/providers.py +0 -163
- package/src/team_agent/quality_gates.py +0 -104
- package/src/team_agent/restart/__init__.py +0 -34
- package/src/team_agent/restart/orchestration.py +0 -554
- package/src/team_agent/restart/selection.py +0 -89
- package/src/team_agent/restart/snapshot.py +0 -70
- package/src/team_agent/routing.py +0 -84
- package/src/team_agent/runtime.py +0 -1239
- package/src/team_agent/rust_core.py +0 -327
- package/src/team_agent/sessions/__init__.py +0 -25
- package/src/team_agent/sessions/capture.py +0 -143
- package/src/team_agent/sessions/inventory.py +0 -44
- package/src/team_agent/sessions/resume.py +0 -135
- package/src/team_agent/simple_yaml.py +0 -236
- package/src/team_agent/spec.py +0 -370
- package/src/team_agent/state.py +0 -602
- package/src/team_agent/status/__init__.py +0 -63
- package/src/team_agent/status/approvals.py +0 -52
- package/src/team_agent/status/compact.py +0 -158
- package/src/team_agent/status/constants.py +0 -18
- package/src/team_agent/status/inbox.py +0 -58
- package/src/team_agent/status/peek.py +0 -117
- package/src/team_agent/status/queries.py +0 -199
- package/src/team_agent/task_graph.py +0 -80
- package/src/team_agent/terminal.py +0 -57
- package/src/team_agent/wake.py +0 -58
- package/src/team_agent/watch/__init__.py +0 -145
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//! step 3 · db — `team.db`(SQLite)的 schema 与迁移层(真相源 `message_store/schema*.py`)。
|
|
2
|
+
//!
|
|
3
|
+
//! §6 step 3:schema/migrations/indexes/WAL+busy-timeout/只读兼容。**消息操作本身(send/
|
|
4
|
+
//! receive/result/watcher 等)是 step 7**,不在此。
|
|
5
|
+
//!
|
|
6
|
+
//! 字节策略(§7):DDL 逐字照搬 → `sqlite_master` 等价;`schema_diagnosis` 比的是
|
|
7
|
+
//! `table_layout`(列名序),与 Python 一致。rusqlite `bundled` 静态链接 SQLite(§8)。
|
|
8
|
+
//!
|
|
9
|
+
//! §10:db 层无 unwrap/expect/panic;rusqlite 错误经 [`DbError`] 传播。
|
|
10
|
+
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
11
|
+
|
|
12
|
+
pub mod migration;
|
|
13
|
+
pub mod schema;
|
|
14
|
+
|
|
15
|
+
use thiserror::Error;
|
|
16
|
+
|
|
17
|
+
#[derive(Debug, Error)]
|
|
18
|
+
pub enum DbError {
|
|
19
|
+
#[error("sqlite: {0}")]
|
|
20
|
+
Sqlite(#[from] rusqlite::Error),
|
|
21
|
+
#[error("schema: {0}")]
|
|
22
|
+
Schema(String),
|
|
23
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
//! `team.db` schema 初始化(真相源 `message_store/schema.py` + `schema_migration.py`)。
|
|
2
|
+
//!
|
|
3
|
+
//! slice 1:fresh DB schema —— 8 表 DDL、6 索引、ALTER 式列迁移、agent_health 重建、
|
|
4
|
+
//! WAL/busy_timeout pragmas、`user_version = SCHEMA_VERSION`。
|
|
5
|
+
//! slice 2(待做):列**顺序**漂移的整表 rebuild(`ensure_table_layout`/`_rebuild_tables`)
|
|
6
|
+
//! 与 `schema_diagnosis`,验 legacy_team_db_fixture + schema_migration 契约。
|
|
7
|
+
|
|
8
|
+
use rusqlite::Connection;
|
|
9
|
+
|
|
10
|
+
use crate::db::DbError;
|
|
11
|
+
|
|
12
|
+
/// `schema.py:90`。
|
|
13
|
+
pub const SCHEMA_VERSION: i64 = 3;
|
|
14
|
+
|
|
15
|
+
/// 8 张表的 DDL(逐字照搬 `schema.py:initialize_schema` 的内联建表;含 `if not exists`)。
|
|
16
|
+
/// 顺序与 Python 一致(leader_notification_log 在 ensure 块后创建)。
|
|
17
|
+
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 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 )";
|
|
18
|
+
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 )";
|
|
19
|
+
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 )";
|
|
20
|
+
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 )";
|
|
21
|
+
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 )";
|
|
22
|
+
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 )";
|
|
23
|
+
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 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 )";
|
|
24
|
+
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 )";
|
|
25
|
+
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 )";
|
|
26
|
+
|
|
27
|
+
/// `schema_migration.py:INDEX_SQL`(6 条,顺序照搬)。
|
|
28
|
+
const INDEX_SQL: &[&str] = &[
|
|
29
|
+
"create index if not exists idx_leader_notification_log_uuid on leader_notification_log(leader_session_uuid, notified_at)",
|
|
30
|
+
"create index if not exists idx_leader_notification_log_team_epoch on leader_notification_log(owner_team_id, owner_epoch, notified_at)",
|
|
31
|
+
"create index if not exists idx_messages_owner_team_id on messages(owner_team_id)",
|
|
32
|
+
"create index if not exists idx_scheduled_events_owner_team_id on scheduled_events(owner_team_id)",
|
|
33
|
+
"create index if not exists idx_agent_health_owner_team_id on agent_health(owner_team_id)",
|
|
34
|
+
"create index if not exists idx_result_watchers_owner_team_id on result_watchers(owner_team_id)",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// 各表 required 列(`schema.py` 的 *_COLUMNS 集合;此处用有序数组,成员判定与集合等价)。
|
|
38
|
+
const MESSAGE_COLUMNS: &[&str] = &[
|
|
39
|
+
"owner_team_id", "message_id", "task_id", "sender", "recipient", "reply_to", "requires_ack",
|
|
40
|
+
"status", "content", "artifact_refs", "created_at", "updated_at", "delivered_at",
|
|
41
|
+
"acknowledged_at", "error", "delivery_attempts",
|
|
42
|
+
];
|
|
43
|
+
const RESULT_COLUMNS: &[&str] =
|
|
44
|
+
&["owner_team_id", "result_id", "task_id", "agent_id", "envelope", "status", "created_at"];
|
|
45
|
+
const SCHEDULED_EVENT_COLUMNS: &[&str] = &[
|
|
46
|
+
"id", "owner_team_id", "due_at", "target", "kind", "payload_json", "status", "created_at",
|
|
47
|
+
"fired_at", "result_json",
|
|
48
|
+
];
|
|
49
|
+
const DELIVERY_TOKEN_COLUMNS: &[&str] = &[
|
|
50
|
+
"message_id", "unique_token", "injected_at", "visible_at", "consumed_at", "failed_at",
|
|
51
|
+
"failure_reason",
|
|
52
|
+
];
|
|
53
|
+
const AGENT_HEALTH_COLUMNS: &[&str] = &[
|
|
54
|
+
"owner_team_id", "agent_id", "status", "last_output_at", "context_usage_pct", "current_task_id",
|
|
55
|
+
"updated_at",
|
|
56
|
+
];
|
|
57
|
+
const PEER_ALLOWLIST_COLUMNS: &[&str] = &["a", "b", "created_at"];
|
|
58
|
+
const RESULT_WATCHER_COLUMNS: &[&str] = &[
|
|
59
|
+
"owner_team_id", "watcher_id", "task_id", "agent_id", "message_id", "leader_id", "status",
|
|
60
|
+
"created_at", "completed_at", "result_id", "notified_message_id", "error",
|
|
61
|
+
];
|
|
62
|
+
const LEADER_NOTIFICATION_LOG_COLUMNS: &[&str] = &[
|
|
63
|
+
"result_id", "owner_team_id", "owner_epoch", "leader_session_uuid", "notified_message_id",
|
|
64
|
+
"notified_at", "leader_pane_id_at_notify", "envelope_content_hash",
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/// 打开 `team.db` 并设 pragmas(`core.py:60-61`:busy_timeout=30000 + WAL)。
|
|
68
|
+
pub fn open_db(path: &std::path::Path) -> Result<Connection, DbError> {
|
|
69
|
+
let conn = Connection::open(path)?;
|
|
70
|
+
conn.execute_batch("PRAGMA busy_timeout=30000; PRAGMA journal_mode=WAL;")?;
|
|
71
|
+
Ok(conn)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// `pragma table_info(table)` 的列名序(`schema_migration.py:table_layout`)。
|
|
75
|
+
pub fn table_layout(conn: &Connection, table: &str) -> Result<Vec<String>, DbError> {
|
|
76
|
+
// table 来自固定常量名,非用户输入 → format 安全。
|
|
77
|
+
let mut stmt = conn.prepare(&format!("pragma table_info({table})"))?;
|
|
78
|
+
let names = stmt
|
|
79
|
+
.query_map([], |row| row.get::<_, String>(1))?
|
|
80
|
+
.collect::<Result<Vec<String>, _>>()?;
|
|
81
|
+
Ok(names)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// `schema_migration.py:ensure_schema_indexes`。
|
|
85
|
+
pub fn ensure_schema_indexes(conn: &Connection) -> Result<(), DbError> {
|
|
86
|
+
for sql in INDEX_SQL {
|
|
87
|
+
conn.execute(sql, [])?;
|
|
88
|
+
}
|
|
89
|
+
Ok(())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// `schema.py:_ensure_table_columns`:缺列且无迁移 → `Err`;缺列有迁移 → 按排序执行 ALTER。
|
|
93
|
+
fn ensure_table_columns(
|
|
94
|
+
conn: &Connection,
|
|
95
|
+
table: &str,
|
|
96
|
+
required: &[&str],
|
|
97
|
+
migrations: &[(&str, &str)],
|
|
98
|
+
) -> Result<(), DbError> {
|
|
99
|
+
let cols = table_layout(conn, table)?;
|
|
100
|
+
let mut missing: Vec<&str> = required
|
|
101
|
+
.iter()
|
|
102
|
+
.copied()
|
|
103
|
+
.filter(|r| !cols.iter().any(|c| c == r))
|
|
104
|
+
.collect();
|
|
105
|
+
let unsupported: Vec<&str> = missing
|
|
106
|
+
.iter()
|
|
107
|
+
.copied()
|
|
108
|
+
.filter(|m| !migrations.iter().any(|(k, _)| k == m))
|
|
109
|
+
.collect();
|
|
110
|
+
if !unsupported.is_empty() {
|
|
111
|
+
let mut names = unsupported;
|
|
112
|
+
names.sort_unstable();
|
|
113
|
+
return Err(DbError::Schema(format!(
|
|
114
|
+
"team.db table {table} is missing required column(s): {}",
|
|
115
|
+
names.join(", ")
|
|
116
|
+
)));
|
|
117
|
+
}
|
|
118
|
+
missing.sort_unstable();
|
|
119
|
+
for name in missing {
|
|
120
|
+
if let Some((_, sql)) = migrations.iter().find(|(k, _)| *k == name) {
|
|
121
|
+
conn.execute(sql, [])?;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
Ok(())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// `schema.py:_migrate_agent_health_owner_team_id`:缺 owner_team_id → 整表重建补 null 列。
|
|
128
|
+
fn migrate_agent_health_owner_team_id(conn: &Connection) -> Result<(), DbError> {
|
|
129
|
+
let cols = table_layout(conn, "agent_health")?;
|
|
130
|
+
if !cols.iter().any(|c| c == "owner_team_id") {
|
|
131
|
+
conn.execute(CREATE_AGENT_HEALTH_NEW, [])?;
|
|
132
|
+
conn.execute(
|
|
133
|
+
"insert into agent_health_new(owner_team_id, agent_id, status, last_output_at, context_usage_pct, current_task_id, updated_at)\n select null, agent_id, status, last_output_at, context_usage_pct, current_task_id, updated_at from agent_health",
|
|
134
|
+
[],
|
|
135
|
+
)?;
|
|
136
|
+
conn.execute("drop table agent_health", [])?;
|
|
137
|
+
conn.execute("alter table agent_health_new rename to agent_health", [])?;
|
|
138
|
+
}
|
|
139
|
+
ensure_table_columns(conn, "agent_health", AGENT_HEALTH_COLUMNS, &[])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// `schema.py:initialize_schema`:建表 + 列迁移 + agent_health 重建 + 索引 + user_version。
|
|
143
|
+
/// 一个事务内完成(对应 Python `with conn:`)。
|
|
144
|
+
pub fn initialize_schema(conn: &Connection, db_path: Option<&std::path::Path>) -> Result<(), DbError> {
|
|
145
|
+
// Gap 46 入口门(`schema.py:117`):先检测/修复物理列序漂移(legacy DB)。
|
|
146
|
+
// fresh DB 下 no-op(无 managed 表 → 无 diff);随后建表 + 列迁移在自己的事务里。
|
|
147
|
+
crate::db::migration::ensure_table_layout(conn, SCHEMA_VERSION, db_path)?;
|
|
148
|
+
let tx = conn.unchecked_transaction()?;
|
|
149
|
+
for ddl in [
|
|
150
|
+
CREATE_MESSAGES,
|
|
151
|
+
CREATE_RESULTS,
|
|
152
|
+
CREATE_SCHEDULED_EVENTS,
|
|
153
|
+
CREATE_DELIVERY_TOKENS,
|
|
154
|
+
CREATE_AGENT_HEALTH,
|
|
155
|
+
CREATE_PEER_ALLOWLIST,
|
|
156
|
+
CREATE_RESULT_WATCHERS,
|
|
157
|
+
] {
|
|
158
|
+
tx.execute(ddl, [])?;
|
|
159
|
+
}
|
|
160
|
+
ensure_table_columns(
|
|
161
|
+
&tx,
|
|
162
|
+
"messages",
|
|
163
|
+
MESSAGE_COLUMNS,
|
|
164
|
+
&[
|
|
165
|
+
("delivery_attempts", "alter table messages add column delivery_attempts integer not null default 0"),
|
|
166
|
+
("owner_team_id", "alter table messages add column owner_team_id text"),
|
|
167
|
+
],
|
|
168
|
+
)?;
|
|
169
|
+
ensure_table_columns(&tx, "results", RESULT_COLUMNS, &[("owner_team_id", "alter table results add column owner_team_id text")])?;
|
|
170
|
+
ensure_table_columns(&tx, "scheduled_events", SCHEDULED_EVENT_COLUMNS, &[("owner_team_id", "alter table scheduled_events add column owner_team_id text")])?;
|
|
171
|
+
ensure_table_columns(&tx, "delivery_tokens", DELIVERY_TOKEN_COLUMNS, &[])?;
|
|
172
|
+
migrate_agent_health_owner_team_id(&tx)?;
|
|
173
|
+
ensure_table_columns(&tx, "peer_allowlist", PEER_ALLOWLIST_COLUMNS, &[])?;
|
|
174
|
+
ensure_table_columns(&tx, "result_watchers", RESULT_WATCHER_COLUMNS, &[("owner_team_id", "alter table result_watchers add column owner_team_id text")])?;
|
|
175
|
+
tx.execute(CREATE_LEADER_NOTIFICATION_LOG, [])?;
|
|
176
|
+
ensure_table_columns(&tx, "leader_notification_log", LEADER_NOTIFICATION_LOG_COLUMNS, &[])?;
|
|
177
|
+
ensure_schema_indexes(&tx)?;
|
|
178
|
+
tx.execute_batch(&format!("pragma user_version = {SCHEMA_VERSION}"))?;
|
|
179
|
+
tx.commit()?;
|
|
180
|
+
Ok(())
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[cfg(test)]
|
|
184
|
+
mod tests {
|
|
185
|
+
#![allow(clippy::unwrap_used)]
|
|
186
|
+
use super::*;
|
|
187
|
+
|
|
188
|
+
fn fresh() -> Connection {
|
|
189
|
+
let conn = Connection::open_in_memory().unwrap();
|
|
190
|
+
initialize_schema(&conn, None).unwrap();
|
|
191
|
+
conn
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// golden 由 Python initialize_schema 新建 DB 取(team-agent-public@439bef8)。
|
|
195
|
+
#[test]
|
|
196
|
+
fn fresh_schema_matches_python_sqlite_master() {
|
|
197
|
+
let conn = fresh();
|
|
198
|
+
let mut stmt = conn
|
|
199
|
+
.prepare("select name, type from sqlite_master where sql is not null order by name")
|
|
200
|
+
.unwrap();
|
|
201
|
+
let got: Vec<(String, String)> = stmt
|
|
202
|
+
.query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)))
|
|
203
|
+
.unwrap()
|
|
204
|
+
.collect::<Result<_, _>>()
|
|
205
|
+
.unwrap();
|
|
206
|
+
let want: Vec<(&str, &str)> = vec![
|
|
207
|
+
("agent_health", "table"),
|
|
208
|
+
("delivery_tokens", "table"),
|
|
209
|
+
("idx_agent_health_owner_team_id", "index"),
|
|
210
|
+
("idx_leader_notification_log_team_epoch", "index"),
|
|
211
|
+
("idx_leader_notification_log_uuid", "index"),
|
|
212
|
+
("idx_messages_owner_team_id", "index"),
|
|
213
|
+
("idx_result_watchers_owner_team_id", "index"),
|
|
214
|
+
("idx_scheduled_events_owner_team_id", "index"),
|
|
215
|
+
("leader_notification_log", "table"),
|
|
216
|
+
("messages", "table"),
|
|
217
|
+
("peer_allowlist", "table"),
|
|
218
|
+
("result_watchers", "table"),
|
|
219
|
+
("results", "table"),
|
|
220
|
+
("scheduled_events", "table"),
|
|
221
|
+
];
|
|
222
|
+
let got_ref: Vec<(&str, &str)> = got.iter().map(|(n, t)| (n.as_str(), t.as_str())).collect();
|
|
223
|
+
assert_eq!(got_ref, want);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn user_version_is_three() {
|
|
228
|
+
let conn = fresh();
|
|
229
|
+
let v: i64 = conn.query_row("pragma user_version", [], |r| r.get(0)).unwrap();
|
|
230
|
+
assert_eq!(v, SCHEMA_VERSION);
|
|
231
|
+
assert_eq!(v, 3);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[test]
|
|
235
|
+
fn messages_table_layout_matches_python() {
|
|
236
|
+
let conn = fresh();
|
|
237
|
+
assert_eq!(
|
|
238
|
+
table_layout(&conn, "messages").unwrap(),
|
|
239
|
+
vec![
|
|
240
|
+
"message_id", "owner_team_id", "task_id", "sender", "recipient", "reply_to",
|
|
241
|
+
"requires_ack", "status", "content", "artifact_refs", "created_at", "updated_at",
|
|
242
|
+
"delivered_at", "acknowledged_at", "error", "delivery_attempts",
|
|
243
|
+
]
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#[test]
|
|
248
|
+
fn idempotent_reinit() {
|
|
249
|
+
// initialize_schema 二次调用(if not exists / 无缺列)→ 不报错、schema 不变 + 索引仍在。
|
|
250
|
+
let conn = fresh();
|
|
251
|
+
initialize_schema(&conn, None).unwrap();
|
|
252
|
+
assert_eq!(table_layout(&conn, "messages").unwrap().len(), 16);
|
|
253
|
+
let idx: i64 = conn
|
|
254
|
+
.query_row("select count(*) from sqlite_master where type='index' and sql is not null", [], |r| r.get(0))
|
|
255
|
+
.unwrap();
|
|
256
|
+
assert_eq!(idx, 6, "二次 init 后 6 个 named index 仍在");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 全 8 表逐列 (name, type, notnull, dflt, pk) golden(Python initialize_schema table_info)。
|
|
260
|
+
#[test]
|
|
261
|
+
fn per_column_table_info_all_eight_tables() {
|
|
262
|
+
type Col = (&'static str, &'static str, i64, Option<&'static str>, i64);
|
|
263
|
+
let golden: &[(&str, &[Col])] = &[
|
|
264
|
+
("messages", &[
|
|
265
|
+
("message_id","TEXT",0,None,1),("owner_team_id","TEXT",0,None,0),("task_id","TEXT",0,None,0),
|
|
266
|
+
("sender","TEXT",0,None,0),("recipient","TEXT",0,None,0),("reply_to","TEXT",0,None,0),
|
|
267
|
+
("requires_ack","INTEGER",0,None,0),("status","TEXT",0,None,0),("content","TEXT",0,None,0),
|
|
268
|
+
("artifact_refs","TEXT",0,None,0),("created_at","TEXT",0,None,0),("updated_at","TEXT",0,None,0),
|
|
269
|
+
("delivered_at","TEXT",0,None,0),("acknowledged_at","TEXT",0,None,0),("error","TEXT",0,None,0),
|
|
270
|
+
("delivery_attempts","INTEGER",1,Some("0"),0),
|
|
271
|
+
]),
|
|
272
|
+
("results", &[
|
|
273
|
+
("result_id","TEXT",0,None,1),("owner_team_id","TEXT",0,None,0),("task_id","TEXT",1,None,0),
|
|
274
|
+
("agent_id","TEXT",1,None,0),("envelope","TEXT",1,None,0),("status","TEXT",1,None,0),("created_at","TEXT",1,None,0),
|
|
275
|
+
]),
|
|
276
|
+
("scheduled_events", &[
|
|
277
|
+
("id","INTEGER",0,None,1),("owner_team_id","TEXT",0,None,0),("due_at","TEXT",1,None,0),
|
|
278
|
+
("target","TEXT",1,None,0),("kind","TEXT",1,None,0),("payload_json","TEXT",1,None,0),
|
|
279
|
+
("status","TEXT",1,None,0),("created_at","TEXT",1,None,0),("fired_at","TEXT",0,None,0),("result_json","TEXT",0,None,0),
|
|
280
|
+
]),
|
|
281
|
+
("delivery_tokens", &[
|
|
282
|
+
("message_id","TEXT",0,None,1),("unique_token","TEXT",1,None,0),("injected_at","TEXT",1,None,0),
|
|
283
|
+
("visible_at","TEXT",0,None,0),("consumed_at","TEXT",0,None,0),("failed_at","TEXT",0,None,0),("failure_reason","TEXT",0,None,0),
|
|
284
|
+
]),
|
|
285
|
+
("agent_health", &[
|
|
286
|
+
("owner_team_id","TEXT",0,None,0),("agent_id","TEXT",1,None,0),("status","TEXT",1,None,0),
|
|
287
|
+
("last_output_at","TEXT",0,None,0),("context_usage_pct","INTEGER",0,None,0),("current_task_id","TEXT",0,None,0),("updated_at","TEXT",1,None,0),
|
|
288
|
+
]),
|
|
289
|
+
("peer_allowlist", &[
|
|
290
|
+
("a","TEXT",1,None,1),("b","TEXT",1,None,2),("created_at","TEXT",1,None,0),
|
|
291
|
+
]),
|
|
292
|
+
("result_watchers", &[
|
|
293
|
+
("watcher_id","TEXT",0,None,1),("owner_team_id","TEXT",0,None,0),("task_id","TEXT",0,None,0),
|
|
294
|
+
("agent_id","TEXT",0,None,0),("message_id","TEXT",0,None,0),("leader_id","TEXT",1,None,0),
|
|
295
|
+
("status","TEXT",1,None,0),("created_at","TEXT",1,None,0),("completed_at","TEXT",0,None,0),
|
|
296
|
+
("result_id","TEXT",0,None,0),("notified_message_id","TEXT",0,None,0),("error","TEXT",0,None,0),
|
|
297
|
+
]),
|
|
298
|
+
("leader_notification_log", &[
|
|
299
|
+
("result_id","TEXT",1,None,1),("owner_team_id","TEXT",1,Some("''"),2),("owner_epoch","INTEGER",1,Some("0"),3),
|
|
300
|
+
("leader_session_uuid","TEXT",0,None,0),("notified_message_id","TEXT",1,None,0),("notified_at","TEXT",1,None,0),
|
|
301
|
+
("leader_pane_id_at_notify","TEXT",0,None,0),("envelope_content_hash","TEXT",0,None,0),
|
|
302
|
+
]),
|
|
303
|
+
];
|
|
304
|
+
let conn = fresh();
|
|
305
|
+
for (table, cols) in golden {
|
|
306
|
+
let mut stmt = conn.prepare(&format!("pragma table_info({table})")).unwrap();
|
|
307
|
+
let got: Vec<(String, String, i64, Option<String>, i64)> = stmt
|
|
308
|
+
.query_map([], |r| Ok((r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?, r.get(5)?)))
|
|
309
|
+
.unwrap()
|
|
310
|
+
.collect::<Result<_, _>>()
|
|
311
|
+
.unwrap();
|
|
312
|
+
let got_ref: Vec<(&str, &str, i64, Option<&str>, i64)> = got
|
|
313
|
+
.iter()
|
|
314
|
+
.map(|(n, t, nn, d, pk)| (n.as_str(), t.as_str(), *nn, d.as_deref(), *pk))
|
|
315
|
+
.collect();
|
|
316
|
+
assert_eq!(&got_ref[..], *cols, "table_info mismatch for {table}");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#[test]
|
|
321
|
+
fn named_indexes_and_autoindexes_match_python() {
|
|
322
|
+
let conn = fresh();
|
|
323
|
+
let mut stmt = conn.prepare("select name, sql from sqlite_master where type='index' and sql is not null order by name").unwrap();
|
|
324
|
+
let idx: Vec<(String, String)> = stmt
|
|
325
|
+
.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))
|
|
326
|
+
.unwrap().collect::<Result<_, _>>().unwrap();
|
|
327
|
+
let want: Vec<(&str, &str)> = vec![
|
|
328
|
+
("idx_agent_health_owner_team_id", "CREATE INDEX idx_agent_health_owner_team_id on agent_health(owner_team_id)"),
|
|
329
|
+
("idx_leader_notification_log_team_epoch", "CREATE INDEX idx_leader_notification_log_team_epoch on leader_notification_log(owner_team_id, owner_epoch, notified_at)"),
|
|
330
|
+
("idx_leader_notification_log_uuid", "CREATE INDEX idx_leader_notification_log_uuid on leader_notification_log(leader_session_uuid, notified_at)"),
|
|
331
|
+
("idx_messages_owner_team_id", "CREATE INDEX idx_messages_owner_team_id on messages(owner_team_id)"),
|
|
332
|
+
("idx_result_watchers_owner_team_id", "CREATE INDEX idx_result_watchers_owner_team_id on result_watchers(owner_team_id)"),
|
|
333
|
+
("idx_scheduled_events_owner_team_id", "CREATE INDEX idx_scheduled_events_owner_team_id on scheduled_events(owner_team_id)"),
|
|
334
|
+
];
|
|
335
|
+
let got: Vec<(&str, &str)> = idx.iter().map(|(n, s)| (n.as_str(), s.as_str())).collect();
|
|
336
|
+
assert_eq!(got, want);
|
|
337
|
+
// 7 个 sqlite_autoindex(每个有 pk/unique 约束的表各一)。
|
|
338
|
+
let auto: Vec<String> = conn
|
|
339
|
+
.prepare("select name from sqlite_master where type='index' and sql is null order by name").unwrap()
|
|
340
|
+
.query_map([], |r| r.get(0)).unwrap().collect::<Result<_, _>>().unwrap();
|
|
341
|
+
assert_eq!(auto, vec![
|
|
342
|
+
"sqlite_autoindex_agent_health_1", "sqlite_autoindex_delivery_tokens_1",
|
|
343
|
+
"sqlite_autoindex_leader_notification_log_1", "sqlite_autoindex_messages_1",
|
|
344
|
+
"sqlite_autoindex_peer_allowlist_1", "sqlite_autoindex_result_watchers_1", "sqlite_autoindex_results_1",
|
|
345
|
+
]);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#[test]
|
|
349
|
+
fn open_db_sets_wal_and_busy_timeout() {
|
|
350
|
+
// 必须用文件路径(in-memory 的 journal_mode 是 'memory')。
|
|
351
|
+
let dir = std::env::temp_dir().join(format!("ta_rs_pragma_{}", std::process::id()));
|
|
352
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
353
|
+
let path = dir.join("team.db");
|
|
354
|
+
let _ = std::fs::remove_file(&path);
|
|
355
|
+
let conn = open_db(&path).unwrap();
|
|
356
|
+
let jm: String = conn.query_row("pragma journal_mode", [], |r| r.get(0)).unwrap();
|
|
357
|
+
assert_eq!(jm.to_lowercase(), "wal");
|
|
358
|
+
let bt: i64 = conn.query_row("pragma busy_timeout", [], |r| r.get(0)).unwrap();
|
|
359
|
+
assert_eq!(bt, 30000);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#[test]
|
|
363
|
+
fn ensure_table_columns_unsupported_missing_errors_with_sorted_names() {
|
|
364
|
+
// results 只建了 result_id → 缺 owner_team_id(有 migration)+ 其余(无 migration,unsupported)。
|
|
365
|
+
let conn = Connection::open_in_memory().unwrap();
|
|
366
|
+
conn.execute("create table results (result_id text primary key)", []).unwrap();
|
|
367
|
+
let err = ensure_table_columns(
|
|
368
|
+
&conn,
|
|
369
|
+
"results",
|
|
370
|
+
&["owner_team_id", "result_id", "task_id", "agent_id", "envelope", "status", "created_at"],
|
|
371
|
+
&[("owner_team_id", "alter table results add column owner_team_id text")],
|
|
372
|
+
).unwrap_err();
|
|
373
|
+
assert_eq!(
|
|
374
|
+
err.to_string(),
|
|
375
|
+
"schema: team.db table results is missing required column(s): agent_id, created_at, envelope, status, task_id"
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|