@team-agent/installer 0.5.61 → 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 +5 -2
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +1 -0
- package/crates/team-agent/src/cli/emit.rs +41 -7
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +6 -1
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +33 -3
- package/crates/team-agent/src/cli/named_address.rs +8 -1
- 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 +1 -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 +1 -0
- 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/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +3 -2
- package/crates/team-agent/src/communication_mode/mod.rs +1 -0
- package/crates/team-agent/src/compiler.rs +1 -0
- 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 +2 -1
- 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/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 +373 -125
- 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 +7 -2
- 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 +3 -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_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 +1 -27
- 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 +3 -3
- 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/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 +4 -3
- package/crates/team-agent/src/mcp_server/tools.rs +1 -0
- package/crates/team-agent/src/mcp_server/types.rs +1 -18
- package/crates/team-agent/src/mcp_server/wire.rs +5 -5
- 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 +4 -3
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
- 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 +2 -1
- package/crates/team-agent/src/messaging/results.rs +1 -0
- 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 +41 -8
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/types.rs +2 -2
- package/crates/team-agent/src/messaging/watchers.rs +19 -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 +1 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -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 +1 -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 +1 -4
- 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 +1 -0
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +1 -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/transport.rs +15 -0
- package/package.json +4 -4
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
//! RED contract (REVISED, inverted): a successful leader claim — including the
|
|
2
|
+
//! `already_bound` branch — requeues only rows that never crossed the
|
|
3
|
+
//! transport; it must NEVER flush `submitted_pending_acceptance` rows.
|
|
4
|
+
//!
|
|
5
|
+
//! REVISION LINEAGE (verifier signature, leader ruling msg_2bc80666aef8):
|
|
6
|
+
//! - 5027d533…6157 (original freeze at 1b2848a): asserted the OPPOSITE — that
|
|
7
|
+
//! an `already_bound` claim flushes a `submitted_pending_acceptance` row.
|
|
8
|
+
//! That premise was wrong at scale (leader co-signed the same error): those
|
|
9
|
+
//! rows are the DESIGNED PARKED STATE of messages that already crossed the
|
|
10
|
+
//! transport and merely lack a provider receipt. Shipping that semantics
|
|
11
|
+
//! (d3a4966, 0.5.55) caused the P0 replay storm of 2026-07-23: one
|
|
12
|
+
//! attach-triggered claim flipped 346 historical parked rows back to
|
|
13
|
+
//! `accepted` and physically re-injected all of them (attempts 1→2),
|
|
14
|
+
//! O(history) with no time/attempt bound (storm-p0-locate.md).
|
|
15
|
+
//! - THIS revision (new SHA): inverts the tooth (`already_bound` never
|
|
16
|
+
//! flushes parked rows), adds the storm tooth (>=3 historical parked rows,
|
|
17
|
+
//! zero flips on claim — the stored-data shape a fresh-environment gate
|
|
18
|
+
//! misses), keeps the positive control (a newly claimed leader still
|
|
19
|
+
//! flushes `queued_until_leader_attach`) and the non-leader negative
|
|
20
|
+
//! control.
|
|
21
|
+
//!
|
|
22
|
+
//! DOCUMENTED RESIDUAL (explicit, not silent): after the 0.5.56 revert, a row
|
|
23
|
+
//! whose physical submit was genuinely LOST (submitted, no receipt, transport
|
|
24
|
+
//! evaporated) parks forever again — the original stranding this contract's
|
|
25
|
+
//! V1 tried to fix. That recovery belongs to the blocked-before-submit /
|
|
26
|
+
//! submitted-awaiting-receipt state-machine separation car (0.5.57+): a typed
|
|
27
|
+
//! recovery arm with attempt budget and age/owner-epoch bounds, never a
|
|
28
|
+
//! blanket claim-time requeue.
|
|
29
|
+
//!
|
|
30
|
+
//! Requirements: N31 (leader-bound delivery cannot remain stuck) applies to
|
|
31
|
+
//! rows that never crossed the transport; N32 (single delivery/requeue
|
|
32
|
+
//! funnel) is unchanged.
|
|
33
|
+
|
|
34
|
+
#![cfg(unix)]
|
|
35
|
+
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
|
|
36
|
+
|
|
37
|
+
use crate as team_agent;
|
|
38
|
+
|
|
39
|
+
use std::path::PathBuf;
|
|
40
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
41
|
+
|
|
42
|
+
use rusqlite::params;
|
|
43
|
+
use serial_test::serial;
|
|
44
|
+
use team_agent::event_log::EventLog;
|
|
45
|
+
use team_agent::leader::{claim_lease_no_incident, LeaseStatus};
|
|
46
|
+
use team_agent::message_store::MessageStore;
|
|
47
|
+
use team_agent::messaging::enqueue_leader_mailbox_until_attach;
|
|
48
|
+
use team_agent::messaging::watchers::requeue_after_claim_leader;
|
|
49
|
+
use team_agent::model::enums::PaneLiveness;
|
|
50
|
+
use team_agent::model::ids::TeamKey;
|
|
51
|
+
use team_agent::state::owner_gate::PaneLivenessProbe;
|
|
52
|
+
use team_agent::transport::PaneId;
|
|
53
|
+
|
|
54
|
+
const TEAM: &str = "current";
|
|
55
|
+
const PANE: &str = "%leader";
|
|
56
|
+
|
|
57
|
+
struct LiveCaller;
|
|
58
|
+
|
|
59
|
+
impl PaneLivenessProbe for LiveCaller {
|
|
60
|
+
fn liveness(&self, pane_id: &str) -> PaneLiveness {
|
|
61
|
+
if pane_id == PANE {
|
|
62
|
+
PaneLiveness::Live
|
|
63
|
+
} else {
|
|
64
|
+
PaneLiveness::Dead
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// INVERTED RED TOOTH — a `submitted_pending_acceptance` row already crossed
|
|
70
|
+
/// the transport; it is the designed parked state awaiting a provider
|
|
71
|
+
/// receipt. A claim (any branch, `already_bound` included) must leave it
|
|
72
|
+
/// COMPLETELY untouched: status, attempts, error, delivered_at, updated_at
|
|
73
|
+
/// and its delivery token row. Baseline 6a4b7a5 red: the claim flips it to
|
|
74
|
+
/// `accepted` and the coordinator physically re-injects (the 2026-07-23
|
|
75
|
+
/// storm, 346 rows, attempts 1->2).
|
|
76
|
+
#[test]
|
|
77
|
+
#[serial(env)]
|
|
78
|
+
fn already_bound_claim_never_flushes_submitted_pending_acceptance_row() {
|
|
79
|
+
let case = Case::new("parked-untouched");
|
|
80
|
+
let message_id = case.seed_incident_row("incident canary");
|
|
81
|
+
case.seed_delivery_token(&message_id, "TOKEN-PARKED-1");
|
|
82
|
+
let before_row = case.full_row(&message_id);
|
|
83
|
+
let before_token = case.token_row(&message_id);
|
|
84
|
+
let mut state = bound_state();
|
|
85
|
+
|
|
86
|
+
let status = case.claim_and_run_success_hook(&mut state);
|
|
87
|
+
|
|
88
|
+
assert_eq!(
|
|
89
|
+
status,
|
|
90
|
+
LeaseStatus::AlreadyBound,
|
|
91
|
+
"fixture must exercise the already_bound enum branch"
|
|
92
|
+
);
|
|
93
|
+
assert_eq!(
|
|
94
|
+
case.full_row(&message_id),
|
|
95
|
+
before_row,
|
|
96
|
+
"a parked (submitted_pending_acceptance) row must be byte-stable across a claim: \
|
|
97
|
+
status/attempts/error/delivered_at/updated_at all unchanged. Flipping it to accepted \
|
|
98
|
+
re-injects a message that already crossed the transport (storm-p0-locate.md B2)"
|
|
99
|
+
);
|
|
100
|
+
assert_eq!(
|
|
101
|
+
case.token_row(&message_id),
|
|
102
|
+
before_token,
|
|
103
|
+
"the delivery token row of a parked message must survive a claim unchanged \
|
|
104
|
+
(stable receipt identity)"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// STORM TOOTH — the stored-data face a fresh-environment gate misses:
|
|
109
|
+
/// three historical parked rows (created on earlier days) + one truly
|
|
110
|
+
/// blocked row + one delivered row. A claim may requeue ONLY the blocked
|
|
111
|
+
/// row; parked and delivered rows see zero flips, zero attempt growth, and
|
|
112
|
+
/// the requeue audit event reports exactly the blocked count (per-status
|
|
113
|
+
/// classification, locate §D3). Baseline red: eligible SQL flips all
|
|
114
|
+
/// parked rows too (O(history), no time/attempt bound).
|
|
115
|
+
#[test]
|
|
116
|
+
#[serial(env)]
|
|
117
|
+
fn claim_with_historical_backlog_requeues_only_truly_blocked_rows() {
|
|
118
|
+
let case = Case::new("storm-backlog");
|
|
119
|
+
let parked: Vec<String> = (0..3)
|
|
120
|
+
.map(|i| {
|
|
121
|
+
let id = case.seed_incident_row(&format!("parked history {i}"));
|
|
122
|
+
case.backdate_created_at(&id, &format!("2026-07-2{}T0{}:00:00Z", 1 + i % 2, i));
|
|
123
|
+
id
|
|
124
|
+
})
|
|
125
|
+
.collect();
|
|
126
|
+
let blocked = case.seed_blocked_row("truly blocked canary");
|
|
127
|
+
let delivered = case.seed_delivered_row("delivered history canary");
|
|
128
|
+
let parked_before: Vec<FullRow> = parked.iter().map(|id| case.full_row(id)).collect();
|
|
129
|
+
let delivered_before = case.full_row(&delivered);
|
|
130
|
+
let mut state = bound_state();
|
|
131
|
+
|
|
132
|
+
let status = case.claim_and_run_success_hook(&mut state);
|
|
133
|
+
assert_eq!(status, LeaseStatus::AlreadyBound);
|
|
134
|
+
|
|
135
|
+
let parked_after: Vec<FullRow> = parked.iter().map(|id| case.full_row(id)).collect();
|
|
136
|
+
assert_eq!(
|
|
137
|
+
parked_after, parked_before,
|
|
138
|
+
"N parked rows must be fully unchanged by a claim — zero flips, zero attempts, \
|
|
139
|
+
zero updated_at churn (the 346-row replay was exactly this set)"
|
|
140
|
+
);
|
|
141
|
+
assert_eq!(
|
|
142
|
+
case.full_row(&delivered).row,
|
|
143
|
+
delivered_before.row,
|
|
144
|
+
"K delivered rows must never re-enter the delivery funnel via a claim"
|
|
145
|
+
);
|
|
146
|
+
assert_eq!(
|
|
147
|
+
case.full_row(&blocked).row,
|
|
148
|
+
Row::accepted(0),
|
|
149
|
+
"M truly blocked rows (failed/leader_not_attached) must still be requeued — \
|
|
150
|
+
the residual N31 face this contract keeps alive"
|
|
151
|
+
);
|
|
152
|
+
assert_eq!(
|
|
153
|
+
case.last_blocked_requeue_count(),
|
|
154
|
+
Some(1),
|
|
155
|
+
"the requeue audit event must report exactly the truly-blocked count (M=1), \
|
|
156
|
+
not parked+blocked — per-original-status classification (locate §D3)"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// WATCHER AMPLIFICATION TOOTH — historical watchers with `result_id=NULL`
|
|
161
|
+
/// can never be retried (the retry arm skips them), so rewriting them to
|
|
162
|
+
/// `notify_failed` + one audit event each on every claim is pure O(history)
|
|
163
|
+
/// write amplification (the 18:22 window: 108 rewrites before the process
|
|
164
|
+
/// was killed). A claim must leave null-result watchers untouched and not
|
|
165
|
+
/// return them as notices. Baseline red: the claim sweep selects every
|
|
166
|
+
/// `notified_message_id is null` watcher unbounded (incident_ts=None).
|
|
167
|
+
#[test]
|
|
168
|
+
#[serial(env)]
|
|
169
|
+
fn claim_does_not_rewrite_null_result_historical_watchers() {
|
|
170
|
+
let case = Case::new("watcher-amp");
|
|
171
|
+
let watchers: Vec<String> = (0..3)
|
|
172
|
+
.map(|i| case.seed_null_result_watcher(i, "2026-07-21T00:00:00Z"))
|
|
173
|
+
.collect();
|
|
174
|
+
let before: Vec<(String, Option<String>)> =
|
|
175
|
+
watchers.iter().map(|id| case.watcher_row(id)).collect();
|
|
176
|
+
let mut state = bound_state();
|
|
177
|
+
|
|
178
|
+
let notices = case.claim_and_collect_notices(&mut state);
|
|
179
|
+
|
|
180
|
+
let after: Vec<(String, Option<String>)> =
|
|
181
|
+
watchers.iter().map(|id| case.watcher_row(id)).collect();
|
|
182
|
+
assert_eq!(
|
|
183
|
+
after, before,
|
|
184
|
+
"null-result historical watchers must not be rewritten by a claim: they are \
|
|
185
|
+
unretryable (retry skips result_id=NULL), so per-claim rewrites are unbounded \
|
|
186
|
+
write amplification (storm-p0-locate.md §4)"
|
|
187
|
+
);
|
|
188
|
+
assert!(
|
|
189
|
+
notices
|
|
190
|
+
.iter()
|
|
191
|
+
.all(|notice| !watchers.contains(¬ice.watcher_id)),
|
|
192
|
+
"null-result watchers must not surface as claim notices; notices={notices:?}"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
#[serial(env)]
|
|
198
|
+
fn newly_claimed_path_still_flushes_queued_leader_mailbox_row() {
|
|
199
|
+
let case = Case::new("new-binding");
|
|
200
|
+
let message_id = case.seed_queued_row("positive-control canary");
|
|
201
|
+
let mut state = serde_json::json!({"session_name": "team-mailbox-positive"});
|
|
202
|
+
|
|
203
|
+
let status = case.claim_and_run_success_hook(&mut state);
|
|
204
|
+
|
|
205
|
+
assert_eq!(
|
|
206
|
+
status,
|
|
207
|
+
LeaseStatus::Claimed,
|
|
208
|
+
"positive control must exercise the newly-bound enum branch"
|
|
209
|
+
);
|
|
210
|
+
assert_eq!(
|
|
211
|
+
case.row(&message_id),
|
|
212
|
+
Row::accepted(0),
|
|
213
|
+
"existing new-binding mailbox flush must remain green"
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#[test]
|
|
218
|
+
#[serial(env)]
|
|
219
|
+
fn claim_does_not_flush_non_leader_submitted_row() {
|
|
220
|
+
let case = Case::new("negative-control");
|
|
221
|
+
let message_id = case.seed_incident_row("negative-control canary");
|
|
222
|
+
case.set_recipient(&message_id, "worker-a");
|
|
223
|
+
let mut state = bound_state();
|
|
224
|
+
|
|
225
|
+
let status = case.claim_and_run_success_hook(&mut state);
|
|
226
|
+
|
|
227
|
+
assert_eq!(status, LeaseStatus::AlreadyBound);
|
|
228
|
+
assert_eq!(
|
|
229
|
+
case.row(&message_id),
|
|
230
|
+
Row::submitted(1),
|
|
231
|
+
"negative control: claim flush is scoped to recipient=leader"
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
struct Case {
|
|
236
|
+
workspace: PathBuf,
|
|
237
|
+
store: MessageStore,
|
|
238
|
+
event_log: EventLog,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
impl Case {
|
|
242
|
+
fn new(tag: &str) -> Self {
|
|
243
|
+
static NEXT: AtomicU64 = AtomicU64::new(0);
|
|
244
|
+
let workspace = std::env::temp_dir().join(format!(
|
|
245
|
+
"ta-claim-mailbox-flush-{tag}-{}-{}",
|
|
246
|
+
std::process::id(),
|
|
247
|
+
NEXT.fetch_add(1, Ordering::Relaxed)
|
|
248
|
+
));
|
|
249
|
+
std::fs::create_dir_all(&workspace).unwrap();
|
|
250
|
+
let store = MessageStore::open(&workspace).unwrap();
|
|
251
|
+
let event_log = EventLog::new(&workspace);
|
|
252
|
+
Self {
|
|
253
|
+
workspace,
|
|
254
|
+
store,
|
|
255
|
+
event_log,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fn seed_queued_row(&self, content: &str) -> String {
|
|
260
|
+
enqueue_leader_mailbox_until_attach(
|
|
261
|
+
&self.workspace,
|
|
262
|
+
TEAM,
|
|
263
|
+
content,
|
|
264
|
+
None,
|
|
265
|
+
"leader",
|
|
266
|
+
&self.event_log,
|
|
267
|
+
)
|
|
268
|
+
.unwrap()
|
|
269
|
+
.message_id
|
|
270
|
+
.expect("mailbox enqueue returns message id")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fn seed_incident_row(&self, content: &str) -> String {
|
|
274
|
+
let message_id = self.seed_queued_row(content);
|
|
275
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
276
|
+
conn.execute(
|
|
277
|
+
"update messages
|
|
278
|
+
set status = 'submitted_pending_acceptance',
|
|
279
|
+
delivered_at = null,
|
|
280
|
+
delivery_attempts = 1,
|
|
281
|
+
error = null
|
|
282
|
+
where message_id = ?1",
|
|
283
|
+
params![message_id],
|
|
284
|
+
)
|
|
285
|
+
.unwrap();
|
|
286
|
+
message_id
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
fn seed_blocked_row(&self, content: &str) -> String {
|
|
290
|
+
let message_id = self.seed_queued_row(content);
|
|
291
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
292
|
+
conn.execute(
|
|
293
|
+
"update messages
|
|
294
|
+
set status = 'failed', error = 'leader_not_attached', delivery_attempts = 0
|
|
295
|
+
where message_id = ?1",
|
|
296
|
+
params![message_id],
|
|
297
|
+
)
|
|
298
|
+
.unwrap();
|
|
299
|
+
message_id
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fn seed_delivered_row(&self, content: &str) -> String {
|
|
303
|
+
let message_id = self.seed_queued_row(content);
|
|
304
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
305
|
+
conn.execute(
|
|
306
|
+
"update messages
|
|
307
|
+
set status = 'delivered', delivered_at = '2026-07-21T12:00:00Z',
|
|
308
|
+
delivery_attempts = 1, error = null
|
|
309
|
+
where message_id = ?1",
|
|
310
|
+
params![message_id],
|
|
311
|
+
)
|
|
312
|
+
.unwrap();
|
|
313
|
+
message_id
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fn backdate_created_at(&self, message_id: &str, created_at: &str) {
|
|
317
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
318
|
+
conn.execute(
|
|
319
|
+
"update messages set created_at = ?2, updated_at = ?2 where message_id = ?1",
|
|
320
|
+
params![message_id, created_at],
|
|
321
|
+
)
|
|
322
|
+
.unwrap();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
fn seed_delivery_token(&self, message_id: &str, token: &str) {
|
|
326
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
327
|
+
conn.execute(
|
|
328
|
+
"insert into delivery_tokens (message_id, unique_token, injected_at)
|
|
329
|
+
values (?1, ?2, '2026-07-21T12:00:00Z')",
|
|
330
|
+
params![message_id, token],
|
|
331
|
+
)
|
|
332
|
+
.unwrap();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
fn token_row(&self, message_id: &str) -> Option<(String, String)> {
|
|
336
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
337
|
+
use rusqlite::OptionalExtension;
|
|
338
|
+
conn.query_row(
|
|
339
|
+
"select unique_token, injected_at from delivery_tokens where message_id = ?1",
|
|
340
|
+
params![message_id],
|
|
341
|
+
|row| Ok((row.get(0)?, row.get(1)?)),
|
|
342
|
+
)
|
|
343
|
+
.optional()
|
|
344
|
+
.unwrap()
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
fn seed_null_result_watcher(&self, index: usize, created_at: &str) -> String {
|
|
348
|
+
let watcher_id = format!("watch-null-{index}");
|
|
349
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
350
|
+
conn.execute(
|
|
351
|
+
"insert into result_watchers
|
|
352
|
+
(watcher_id, owner_team_id, leader_id, status, created_at, completed_at,
|
|
353
|
+
result_id, notified_message_id)
|
|
354
|
+
values (?1, ?2, 'leader', 'notify_failed', ?3, ?3, null, null)",
|
|
355
|
+
params![watcher_id, TEAM, created_at],
|
|
356
|
+
)
|
|
357
|
+
.unwrap();
|
|
358
|
+
watcher_id
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fn watcher_row(&self, watcher_id: &str) -> (String, Option<String>) {
|
|
362
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
363
|
+
conn.query_row(
|
|
364
|
+
"select status, completed_at from result_watchers where watcher_id = ?1",
|
|
365
|
+
params![watcher_id],
|
|
366
|
+
|row| Ok((row.get(0)?, row.get(1)?)),
|
|
367
|
+
)
|
|
368
|
+
.unwrap()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
fn last_blocked_requeue_count(&self) -> Option<i64> {
|
|
372
|
+
let path = self
|
|
373
|
+
.workspace
|
|
374
|
+
.join(".team")
|
|
375
|
+
.join("logs")
|
|
376
|
+
.join("events.jsonl");
|
|
377
|
+
let text = std::fs::read_to_string(path).ok()?;
|
|
378
|
+
text.lines()
|
|
379
|
+
.rev()
|
|
380
|
+
.filter_map(|line| serde_json::from_str::<serde_json::Value>(line).ok())
|
|
381
|
+
.find(|event| {
|
|
382
|
+
event.get("event").and_then(serde_json::Value::as_str)
|
|
383
|
+
== Some("leader_receiver.blocked_messages_requeued")
|
|
384
|
+
})
|
|
385
|
+
.and_then(|event| event.get("count").and_then(serde_json::Value::as_i64))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
fn set_recipient(&self, message_id: &str, recipient: &str) {
|
|
389
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
390
|
+
conn.execute(
|
|
391
|
+
"update messages set recipient = ?2 where message_id = ?1",
|
|
392
|
+
params![message_id, recipient],
|
|
393
|
+
)
|
|
394
|
+
.unwrap();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
fn claim_and_run_success_hook(&self, state: &mut serde_json::Value) -> LeaseStatus {
|
|
398
|
+
let (status, _) = self.claim_inner(state);
|
|
399
|
+
status
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
fn claim_and_collect_notices(
|
|
403
|
+
&self,
|
|
404
|
+
state: &mut serde_json::Value,
|
|
405
|
+
) -> Vec<team_agent::messaging::WatcherNotice> {
|
|
406
|
+
let (_, notices) = self.claim_inner(state);
|
|
407
|
+
notices
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
fn claim_inner(
|
|
411
|
+
&self,
|
|
412
|
+
state: &mut serde_json::Value,
|
|
413
|
+
) -> (LeaseStatus, Vec<team_agent::messaging::WatcherNotice>) {
|
|
414
|
+
let team = TeamKey::new(TEAM);
|
|
415
|
+
let pane = PaneId::new(PANE);
|
|
416
|
+
let result = claim_lease_no_incident(
|
|
417
|
+
&self.workspace,
|
|
418
|
+
state,
|
|
419
|
+
None,
|
|
420
|
+
&team,
|
|
421
|
+
&pane,
|
|
422
|
+
true,
|
|
423
|
+
&self.event_log,
|
|
424
|
+
&LiveCaller,
|
|
425
|
+
)
|
|
426
|
+
.unwrap();
|
|
427
|
+
let mut notices = Vec::new();
|
|
428
|
+
if result.ok {
|
|
429
|
+
notices = requeue_after_claim_leader(
|
|
430
|
+
&self.workspace,
|
|
431
|
+
&self.store,
|
|
432
|
+
&self.event_log,
|
|
433
|
+
&team,
|
|
434
|
+
result
|
|
435
|
+
.bound_pane_id
|
|
436
|
+
.as_ref()
|
|
437
|
+
.expect("successful claim has pane"),
|
|
438
|
+
None,
|
|
439
|
+
)
|
|
440
|
+
.unwrap();
|
|
441
|
+
}
|
|
442
|
+
(result.status, notices)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/// Row plus updated_at — the storm/inverted teeth require byte-stability
|
|
446
|
+
/// of the WHOLE observable row, not just the status.
|
|
447
|
+
fn full_row(&self, message_id: &str) -> FullRow {
|
|
448
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
449
|
+
conn.query_row(
|
|
450
|
+
"select status, delivered_at, delivery_attempts, error, updated_at
|
|
451
|
+
from messages where message_id = ?1",
|
|
452
|
+
params![message_id],
|
|
453
|
+
|row| {
|
|
454
|
+
Ok(FullRow {
|
|
455
|
+
row: Row {
|
|
456
|
+
status: row.get(0)?,
|
|
457
|
+
delivered_at: row.get(1)?,
|
|
458
|
+
attempts: row.get(2)?,
|
|
459
|
+
error: row.get(3)?,
|
|
460
|
+
},
|
|
461
|
+
updated_at: row.get(4)?,
|
|
462
|
+
})
|
|
463
|
+
},
|
|
464
|
+
)
|
|
465
|
+
.unwrap()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
fn row(&self, message_id: &str) -> Row {
|
|
469
|
+
let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
|
|
470
|
+
conn.query_row(
|
|
471
|
+
"select status, delivered_at, delivery_attempts, error
|
|
472
|
+
from messages where message_id = ?1",
|
|
473
|
+
params![message_id],
|
|
474
|
+
|row| {
|
|
475
|
+
Ok(Row {
|
|
476
|
+
status: row.get(0)?,
|
|
477
|
+
delivered_at: row.get(1)?,
|
|
478
|
+
attempts: row.get(2)?,
|
|
479
|
+
error: row.get(3)?,
|
|
480
|
+
})
|
|
481
|
+
},
|
|
482
|
+
)
|
|
483
|
+
.unwrap()
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
impl Drop for Case {
|
|
488
|
+
fn drop(&mut self) {
|
|
489
|
+
let _ = std::fs::remove_dir_all(&self.workspace);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
494
|
+
struct FullRow {
|
|
495
|
+
row: Row,
|
|
496
|
+
updated_at: Option<String>,
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
500
|
+
struct Row {
|
|
501
|
+
status: String,
|
|
502
|
+
delivered_at: Option<String>,
|
|
503
|
+
attempts: i64,
|
|
504
|
+
error: Option<String>,
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
impl Row {
|
|
508
|
+
fn accepted(attempts: i64) -> Self {
|
|
509
|
+
Self {
|
|
510
|
+
status: "accepted".to_string(),
|
|
511
|
+
delivered_at: None,
|
|
512
|
+
attempts,
|
|
513
|
+
error: None,
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
fn submitted(attempts: i64) -> Self {
|
|
518
|
+
Self {
|
|
519
|
+
status: "submitted_pending_acceptance".to_string(),
|
|
520
|
+
delivered_at: None,
|
|
521
|
+
attempts,
|
|
522
|
+
error: None,
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
fn bound_state() -> serde_json::Value {
|
|
528
|
+
serde_json::json!({
|
|
529
|
+
"session_name": "team-mailbox-already-bound",
|
|
530
|
+
"team_owner": {
|
|
531
|
+
"pane_id": PANE,
|
|
532
|
+
"provider": "codex",
|
|
533
|
+
"owner_epoch": 7,
|
|
534
|
+
"claimed_at": "2026-07-23T05:13:42Z",
|
|
535
|
+
"claimed_via": "claim-leader"
|
|
536
|
+
},
|
|
537
|
+
"leader_receiver": {
|
|
538
|
+
"pane_id": PANE,
|
|
539
|
+
"provider": "codex",
|
|
540
|
+
"owner_epoch": 7,
|
|
541
|
+
"status": "attached"
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
}
|