@team-agent/installer 0.5.1 → 0.5.3
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 +120 -9
- package/Cargo.toml +2 -2
- package/crates/team-agent/Cargo.toml +21 -0
- package/crates/team-agent/src/app_server_test_support.rs +258 -0
- package/crates/team-agent/src/cli/adapters.rs +32 -3
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +16 -0
- package/crates/team-agent/src/cli/diagnose.rs +14 -5
- package/crates/team-agent/src/cli/emit.rs +79 -1
- package/crates/team-agent/src/cli/mod.rs +190 -80
- package/crates/team-agent/src/cli/named_address.rs +111 -0
- package/crates/team-agent/src/cli/send.rs +98 -3
- package/crates/team-agent/src/cli/status_port.rs +44 -6
- package/crates/team-agent/src/cli/tests/named_address.rs +135 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
- package/crates/team-agent/src/cli/types.rs +17 -0
- package/crates/team-agent/src/codex_app_server.rs +549 -0
- package/crates/team-agent/src/conpty/backend.rs +822 -0
- package/crates/team-agent/src/conpty/mod.rs +32 -0
- package/crates/team-agent/src/coordinator/backoff.rs +132 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +730 -0
- package/crates/team-agent/src/coordinator/health.rs +97 -11
- package/crates/team-agent/src/coordinator/mod.rs +8 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +2 -1
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +6 -0
- package/crates/team-agent/src/diagnose/orphans.rs +29 -10
- package/crates/team-agent/src/leader/lease.rs +144 -7
- package/crates/team-agent/src/leader/owner_bind.rs +5 -2
- package/crates/team-agent/src/leader/provider_attribution.rs +19 -34
- package/crates/team-agent/src/leader/start.rs +18 -5
- package/crates/team-agent/src/leader/tests/lease_api.rs +179 -0
- package/crates/team-agent/src/lib.rs +36 -0
- package/crates/team-agent/src/lifecycle/launch.rs +207 -94
- package/crates/team-agent/src/lifecycle/lock.rs +40 -33
- package/crates/team-agent/src/lifecycle/restart/agent.rs +10 -18
- package/crates/team-agent/src/lifecycle/restart/common.rs +70 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +63 -0
- package/crates/team-agent/src/mcp_server/wire.rs +6 -7
- package/crates/team-agent/src/messaging/delivery.rs +329 -9
- package/crates/team-agent/src/messaging/leader_receiver.rs +17 -138
- package/crates/team-agent/src/messaging/mod.rs +2 -2
- package/crates/team-agent/src/messaging/results.rs +78 -21
- package/crates/team-agent/src/messaging/tests/runtime.rs +237 -0
- package/crates/team-agent/src/packaging/tests.rs +41 -6
- package/crates/team-agent/src/packaging/types.rs +31 -3
- package/crates/team-agent/src/platform/argv.rs +324 -0
- package/crates/team-agent/src/platform/errors.rs +95 -0
- package/crates/team-agent/src/platform/file_lock.rs +418 -0
- package/crates/team-agent/src/platform/mod.rs +66 -0
- package/crates/team-agent/src/platform/process.rs +555 -0
- package/crates/team-agent/src/state/persist.rs +205 -25
- package/crates/team-agent/src/tmux_backend.rs +94 -13
- package/crates/team-agent/src/transport_factory.rs +751 -0
- package/package.json +4 -4
|
@@ -724,6 +724,19 @@ fn report_result_for_owner_team_inner(
|
|
|
724
724
|
&event_log,
|
|
725
725
|
) {
|
|
726
726
|
Ok(outcome) => outcome,
|
|
727
|
+
Err(error) if report_state_has_app_server_receiver(&state) => {
|
|
728
|
+
let message_id = format!("fallback:{result_id}");
|
|
729
|
+
crate::messaging::DeliveryOutcome {
|
|
730
|
+
ok: false,
|
|
731
|
+
status: crate::messaging::DeliveryStatus::Failed,
|
|
732
|
+
message_status: super::helpers::MessageStatusShadow("failed".to_string()),
|
|
733
|
+
message_id: Some(message_id),
|
|
734
|
+
verification: Some(format!("leader_funnel_error:{error}")),
|
|
735
|
+
stage: None,
|
|
736
|
+
reason: Some(crate::messaging::DeliveryRefusal::LeaderNotAttached),
|
|
737
|
+
channel: Some("codex_app_server".to_string()),
|
|
738
|
+
}
|
|
739
|
+
}
|
|
727
740
|
Err(error) => {
|
|
728
741
|
let message_id = format!("fallback:{result_id}");
|
|
729
742
|
let fallback_error = fallback_primary_error_text(
|
|
@@ -744,17 +757,32 @@ fn report_result_for_owner_team_inner(
|
|
|
744
757
|
};
|
|
745
758
|
if let Some(message_id) = outcome.message_id.clone() {
|
|
746
759
|
let store = MessageStore::open(workspace)?;
|
|
747
|
-
|
|
760
|
+
// 0.5.x Phase 1d Batch 4: report_result retry uses the runtime
|
|
761
|
+
// factory transport so a conpty team's report notification
|
|
762
|
+
// goes to the shim (design §Batch 4). Previously we always
|
|
763
|
+
// built `TmuxBackend::for_workspace`, which for a conpty team
|
|
764
|
+
// silently no-op'd the retry. Factory refusal falls back to
|
|
765
|
+
// tmux workspace (byte-equivalent tmux teams; honest failure
|
|
766
|
+
// for conpty teams without a resolvable factory input).
|
|
748
767
|
let delivery_state_raw = crate::state::persist::load_runtime_state(workspace)
|
|
749
768
|
.unwrap_or_else(|_| state_for_owner.clone());
|
|
750
769
|
let delivery_state = report_owner_state(&delivery_state_raw, &owner_team);
|
|
770
|
+
let transport: Box<dyn crate::transport::Transport> =
|
|
771
|
+
match crate::transport_factory::resolve_read_only_transport(
|
|
772
|
+
workspace,
|
|
773
|
+
Some(&delivery_state),
|
|
774
|
+
crate::transport_factory::TransportPurpose::MessageDelivery,
|
|
775
|
+
) {
|
|
776
|
+
Ok(r) => r.backend,
|
|
777
|
+
Err(_) => Box::new(crate::tmux_backend::TmuxBackend::for_workspace(workspace)),
|
|
778
|
+
};
|
|
751
779
|
let mut primary_error: Option<String> = None;
|
|
752
780
|
for attempt in 0..3 {
|
|
753
781
|
let _ = store.mark(&message_id, "accepted", None);
|
|
754
782
|
match super::delivery::deliver_pending_message(
|
|
755
783
|
workspace,
|
|
756
784
|
&store,
|
|
757
|
-
|
|
785
|
+
transport.as_ref(),
|
|
758
786
|
&message_id,
|
|
759
787
|
&event_log,
|
|
760
788
|
&delivery_state,
|
|
@@ -781,7 +809,7 @@ fn report_result_for_owner_team_inner(
|
|
|
781
809
|
match super::delivery::deliver_pending_messages(
|
|
782
810
|
workspace,
|
|
783
811
|
&delivery_state,
|
|
784
|
-
|
|
812
|
+
transport.as_ref(),
|
|
785
813
|
&event_log,
|
|
786
814
|
) {
|
|
787
815
|
Ok(delivered)
|
|
@@ -824,24 +852,26 @@ fn report_result_for_owner_team_inner(
|
|
|
824
852
|
}
|
|
825
853
|
}
|
|
826
854
|
if !outcome.ok {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
855
|
+
if !report_state_has_app_server_receiver(&delivery_state) {
|
|
856
|
+
let fallback_error = primary_error.unwrap_or_else(|| {
|
|
857
|
+
format!(
|
|
858
|
+
"leader_notification_primary_failed:{}",
|
|
859
|
+
super::helpers::status_wire(outcome.status)
|
|
860
|
+
)
|
|
861
|
+
});
|
|
862
|
+
let fallback_error =
|
|
863
|
+
fallback_primary_error_text(fallback_primary_error, fallback_error);
|
|
864
|
+
outcome = super::leader_receiver::deliver_to_leader_fallback_pane(
|
|
865
|
+
workspace,
|
|
866
|
+
&delivery_state,
|
|
867
|
+
&message_id,
|
|
868
|
+
Some(&result_id),
|
|
869
|
+
&content,
|
|
870
|
+
false,
|
|
871
|
+
Some(&fallback_error),
|
|
872
|
+
&event_log,
|
|
873
|
+
)?;
|
|
874
|
+
}
|
|
845
875
|
}
|
|
846
876
|
}
|
|
847
877
|
let leader_notified = outcome.ok;
|
|
@@ -937,6 +967,33 @@ fn report_owner_state(state: &serde_json::Value, owner_team: &str) -> serde_json
|
|
|
937
967
|
state
|
|
938
968
|
}
|
|
939
969
|
|
|
970
|
+
fn report_state_has_app_server_receiver(state: &serde_json::Value) -> bool {
|
|
971
|
+
report_leader_receiver_value(state).is_some_and(crate::codex_app_server::receiver_is_app_server)
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
fn report_leader_receiver_value(state: &serde_json::Value) -> Option<&serde_json::Value> {
|
|
975
|
+
state
|
|
976
|
+
.get("leader_receiver")
|
|
977
|
+
.or_else(|| {
|
|
978
|
+
state
|
|
979
|
+
.get("active_team_key")
|
|
980
|
+
.and_then(serde_json::Value::as_str)
|
|
981
|
+
.and_then(|team| state.get("teams").and_then(|teams| teams.get(team)))
|
|
982
|
+
.and_then(|team| team.get("leader_receiver"))
|
|
983
|
+
})
|
|
984
|
+
.or_else(|| {
|
|
985
|
+
let teams = state.get("teams").and_then(serde_json::Value::as_object)?;
|
|
986
|
+
if teams.len() == 1 {
|
|
987
|
+
teams
|
|
988
|
+
.values()
|
|
989
|
+
.next()
|
|
990
|
+
.and_then(|team| team.get("leader_receiver"))
|
|
991
|
+
} else {
|
|
992
|
+
None
|
|
993
|
+
}
|
|
994
|
+
})
|
|
995
|
+
}
|
|
996
|
+
|
|
940
997
|
fn insert_result_if_absent(
|
|
941
998
|
conn: &rusqlite::Connection,
|
|
942
999
|
result_id: &str,
|
|
@@ -1894,6 +1894,243 @@ fn e51_delivery_keeps_same_socket_pane_conflict_guard() {
|
|
|
1894
1894
|
);
|
|
1895
1895
|
}
|
|
1896
1896
|
|
|
1897
|
+
fn app_server_state(
|
|
1898
|
+
ws: &Path,
|
|
1899
|
+
endpoint: &str,
|
|
1900
|
+
thread_id: &str,
|
|
1901
|
+
session_id: &str,
|
|
1902
|
+
) -> serde_json::Value {
|
|
1903
|
+
serde_json::json!({
|
|
1904
|
+
"active_team_key": "team-a",
|
|
1905
|
+
"teams": {
|
|
1906
|
+
"team-a": {
|
|
1907
|
+
"team_key": "team-a",
|
|
1908
|
+
"agents": {},
|
|
1909
|
+
"leader_receiver": {
|
|
1910
|
+
"mode": "codex_app_server",
|
|
1911
|
+
"transport_kind": "codex_app_server",
|
|
1912
|
+
"status": "attached",
|
|
1913
|
+
"provider": "codex",
|
|
1914
|
+
"owner_epoch": 7,
|
|
1915
|
+
"app_server": {
|
|
1916
|
+
"socket": endpoint,
|
|
1917
|
+
"thread_id": thread_id,
|
|
1918
|
+
"session_id": session_id,
|
|
1919
|
+
"cwd": ws.to_string_lossy(),
|
|
1920
|
+
"cli_version": "codex-appserver-team-agent-test/0.139.0",
|
|
1921
|
+
"bound_at": "2026-07-05T00:00:00Z"
|
|
1922
|
+
}
|
|
1923
|
+
},
|
|
1924
|
+
"team_owner": {
|
|
1925
|
+
"provider": "codex",
|
|
1926
|
+
"transport_kind": "codex_app_server",
|
|
1927
|
+
"owner_epoch": 7
|
|
1928
|
+
},
|
|
1929
|
+
"owner_epoch": 7
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
})
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// 0.5.x Windows portability Batch 5: FakeAppServer uses Unix domain
|
|
1936
|
+
// sockets — Unix-only.
|
|
1937
|
+
#[cfg(unix)]
|
|
1938
|
+
#[test]
|
|
1939
|
+
fn app_server_leader_delivery_marks_delivered_without_tmux_transport() {
|
|
1940
|
+
let ws = tmp_ws("appdeliver");
|
|
1941
|
+
let fake = crate::app_server_test_support::FakeAppServer::start(
|
|
1942
|
+
"deliver-ok",
|
|
1943
|
+
crate::app_server_test_support::FakeAppServerScript::happy(
|
|
1944
|
+
"thread-live",
|
|
1945
|
+
"session-live",
|
|
1946
|
+
ws.to_str().unwrap(),
|
|
1947
|
+
),
|
|
1948
|
+
);
|
|
1949
|
+
let state = app_server_state(&ws, fake.endpoint(), "thread-live", "session-live");
|
|
1950
|
+
crate::state::persist::save_runtime_state(&ws, &state).unwrap();
|
|
1951
|
+
let store = store_for(&ws);
|
|
1952
|
+
let log = EventLog::new(&ws);
|
|
1953
|
+
let message_id = store
|
|
1954
|
+
.create_message(
|
|
1955
|
+
None,
|
|
1956
|
+
"worker_a",
|
|
1957
|
+
"leader",
|
|
1958
|
+
"app server hello",
|
|
1959
|
+
None,
|
|
1960
|
+
false,
|
|
1961
|
+
Some("team-a"),
|
|
1962
|
+
)
|
|
1963
|
+
.unwrap();
|
|
1964
|
+
|
|
1965
|
+
let out = deliver_pending_message(&ws, &store, &NoopTransport, &message_id, &log, &state)
|
|
1966
|
+
.expect("app-server delivery should not require tmux transport");
|
|
1967
|
+
|
|
1968
|
+
assert!(
|
|
1969
|
+
out.ok,
|
|
1970
|
+
"app-server turn/start acceptance is delivery proof: {out:?}"
|
|
1971
|
+
);
|
|
1972
|
+
assert_eq!(out.message_status.0, "delivered");
|
|
1973
|
+
assert_eq!(out.channel.as_deref(), Some("codex_app_server"));
|
|
1974
|
+
let turns = fake.received_turns();
|
|
1975
|
+
assert_eq!(turns.len(), 1);
|
|
1976
|
+
assert_eq!(
|
|
1977
|
+
turns[0]["params"]["clientUserMessageId"],
|
|
1978
|
+
serde_json::json!(message_id)
|
|
1979
|
+
);
|
|
1980
|
+
let events = log.tail(0).unwrap();
|
|
1981
|
+
assert!(
|
|
1982
|
+
events.iter().any(|event| {
|
|
1983
|
+
event.get("event").and_then(serde_json::Value::as_str)
|
|
1984
|
+
== Some("leader_receiver.app_server_submitted")
|
|
1985
|
+
}),
|
|
1986
|
+
"delivery must emit app-server submission event; events={events:?}"
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
#[cfg(unix)]
|
|
1991
|
+
#[test]
|
|
1992
|
+
fn app_server_leader_delivery_stale_tuple_fails_closed_with_event() {
|
|
1993
|
+
let ws = tmp_ws("appstale");
|
|
1994
|
+
let fake = crate::app_server_test_support::FakeAppServer::start(
|
|
1995
|
+
"deliver-stale",
|
|
1996
|
+
crate::app_server_test_support::FakeAppServerScript::happy(
|
|
1997
|
+
"thread-live",
|
|
1998
|
+
"session-new",
|
|
1999
|
+
ws.to_str().unwrap(),
|
|
2000
|
+
),
|
|
2001
|
+
);
|
|
2002
|
+
let state = app_server_state(&ws, fake.endpoint(), "thread-live", "session-old");
|
|
2003
|
+
crate::state::persist::save_runtime_state(&ws, &state).unwrap();
|
|
2004
|
+
let store = store_for(&ws);
|
|
2005
|
+
let log = EventLog::new(&ws);
|
|
2006
|
+
let message_id = store
|
|
2007
|
+
.create_message(
|
|
2008
|
+
None,
|
|
2009
|
+
"worker_a",
|
|
2010
|
+
"leader",
|
|
2011
|
+
"stale",
|
|
2012
|
+
None,
|
|
2013
|
+
false,
|
|
2014
|
+
Some("team-a"),
|
|
2015
|
+
)
|
|
2016
|
+
.unwrap();
|
|
2017
|
+
|
|
2018
|
+
let out = deliver_pending_message(&ws, &store, &NoopTransport, &message_id, &log, &state)
|
|
2019
|
+
.expect("stale app-server binding should be a business failure, not panic");
|
|
2020
|
+
|
|
2021
|
+
assert!(!out.ok);
|
|
2022
|
+
assert_eq!(out.channel.as_deref(), Some("rebind_required"));
|
|
2023
|
+
assert_eq!(out.message_status.0, "failed");
|
|
2024
|
+
let events = log.tail(0).unwrap();
|
|
2025
|
+
assert!(
|
|
2026
|
+
events.iter().any(|event| {
|
|
2027
|
+
event.get("event").and_then(serde_json::Value::as_str)
|
|
2028
|
+
== Some("leader_receiver.app_server_thread_stale")
|
|
2029
|
+
&& event.get("message_id").and_then(serde_json::Value::as_str)
|
|
2030
|
+
== Some(message_id.as_str())
|
|
2031
|
+
}),
|
|
2032
|
+
"stale app-server tuple must emit app_server_thread_stale; events={events:?}"
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
#[test]
|
|
2037
|
+
fn app_server_leader_delivery_fails_closed_on_mode_transport_conflict() {
|
|
2038
|
+
let ws = tmp_ws("appconflict");
|
|
2039
|
+
let mut state = app_server_state(
|
|
2040
|
+
&ws,
|
|
2041
|
+
"unix:///tmp/team-agent-should-not-connect.sock",
|
|
2042
|
+
"thread-live",
|
|
2043
|
+
"session-live",
|
|
2044
|
+
);
|
|
2045
|
+
state["teams"]["team-a"]["leader_receiver"]["mode"] = serde_json::json!("direct_tmux");
|
|
2046
|
+
crate::state::persist::save_runtime_state(&ws, &state).unwrap();
|
|
2047
|
+
let store = store_for(&ws);
|
|
2048
|
+
let log = EventLog::new(&ws);
|
|
2049
|
+
let message_id = store
|
|
2050
|
+
.create_message(
|
|
2051
|
+
None,
|
|
2052
|
+
"worker_a",
|
|
2053
|
+
"leader",
|
|
2054
|
+
"conflict",
|
|
2055
|
+
None,
|
|
2056
|
+
false,
|
|
2057
|
+
Some("team-a"),
|
|
2058
|
+
)
|
|
2059
|
+
.unwrap();
|
|
2060
|
+
|
|
2061
|
+
let out = deliver_pending_message(&ws, &store, &NoopTransport, &message_id, &log, &state)
|
|
2062
|
+
.expect("transport conflict should be a business failure");
|
|
2063
|
+
|
|
2064
|
+
assert!(!out.ok);
|
|
2065
|
+
assert_eq!(out.channel.as_deref(), Some("rebind_required"));
|
|
2066
|
+
assert_eq!(out.message_status.0, "failed");
|
|
2067
|
+
assert_eq!(
|
|
2068
|
+
out.verification.as_deref(),
|
|
2069
|
+
Some("run team-agent claim-leader, takeover, or attach-app-server-leader")
|
|
2070
|
+
);
|
|
2071
|
+
let events = log.tail(0).unwrap();
|
|
2072
|
+
assert!(
|
|
2073
|
+
events.iter().any(|event| {
|
|
2074
|
+
event.get("event").and_then(serde_json::Value::as_str)
|
|
2075
|
+
== Some("leader_receiver.delivery_blocked")
|
|
2076
|
+
&& event.get("reason").and_then(serde_json::Value::as_str)
|
|
2077
|
+
== Some("leader_receiver_transport_conflict")
|
|
2078
|
+
}),
|
|
2079
|
+
"transport conflict must emit explicit fail-closed event; events={events:?}"
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
#[cfg(unix)]
|
|
2084
|
+
#[test]
|
|
2085
|
+
fn app_server_leader_busy_is_retryable_and_does_not_steer() {
|
|
2086
|
+
let ws = tmp_ws("appbusy");
|
|
2087
|
+
let mut script = crate::app_server_test_support::FakeAppServerScript::happy(
|
|
2088
|
+
"thread-live",
|
|
2089
|
+
"session-live",
|
|
2090
|
+
ws.to_str().unwrap(),
|
|
2091
|
+
);
|
|
2092
|
+
script.turn_error = Some("thread already has an active turn".to_string());
|
|
2093
|
+
let fake = crate::app_server_test_support::FakeAppServer::start("deliver-busy", script);
|
|
2094
|
+
let state = app_server_state(&ws, fake.endpoint(), "thread-live", "session-live");
|
|
2095
|
+
crate::state::persist::save_runtime_state(&ws, &state).unwrap();
|
|
2096
|
+
let store = store_for(&ws);
|
|
2097
|
+
let log = EventLog::new(&ws);
|
|
2098
|
+
let message_id = store
|
|
2099
|
+
.create_message(
|
|
2100
|
+
None,
|
|
2101
|
+
"worker_a",
|
|
2102
|
+
"leader",
|
|
2103
|
+
"busy",
|
|
2104
|
+
None,
|
|
2105
|
+
false,
|
|
2106
|
+
Some("team-a"),
|
|
2107
|
+
)
|
|
2108
|
+
.unwrap();
|
|
2109
|
+
|
|
2110
|
+
let out = deliver_pending_message(&ws, &store, &NoopTransport, &message_id, &log, &state)
|
|
2111
|
+
.expect("leader busy should be retryable");
|
|
2112
|
+
|
|
2113
|
+
assert!(!out.ok);
|
|
2114
|
+
assert_eq!(out.status, DeliveryStatus::RetryScheduled);
|
|
2115
|
+
assert_eq!(out.channel.as_deref(), Some("leader_busy"));
|
|
2116
|
+
let conn = crate::db::schema::open_db(store.db_path()).unwrap();
|
|
2117
|
+
let status: String = conn
|
|
2118
|
+
.query_row(
|
|
2119
|
+
"select status from messages where message_id = ?1",
|
|
2120
|
+
[&message_id],
|
|
2121
|
+
|row| row.get(0),
|
|
2122
|
+
)
|
|
2123
|
+
.unwrap();
|
|
2124
|
+
assert_eq!(status, "target_resolved", "busy leader remains retryable");
|
|
2125
|
+
let turns = fake.received_turns();
|
|
2126
|
+
assert_eq!(
|
|
2127
|
+
turns.len(),
|
|
2128
|
+
1,
|
|
2129
|
+
"must only call turn/start, never turn/steer"
|
|
2130
|
+
);
|
|
2131
|
+
assert_eq!(turns[0]["method"], serde_json::json!("turn/start"));
|
|
2132
|
+
}
|
|
2133
|
+
|
|
1897
2134
|
#[test]
|
|
1898
2135
|
fn u1_multi_team_send_does_not_backfill_top_level_leader_binding() {
|
|
1899
2136
|
let ws = tmp_ws("u1backfill");
|
|
@@ -222,12 +222,47 @@ fn linux_aarch64_is_native() {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
#[test]
|
|
225
|
-
fn
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
fn windows_x8664_is_preview_compile_only_during_batch_0_to_5() {
|
|
226
|
+
// 0.5.x Windows portability CR C-1 (P0):
|
|
227
|
+
// Windows previously claimed `PlatformSupport::Native` but
|
|
228
|
+
// `cargo check --target x86_64-pc-windows-msvc` was RED — the
|
|
229
|
+
// Unix-only `std::os::unix` / `libc::pid_t` calls in
|
|
230
|
+
// tmux_backend/coordinator/lifecycle/packaging never compiled on
|
|
231
|
+
// Windows. Claiming Native was a MUST-NOT-13 假绿承诺 (user
|
|
232
|
+
// installs and immediately hits build failures).
|
|
233
|
+
//
|
|
234
|
+
// This test locks the honest downgrade in place until Batch 6/7
|
|
235
|
+
// real-machine gates pass. Promoting Windows back to `Native`
|
|
236
|
+
// requires:
|
|
237
|
+
// 1. Batch 6 fake-provider smoke on the SSH host (design §Batch 6)
|
|
238
|
+
// 2. Batch 7 real-machine subscription serial (CR §C-5)
|
|
239
|
+
// Both flip this test to `assert PlatformSupport::Native` in a
|
|
240
|
+
// deliberate PR — no silent regen.
|
|
241
|
+
//
|
|
242
|
+
// Truth source: `.team/artifacts/0.5.x-windows-portability-cr-verdict.md` §C-1.
|
|
243
|
+
match platform_support(ReleaseTarget::WindowsX8664) {
|
|
244
|
+
PlatformSupport::PreviewCompileOnly { preview_gate, note } => {
|
|
245
|
+
assert_eq!(preview_gate, "compile_gate");
|
|
246
|
+
assert!(note.contains("compile_gate") || note.contains("cargo check"));
|
|
247
|
+
}
|
|
248
|
+
other => panic!(
|
|
249
|
+
"Windows must stay `PreviewCompileOnly` while the compile gate is RED; \
|
|
250
|
+
promoting back to `Native` requires Batch 6 + Batch 7 real-machine gates. \
|
|
251
|
+
Got {other:?}"
|
|
252
|
+
),
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[test]
|
|
257
|
+
fn windows_x8664_is_not_native_yet_c1_hard_lock() {
|
|
258
|
+
// Belt-and-braces companion for the above — a distinct assertion
|
|
259
|
+
// that fires clearly if a future refactor of `PlatformSupport`
|
|
260
|
+
// changes variant names.
|
|
261
|
+
let support = platform_support(ReleaseTarget::WindowsX8664);
|
|
262
|
+
assert_ne!(
|
|
263
|
+
support,
|
|
264
|
+
PlatformSupport::Native,
|
|
265
|
+
"CR C-1: Windows must NOT be Native until Batch 6/7 gates pass"
|
|
231
266
|
);
|
|
232
267
|
}
|
|
233
268
|
|
|
@@ -162,13 +162,27 @@ pub enum ReleaseTarget {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/// 平台支持等级(§8:如实声明,不假装兼容)。
|
|
165
|
+
///
|
|
166
|
+
/// 0.5.x Windows portability CR C-1 (P0) 引入 `PreviewCompileOnly`:
|
|
167
|
+
/// Windows 目前 `cargo check --target x86_64-pc-windows-msvc` **仍红**
|
|
168
|
+
/// (三处 non-Unix fallback + platform 层未接入 + Batch 1-4 未完成);
|
|
169
|
+
/// 保持 `Native` 会构成 MUST-NOT-13 假绿承诺 → 用户装 Windows 二进制
|
|
170
|
+
/// 会真报错。降级为 `PreviewCompileOnly` 明示"当前 Windows 尚未通过
|
|
171
|
+
/// 编译门,Batch 6/7 真机订阅测通过前不承诺 Native"。
|
|
172
|
+
/// Truth source: `.team/artifacts/0.5.x-windows-portability-cr-verdict.md` §C-1。
|
|
165
173
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
166
174
|
#[serde(rename_all = "snake_case", tag = "level")]
|
|
167
175
|
pub enum PlatformSupport {
|
|
168
|
-
/// 原生一等(macOS/Linux tmux
|
|
176
|
+
/// 原生一等(macOS/Linux tmux)。
|
|
169
177
|
Native,
|
|
170
178
|
/// 需 WSL + tmux backend(显式标要求,不假装原生)。
|
|
171
179
|
RequiresWslTmux { note: String },
|
|
180
|
+
/// Windows 移植进行中,`cargo check` 仍红或 Batch 6/7 真机订阅测未过。
|
|
181
|
+
/// 用户不应期待可运行的 `.exe`;`preview_gate` 字段说明当前挡位
|
|
182
|
+
/// (`compile_gate` = Batch 0-4 编译门,`fake_provider_smoke` = Batch 6,
|
|
183
|
+
/// `subscription_realmachine` = Batch 7)。
|
|
184
|
+
/// 该档位不承诺可用,仅承诺"正在移植 + 每 Batch 有 CI 编译门追踪"。
|
|
185
|
+
PreviewCompileOnly { preview_gate: String, note: String },
|
|
172
186
|
/// 当前 release 矩阵不覆盖。
|
|
173
187
|
Unsupported { reason: String },
|
|
174
188
|
}
|
|
@@ -179,8 +193,22 @@ pub fn platform_support(target: ReleaseTarget) -> PlatformSupport {
|
|
|
179
193
|
ReleaseTarget::MacosAarch64
|
|
180
194
|
| ReleaseTarget::MacosX8664
|
|
181
195
|
| ReleaseTarget::LinuxX8664
|
|
182
|
-
| ReleaseTarget::LinuxAarch64
|
|
183
|
-
|
|
196
|
+
| ReleaseTarget::LinuxAarch64 => PlatformSupport::Native,
|
|
197
|
+
// 0.5.x Windows portability CR C-1 (P0):
|
|
198
|
+
// `cargo check --target x86_64-pc-windows-msvc` 是当前挡位;
|
|
199
|
+
// Batch 6 fake-provider smoke + Batch 7 真机订阅测通过后
|
|
200
|
+
// 才升回 `Native`。历史 tag 期间 Windows 曾声明为 Native 但
|
|
201
|
+
// 从未通过编译门,构成 MUST-NOT-13 假绿承诺 → 本 batch 降级。
|
|
202
|
+
ReleaseTarget::WindowsX8664 => PlatformSupport::PreviewCompileOnly {
|
|
203
|
+
preview_gate: "compile_gate".to_string(),
|
|
204
|
+
note: "Windows native port in progress; `cargo check --target x86_64-pc-windows-msvc` \
|
|
205
|
+
is the current gate. See \
|
|
206
|
+
`.team/artifacts/0.5.x-windows-portability-survey-design.md` §Batch 0-6 \
|
|
207
|
+
and CR verdict §C-1 for burn-down status. Promote back to `Native` only \
|
|
208
|
+
after Batch 6 (fake-provider smoke) + Batch 7 (subscription real-machine) \
|
|
209
|
+
pass on the SSH host."
|
|
210
|
+
.to_string(),
|
|
211
|
+
},
|
|
184
212
|
}
|
|
185
213
|
}
|
|
186
214
|
|