Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
146e4f2
docs: update CI checkpoint and smoke entrypoints for cross-platform b…
Spenquatch Apr 24, 2026
3c463b6
docs: update execution preflight report and session log for quality g…
Spenquatch Apr 24, 2026
e8494b5
Enhance session logging and implement gateway contract checks
Spenquatch Apr 24, 2026
266e30a
docs: start ITPS0 code+test
Spenquatch Apr 24, 2026
992552b
task: ITPS0-test
Spenquatch Apr 24, 2026
9d47afb
docs: finish ITPS0 code+test
Spenquatch Apr 24, 2026
24be300
docs: start ITPS0-integ
Spenquatch Apr 24, 2026
ab626e9
Merge branch 'adr-0027-identity-tuple-policy-surface-itps0-test' into…
Spenquatch Apr 24, 2026
5130be4
docs: close out ITPS0 integration
Spenquatch Apr 24, 2026
5988c3e
merge: ITPS0-integ (adr-0027-identity-tuple-policy-surface-itps0-inte…
Spenquatch Apr 24, 2026
80080eb
docs: finish ITPS0-integ
Spenquatch Apr 24, 2026
3a996bf
docs: start ITPS1 code+test
Spenquatch Apr 24, 2026
da0ff78
docs: finish ITPS1 code+test
Spenquatch Apr 24, 2026
2ec621a
docs: start ITPS1-integ
Spenquatch Apr 24, 2026
b95d190
task: ITPS1-integ
Spenquatch Apr 24, 2026
f02794d
docs: update ITPS1 closeout report
Spenquatch Apr 24, 2026
190ad81
merge: ITPS1-integ (adr-0027-identity-tuple-policy-surface-itps1-inte…
Spenquatch Apr 24, 2026
07f220e
docs: finish ITPS1-integ
Spenquatch Apr 24, 2026
36c9278
docs: start ITPS2 code+test
Spenquatch Apr 24, 2026
020cd41
docs: admit ITPS2 policy model test touch
Spenquatch Apr 24, 2026
6ff949f
docs: finish ITPS2 code+test
Spenquatch Apr 24, 2026
d1525cc
docs: start ITPS2-integ
Spenquatch Apr 24, 2026
20603b9
task: ITPS2-test
Spenquatch Apr 24, 2026
cc9239d
docs: update ITPS2 closeout report
Spenquatch Apr 24, 2026
0c49400
merge: ITPS2-integ (adr-0027-identity-tuple-policy-surface-itps2-inte…
Spenquatch Apr 24, 2026
6a3b0ba
docs: finish ITPS2-integ
Spenquatch Apr 24, 2026
5e91ca9
docs: start ITPS3 code+test
Spenquatch Apr 24, 2026
a398481
task: ITPS3-code
Spenquatch Apr 24, 2026
a37929b
task: ITPS3-test
Spenquatch Apr 24, 2026
16edd9a
docs: finish ITPS3 code+test
Spenquatch Apr 24, 2026
549e48b
docs: start ITPS3-integ-core
Spenquatch Apr 24, 2026
009a679
Merge branch 'adr-0027-identity-tuple-policy-surface-itps3-code' into…
Spenquatch Apr 24, 2026
d1210f3
Merge branch 'adr-0027-identity-tuple-policy-surface-itps3-test' into…
Spenquatch Apr 24, 2026
6f8e58a
docs: finish ITPS3-integ-core
Spenquatch Apr 24, 2026
2f50a2d
docs: start CP1 checkpoint
Spenquatch Apr 24, 2026
ea4f6f7
docs: fix ITPS3 checkpoint wiring
Spenquatch Apr 24, 2026
0b1c74f
docs: unblock ITPS3 platform fixes
Spenquatch Apr 24, 2026
007af75
Merge branch 'adr-0027-identity-tuple-policy-surface-itps3-integ-core…
Spenquatch Apr 24, 2026
b3149ca
task: ITPS3-integ-macos
Spenquatch Apr 24, 2026
167f7c5
fix(macos): isolate gateway socket lookup
Spenquatch Apr 24, 2026
2c985e3
docs: record ITPS3 macos fix
Spenquatch Apr 24, 2026
adbbd6b
task: ITPS3-integ-macos
Spenquatch Apr 24, 2026
6f698fb
docs: complete ITPS3 checkpoint
Spenquatch Apr 24, 2026
0fd5f7c
merge: ITPS3-integ-macos -> ITPS3-integ
Spenquatch Apr 24, 2026
c0d1f4a
task: ITPS3-integ
Spenquatch Apr 24, 2026
4e0572f
docs: finish ITPS3-integ
Spenquatch Apr 24, 2026
b4a8dd2
chore: add .gitattributes for Markdown file handling
Spenquatch Apr 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Keep Markdown docs byte-stable across platforms for doc-lock tests.
*.md text eol=lf
510 changes: 510 additions & 0 deletions crates/broker/src/tests.rs

Large diffs are not rendered by default.

182 changes: 129 additions & 53 deletions crates/shell/src/builtins/world_gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,21 @@ fn build_macos_gateway_client() -> anyhow::Result<MacosGatewayClient> {
});
}

let default_sock = dirs::home_dir()
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".substrate/sock/agent.sock");
let default_sock = macos_default_world_socket_path();
if default_sock.exists() && probe_gateway_caps_uds(&default_sock) {
return Ok(MacosGatewayClient {
client: AgentClient::unix_socket(default_sock)?,
_forwarding: None,
});
}

