use shepherd_core::{Harness, dispatch::*};
fn child_identity(event: &str) -> NormalizedIdentity {
RawIdentity::new(
Harness::Pi,
event,
"session-child",
Some("agent-child"),
Some("shepherd:coder"),
None,
None,
None,
)
.normalize()
.expect("fixture identity")
}
fn binding() -> DispatchBinding {
DispatchBinding::new(
Some(RunId::new("v645").expect("run")),
Some(Role::Coder),
Some(LaneId::new("l1-engine").expect("lane")),
Some(AgentId::new("parent").expect("parent")),
vec!["crates/core/src/dispatch/**".into()],
None,
[
"read",
"search",
"shell",
"skill-load",
"write",
"subagent-provider",
],
"native",
"1",
None,
60_000,
)
.expect("binding")
}
#[test]
fn child_start_and_resume_never_become_native_dispatch_requests() {
let start =
plan_lifecycle(&child_identity("SubagentStart"), Some(&binding())).expect("start plan");
assert!(matches!(
start,
DispatchPlan::Blocked(DispatchError::BrokerRequired)
));
let mut resume_binding = binding();
resume_binding.source_agent_id = Some(AgentId::new("source").expect("source"));
let resume = plan_lifecycle(&child_identity("SubagentResume"), Some(&resume_binding))
.expect("resume plan");
assert!(matches!(
resume,
DispatchPlan::Blocked(DispatchError::BrokerRequired)
));
}
#[test]
fn terminal_cleanup_response_is_strict_and_not_replayable() {
let response = LaunchCleanupResponse {
schema: LAUNCH_CLEANUP_SCHEMA.into(),
launch_id_hash: [4; 32],
state: PendingLaunchState::Expired,
};
response.validate().expect("terminal cleanup");
let mut replay = response.clone();
replay.state = PendingLaunchState::Active;
assert!(replay.validate().is_err());
let mut malformed = response;
malformed.schema = "wrong".into();
assert!(malformed.validate().is_err());
}
#[test]
fn digest_comparison_is_fixed_width_and_constant_time_api() {
let mut other = [0_u8; 32];
other[31] = 1;
assert!(constant_time_digest_eq(&[7; 32], &[7; 32]));
assert!(!constant_time_digest_eq(&[7; 32], &other));
}
#[test]
fn pending_launch_state_has_one_claim_and_no_direct_active_edge() {
assert!(PendingLaunchState::Pending.can_claim());
assert!(!PendingLaunchState::ClaimedUnspawned.can_claim());
assert!(!PendingLaunchState::Active.can_claim());
assert!(PendingLaunchState::ClaimedUnspawned.can_activate());
assert!(!PendingLaunchState::Pending.can_activate());
assert!(!PendingLaunchState::Active.can_activate());
assert!(PendingLaunchState::LaunchFailed.is_terminal());
assert!(PendingLaunchState::Canceled.is_terminal());
assert!(PendingLaunchState::Expired.is_terminal());
}
#[test]
fn scope_validation_rejects_unicode_and_alias_forms_and_keeps_case_distinct() {
let scopes = ["crates/core/**".to_owned()];
for path in [
"crates/core/src/é.rs",
"crates/core/./src/lib.rs",
"crates/core/src/../lib.rs",
"crates/core/src/lib~1.rs",
] {
assert!(
path_in_write_scope(path, &scopes).is_err(),
"unsafe path accepted: {path}"
);
}
assert!(path_in_write_scope("crates/core/src/lib.rs", &scopes).expect("safe path"));
assert!(!path_in_write_scope("Crates/core/src/lib.rs", &scopes).expect("case stays distinct"));
assert!(
!path_in_write_scope("crates/core/src/lib.rs", &["Crates/core/**".into()])
.expect("case stays distinct")
);
}