@team-agent/installer 0.5.60 → 0.5.62
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 +49 -3
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +184 -0
- package/crates/team-agent/src/cli/emit.rs +104 -21
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +14 -2
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +296 -8
- package/crates/team-agent/src/cli/named_address.rs +26 -2
- package/crates/team-agent/src/cli/profile.rs +2 -1
- package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
- package/crates/team-agent/src/cli/send/persist.rs +1 -0
- package/crates/team-agent/src/cli/send/presentation.rs +3 -0
- package/crates/team-agent/src/cli/send/resolve.rs +206 -1
- package/crates/team-agent/src/cli/send.rs +2 -1
- package/crates/team-agent/src/cli/spec.rs +5 -1
- package/crates/team-agent/src/cli/status.rs +2 -1
- package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
- package/crates/team-agent/src/cli/status_port/format.rs +1 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
- package/crates/team-agent/src/cli/status_port/store.rs +7 -3
- package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +36 -2
- package/crates/team-agent/src/communication_mode/mod.rs +54 -0
- package/crates/team-agent/src/compiler/tests.rs +5 -5
- package/crates/team-agent/src/compiler.rs +54 -1
- package/crates/team-agent/src/conpty/backend.rs +1 -0
- package/crates/team-agent/src/conpty/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/backoff.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
- package/crates/team-agent/src/coordinator/health.rs +1 -0
- package/crates/team-agent/src/coordinator/mod.rs +2 -2
- package/crates/team-agent/src/coordinator/orphan.rs +4 -116
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +6 -5
- package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
- package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +0 -9
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
- package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -42
- package/crates/team-agent/src/coordinator/tick.rs +24 -28
- package/crates/team-agent/src/coordinator/types.rs +3 -176
- package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
- package/crates/team-agent/src/db/message_store.rs +47 -2
- package/crates/team-agent/src/db/migration.rs +5 -4
- package/crates/team-agent/src/db/mod.rs +1 -0
- package/crates/team-agent/src/db/schema.rs +1 -0
- package/crates/team-agent/src/diagnose/comms.rs +2 -1
- package/crates/team-agent/src/diagnose/mod.rs +1 -0
- package/crates/team-agent/src/diagnose/orphans.rs +1 -0
- package/crates/team-agent/src/fake_worker.rs +21 -1
- package/crates/team-agent/src/layout/manager.rs +3 -7
- package/crates/team-agent/src/layout/mod.rs +2 -2
- package/crates/team-agent/src/layout/overlay.rs +6 -1
- package/crates/team-agent/src/layout/placement.rs +1 -0
- package/crates/team-agent/src/layout/recovery.rs +3 -0
- package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
- package/crates/team-agent/src/layout/sessions.rs +8 -1
- package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
- package/crates/team-agent/src/layout/worker_env.rs +3 -0
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/helpers.rs +1 -0
- package/crates/team-agent/src/leader/incident.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +27 -22
- package/crates/team-agent/src/leader/mod.rs +2 -3
- package/crates/team-agent/src/leader/owner_bind.rs +13 -3
- package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +4 -17
- package/crates/team-agent/src/leader/registry.rs +3 -15
- package/crates/team-agent/src/leader/start.rs +1072 -186
- package/crates/team-agent/src/leader/takeover.rs +10 -232
- package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
- package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
- package/crates/team-agent/src/leader/tests/identity.rs +71 -59
- package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
- package/crates/team-agent/src/leader/tests/idle.rs +0 -193
- package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
- package/crates/team-agent/src/leader/tests/mod.rs +4 -41
- package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
- package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
- package/crates/team-agent/src/leader/types.rs +1 -28
- package/crates/team-agent/src/lib.rs +7 -0
- package/crates/team-agent/src/lifecycle/display.rs +7 -1
- package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -28
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch.rs +11 -12
- package/crates/team-agent/src/lifecycle/mod.rs +2 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +4 -4
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
- package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
- package/crates/team-agent/src/lifecycle/restart.rs +13 -11
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
- package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
- package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
- package/crates/team-agent/src/lifecycle/tests.rs +59 -0
- package/crates/team-agent/src/lifecycle/types.rs +0 -9
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
- package/crates/team-agent/src/mcp_server/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +5 -3
- package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
- package/crates/team-agent/src/mcp_server/tools.rs +101 -66
- package/crates/team-agent/src/mcp_server/types.rs +6 -18
- package/crates/team-agent/src/mcp_server/wire.rs +32 -26
- package/crates/team-agent/src/messaging/activity.rs +1 -0
- package/crates/team-agent/src/messaging/address.rs +1 -0
- package/crates/team-agent/src/messaging/delivery.rs +109 -41
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +34 -10
- package/crates/team-agent/src/messaging/leader_receiver.rs +42 -24
- package/crates/team-agent/src/messaging/mod.rs +10 -14
- package/crates/team-agent/src/messaging/peers.rs +2 -0
- package/crates/team-agent/src/messaging/persist.rs +3 -1
- package/crates/team-agent/src/messaging/presentation.rs +111 -1
- package/crates/team-agent/src/messaging/results.rs +166 -17
- package/crates/team-agent/src/messaging/scheduler.rs +1 -0
- package/crates/team-agent/src/messaging/selftest.rs +1 -1
- package/crates/team-agent/src/messaging/send.rs +135 -89
- package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
- package/crates/team-agent/src/messaging/types.rs +7 -2
- package/crates/team-agent/src/messaging/watchers.rs +24 -13
- package/crates/team-agent/src/model/enums.rs +1 -0
- package/crates/team-agent/src/model/errors.rs +1 -0
- package/crates/team-agent/src/model/ids.rs +1 -0
- package/crates/team-agent/src/model/mod.rs +2 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +359 -0
- package/crates/team-agent/src/model/paths.rs +1 -0
- package/crates/team-agent/src/model/permissions.rs +3 -2
- package/crates/team-agent/src/model/routing.rs +1 -0
- package/crates/team-agent/src/model/spec.rs +17 -0
- package/crates/team-agent/src/model/task_graph.rs +1 -0
- package/crates/team-agent/src/model/yaml/tests.rs +1 -0
- package/crates/team-agent/src/model/yaml.rs +1 -0
- package/crates/team-agent/src/packaging/install.rs +9 -147
- package/crates/team-agent/src/packaging/migrate.rs +2 -0
- package/crates/team-agent/src/packaging/mod.rs +1 -0
- package/crates/team-agent/src/packaging/repair.rs +2 -0
- package/crates/team-agent/src/packaging/tests.rs +29 -218
- package/crates/team-agent/src/packaging/types.rs +8 -15
- package/crates/team-agent/src/platform/argv.rs +4 -0
- package/crates/team-agent/src/platform/errors.rs +10 -1
- package/crates/team-agent/src/platform/file_lock.rs +4 -157
- package/crates/team-agent/src/platform/mod.rs +0 -61
- package/crates/team-agent/src/platform/process.rs +1 -0
- package/crates/team-agent/src/provider/adapter.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
- package/crates/team-agent/src/provider/classify.rs +1 -0
- package/crates/team-agent/src/provider/faults.rs +1 -26
- package/crates/team-agent/src/provider/helpers.rs +1 -0
- package/crates/team-agent/src/provider/mod.rs +1 -1
- package/crates/team-agent/src/provider/session/capture.rs +41 -22
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
- package/crates/team-agent/src/provider/session/mod.rs +2 -4
- package/crates/team-agent/src/provider/session/resume.rs +2 -209
- package/crates/team-agent/src/provider/session_scan/claude.rs +93 -3
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +24 -7
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +6 -42
- package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
- package/crates/team-agent/src/provider/types.rs +1 -0
- package/crates/team-agent/src/provider/wire.rs +1 -0
- package/crates/team-agent/src/state/identity.rs +16 -5
- package/crates/team-agent/src/state/identity_keys.rs +1 -0
- package/crates/team-agent/src/state/mod.rs +2 -0
- package/crates/team-agent/src/state/owner_gate.rs +5 -0
- package/crates/team-agent/src/state/ownership.rs +10 -4
- package/crates/team-agent/src/state/paths.rs +12 -0
- package/crates/team-agent/src/state/persist.rs +9 -3
- package/crates/team-agent/src/state/projection.rs +15 -2
- package/crates/team-agent/src/state/repository/intent.rs +1 -0
- package/crates/team-agent/src/state/repository/tests.rs +1 -0
- package/crates/team-agent/src/state/repository.rs +7 -0
- package/crates/team-agent/src/state/selector.rs +1 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
- package/crates/team-agent/src/tmux_backend.rs +63 -0
- package/crates/team-agent/src/topology.rs +3 -0
- package/crates/team-agent/src/transport.rs +15 -0
- package/package.json +4 -4
- package/skills/team-agent/command-coverage.json +379 -0
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
//! RED contract — 0.5.57 typed-state car · batch F
|
|
2
|
+
//! Terminal-state immutability + framework-replay vs provider-re-render
|
|
3
|
+
//! diagnostic separation.
|
|
4
|
+
//!
|
|
5
|
+
//! LINEAGE:
|
|
6
|
+
//! - Baseline: 5b847e4 (0.5.56 tested tip).
|
|
7
|
+
//! - Distillation §1.4 "两种历史消息重现必须分诊" and §3.3#5
|
|
8
|
+
//! "Submitted→Delivered 后任何 claim/attach/restart 都不可逆".
|
|
9
|
+
//! - Storm remote-control export proved that `delivered` was never
|
|
10
|
+
//! re-injected (attempts / delivered_at unchanged); the provider
|
|
11
|
+
//! re-rendered the historical transcript on `codex resume`. Any
|
|
12
|
+
//! diagnostic that conflates the two will misattribute
|
|
13
|
+
//! provider-side redraw as a framework replay.
|
|
14
|
+
//!
|
|
15
|
+
//! TEETH (RED at 5b847e4):
|
|
16
|
+
//! 1. `delivered_row_immutable_under_any_transition`
|
|
17
|
+
//! — a `delivered` row's `delivered_at`, `delivery_attempts`,
|
|
18
|
+
//! `status` and delivery token must be BYTE-STABLE across any
|
|
19
|
+
//! claim/attach/requeue call from the typed-state surface.
|
|
20
|
+
//! Baseline: passes (invariant lock) → green.
|
|
21
|
+
//! 2. `acknowledged_row_immutable_under_generic_mark`
|
|
22
|
+
//! — an `acknowledged` row must not be overwritten by a generic
|
|
23
|
+
//! `store.mark` call that could regress it to a delivery status.
|
|
24
|
+
//! Baseline: distillation §1.2 acknowledged row notes "current
|
|
25
|
+
//! guard still allows other status to overwrite; typed car needs
|
|
26
|
+
//! to tighten." → red.
|
|
27
|
+
//! 3. `consumed_row_immutable_under_any_transition`
|
|
28
|
+
//! — a `consumed` row must not re-enter any recovery arm.
|
|
29
|
+
//! Baseline: source scan — no explicit exclusion. → red.
|
|
30
|
+
//! 4. `typed_event_carries_replay_vs_rerender_kind`
|
|
31
|
+
//! — the framework must emit a typed marker distinguishing
|
|
32
|
+
//! `framework_replay` (new attempt) from `provider_rerender`
|
|
33
|
+
//! (transcript redraw, no new attempt). Baseline: no such
|
|
34
|
+
//! marker; every `send.injected` looks the same regardless of
|
|
35
|
+
//! whether it is a fresh submit or a resume redraw. → red.
|
|
36
|
+
//! 5. `restart_never_marks_delivered_as_accepted`
|
|
37
|
+
//! — a restart / resume path must not translate a re-rendered
|
|
38
|
+
//! transcript token into a new `messages.status = 'accepted'`
|
|
39
|
+
//! write for an already-`delivered` row. Baseline (invariant
|
|
40
|
+
//! lock, storm proved): green.
|
|
41
|
+
//!
|
|
42
|
+
//! POSITIVE CONTROL:
|
|
43
|
+
//! 6. `delivered_to_acknowledged_transition_still_allowed` — the
|
|
44
|
+
//! legitimate business terminal transition (delivered →
|
|
45
|
+
//! acknowledged via the ack API) must remain functional.
|
|
46
|
+
//!
|
|
47
|
+
//! NEGATIVE CONTROL:
|
|
48
|
+
//! 7. `stray_ack_call_does_not_promote_pending_row_to_acknowledged`
|
|
49
|
+
//! — a mis-scoped ack call must not skip a row's delivery lifecycle
|
|
50
|
+
//! and jump straight from `accepted` to `acknowledged`.
|
|
51
|
+
//!
|
|
52
|
+
//! FROZEN by verifier — do NOT modify without a new SHA256 signature.
|
|
53
|
+
|
|
54
|
+
#![cfg(unix)]
|
|
55
|
+
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
|
|
56
|
+
|
|
57
|
+
use crate as team_agent;
|
|
58
|
+
|
|
59
|
+
#[path = "rehomed_env.rs"]
|
|
60
|
+
mod rehomed_env;
|
|
61
|
+
|
|
62
|
+
use std::path::PathBuf;
|
|
63
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
64
|
+
|
|
65
|
+
use rusqlite::params;
|
|
66
|
+
use serial_test::serial;
|
|
67
|
+
use team_agent::event_log::EventLog;
|
|
68
|
+
use team_agent::leader::{claim_lease_no_incident, LeaseStatus};
|
|
69
|
+
use team_agent::message_store::MessageStore;
|
|
70
|
+
use team_agent::messaging::enqueue_leader_mailbox_until_attach;
|
|
71
|
+
use team_agent::messaging::watchers::requeue_after_claim_leader;
|
|
72
|
+
use team_agent::model::enums::PaneLiveness;
|
|
73
|
+
use team_agent::model::ids::TeamKey;
|
|
74
|
+
use team_agent::state::owner_gate::PaneLivenessProbe;
|
|
75
|
+
use team_agent::transport::PaneId;
|
|
76
|
+
|
|
77
|
+
const TEAM: &str = "current";
|
|
78
|
+
const PANE: &str = "%leader";
|
|
79
|
+
|
|
80
|
+
struct LiveCaller;
|
|
81
|
+
impl PaneLivenessProbe for LiveCaller {
|
|
82
|
+
fn liveness(&self, pane_id: &str) -> PaneLiveness {
|
|
83
|
+
if pane_id == PANE {
|
|
84
|
+
PaneLiveness::Live
|
|
85
|
+
} else {
|
|
86
|
+
PaneLiveness::Dead
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn bound_state() -> serde_json::Value {
|
|
92
|
+
serde_json::json!({
|
|
93
|
+
"session_name": "team-terminal-inv",
|
|
94
|
+
"team_owner": {
|
|
95
|
+
"pane_id": PANE, "provider": "codex", "owner_epoch": 7,
|
|
96
|
+
"claimed_at": "2026-07-24T00:00:00Z", "claimed_via": "claim-leader"
|
|
97
|
+
},
|
|
98
|
+
"leader_receiver": {
|
|
99
|
+
"pane_id": PANE, "provider": "codex", "owner_epoch": 7, "status": "attached"
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
struct TermCase {
|
|
105
|
+
_env: rehomed_env::RehomedTestEnv,
|
|
106
|
+
workspace: PathBuf,
|
|
107
|
+
store: MessageStore,
|
|
108
|
+
event_log: EventLog,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
impl TermCase {
|
|
112
|
+
fn new(tag: &str) -> Self {
|
|
113
|
+
static N: AtomicU64 = AtomicU64::new(0);
|
|
114
|
+
let env = rehomed_env::RehomedTestEnv::enter(tag);
|
|
115
|
+
let workspace = env.workspace(&format!(
|
|
116
|
+
"057f-term-{tag}-{}",
|
|
117
|
+
N.fetch_add(1, Ordering::Relaxed)
|
|
118
|
+
));
|
|
119
|
+
let store = MessageStore::open(&workspace).unwrap();
|
|
120
|
+
let event_log = EventLog::new(&workspace);
|
|
121
|
+
Self {
|
|
122
|
+
_env: env,
|
|
123
|
+
workspace,
|
|
124
|
+
store,
|
|
125
|
+
event_log,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn seed(&self, content: &str, status: &str, delivered_at: Option<&str>) -> String {
|
|
130
|
+
let mid = enqueue_leader_mailbox_until_attach(
|
|
131
|
+
&self.workspace,
|
|
132
|
+
TEAM,
|
|
133
|
+
content,
|
|
134
|
+
None,
|
|
135
|
+
"leader",
|
|
136
|
+
&self.event_log,
|
|
137
|
+
)
|
|
138
|
+
.unwrap()
|
|
139
|
+
.message_id
|
|
140
|
+
.expect("mailbox enqueue");
|
|
141
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
142
|
+
conn.execute(
|
|
143
|
+
"update messages set status = ?2, delivered_at = ?3, delivery_attempts = 1
|
|
144
|
+
where message_id = ?1",
|
|
145
|
+
params![mid, status, delivered_at],
|
|
146
|
+
)
|
|
147
|
+
.unwrap();
|
|
148
|
+
mid
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fn full(&self, mid: &str) -> (String, Option<String>, i64) {
|
|
152
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
153
|
+
conn.query_row(
|
|
154
|
+
"select status, delivered_at, delivery_attempts from messages where message_id = ?1",
|
|
155
|
+
params![mid],
|
|
156
|
+
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
|
|
157
|
+
)
|
|
158
|
+
.unwrap()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn claim_and_requeue(&self) -> LeaseStatus {
|
|
162
|
+
let team = TeamKey::new(TEAM.to_string());
|
|
163
|
+
let pane = PaneId::new(PANE);
|
|
164
|
+
let mut state = bound_state();
|
|
165
|
+
let result = claim_lease_no_incident(
|
|
166
|
+
&self.workspace,
|
|
167
|
+
&mut state,
|
|
168
|
+
None,
|
|
169
|
+
&team,
|
|
170
|
+
&pane,
|
|
171
|
+
true,
|
|
172
|
+
&self.event_log,
|
|
173
|
+
&LiveCaller,
|
|
174
|
+
)
|
|
175
|
+
.unwrap();
|
|
176
|
+
if result.ok {
|
|
177
|
+
let _ = requeue_after_claim_leader(
|
|
178
|
+
&self.workspace,
|
|
179
|
+
&self.store,
|
|
180
|
+
&self.event_log,
|
|
181
|
+
&team,
|
|
182
|
+
result.bound_pane_id.as_ref().expect("pane"),
|
|
183
|
+
None,
|
|
184
|
+
)
|
|
185
|
+
.unwrap();
|
|
186
|
+
}
|
|
187
|
+
result.status
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Tooth 1 — delivered row is byte-stable across claim (invariant lock)
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
#[test]
|
|
196
|
+
#[serial(env)]
|
|
197
|
+
fn delivered_row_immutable_under_any_transition() {
|
|
198
|
+
let case = TermCase::new("delivered-lock");
|
|
199
|
+
let mid = case.seed(
|
|
200
|
+
"delivered canary",
|
|
201
|
+
"delivered",
|
|
202
|
+
Some("2026-07-21T12:00:00Z"),
|
|
203
|
+
);
|
|
204
|
+
let before = case.full(&mid);
|
|
205
|
+
let _ = case.claim_and_requeue();
|
|
206
|
+
let after = case.full(&mid);
|
|
207
|
+
assert_eq!(
|
|
208
|
+
before, after,
|
|
209
|
+
"delivered row must be byte-stable across any typed-state transition; \
|
|
210
|
+
a re-enqueue would create a second physical submit for a message the \
|
|
211
|
+
framework already saw succeed. §3.3#5 + storm proof."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Tooth 2 — acknowledged row cannot be regressed by generic mark
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
#[test]
|
|
220
|
+
#[serial(env)]
|
|
221
|
+
fn acknowledged_row_immutable_under_generic_mark() {
|
|
222
|
+
let case = TermCase::new("ack-lock");
|
|
223
|
+
let mid = case.seed("acked canary", "acknowledged", Some("2026-07-21T12:00:00Z"));
|
|
224
|
+
let before = case.full(&mid);
|
|
225
|
+
// Baseline weakness (distillation §1.2 acknowledged note): a generic
|
|
226
|
+
// `store.mark(mid, "target_resolved", None)` currently succeeds and
|
|
227
|
+
// silently regresses the row. Typed refactor MUST make this a
|
|
228
|
+
// hard-refused transition (typed error).
|
|
229
|
+
let mark_result = case.store.mark(&mid, "target_resolved", None);
|
|
230
|
+
let after = case.full(&mid);
|
|
231
|
+
assert!(
|
|
232
|
+
mark_result.is_err() || after == before,
|
|
233
|
+
"acknowledged row was regressed by a generic mark call: before={:?} \
|
|
234
|
+
after={:?} mark_result={:?}. §1.2 requires terminal states to refuse \
|
|
235
|
+
any downgrade from a non-ack API; typed refactor must return a typed \
|
|
236
|
+
error rather than silently overwrite.",
|
|
237
|
+
before,
|
|
238
|
+
after,
|
|
239
|
+
mark_result
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Tooth 3 — consumed row is untouchable by any recovery arm
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
#[test]
|
|
248
|
+
#[serial(env)]
|
|
249
|
+
fn consumed_row_immutable_under_any_transition() {
|
|
250
|
+
let case = TermCase::new("consumed-lock");
|
|
251
|
+
let mid = case.seed("consumed canary", "consumed", None);
|
|
252
|
+
let before = case.full(&mid);
|
|
253
|
+
// A generic mark attempt should also refuse to regress a `consumed`
|
|
254
|
+
// row. Baseline: the same generic mark path applies, distillation
|
|
255
|
+
// §1.2 already flags this. Also verify claim/requeue does not touch
|
|
256
|
+
// it.
|
|
257
|
+
let mark_result = case.store.mark(&mid, "accepted", None);
|
|
258
|
+
let _ = case.claim_and_requeue();
|
|
259
|
+
let after = case.full(&mid);
|
|
260
|
+
assert!(
|
|
261
|
+
(mark_result.is_err() || after.0 == "consumed") && after.0 == "consumed",
|
|
262
|
+
"consumed row was affected: before={:?} after={:?} mark_result={:?}. \
|
|
263
|
+
§1.2 consumed is a terminal disposition; typed refactor must refuse \
|
|
264
|
+
any downgrade or requeue.",
|
|
265
|
+
before,
|
|
266
|
+
after,
|
|
267
|
+
mark_result
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// Tooth 4 — typed event carries framework_replay vs provider_rerender kind
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
#[test]
|
|
276
|
+
#[serial(env)]
|
|
277
|
+
fn typed_event_carries_replay_vs_rerender_kind() {
|
|
278
|
+
// Source-shape probe: the delivery + acceptance event schemas must
|
|
279
|
+
// include a typed `redisplay_kind` (or equivalent) enum with at
|
|
280
|
+
// least the two values.
|
|
281
|
+
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
282
|
+
let src = manifest.join("src");
|
|
283
|
+
let mut has_marker = false;
|
|
284
|
+
fn walk(dir: &std::path::Path, has_marker: &mut bool) {
|
|
285
|
+
let Ok(es) = std::fs::read_dir(dir) else {
|
|
286
|
+
return;
|
|
287
|
+
};
|
|
288
|
+
for e in es.flatten() {
|
|
289
|
+
let p = e.path();
|
|
290
|
+
if p.is_dir() {
|
|
291
|
+
walk(&p, has_marker);
|
|
292
|
+
} else if p.extension().and_then(|s| s.to_str()) == Some("rs") {
|
|
293
|
+
if let Ok(t) = std::fs::read_to_string(&p) {
|
|
294
|
+
if (t.contains("framework_replay") && t.contains("provider_rerender"))
|
|
295
|
+
|| t.contains("RedisplayKind::FrameworkReplay")
|
|
296
|
+
|| t.contains("RedisplayKind::ProviderRerender")
|
|
297
|
+
{
|
|
298
|
+
*has_marker = true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
walk(&src, &mut has_marker);
|
|
305
|
+
assert!(
|
|
306
|
+
has_marker,
|
|
307
|
+
"no typed redisplay-kind marker found (framework_replay / provider_rerender). \
|
|
308
|
+
§1.4 requires diagnostics to distinguish framework replay (new attempt) from \
|
|
309
|
+
provider re-render (transcript redraw, no new attempt). Baseline: every \
|
|
310
|
+
'send.injected' event looks identical."
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
// Tooth 5 — restart never marks delivered as accepted (invariant lock)
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
#[test]
|
|
319
|
+
#[serial(env)]
|
|
320
|
+
fn restart_never_marks_delivered_as_accepted() {
|
|
321
|
+
// Source-scan: no file may contain a SQL literal that translates a
|
|
322
|
+
// `delivered` row to `accepted`. We check for the substring
|
|
323
|
+
// `set status = 'accepted'` inside any block that also mentions
|
|
324
|
+
// `delivered`. This is a coarse guard; the more precise test lives
|
|
325
|
+
// in tooth 1 (behavioral). Both are cheap and complementary.
|
|
326
|
+
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
327
|
+
let src = manifest.join("src");
|
|
328
|
+
let mut violations: Vec<PathBuf> = Vec::new();
|
|
329
|
+
fn walk(dir: &std::path::Path, out: &mut Vec<PathBuf>) {
|
|
330
|
+
let Ok(es) = std::fs::read_dir(dir) else {
|
|
331
|
+
return;
|
|
332
|
+
};
|
|
333
|
+
for e in es.flatten() {
|
|
334
|
+
let p = e.path();
|
|
335
|
+
if p.is_dir() {
|
|
336
|
+
if p.file_name().and_then(|s| s.to_str()) == Some("tests") {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
walk(&p, out);
|
|
340
|
+
} else if p.extension().and_then(|s| s.to_str()) == Some("rs") {
|
|
341
|
+
if let Ok(t) = std::fs::read_to_string(&p) {
|
|
342
|
+
let lines: Vec<&str> = t.lines().collect();
|
|
343
|
+
for (i, l) in lines.iter().enumerate() {
|
|
344
|
+
if l.contains("set status = 'accepted'") {
|
|
345
|
+
let start = i.saturating_sub(20);
|
|
346
|
+
let end = (i + 20).min(lines.len());
|
|
347
|
+
let window = lines[start..end].join("\n");
|
|
348
|
+
// Trigger only if `delivered` (not
|
|
349
|
+
// `delivered_at` alone) is treated as
|
|
350
|
+
// eligible.
|
|
351
|
+
if window.contains("status = 'delivered'")
|
|
352
|
+
|| window.contains("'delivered'")
|
|
353
|
+
{
|
|
354
|
+
out.push(p.clone());
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
walk(&src, &mut violations);
|
|
363
|
+
// 0.5.55 storm proved this invariant held (delivered was untouched).
|
|
364
|
+
// Green at baseline; locked here.
|
|
365
|
+
assert!(
|
|
366
|
+
violations.is_empty(),
|
|
367
|
+
"regression: SQL that flips a `delivered` row to `accepted` found: {:?}. \
|
|
368
|
+
§3.3#5 storm proof — delivered must be irreversibly terminal.",
|
|
369
|
+
violations
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
// Tooth 6 — positive control: delivered→acknowledged is still supported
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
|
|
377
|
+
#[test]
|
|
378
|
+
#[serial(env)]
|
|
379
|
+
fn delivered_to_acknowledged_transition_still_allowed() {
|
|
380
|
+
let case = TermCase::new("pos-ack");
|
|
381
|
+
let mid = case.seed("business ack", "delivered", Some("2026-07-24T00:01:00Z"));
|
|
382
|
+
// Business ack path: mark the row acknowledged via the store API.
|
|
383
|
+
let r = case.store.mark(&mid, "acknowledged", None);
|
|
384
|
+
let (status, _, _) = case.full(&mid);
|
|
385
|
+
assert!(
|
|
386
|
+
r.is_ok() && status == "acknowledged",
|
|
387
|
+
"delivered → acknowledged legitimate business transition must remain \
|
|
388
|
+
functional (mark_result={:?}, status={})",
|
|
389
|
+
r,
|
|
390
|
+
status
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ---------------------------------------------------------------------------
|
|
395
|
+
// Tooth 7 — negative control: stray ack cannot leapfrog delivery
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
|
|
398
|
+
#[test]
|
|
399
|
+
#[serial(env)]
|
|
400
|
+
fn stray_ack_call_does_not_promote_pending_row_to_acknowledged() {
|
|
401
|
+
let case = TermCase::new("neg-stray-ack");
|
|
402
|
+
let mid = case.seed("pending row", "accepted", None);
|
|
403
|
+
let mark_result = case.store.mark(&mid, "acknowledged", None);
|
|
404
|
+
let (status, _, _) = case.full(&mid);
|
|
405
|
+
// Baseline weakness: generic mark permits a jump. Typed refactor
|
|
406
|
+
// must refuse. If mark succeeds today, this tooth is red; if it
|
|
407
|
+
// refuses, tooth stays green.
|
|
408
|
+
assert!(
|
|
409
|
+
mark_result.is_err() || status != "acknowledged",
|
|
410
|
+
"generic mark leap-frogged an accepted row directly to acknowledged \
|
|
411
|
+
(mark_result={:?}, status={}). §1.2 acknowledged transition must be \
|
|
412
|
+
enforced through the ack API's lifecycle guard, not permitted by \
|
|
413
|
+
any writer.",
|
|
414
|
+
mark_result,
|
|
415
|
+
status
|
|
416
|
+
);
|
|
417
|
+
}
|
|
@@ -1,94 +1,5 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
-
// =====================================================================
|
|
4
|
-
// 4. wake 层纯函数(unimplemented → RED)。golden:probe_leader.py 5 分支 + boundary。
|
|
5
|
-
// =====================================================================
|
|
6
|
-
|
|
7
|
-
#[test]
|
|
8
|
-
fn should_reread_no_file_when_current_mtime_none() {
|
|
9
|
-
// current_mtime is None → {reread:false, no_file}(wake.py:31-32)。
|
|
10
|
-
let d = should_reread(None, None, None, 100.0, 60.0);
|
|
11
|
-
assert!(!d.reread);
|
|
12
|
-
assert_eq!(d.reason, RereadReason::NoFile);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
#[test]
|
|
16
|
-
fn should_reread_never_classified() {
|
|
17
|
-
// last_classified_mtime is None → {reread:true, never_classified}。
|
|
18
|
-
let d = should_reread(None, Some(10.0), None, 100.0, 60.0);
|
|
19
|
-
assert!(d.reread);
|
|
20
|
-
assert_eq!(d.reason, RereadReason::NeverClassified);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
#[test]
|
|
24
|
-
fn should_reread_file_changed() {
|
|
25
|
-
// current != last_classified → {reread:true, file_changed}。
|
|
26
|
-
let d = should_reread(Some(5.0), Some(20.0), Some(10.0), 100.0, 60.0);
|
|
27
|
-
assert!(d.reread);
|
|
28
|
-
assert_eq!(d.reason, RereadReason::FileChanged);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#[test]
|
|
32
|
-
fn should_reread_quiescent_already_classified_at_and_past_debounce() {
|
|
33
|
-
// current==last_classified 且 silent_for >= debounce → quiescent(不再重读)。
|
|
34
|
-
// silent_for = max(0, now-current_mtime) = 100-10 = 90 >= 60。
|
|
35
|
-
let d = should_reread(Some(10.0), Some(10.0), Some(10.0), 100.0, 60.0);
|
|
36
|
-
assert!(!d.reread);
|
|
37
|
-
assert_eq!(d.reason, RereadReason::QuiescentAlreadyClassified);
|
|
38
|
-
// 边界:silent_for 恰 == debounce(now-current=60)→ 仍 quiescent(>= 比较)。
|
|
39
|
-
let b = should_reread(Some(40.0), Some(40.0), Some(40.0), 100.0, 60.0);
|
|
40
|
-
assert!(!b.reread);
|
|
41
|
-
assert_eq!(b.reason, RereadReason::QuiescentAlreadyClassified);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
#[test]
|
|
45
|
-
fn should_reread_unchanged_within_debounce() {
|
|
46
|
-
// current==last_classified 且 silent_for < debounce → unchanged。
|
|
47
|
-
// now-current = 100-95 = 5 < 60。注:last_mtime 在 Python body 中未被使用。
|
|
48
|
-
let d = should_reread(Some(10.0), Some(95.0), Some(95.0), 100.0, 60.0);
|
|
49
|
-
assert!(!d.reread);
|
|
50
|
-
assert_eq!(d.reason, RereadReason::Unchanged);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
#[test]
|
|
54
|
-
fn on_file_changed_adds_node_sorted_and_records_mtime() {
|
|
55
|
-
// golden probe_leader.py:add b → pending ["b"];add a → sorted ["a","b"]。
|
|
56
|
-
let s0 = on_file_changed(None, "b", 1.0);
|
|
57
|
-
assert_eq!(s0.pending, vec!["b".to_string()]);
|
|
58
|
-
assert_eq!(s0.mtimes.get("b"), Some(&1.0));
|
|
59
|
-
let s1 = on_file_changed(Some(&s0), "a", 2.0);
|
|
60
|
-
assert_eq!(
|
|
61
|
-
s1.pending,
|
|
62
|
-
vec!["a".to_string(), "b".to_string()],
|
|
63
|
-
"pending 必须 sorted"
|
|
64
|
-
);
|
|
65
|
-
assert_eq!(s1.mtimes.get("a"), Some(&2.0));
|
|
66
|
-
// 重复 add b:pending 仍是 set(不重复),mtime 被更新到 3.0。
|
|
67
|
-
let s2 = on_file_changed(Some(&s1), "b", 3.0);
|
|
68
|
-
assert_eq!(s2.pending, vec!["a".to_string(), "b".to_string()]);
|
|
69
|
-
assert_eq!(s2.mtimes.get("b"), Some(&3.0));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
#[test]
|
|
73
|
-
fn take_pending_drains_sorted_and_clears_pending_but_keeps_mtimes() {
|
|
74
|
-
// golden probe_leader.py:drain → (["a","b"], state{pending:[], mtimes 保留})。
|
|
75
|
-
let mut st = on_file_changed(None, "b", 3.0);
|
|
76
|
-
st = on_file_changed(Some(&st), "a", 2.0);
|
|
77
|
-
let (drained, after) = take_pending(Some(&st));
|
|
78
|
-
assert_eq!(drained, vec!["a".to_string(), "b".to_string()]);
|
|
79
|
-
assert!(after.pending.is_empty());
|
|
80
|
-
// mtimes 不被 drain 清空。
|
|
81
|
-
assert_eq!(after.mtimes.get("a"), Some(&2.0));
|
|
82
|
-
assert_eq!(after.mtimes.get("b"), Some(&3.0));
|
|
83
|
-
// 再 drain → 空。
|
|
84
|
-
let (drained2, _after2) = take_pending(Some(&after));
|
|
85
|
-
assert!(drained2.is_empty());
|
|
86
|
-
// drain None → ([], default state)。
|
|
87
|
-
let (none_drained, none_state) = take_pending(None);
|
|
88
|
-
assert!(none_drained.is_empty());
|
|
89
|
-
assert!(none_state.pending.is_empty());
|
|
90
|
-
}
|
|
91
|
-
|
|
92
3
|
// =====================================================================
|
|
93
4
|
// 5. leader_session_name — sha1 派生 + 文件夹消毒(unimplemented → RED)
|
|
94
5
|
// =====================================================================
|
|
@@ -194,28 +105,33 @@ fn owner_bind_refused_event_name_is_owner_bind_refused() {
|
|
|
194
105
|
assert_eq!(LeaderEvent::OwnerBindRefused.name(), "owner.bind_refused");
|
|
195
106
|
}
|
|
196
107
|
|
|
197
|
-
//
|
|
108
|
+
// bind_owner_from_caller_pane:成功绑定后调用 owner.bound_from_caller_pane 审计 hook
|
|
109
|
+
// (leader_binding.py:162-183)。
|
|
198
110
|
// 强化(no-full-uuid-leak 命门):事件只写 derived_uuid_prefix == derived[:12](12 hex),
|
|
199
111
|
// old uuid 为 None → old_uuid_prefix == ""(空串,非缺省);全 32 hex uuid 绝不出现在任何字段。
|
|
200
112
|
// unimplemented → RED。
|
|
201
113
|
#[test]
|
|
202
114
|
fn emit_owner_bound_event_logs_prefix_only_never_full_uuid() {
|
|
115
|
+
let _lock = ENV_LOCK.lock().unwrap();
|
|
203
116
|
let ws = std::env::temp_dir().join(format!("ta_rs_emit_{}", std::process::id()));
|
|
204
117
|
std::fs::create_dir_all(&ws).unwrap();
|
|
205
|
-
let
|
|
206
|
-
|
|
207
|
-
|
|
118
|
+
let _env = EnvGuard::apply(&[
|
|
119
|
+
("TMUX_PANE", Some("%7")),
|
|
120
|
+
("TEAM_AGENT_MACHINE_FINGERPRINT", Some("fp")),
|
|
121
|
+
("TEAM_AGENT_LEADER_PROVIDER", Some("codex")),
|
|
122
|
+
("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
|
|
123
|
+
]);
|
|
124
|
+
let result = bind_owner_from_caller_pane(&ws, &TeamKey::new("default"), None).unwrap();
|
|
125
|
+
assert!(result.ok, "caller pane should produce an owner binding");
|
|
126
|
+
let owner = result.owner.as_ref().expect("successful bind has owner");
|
|
127
|
+
let full = owner
|
|
128
|
+
.leader_session_uuid
|
|
129
|
+
.as_ref()
|
|
130
|
+
.expect("successful bind has leader session uuid")
|
|
131
|
+
.as_str()
|
|
132
|
+
.to_string();
|
|
208
133
|
assert_eq!(full.len(), 32, "derive 产 32 hex");
|
|
209
134
|
let prefix12 = full[..12].to_string();
|
|
210
|
-
emit_owner_bound_event(
|
|
211
|
-
&ws,
|
|
212
|
-
&caller,
|
|
213
|
-
"codex",
|
|
214
|
-
&derived,
|
|
215
|
-
&TeamKey::new("default"),
|
|
216
|
-
None,
|
|
217
|
-
)
|
|
218
|
-
.unwrap();
|
|
219
135
|
// 读回审计事件:恰一条 owner.bound_from_caller_pane。
|
|
220
136
|
let events = crate::event_log::EventLog::new(&ws).tail(50).unwrap();
|
|
221
137
|
let ev = events
|
|
@@ -223,7 +139,10 @@ fn emit_owner_bound_event_logs_prefix_only_never_full_uuid() {
|
|
|
223
139
|
.find(|e| e["event"] == serde_json::json!("owner.bound_from_caller_pane"))
|
|
224
140
|
.expect("必写 owner.bound_from_caller_pane");
|
|
225
141
|
assert_eq!(ev["caller_pane_id"], serde_json::json!("%7"));
|
|
226
|
-
assert_eq!(
|
|
142
|
+
assert_eq!(
|
|
143
|
+
ev["caller_current_command"],
|
|
144
|
+
serde_json::json!(result.caller_current_command)
|
|
145
|
+
);
|
|
227
146
|
assert_eq!(ev["team_id"], serde_json::json!("default"));
|
|
228
147
|
// derived_uuid_prefix == derived[:12](只前缀,12 hex)。
|
|
229
148
|
assert_eq!(ev["derived_uuid_prefix"], serde_json::json!(prefix12));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! leader 数据层 — lease/owner/incident 枚举 + struct + 错误 + cross-lane 占位/trait + 锁名常量。
|
|
2
3
|
|
|
3
4
|
use std::collections::BTreeMap;
|
|
@@ -472,34 +473,6 @@ pub enum LeaderError {
|
|
|
472
473
|
Messaging(#[from] crate::messaging::MessagingError),
|
|
473
474
|
}
|
|
474
475
|
|
|
475
|
-
// ===========================================================================
|
|
476
|
-
// CROSS-LANE PLACEHOLDERS — 其他并行 lane 的 typed 依赖,leader 集成时 reconcile。
|
|
477
|
-
// (本骨架定义 MINIMAL 本地占位,不猜对方精确命名;见 cross_deps_or_placeholders。)
|
|
478
|
-
// ===========================================================================
|
|
479
|
-
|
|
480
|
-
/// 命名互斥锁句柄占位(step 11 messaging / runtime 的 `_runtime_lock(workspace, name)`)。
|
|
481
|
-
/// `LEADER_OWNERSHIP_LOCK = "send"`:attach/claim/takeover/autobind 共享同一临界区。
|
|
482
|
-
/// **PLACEHOLDER**:真锁类型(文件锁 / RAII guard)在 runtime/messaging lane 落地。
|
|
483
|
-
pub struct RuntimeLockGuard {
|
|
484
|
-
pub _name: String,
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/// MessageStore 占位引用(step 7 已有 `message_store::MessageStore`,但 requeue 方法
|
|
488
|
-
/// `requeue_delivery_exhausted_watchers` / `requeue_after_claim_leader` 属 step 11 messaging)。
|
|
489
|
-
/// **PLACEHOLDER**:claim/attach 后 requeue exhausted result watchers 的接口签名,
|
|
490
|
-
/// 由 messaging lane 落地。
|
|
491
|
-
pub struct RequeueOutcome {
|
|
492
|
-
pub watcher_ids: Vec<String>,
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/// `_resolve_leader_pane` 返回的 pane 信息占位(step 9 transport `PaneInfo` 的相邻产物)。
|
|
496
|
-
/// `__init__.py:276` 调 `runtime._resolve_leader_pane` 返 `(pane_info, discovery)`。
|
|
497
|
-
/// **PLACEHOLDER**:实际 pane_info 字段映射到 `transport::PaneInfo`,resolve 逻辑在 step 9/11。
|
|
498
|
-
pub struct ResolvedLeaderPane {
|
|
499
|
-
pub pane_info: Value,
|
|
500
|
-
pub discovery: Discovery,
|
|
501
|
-
}
|
|
502
|
-
|
|
503
476
|
// ===========================================================================
|
|
504
477
|
// 常量 / 锁名
|
|
505
478
|
// ===========================================================================
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
//! provider client crate,测试断言 mock 调用计数 = 0。
|
|
36
36
|
|
|
37
37
|
// step 2 (model) — typed bedrock。其余 step 落地时在此挂载。
|
|
38
|
+
#[cfg(test)]
|
|
39
|
+
extern crate self as team_agent;
|
|
40
|
+
|
|
38
41
|
pub mod model;
|
|
39
42
|
|
|
40
43
|
// step 3 (db) — team.db SQLite schema/migration(rusqlite bundled)。
|
|
@@ -50,6 +53,10 @@ pub mod state;
|
|
|
50
53
|
// step 6 (compiler) — TEAM.md + agents/*.md → 规范 team.spec dict(doc→spec 纯变换)。
|
|
51
54
|
pub mod compiler;
|
|
52
55
|
|
|
56
|
+
// Official communication-mode catalog. Configuration compilation and every
|
|
57
|
+
// future projection consumer must use this typed single source of truth.
|
|
58
|
+
pub mod communication_mode;
|
|
59
|
+
|
|
53
60
|
// step 7 (message_store) — team.db 上的核心消息生命周期(create/claim/mark/通知去重)。
|
|
54
61
|
// unit-10 (Stage 4) compat shim. Physical home is now `crate::db::message_store`.
|
|
55
62
|
pub use crate::db::message_store;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//!
|
|
1
2
|
//! lifecycle::display —— 能力门 / 后端解析 / 开关 / rebind 后重建。
|
|
2
3
|
//!
|
|
3
4
|
//! 0.5.39 Slice 1 (tmux-server-death-locate §7 Slice 1): all tmux
|
|
@@ -19,6 +20,7 @@ use super::*;
|
|
|
19
20
|
|
|
20
21
|
// ── lifecycle::display —— 能力门 / 后端解析 / 开关 / rebind 后重建 ────────────
|
|
21
22
|
|
|
23
|
+
///
|
|
22
24
|
/// `resolve_display_backend(requested, recorded, source)`(`display/backend.py`)。默认
|
|
23
25
|
/// adaptive;显式 none 是逃生口。
|
|
24
26
|
pub fn resolve_display_backend(
|
|
@@ -32,6 +34,7 @@ pub fn resolve_display_backend(
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
///
|
|
35
38
|
/// `probe_display_capabilities(...)`(`display/adaptive.py:31`,C13)。能力探测,分支只看
|
|
36
39
|
/// 结果不看 `cfg!(target_os)`;Windows/WSL → `NotImplementedThisPlatform`。
|
|
37
40
|
pub fn probe_display_capabilities(_workspace: &Path) -> Result<DisplayProbe, LifecycleError> {
|
|
@@ -68,10 +71,11 @@ pub fn probe_display_capabilities(_workspace: &Path) -> Result<DisplayProbe, Lif
|
|
|
68
71
|
})
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
///
|
|
71
75
|
/// `open_worker_displays(workspace, session_name, jobs, backend, capability_probe)`
|
|
72
76
|
/// (`display/worker_window.py`)。按后端分派 adaptive / ghostty_workspace / ghostty_window;
|
|
73
77
|
/// 显示失败**不阻塞** team readiness(C14)。
|
|
74
|
-
pub fn open_worker_displays(
|
|
78
|
+
pub(crate) fn open_worker_displays(
|
|
75
79
|
workspace: &Path,
|
|
76
80
|
session_name: &SessionName,
|
|
77
81
|
backend: DisplayBackend,
|
|
@@ -91,6 +95,7 @@ pub fn open_worker_displays(
|
|
|
91
95
|
Ok(OpenDisplaysReport { backend, displays })
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
///
|
|
94
99
|
/// `close_team_display_backends(state, event_log)`(`display/close.py`,C9)。按 state
|
|
95
100
|
/// 记录的后端关显示;adaptive 只删带 team tag 的窗口(C2 leader pane 安全)+ orphan 清理。
|
|
96
101
|
pub fn close_team_display_backends(
|
|
@@ -100,6 +105,7 @@ pub fn close_team_display_backends(
|
|
|
100
105
|
close_adaptive_displays(workspace, session_name)
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
///
|
|
103
109
|
/// `rebuild_adaptive_display_after_rebind(...)`(`display/rebuild.py`,C12)。restart 在
|
|
104
110
|
/// leader rebind **之后**重建 adaptive 窗口(读 `leader_receiver.rebind_applied` 事件)。
|
|
105
111
|
pub fn rebuild_adaptive_display_after_rebind(
|