@team-agent/installer 0.5.85 → 0.5.89

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 (63) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +2 -2
  4. package/crates/team-agent/src/cli/emit.rs +56 -12
  5. package/crates/team-agent/src/cli/mod.rs +0 -1
  6. package/crates/team-agent/src/cli/status_port/brief.rs +187 -7
  7. package/crates/team-agent/src/compiler/tests.rs +13 -24
  8. package/crates/team-agent/src/compiler.rs +5 -57
  9. package/crates/team-agent/src/leader/start.rs +0 -2
  10. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -1
  11. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -1
  12. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +0 -6
  13. package/crates/team-agent/src/lifecycle/launch/identity.rs +39 -22
  14. package/crates/team-agent/src/lifecycle/launch/layout.rs +0 -1
  15. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +0 -1
  16. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +0 -58
  17. package/crates/team-agent/src/lifecycle/launch/ownership.rs +0 -1
  18. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +0 -3
  19. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +14 -24
  20. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +0 -1
  21. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -1
  22. package/crates/team-agent/src/lifecycle/launch/spawn.rs +5 -9
  23. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +0 -1
  24. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -1
  25. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +0 -1
  26. package/crates/team-agent/src/lifecycle/launch.rs +0 -9
  27. package/crates/team-agent/src/lifecycle/restart/common.rs +5 -9
  28. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +3 -3
  29. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +1 -3
  30. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +42 -46
  31. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +30 -44
  32. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +32 -77
  33. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -13
  34. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +0 -1
  35. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +0 -2
  36. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -85
  37. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -1
  38. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +0 -5
  39. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +0 -1
  40. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +4 -4
  41. package/crates/team-agent/src/lifecycle/tests.rs +0 -1
  42. package/crates/team-agent/src/lifecycle/types.rs +0 -10
  43. package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -248
  44. package/crates/team-agent/src/model/enums.rs +17 -38
  45. package/crates/team-agent/src/model/mod.rs +0 -1
  46. package/crates/team-agent/src/model/spec.rs +31 -91
  47. package/crates/team-agent/src/provider/adapter.rs +48 -50
  48. package/crates/team-agent/src/provider/adapters/claude.rs +5 -30
  49. package/crates/team-agent/src/provider/adapters/codex.rs +5 -25
  50. package/crates/team-agent/src/provider/adapters/copilot.rs +8 -41
  51. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +4 -8
  52. package/crates/team-agent/src/provider/adapters/grok.rs +5 -58
  53. package/crates/team-agent/src/provider/adapters/mod.rs +5 -7
  54. package/crates/team-agent/src/provider/adapters/pi.rs +1 -56
  55. package/crates/team-agent/src/provider/bypass_flags.rs +1 -2
  56. package/crates/team-agent/src/provider/session/capture.rs +4 -4
  57. package/crates/team-agent/src/provider/tests/adapter.rs +7 -24
  58. package/crates/team-agent/src/provider/types.rs +1 -1
  59. package/crates/team-agent/src/provider/wire.rs +1 -1
  60. package/package.json +4 -4
  61. package/schemas/team.schema.json +4 -9
  62. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +0 -784
  63. package/crates/team-agent/src/model/permissions.rs +0 -721
@@ -30,7 +30,6 @@ use std::process::Command;
30
30
  use crate::lifecycle::*;
31
31
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
32
32
  use crate::model::ids::AgentId;
33
- use crate::model::permissions::{self, AgentPermissionInput};
34
33
  use crate::model::yaml::{self, Value};
35
34
  use crate::state::persist::load_runtime_state;
36
35
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -32,7 +32,6 @@ use crate::lifecycle::profile_launch::parse_provider;
32
32
  use crate::lifecycle::*;
33
33
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
34
34
  use crate::model::ids::AgentId;
35
- use crate::model::permissions::{self, AgentPermissionInput};
36
35
  use crate::model::yaml::{self, Value};
37
36
  use crate::state::persist::load_runtime_state;
38
37
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -123,11 +122,6 @@ pub(super) fn spawn_agents(
123
122
  )?;
124
123
  let system_prompt =
125
124
  crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
