@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.
Files changed (55) hide show
  1. package/Cargo.lock +120 -9
  2. package/Cargo.toml +2 -2
  3. package/crates/team-agent/Cargo.toml +21 -0
  4. package/crates/team-agent/src/app_server_test_support.rs +258 -0
  5. package/crates/team-agent/src/cli/adapters.rs +32 -3
  6. package/crates/team-agent/src/cli/attach_app_server_leader.rs +16 -0
  7. package/crates/team-agent/src/cli/diagnose.rs +14 -5
  8. package/crates/team-agent/src/cli/emit.rs +79 -1
  9. package/crates/team-agent/src/cli/mod.rs +190 -80
  10. package/crates/team-agent/src/cli/named_address.rs +111 -0
  11. package/crates/team-agent/src/cli/send.rs +98 -3
  12. package/crates/team-agent/src/cli/status_port.rs +44 -6
  13. package/crates/team-agent/src/cli/tests/named_address.rs +135 -0
  14. package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
  15. package/crates/team-agent/src/cli/types.rs +17 -0
  16. package/crates/team-agent/src/codex_app_server.rs +549 -0
  17. package/crates/team-agent/src/conpty/backend.rs +822 -0
  18. package/crates/team-agent/src/conpty/mod.rs +32 -0
  19. package/crates/team-agent/src/coordinator/backoff.rs +132 -7
  20. package/crates/team-agent/src/coordinator/conpty_shim.rs +730 -0
  21. package/crates/team-agent/src/coordinator/health.rs +97 -11
  22. package/crates/team-agent/src/coordinator/mod.rs +8 -0
  23. package/crates/team-agent/src/coordinator/tests/daemon.rs +2 -1
  24. package/crates/team-agent/src/coordinator/tests/tick_core.rs +6 -0
  25. package/crates/team-agent/src/diagnose/orphans.rs +29 -10
  26. package/crates/team-agent/src/leader/lease.rs +144 -7
  27. package/crates/team-agent/src/leader/owner_bind.rs +5 -2
  28. package/crates/team-agent/src/leader/provider_attribution.rs +19 -34
  29. package/crates/team-agent/src/leader/start.rs +18 -5
  30. package/crates/team-agent/src/leader/tests/lease_api.rs +179 -0
  31. package/crates/team-agent/src/lib.rs +36 -0
  32. package/crates/team-agent/src/lifecycle/launch.rs +207 -94
  33. package/crates/team-agent/src/lifecycle/lock.rs +40 -33
  34. package/crates/team-agent/src/lifecycle/restart/agent.rs +10 -18
  35. package/crates/team-agent/src/lifecycle/restart/common.rs +70 -0
  36. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +7 -0
  37. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +7 -0
  38. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +63 -0
  39. package/crates/team-agent/src/mcp_server/wire.rs +6 -7
  40. package/crates/team-agent/src/messaging/delivery.rs +329 -9
  41. package/crates/team-agent/src/messaging/leader_receiver.rs +17 -138
  42. package/crates/team-agent/src/messaging/mod.rs +2 -2
  43. package/crates/team-agent/src/messaging/results.rs +78 -21
  44. package/crates/team-agent/src/messaging/tests/runtime.rs +237 -0
  45. package/crates/team-agent/src/packaging/tests.rs +41 -6
  46. package/crates/team-agent/src/packaging/types.rs +31 -3
  47. package/crates/team-agent/src/platform/argv.rs +324 -0
  48. package/crates/team-agent/src/platform/errors.rs +95 -0
  49. package/crates/team-agent/src/platform/file_lock.rs +418 -0
  50. package/crates/team-agent/src/platform/mod.rs +66 -0
  51. package/crates/team-agent/src/platform/process.rs +555 -0
  52. package/crates/team-agent/src/state/persist.rs +205 -25
  53. package/crates/team-agent/src/tmux_backend.rs +94 -13
  54. package/crates/team-agent/src/transport_factory.rs +751 -0
  55. 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
- let transport = crate::tmux_backend::TmuxBackend::for_workspace(workspace);
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
- &transport,
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
- &transport,
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
- let fallback_error = primary_error.unwrap_or_else(|| {
828
- format!(
829
- "leader_notification_primary_failed:{}",
830
- super::helpers::status_wire(outcome.status)
831
- )
832
- });
833
- let fallback_error =
834
- fallback_primary_error_text(fallback_primary_error, fallback_error);
835
- outcome = super::leader_receiver::deliver_to_leader_fallback_pane(
836
- workspace,
837
- &delivery_state,
838
- &message_id,
839
- Some(&result_id),
840
- &content,
841
- false,
842
- Some(&fallback_error),
843
- &event_log,
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 windows_x8664_is_native_per_transport_design() {
226
- // skeleton:203/211 Windows 原生一等 (WezTerm/ConPTY,非 tmux)
227
- // 不是 Unsupported,不是 RequiresWslTmux 是 Native.
228
- assert_eq!(
229
- platform_support(ReleaseTarget::WindowsX8664),
230
- PlatformSupport::Native
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;Windows WezTerm/ConPTY,见 transport-backend-design.md)。
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
- | ReleaseTarget::WindowsX8664 => PlatformSupport::Native,
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