if substrate_home_is_explicitly_set() {
return Ok(MacosGatewayClient {
client: AgentClient::unix_socket(default_sock)?,
_forwarding: None,
});
}

let vm_name = std::env::var("SUBSTRATE_LIMA_VM_NAME")
.or_else(|_| std::env::var("LIMA_VM_NAME"))
.unwrap_or_else(|_| "substrate".to_string());
Expand Down Expand Up @@ -242,11 +247,11 @@ fn resolve_macos_gateway_client_endpoint() -> MacosGatewayClientEndpoint {
return MacosGatewayClientEndpoint::Unix(std::path::PathBuf::from(socket_path));
}

let default_sock = dirs::home_dir()
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".substrate/sock/agent.sock");
let default_sock = macos_default_world_socket_path();

if default_sock.exists() && probe_gateway_caps_uds(&default_sock) {
if substrate_home_is_explicitly_set()
|| (default_sock.exists() && probe_gateway_caps_uds(&default_sock))
{
MacosGatewayClientEndpoint::Unix(default_sock)
} else {
MacosGatewayClientEndpoint::Tcp {
Expand Down Expand Up @@ -277,6 +282,22 @@ fn probe_gateway_caps_uds(path: &std::path::Path) -> bool {
}
}

#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
fn substrate_home_is_explicitly_set() -> bool {
std::env::var_os("SUBSTRATE_HOME").is_some_and(|value| !value.is_empty())
}

#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
fn macos_default_world_socket_path() -> PathBuf {
substrate_common::paths::substrate_home()
.unwrap_or_else(|_| {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".substrate")
})
.join("sock/agent.sock")
}

fn build_gateway_request_context() -> anyhow::Result<GatewayLifecycleRequestContext> {
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let (effective_config, config_explain) = config_model::resolve_effective_config_with_explain(
Expand All @@ -288,12 +309,16 @@ fn build_gateway_request_context() -> anyhow::Result<GatewayLifecycleRequestCont
let (effective_policy, _) =
substrate_broker::resolve_effective_policy_with_explain(&cwd, false)
.map_err(|err| config_model::user_error(err.to_string()))?;
let selected_backend = effective_config
.llm
.routing
.default_backend
.trim()
.to_string();
let backend_entry =
validate_gateway_backend_selection(&cwd, &effective_config, &effective_policy)?;
validate_gateway_backend_selection(&cwd, &effective_policy, &selected_backend)?;
let network_policy = resolve_world_network_policy_for_cwd(&cwd)?;
let world_network = request_world_network_routing(&network_policy);
let integrated_auth =
resolve_integrated_auth_payload(&effective_config, &effective_policy, &backend_entry)?;
let gateway_mode = match effective_config.llm.gateway.mode {
LlmGatewayMode::InWorld => "in_world",
LlmGatewayMode::HostOnly => "host_only",
Expand All @@ -317,18 +342,21 @@ fn build_gateway_request_context() -> anyhow::Result<GatewayLifecycleRequestCont
);

let agent_id = std::env::var("SUBSTRATE_AGENT_ID").unwrap_or_else(|_| "human".to_string());
let selected_backend = effective_config
.llm
.routing
.default_backend
.trim()
.to_string();
let identity_tuple = Some(derive_gateway_identity_tuple(
&agent_id,
&effective_policy,
&selected_backend,
integrated_auth.as_ref(),
)?);
let mut identity_tuple =
derive_gateway_identity_tuple(&agent_id, &effective_policy, &selected_backend)?;
let integrated_auth =
resolve_integrated_auth_payload(&effective_config, &effective_policy, &backend_entry)?;
identity_tuple.auth_authority = derive_gateway_auth_authority(integrated_auth.as_ref());
enforce_identity_constraint(
"llm.constraints.auth_authorities",
"auth authority",
identity_tuple.auth_authority.as_deref(),
&effective_policy.llm_constraints_auth_authorities,
)?;
identity_tuple
.validate()
.map_err(gateway_invalid_integration_error)?;
let identity_tuple = Some(identity_tuple);
let placement_posture = Some(derive_gateway_placement_posture(&effective_config)?);

let request = GatewayLifecycleRequestV1 {
Expand Down Expand Up @@ -357,7 +385,6 @@ fn derive_gateway_identity_tuple(
agent_id: &str,
effective_policy: &substrate_broker::Policy,
selected_backend: &str,
integrated_auth: Option<&GatewayIntegratedAuthPayloadV1>,
) -> anyhow::Result<IdentityTuple> {
let protocol = match selected_backend {
CLI_CODEX_BACKEND | API_OPENAI_BACKEND => "openai.responses",
Expand All @@ -374,28 +401,12 @@ fn derive_gateway_identity_tuple(
API_ANTHROPIC_BACKEND => Some("anthropic".to_string()),
_ => None,
};
let auth_authority = integrated_auth.and_then(|auth| {
if auth.cli_codex.is_some() {
Some("codex_subscription".to_string())
} else if let Some(api_env) = auth.api_env.as_ref() {
if api_env.env.contains_key(OPENAI_API_KEY_ENV) {
Some("openai_api_key".to_string())
} else if api_env.env.contains_key(ANTHROPIC_API_KEY_ENV) {
Some("anthropic_api_key".to_string())
} else {
None
}
} else {
None
}
});

let tuple = IdentityTuple {
client: resolve_originating_client(agent_id),
router: SUBSTRATE_GATEWAY_ROUTER.to_string(),
protocol: protocol.to_string(),
provider,
auth_authority,
auth_authority: None,
};

enforce_identity_constraint(
Expand All @@ -416,18 +427,29 @@ fn derive_gateway_identity_tuple(
tuple.provider.as_deref(),
&effective_policy.llm_constraints_providers,
)?;
enforce_identity_constraint(
"llm.constraints.auth_authorities",
"auth authority",
tuple.auth_authority.as_deref(),
&effective_policy.llm_constraints_auth_authorities,
)?;
tuple
.validate()
.map_err(gateway_invalid_integration_error)?;
Ok(tuple)
}

fn derive_gateway_auth_authority(
integrated_auth: Option<&GatewayIntegratedAuthPayloadV1>,
) -> Option<String> {
integrated_auth.and_then(|auth| {
if auth.cli_codex.is_some() {
Some("codex_subscription".to_string())
} else if let Some(api_env) = auth.api_env.as_ref() {
if api_env.env.contains_key(OPENAI_API_KEY_ENV) {
Some("openai_api_key".to_string())
} else if api_env.env.contains_key(ANTHROPIC_API_KEY_ENV) {
Some("anthropic_api_key".to_string())
} else {
None
}
} else {
None
}
})
}

fn derive_gateway_placement_posture(
effective_config: &config_model::SubstrateConfig,
) -> anyhow::Result<PlacementPosture> {
Expand Down Expand Up @@ -558,10 +580,9 @@ fn print_status_identity_metadata_impl(response: &GatewayLifecycleResponseV1, st

fn validate_gateway_backend_selection(
cwd: &std::path::Path,
effective_config: &config_model::SubstrateConfig,
effective_policy: &substrate_broker::Policy,
selected_backend: &str,
) -> anyhow::Result<agent_inventory::AgentInventoryEntryV1> {
let selected_backend = effective_config.llm.routing.default_backend.trim();
let entry = agent_inventory::resolve_gateway_backend_inventory_entry(
cwd,
selected_backend,
Expand Down Expand Up @@ -784,8 +805,9 @@ fn ensure_env_name_allowed(
}

fn codex_auth_state_path() -> PathBuf {
std::env::var_os("HOME")
.map(PathBuf::from)
dirs::home_dir()
.or_else(|| std::env::var_os("HOME").map(PathBuf::from))
.or_else(|| std::env::var_os("USERPROFILE").map(PathBuf::from))
.unwrap_or_else(|| PathBuf::from("."))
.join(".codex")
.join("auth.json")
Expand Down Expand Up @@ -905,6 +927,9 @@ fn error_is_component_unavailable(err: &anyhow::Error) -> bool {
|| msg.contains("listener missing")
|| msg.contains("no such file or directory")
|| msg.contains("failed to open named pipe")
|| msg.contains("no forwarding transport available")
|| msg.contains("lima ssh config not found")
|| msg.contains("limactl not found")
})
}

Expand Down Expand Up @@ -1088,3 +1113,54 @@ mod tests {
});
}
}

#[cfg(test)]
mod classification_tests {
use super::{
error_is_component_unavailable, macos_default_world_socket_path,
substrate_home_is_explicitly_set,
};

fn with_env_var<T>(key: &str, value: Option<&std::ffi::OsStr>, f: impl FnOnce() -> T) -> T {
let prev = std::env::var_os(key);
match value {
Some(value) => std::env::set_var(key, value),
None => std::env::remove_var(key),
}
let result = f();
match prev {
Some(value) => std::env::set_var(key, value),
None => std::env::remove_var(key),
}
result
}

#[test]
fn component_unavailable_includes_macos_forwarding_bootstrap_failures() {
for message in [
"No forwarding transport available. Run scripts/mac/lima-doctor.sh",
"Lima SSH config not found at: /Users/test/.lima/substrate/ssh.config",
"limactl not found. Install Lima with: brew install lima",
] {
let err = anyhow::anyhow!(message);
assert!(
error_is_component_unavailable(&err),
"expected macOS bootstrap error to classify as component unavailable: {message}"
);
}
}

#[test]
fn macos_default_world_socket_path_respects_explicit_substrate_home() {
let temp = tempfile::tempdir().expect("tempdir");
let substrate_home = temp.path().join("isolated-substrate-home");

with_env_var("SUBSTRATE_HOME", Some(substrate_home.as_os_str()), || {
assert!(substrate_home_is_explicitly_set());
assert_eq!(
macos_default_world_socket_path(),
substrate_home.join("sock/agent.sock")
);
});
}
}
86 changes: 86 additions & 0 deletions crates/shell/src/execution/policy_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1741,4 +1741,90 @@ mod tests {
.to_string()
.contains("unknown policy key 'world_fs.read_allowlist'"));
}

#[test]
fn policy_patch_parses_tuple_axes_under_existing_llm_constraints_root() {
let path = Path::new("policy.yaml");
let patch = parse_policy_patch_yaml(
path,
r#"
llm:
allowed_backends:
- "api:openai"
constraints:
routers:
- "substrate_gateway"
providers:
- "openai"
protocols:
- "openai.responses"
auth_authorities:
- "openai_api_key"
"#,
)
.expect("tuple-axis keys should parse under llm.constraints");

assert_eq!(
patch.llm.allowed_backends.as_deref(),
Some(&["api:openai".to_string()][..])
);
assert_eq!(
patch.llm.constraints.routers.as_deref(),
Some(&["substrate_gateway".to_string()][..])
);
assert_eq!(
patch.llm.constraints.providers.as_deref(),
Some(&["openai".to_string()][..])
);
assert_eq!(
patch.llm.constraints.protocols.as_deref(),
Some(&["openai.responses".to_string()][..])
);
assert_eq!(
patch.llm.constraints.auth_authorities.as_deref(),
Some(&["openai_api_key".to_string()][..])
);
}

#[test]
fn policy_patch_rejects_second_tuple_policy_roots_and_unknown_tuple_axes() {
let path = Path::new("policy.yaml");
let invalid_cases = [
(
r#"
llm:
identity_tuple:
providers:
- "openai"
"#,
"unknown field `identity_tuple`",
),
(
r#"
trace:
identity_tuple:
provider: "openai"
"#,
"unknown field `trace`",
),
(
r#"
llm:
constraints:
clients:
- "codex"
"#,
"unknown field `clients`",
),
];

for (raw, expected_fragment) in invalid_cases {
let err = parse_policy_patch_yaml(path, raw)
.expect_err("invalid tuple-policy roots should fail validation");
assert!(
err.to_string().contains(expected_fragment),
"expected `{expected_fragment}` in error, got: {err}"
);
}
}
}
Loading