126
- let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
127
- &command_agent,
128
- provider,
129
- )?;
130
- let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
131
125
  // 0.5.66 bypass 单源:per-agent safety 只服务 env 注入与审计,不参与 argv 决策。
132
126
  let safety = effective_runtime_config_for_worker_spawn(agent, provider)?;
133
127
  let mcp_team_id = runtime_team_key_for_spec(spec_path, spec, session_name);
@@ -158,7 +152,7 @@ pub(super) fn spawn_agents(
158
152
  // 0.4.x provider effort MVP step 4 + 7: resolve effort and emit
159
153
  // unsupported warning event when the spec asked for an effort the
160
154
  // provider can't satisfy.
161
- let agent_effort = provider_effort_for_spawn(agent, provider);
155
+ let agent_effort = provider_effort_for_spawn(agent, provider)?;
162
156
  if let Some(event_value) = provider_effort_event_if_dropped(agent, provider, agent_id_raw) {
163
157
  let _ = crate::event_log::EventLog::new(workspace)
164
158
  .write("provider.effort_unsupported", event_value);
@@ -168,7 +162,10 @@ pub(super) fn spawn_agents(
168
162
  mcp_config: Some(&mcp_config),
169
163
  system_prompt: Some(system_prompt.as_str()),
170
164
  model: command_model,
171
- tools: &resolved_tool_refs,
165
+ dangerously_skip_permissions: matches!(
166
+ agent.get("dangerously_skip_permissions"),
167
+ Some(Value::Bool(true))
168
+ ),
172
169
  profile_launch: Some(&profile_launch),
173
170
  // Layer 1 self-healing (architect probe 2026-06-22): expose
174
171
  // agent_id as a display-name hint so Claude / Copilot
@@ -186,7 +183,6 @@ pub(super) fn spawn_agents(
186
183
  model: command_model,
187
184
  effort: agent_effort,
188
185
  system_prompt: &system_prompt,
189
- tool_categories: &resolved_tool_refs,
190
186
  team_mcp_tools: &["send_message", "report_result"],
191
187
  mcp_config: &mcp_config,
192
188
  session_scope: crate::lifecycle::launch::pi_mcp::PiSessionScope::Isolated,
@@ -62,7 +62,6 @@ use std::process::Command;
62
62
  use crate::lifecycle::*;
63
63
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
64
64
  use crate::model::ids::AgentId;
65
- use crate::model::permissions::{self, AgentPermissionInput};
66
65
  use crate::model::yaml::{self, Value};
67
66
  use crate::state::persist::load_runtime_state;
68
67
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -37,7 +37,6 @@ use crate::lifecycle::profile_launch::parse_provider;
37
37
  use crate::lifecycle::*;
38
38
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
39
39
  use crate::model::ids::AgentId;
40
- use crate::model::permissions::{self, AgentPermissionInput};
41
40
  use crate::model::yaml::{self, Value};
42
41
  use crate::state::persist::load_runtime_state;
43
42
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -30,7 +30,6 @@ use std::process::Command;
30
30
  use crate::lifecycle::*;
31
31
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
32
32
  use crate::model::ids::AgentId;
33
- use crate::model::permissions::{self, AgentPermissionInput};
34
33
  use crate::model::yaml::{self, Value};
35
34
  use crate::state::persist::load_runtime_state;
36
35
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -17,7 +17,6 @@ use std::process::Command;
17
17
 
18
18
  use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
19
19
  use crate::model::ids::AgentId;
20
- use crate::model::permissions::{self, AgentPermissionInput};
21
20
  use crate::model::yaml::{self, Value};
22
21
  use crate::state::persist::load_runtime_state;
23
22
  use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
@@ -133,13 +132,6 @@ pub fn launch_with_transport_in_workspace(
133
132
  session_name.as_str()
134
133
  )));
135
134
  }
136
- let permissions = spec_agents(&spec)
137
- .into_iter()
138
- .map(|agent| PermissionSummary {
139
- agent_id: agent,
140
- raw: serde_json::json!({"source": "compiled_spec"}),
141
- })
142
- .collect::<Vec<_>>();
143
135
  let routes = spec_routes(&spec);
144
136
  let started = if dry_run {
145
137
  Vec::new()
@@ -208,7 +200,6 @@ pub fn launch_with_transport_in_workspace(
208
200
  dry_run,
209
201
  tmux_endpoint: transport.tmux_endpoint(),
210
202
  routes,
211
- permissions,
212
203
  leader_receiver_attached: false,
213
204
  leader_bind_stage: None,
214
205
  leader_bind_reason: None,
@@ -301,11 +301,6 @@ pub(super) fn spawn_agent_window(
301
301
  )?;
302
302
  let system_prompt =
303
303
  crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
304
- let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
305
- &command_agent,
306
- provider,
307
- )?;
308
- let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
309
304
  // owner_team_id resolution priority (Issue 2 fix):
310
305
  // 1. caller's explicit override (restart paths pass `selected.team_key`)
311
306
  // 2. agent row's persisted `owner_team_id` (set by prior launch/restart)
@@ -360,7 +355,7 @@ pub(super) fn spawn_agent_window(
360
355
  let command_model = profile_launch.command_overrides.model.as_deref().or(model);
361
356
  // 0.4.x provider effort MVP: restart/resume preserves effort from the
362
357
  // persisted agent JSON (state's agent["effort"] field, set by launch).
363
- let restart_effort = crate::lifecycle::launch::provider_effort_for_spawn_json(agent, provider);
358
+ let restart_effort = crate::lifecycle::launch::provider_effort_for_spawn_json(agent, provider)?;
364
359
  if let Some(event_value) = crate::lifecycle::launch::provider_effort_event_if_dropped_json(
365
360
  agent,
366
361
  provider,
@@ -374,7 +369,10 @@ pub(super) fn spawn_agent_window(
374
369
  mcp_config: Some(&mcp_config),
375
370
  system_prompt: Some(system_prompt.as_str()),
376
371
  model: command_model,
377
- tools: &resolved_tool_refs,
372
+ dangerously_skip_permissions: agent
373
+ .get("dangerously_skip_permissions")
374
+ .and_then(serde_json::Value::as_bool)
375
+ .unwrap_or(false),
378
376
  profile_launch: Some(&profile_launch),
379
377
  agent_id_hint: Some(agent_id.as_str()),
380
378
  effort: restart_effort,
@@ -399,7 +397,6 @@ pub(super) fn spawn_agent_window(
399
397
  model: command_model,
400
398
  effort: restart_effort,
401
399
  system_prompt: &system_prompt,
402
- tool_categories: &resolved_tool_refs,
403
400
  team_mcp_tools: &["send_message", "report_result"],
404
401
  mcp_config: &mcp_config,
405
402
  session_scope: crate::lifecycle::launch::pi_mcp::PiSessionScope::Isolated,
@@ -744,7 +741,6 @@ fn merge_command_context_fields(
744
741
  };
745
742
  for field in [
746
743
  "role",
747
- "tools",
748
744
  "system_prompt",
749
745
  "output_contract",
750
746
  "provider",
@@ -99,17 +99,17 @@ fn stale_snapshot_cannot_flip_status_or_diagnose_ok_readiness() {
99
99
  let case = B0HideOneCase::new("status-diagnose-hide-one");
100
100
  let root = case.current_state();
101
101
  save_runtime_state(&case.workspace, &root).expect("seed current root state");
102
- let probe = brief_probe::install_trusted_nodeprobe(
102
+ let tmux = brief_probe::install_native_tmux(
103
103
  &case.workspace.join("probe"),
104
104
  NEW_ENDPOINT,
105
105
  SESSION,
106
106
  WORKER,
107
107
  "%new",
108
- "fake",
108
+ "bash",
109
109
  );
110
110
  let _path = case.env.with_env(
111
111
  "PATH",
112
- &brief_probe::path_with_probe(&probe, std::env::var_os("PATH").as_deref()),
112
+ &brief_probe::path_with_probe(&tmux, std::env::var_os("PATH").as_deref()),
113
113
  );
114
114
 
115
115
  let status_before = case.run_json(&[
@@ -97,15 +97,13 @@ fn managed_profile_command_plan_omits_mcp_config_even_when_mcp_config_is_availab
97
97
  }
98
98
  }),
99
99
  };
100
- let tools = ["mcp_team"];
101
-
102
100
  let plan = get_adapter(Provider::Claude)
103
101
  .build_command_plan(ProviderCommandContext {
104
102
  auth_mode: AuthMode::CompatibleApi,
105
103
  mcp_config: Some(&config),
106
104
  system_prompt: Some("worker prompt requiring native MCP"),
107
105
  model: Some("profile-effective-haiku"),
108
- tools: &tools,
106
+ dangerously_skip_permissions: false,
109
107
  profile_launch: Some(&profile),
110
108
  agent_id_hint: None,
111
109
  effort: None,
@@ -1,9 +1,9 @@
1
1
  //! ---
2
- //! purpose: 回归——clone-agent 分身 tools 必须与源席集合相等,禁止静默夹成 leader 三件套
2
+ //! purpose: 回归——clone-agent 分身保留源席 tools,编译 spec 不投影 tools
3
3
  //! contract:
4
4
  //! provides:
5
5
  //! - name: clone_agent_preserves_source_tools
6
- //! what: 真跑 clone-agent 后,dynamic-role-files / runtime spec 与源席 tools 集合相等
6
+ //! what: 真跑 clone-agent 后,dynamic-role-files 保留源席 tools,runtime spec 不投影 tools
7
7
  //! requires:
8
8
  //! - name: source-six-set-vs-leader-three-set
9
9
  //! what: 同一 team 里 leader 三件套 + 源席六件套的共享冲突面
@@ -275,6 +275,10 @@ fn role_tools(path: &Path) -> BTreeSet<String> {
275
275
  .collect()
276
276
  }
277
277
 
278
+ fn set_of(items: &[&str]) -> BTreeSet<String> {
279
+ items.iter().map(|s| (*s).to_string()).collect()
280
+ }
281
+
278
282
  fn load_runtime_spec(workspace: &Path) -> Value {
279
283
  let runtime = workspace.join(".team").join("runtime");
280
284
  let spec = std::fs::read_dir(&runtime)
@@ -287,17 +291,7 @@ fn load_runtime_spec(workspace: &Path) -> Value {
287
291
  .expect("parse spec")
288
292
  }
289
293
 
290
- fn yaml_tool_set(node: &Value) -> BTreeSet<String> {
291
- node.get("tools")
292
- .and_then(Value::as_list)
293
- .unwrap_or_else(|| panic!("tools list missing"))
294
- .iter()
295
- .filter_map(Value::as_str)
296
- .map(str::to_string)
297
- .collect()
298
- }
299
-
300
- fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
294
+ fn spec_projects_tools(workspace: &Path, agent: &str) -> bool {
301
295
  let parsed = load_runtime_spec(workspace);
302
296
  let agents = parsed
303
297
  .get("agents")
@@ -305,22 +299,18 @@ fn spec_tools(workspace: &Path, agent: &str) -> BTreeSet<String> {
305
299
  .expect("spec agents");
306
300
  for row in agents {
307
301
  if row.get("id").and_then(Value::as_str) == Some(agent) {
308
- return yaml_tool_set(row);
302
+ return row.get("tools").is_some();
309
303
  }
310
304
  }
311
305
  panic!("agent {agent} missing from runtime spec");
312
306
  }
313
307
 
314
- fn leader_spec_tools(workspace: &Path) -> BTreeSet<String> {
315
- yaml_tool_set(
316
- load_runtime_spec(workspace)
317
- .get("leader")
318
- .expect("spec leader"),
319
- )
320
- }
321
-
322
- fn set_of(items: &[&str]) -> BTreeSet<String> {
323
- items.iter().map(|s| (*s).to_string()).collect()
308
+ fn leader_spec_projects_tools(workspace: &Path) -> bool {
309
+ load_runtime_spec(workspace)
310
+ .get("leader")
311
+ .expect("spec leader")
312
+ .get("tools")
313
+ .is_some()
324
314
  }
325
315
 
326
316
  fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
@@ -347,7 +337,7 @@ fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
347
337
  );
348
338
  }
349
339
 
350
- /// clone 后分身 role + spec tools 必须等于源席;三件套源不得被扩成六件。
340
+ /// clone 后分身 role 保留源席 tools;编译 spec 不投影 tools。
351
341
  #[test]
352
342
  fn clone_agent_preserves_source_tools() {
353
343
  let case = Case::start();
@@ -380,21 +370,18 @@ fn clone_agent_preserves_source_tools() {
380
370
  String::from_utf8_lossy(&qs.stdout)
381
371
  );
382
372
 
383
- let source_spec = spec_tools(&case.workspace, SOURCE);
384
- assert_eq!(
385
- source_spec,
386
- set_of(SIX),
387
- "fixture source must be the six-set"
373
+ assert!(
374
+ !spec_projects_tools(&case.workspace, SOURCE),
375
+ "compiled source spec must not project tools metadata"
388
376
  );
389
- let leader_spec = leader_spec_tools(&case.workspace);
390
- assert_eq!(
391
- leader_spec,
392
- set_of(THREE),
393
- "leader must stay the three-set ceiling so the conflict surface exists; leader={leader_spec:?}"
377
+ assert!(
378
+ !leader_spec_projects_tools(&case.workspace),
379
+ "compiled leader spec must not project tools metadata"
394
380
  );
395
381
 
396
382
  clone_ok(&case, SOURCE, CLONE, "first_clone");
397
383
 
384
+ let source_role = role_tools(&case.workspace.join("agents").join(format!("{SOURCE}.md")));
398
385
  let clone_role = role_tools(
399
386
  &case
400
387
  .workspace
@@ -402,23 +389,32 @@ fn clone_agent_preserves_source_tools() {
402
389
  .join("dynamic-role-files")
403
390
  .join(format!("{CLONE}.md")),
404
391
  );
405
- let clone_spec = spec_tools(&case.workspace, CLONE);
406
392
  assert_eq!(
407
- clone_role, source_spec,
408
- "clone role tools must equal source; source={source_spec:?} clone={clone_role:?}"
393
+ clone_role, source_role,
394
+ "clone role tools must equal source; source={source_role:?} clone={clone_role:?}"
409
395
  );
410
- assert_eq!(
411
- clone_spec, source_spec,
412
- "clone spec tools must equal source; source={source_spec:?} spec={clone_spec:?}"
396
+ assert!(
397
+ !spec_projects_tools(&case.workspace, CLONE),
398
+ "compiled clone spec must not project tools metadata"
413
399
  );
414
400
 
415
- let narrow_src = spec_tools(&case.workspace, NARROW);
416
- assert_eq!(narrow_src, set_of(THREE), "narrow source fixture");
401
+ let narrow_source_role = role_tools(&case.workspace.join("agents").join(format!("{NARROW}.md")));
402
+ assert_eq!(narrow_source_role, set_of(THREE), "narrow source fixture");
417
403
  clone_ok(&case, NARROW, NARROW_CLONE, "second_clone");
418
- let narrow_clone = spec_tools(&case.workspace, NARROW_CLONE);
404
+ let narrow_clone_role = role_tools(
405
+ &case
406
+ .workspace
407
+ .join(".team")
408
+ .join("dynamic-role-files")
409
+ .join(format!("{NARROW_CLONE}.md")),
410
+ );
419
411
  assert_eq!(
420
- narrow_clone, narrow_src,
421
- "narrow clone must keep the three-set (preserve, do not widen); got={narrow_clone:?}"
412
+ narrow_clone_role, narrow_source_role,
413
+ "narrow clone role must keep the three-set (preserve, do not widen); got={narrow_clone_role:?}"
414
+ );
415
+ assert!(
416
+ !spec_projects_tools(&case.workspace, NARROW_CLONE),
417
+ "compiled narrow clone spec must not project tools metadata"
422
418
  );
423
419
 
424
420
  case.shutdown();
@@ -17,7 +17,7 @@
17
17
  //! 1. worker argv golden (§C1 + C-5-1/3) — copilot_argv_* tests
18
18
  //! 2. per-worker AGENTS.md == compiled prompt — copilot_agents_md_* tests (C-1-1/2,
19
19
  //! identity-first per the 0.3.4 B2/D6 lock)
20
- //! 3. permission mapping (C-2-1, via the prompt's Permission note + argv deny flags)
20
+ //! 3. tools metadata transparency (C-2-1, no prompt permission note or argv deny flags)
21
21
  //! 4. fork = structured CapabilityUnsupported (C-4-2/3)
22
22
  //! 5. startup recognizer supports copilot trust/ready; turn-state Unknown never idles
23
23
  //! (N11, C-3-1/4)
@@ -62,12 +62,12 @@ const ANCESTRY_KEY: &str = "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON";
62
62
  const NEUTRAL_ANCESTRY: &str = "[\"/bin/zsh\"]";
63
63
  const COPILOT_TMP_PREFIX: &str = "ta-rs-copilot";
64
64
 
65
- /// Face 1+3 (§C1 golden + C-5-3, verdict cases `copilot_argv_golden_per_role_tools`
66
- /// and `non_dangerous_argv_contains_deny_flags_no_allow_all`): a restricted copilot
67
- /// worker (mcp_team only) gets the full §C1 argv — noise control, inline/`@file` MCP
68
- /// config carrying team_orchestrator, a pre-assigned `--session-id` equal to the
69
- /// persisted `_pending_session_id`, `-C <workspace>`, deny flags for the missing
70
- /// capabilities, and NO --allow-all family flag.
65
+ /// Face 1+3 (§C1 golden + C-5-3, verdict case `copilot_argv_golden_per_role_tools`):
66
+ /// a copilot worker (mcp_team only) gets the full §C1 argv — noise control,
67
+ /// inline/`@file` MCP config carrying team_orchestrator, a pre-assigned
68
+ /// `--session-id` equal to the persisted `_pending_session_id`, and `-C <workspace>`.
69
+ /// Tools metadata must not derive deny flags; non-dangerous workers still omit the
70
+ /// --allow-all family flag.
71
71
  #[test]
72
72
  #[serial(env)]
73
73
  fn copilot_argv_golden_restricted_role() {
@@ -176,21 +176,11 @@ team_orchestrator or @<.team/runtime/mcp/worker_a.json>; got {value:?}"
176
176
  flag_value(argv, "-C")
177
177
  ));
178
178
  }
179
- // C-5-3: restricted role (no execute_bash / fs_write / network) → deny flags, and
180
- // no --allow-all family flag anywhere.
181
- let deny_tools = flag_values(argv, "--deny-tool");
182
- for denied in ["shell", "write"] {
183
- if !deny_tools.iter().any(|tool| tool.contains(denied)) {
184
- failures.push(format!(
185
- "C-5-3: restricted copilot worker must carry --deny-tool '{denied}'; deny={deny_tools:?}"
186
- ));
187
- }
188
- }
189
- // RC-15 (C-5-2 v2): network deny is `--deny-tool 'url'` (the help has only shell/
190
- // write/mcp/url tool kinds; v1's --deny-url is not the v2 form).
191
- if !deny_tools.iter().any(|tool| tool.contains("url")) {
179
+ // Tools metadata is transparent compatibility data; it must not derive
180
+ // provider-specific deny flags (shell/write/url or any other category).
181
+ if argv.iter().any(|arg| arg == "--deny-tool") {
192
182
  failures.push(format!(
193
- "RC-15/C-5-2: restricted copilot worker must deny network via --deny-tool 'url'; deny={deny_tools:?}"
183
+ "C-5-3: tools metadata must not derive copilot --deny-tool flags; argv={argv:?}"
194
184
  ));
195
185
  }
196
186
  // RC-10 (C-3-5/C-5-2): mcp_team is allowlisted by server name (no approval prompt).
@@ -289,8 +279,8 @@ used for the dangerous tier; argv={argv:?}"
289
279
  /// the spawn env must carry COPILOT_CUSTOM_INSTRUCTIONS_DIRS pointing at the
290
280
  /// per-worker dir (path contains the agent id; works with NO profile configured), the
291
281
  /// AGENTS.md there must BE the compiled worker system prompt (single source: identity
292
- /// first, runtime contract, role body, output contract, permission note — 0.3.4 B2/D6
293
- /// lock), and no global ~/.copilot/AGENTS.md may be written.
282
+ /// first, runtime contract, role body, and output contract), and no global
283
+ /// ~/.copilot/AGENTS.md may be written.
294
284
  #[test]
295
285
  #[serial(env)]
296
286
  fn copilot_agents_md_is_the_compiled_prompt_and_env_points_at_it() {
@@ -362,7 +352,6 @@ with the identity section FIRST; head={:?}",
362
352
  "# Team Agent Teammate Runtime Contract",
363
353
  "COPILOT ROLE BODY SENTINEL",
364
354
  "report_result exactly once",
365
- "Permission note: these tools are prompt-only for this provider and not hard-enforced:",
366
355
  ] {
367
356
  if !agents_md.contains(marker) {
368
357
  failures.push(format!(
@@ -386,14 +375,11 @@ with the identity section FIRST; head={:?}",
386
375
  );
387
376
  }
388
377
 
389
- /// Face 3 (C-2-1, verdict `permission_enforcement_table_copilot_row_prompt_only_for_fs`):
390
- /// the copilot enforcement row must honestly register the no-hard-deny tools as
391
- /// prompt-only. Observable single-source: the compiled prompt's Permission note (built
392
- /// from PROVIDER_ENFORCEMENT) for a full-capability role must list exactly
393
- /// fs_list/fs_read/git_diff/provider_builtin (sorted).
378
+ /// Face 3: tools metadata is transparent compatibility data and must not produce
379
+ /// a provider-specific Permission note in the compiled Copilot prompt.
394
380
  #[test]
395
381
  #[serial(env)]
396
- fn copilot_permission_note_registers_fs_tools_prompt_only() {
382
+ fn copilot_prompt_omits_removed_permission_note() {
397
383
  let _hermetic = enter_hermetic("copilot-permission-note");
398
384
  let _guard = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
399
385
  let ws = tmp_ws("enforcement");
@@ -428,21 +414,21 @@ fn copilot_permission_note_registers_fs_tools_prompt_only() {
428
414
  .env
429
415
  .get("COPILOT_CUSTOM_INSTRUCTIONS_DIRS")
430
416
  .cloned()
431
- .unwrap_or_default();
432
- let agents_md = std::fs::read_to_string(Path::new(&dir).join("AGENTS.md")).unwrap_or_default();
433
-
434
- let note_line = agents_md
435
- .lines()
436
- .find(|line| line.starts_with("Permission note:"))
437
- .unwrap_or("")
438
- .to_string();
417
+ .expect("Copilot spawn must carry COPILOT_CUSTOM_INSTRUCTIONS_DIRS");
418
+ assert!(
419
+ !dir.is_empty(),
420
+ "Copilot instructions directory must not be empty"
421
+ );
422
+ let agents_md = std::fs::read_to_string(Path::new(&dir).join("AGENTS.md"))
423
+ .expect("per-worker Copilot AGENTS.md must be readable");
424
+ let identity = "You are Team Agent worker `worker_a` with role `Copilot Worker`.";
425
+ assert!(
426
+ agents_md.starts_with(identity) && agents_md.contains("COPILOT ROLE BODY SENTINEL"),
427
+ "C-2-1: compiled Copilot prompt must include its identity and role body; content={agents_md:?}"
428
+ );
439
429
  assert!(
440
- note_line.contains("fs_list, fs_read, git_diff, provider_builtin"),
441
- "C-2-1: the copilot enforcement row must register exactly \
442
- fs_read/fs_list/git_diff/provider_builtin as prompt-only (sorted in the Permission \
443
- note; execute_bash/fs_write/network/mcp_team are hard); note={note_line:?} \
444
- agents_md_present={}",
445
- !agents_md.is_empty()
430
+ !agents_md.contains("Permission note:"),
431
+ "C-2-1: removed provider tool enforcement must not emit a Permission note; content={agents_md:?}"
446
432
  );
447
433
  }
448
434