use std::collections::BTreeMap;
use lifeloop::router::{
AdapterRegistry, AdapterResolution, CapabilityKind, CapabilityRequest, CapabilityRequirement,
DefaultNegotiationStrategy, NegotiationStrategy, PayloadPlacementDecision, PlacementRejection,
RoutingPlan, negotiate, route,
};
use lifeloop::{
AcceptablePlacement, AdapterManifest, AdapterRole, CallbackRequest, ConformanceLevel,
FailureClass, IntegrationMode, LifecycleEventKind, ManifestContextPressure,
ManifestLifecycleEventSupport, ManifestPlacementClass, ManifestPlacementSupport,
ManifestReceipts, ManifestRenewal, ManifestRenewalContinuation, ManifestRenewalReset,
ManifestSessionIdentity, NegotiationOutcome, PayloadEnvelope, PlacementClass,
RegisteredAdapter, RequirementLevel, SCHEMA_VERSION, SupportState,
};
const FAKE_ID: &str = "fake-adapter";
const FAKE_VERSION: &str = "0.0.1";
fn manifest_with(
placement: BTreeMap<ManifestPlacementClass, ManifestPlacementSupport>,
lifecycle: BTreeMap<LifecycleEventKind, ManifestLifecycleEventSupport>,
context_pressure: SupportState,
session_identity: Option<ManifestSessionIdentity>,
) -> AdapterManifest {
AdapterManifest {
contract_version: SCHEMA_VERSION.to_string(),
adapter_id: FAKE_ID.into(),
adapter_version: FAKE_VERSION.into(),
display_name: "Fake".into(),
role: AdapterRole::PrimaryWorker,
integration_modes: vec![IntegrationMode::NativeHook],
lifecycle_events: lifecycle,
placement,
context_pressure: ManifestContextPressure {
support: context_pressure,
evidence: None,
},
receipts: ManifestReceipts {
native: false,
lifeloop_synthesized: true,
receipt_ledger: SupportState::Unavailable,
},
session_identity,
session_rename: None,
renewal: None,
approval_surface: None,
failure_modes: Vec::new(),
telemetry_sources: Vec::new(),
known_degradations: Vec::new(),
}
}
fn full_placement_support() -> BTreeMap<ManifestPlacementClass, ManifestPlacementSupport> {
BTreeMap::from([
(
ManifestPlacementClass::PreSession,
ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: None,
},
),
(
ManifestPlacementClass::PreFrameLeading,
ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: None,
},
),
(
ManifestPlacementClass::PreFrameTrailing,
ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: Some(1024),
},
),
(
ManifestPlacementClass::ToolResult,
ManifestPlacementSupport {
support: SupportState::Synthesized,
max_bytes: None,
},
),
(
ManifestPlacementClass::ManualOperator,
ManifestPlacementSupport {
support: SupportState::Manual,
max_bytes: None,
},
),
])
}
struct Fixture {
manifest: AdapterManifest,
}
impl AdapterRegistry for Fixture {
fn resolve(&self, adapter_id: &str, adapter_version: &str) -> AdapterResolution {
if adapter_id != self.manifest.adapter_id {
return AdapterResolution::UnknownId;
}
if adapter_version != self.manifest.adapter_version {
return AdapterResolution::VersionMismatch {
registered_version: self.manifest.adapter_version.clone(),
};
}
AdapterResolution::Found(RegisteredAdapter {
manifest: self.manifest.clone(),
conformance: ConformanceLevel::PreConformance,
})
}
}
fn base_request() -> CallbackRequest {
CallbackRequest {
schema_version: SCHEMA_VERSION.to_string(),
event: LifecycleEventKind::SessionStarting,
event_id: "evt_1".into(),
adapter_id: FAKE_ID.into(),
adapter_version: FAKE_VERSION.into(),
invocation_id: "inv_1".into(),
harness_session_id: None,
harness_run_id: None,
harness_task_id: None,
integration_mode: IntegrationMode::NativeHook,
frame_context: None,
payload_refs: Vec::new(),
capability_snapshot_ref: None,
sequence: None,
idempotency_key: None,
metadata: serde_json::Map::new(),
}
}
fn build_plan(manifest: AdapterManifest) -> RoutingPlan {
let fx = Fixture { manifest };
route(&base_request(), &fx).expect("plan builds")
}
fn payload_with(
id: &str,
body: &str,
byte_size: u64,
placements: Vec<(PlacementClass, RequirementLevel)>,
) -> PayloadEnvelope {
PayloadEnvelope {
schema_version: SCHEMA_VERSION.to_string(),
payload_id: id.into(),
client_id: "test-client".into(),
payload_kind: "instruction_frame".into(),
format: "client-defined".into(),
content_encoding: "utf8".into(),
body: Some(body.to_string()),
body_ref: None,
byte_size,
content_digest: None,
acceptable_placements: placements
.into_iter()
.map(|(placement, requirement)| AcceptablePlacement {
placement,
requirement,
})
.collect(),
idempotency_key: None,
expires_at_epoch_s: None,
redaction: None,
metadata: serde_json::Map::new(),
}
}
#[test]
fn satisfied_outcome_when_all_required_and_preferred_are_met() {
let mut lifecycle = BTreeMap::new();
lifecycle.insert(
LifecycleEventKind::SessionStarting,
ManifestLifecycleEventSupport {
support: SupportState::Native,
modes: vec![IntegrationMode::NativeHook],
},
);
let m = manifest_with(
full_placement_support(),
lifecycle,
SupportState::Native,
None,
);
let plan = build_plan(m);
let req = CapabilityRequest::new()
.with(CapabilityRequirement::required(
CapabilityKind::LifecycleEvent(LifecycleEventKind::SessionStarting),
SupportState::Native,
))
.with(CapabilityRequirement::preferred(
CapabilityKind::ContextPressure,
SupportState::Synthesized,
));
let r = negotiate(&plan, &req, &[]);
assert_eq!(r.outcome, NegotiationOutcome::Satisfied);
assert!(r.capability_degradations.is_empty());
assert!(r.warnings.is_empty());
assert!(r.failure_class.is_none());
assert!(!r.blocks_dispatch());
}
#[test]
fn degraded_outcome_when_preferred_capability_lacks_support() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::from([(
LifecycleEventKind::SessionStarting,
ManifestLifecycleEventSupport {
support: SupportState::Native,
modes: vec![IntegrationMode::NativeHook],
},
)]),
SupportState::Unavailable,
None,
));
let req = CapabilityRequest::new()
.with(CapabilityRequirement::required(
CapabilityKind::LifecycleEvent(LifecycleEventKind::SessionStarting),
SupportState::Native,
))
.with(CapabilityRequirement::preferred(
CapabilityKind::ContextPressure,
SupportState::Native,
));
let r = negotiate(&plan, &req, &[]);
assert_eq!(r.outcome, NegotiationOutcome::Degraded);
assert_eq!(r.capability_degradations.len(), 1);
let deg = &r.capability_degradations[0];
assert_eq!(deg.capability, "context_pressure");
assert_eq!(deg.previous_support, SupportState::Native);
assert_eq!(deg.current_support, SupportState::Unavailable);
assert!(!r.warnings.is_empty());
assert!(r.failure_class.is_none());
assert!(!r.blocks_dispatch());
}
#[test]
fn unsupported_outcome_when_required_capability_unavailable() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Unavailable,
None,
));
let req = CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::LifecycleEvent(LifecycleEventKind::SessionStarting),
SupportState::Native,
));
let r = negotiate(&plan, &req, &[]);
assert_eq!(r.outcome, NegotiationOutcome::Unsupported);
assert_eq!(r.failure_class, Some(FailureClass::CapabilityUnsupported));
assert!(r.blocks_dispatch());
assert!(r.capability_degradations.is_empty());
}
#[test]
fn requires_operator_outcome_when_required_capability_only_manual() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Unavailable,
None,
));
let mut m = plan.adapter.clone();
m.approval_surface = Some(lifeloop::ManifestApprovalSurface {
support: SupportState::Manual,
});
let plan = build_plan(m);
let req = CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::ApprovalSurface,
SupportState::Native,
));
let r = negotiate(&plan, &req, &[]);
assert_eq!(r.outcome, NegotiationOutcome::RequiresOperator);
assert_eq!(r.failure_class, Some(FailureClass::OperatorRequired));
assert!(r.blocks_dispatch());
}
#[test]
fn optional_capabilities_may_be_omitted_without_warning() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Unavailable,
None,
));
let req = CapabilityRequest::new().with(CapabilityRequirement::optional(
CapabilityKind::HarnessSessionId,
SupportState::Native,
));
let r = negotiate(&plan, &req, &[]);
assert_eq!(r.outcome, NegotiationOutcome::Satisfied);
assert!(r.warnings.is_empty());
assert!(r.capability_degradations.is_empty());
}
#[test]
fn capability_kind_name_pins_lifecycle_event_wire_name() {
assert_eq!(
CapabilityKind::LifecycleEvent(LifecycleEventKind::FrameOpening).name(),
"lifecycle_event:frame.opening"
);
assert_eq!(
CapabilityKind::RenewalContinuationPayloadDelivery.name(),
"renewal.continuation.payload_delivery"
);
}
#[test]
fn support_strength_order_degrades_weaker_claims() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Partial,
None,
));
let req = CapabilityRequest::new().with(CapabilityRequirement::preferred(
CapabilityKind::ContextPressure,
SupportState::Synthesized,
));
let r = negotiate(&plan, &req, &[]);
assert_eq!(r.outcome, NegotiationOutcome::Degraded);
assert_eq!(r.capability_degradations.len(), 1);
assert_eq!(
r.capability_degradations[0].previous_support,
SupportState::Synthesized
);
assert_eq!(
r.capability_degradations[0].current_support,
SupportState::Partial
);
}
#[test]
fn manual_requirement_accepts_stronger_non_unavailable_support() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let req = CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::ContextPressure,
SupportState::Manual,
));
let r = negotiate(&plan, &req, &[]);
assert_eq!(r.outcome, NegotiationOutcome::Satisfied);
assert_eq!(r.failure_class, None);
assert!(r.warnings.is_empty());
}
#[test]
fn renewal_negotiation_distinguishes_observation_from_payload_delivery() {
let mut manifest = manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
);
manifest.renewal = Some(ManifestRenewal {
reset: ManifestRenewalReset {
native: SupportState::Unavailable,
wrapper_mediated: SupportState::Partial,
manual: SupportState::Manual,
},
continuation: ManifestRenewalContinuation {
observation: SupportState::Partial,
payload_delivery: SupportState::Unavailable,
},
evidence: Some("fixture renewal surface".into()),
});
let plan = build_plan(manifest);
let observation = negotiate(
&plan,
&CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::RenewalContinuationObservation,
SupportState::Partial,
)),
&[],
);
assert_eq!(observation.outcome, NegotiationOutcome::Satisfied);
assert_eq!(observation.failure_class, None);
let delivery = negotiate(
&plan,
&CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::RenewalContinuationPayloadDelivery,
SupportState::Partial,
)),
&[],
);
assert_eq!(delivery.outcome, NegotiationOutcome::Unsupported);
assert_eq!(
delivery.failure_class,
Some(FailureClass::CapabilityUnsupported)
);
assert_eq!(
delivery.warnings[0].capability.as_deref(),
Some("renewal.continuation.payload_delivery")
);
}
#[test]
fn renewal_manual_reset_distinguishes_manual_from_non_manual_requests() {
let mut manifest = manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
);
manifest.renewal = Some(ManifestRenewal {
reset: ManifestRenewalReset {
native: SupportState::Unavailable,
wrapper_mediated: SupportState::Unavailable,
manual: SupportState::Manual,
},
continuation: ManifestRenewalContinuation {
observation: SupportState::Manual,
payload_delivery: SupportState::Manual,
},
evidence: None,
});
let plan = build_plan(manifest);
let manual = negotiate(
&plan,
&CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::RenewalResetManual,
SupportState::Manual,
)),
&[],
);
assert_eq!(manual.outcome, NegotiationOutcome::Satisfied);
assert_eq!(manual.failure_class, None);
let non_manual = negotiate(
&plan,
&CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::RenewalResetManual,
SupportState::Partial,
)),
&[],
);
assert_eq!(non_manual.outcome, NegotiationOutcome::RequiresOperator);
assert_eq!(
non_manual.failure_class,
Some(FailureClass::OperatorRequired)
);
}
#[test]
fn placement_chosen_first_preference_developer_equivalent_frame() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let env = payload_with(
"p1",
"opaque-body",
100,
vec![(
PlacementClass::DeveloperEquivalentFrame,
RequirementLevel::Required,
)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), std::slice::from_ref(&env));
assert_eq!(r.outcome, NegotiationOutcome::Satisfied);
assert_eq!(r.placement_decisions.len(), 1);
match &r.placement_decisions[0] {
PayloadPlacementDecision::Chosen {
chosen,
first_preference,
payload_id,
payload_kind,
byte_size,
content_digest,
..
} => {
assert_eq!(*chosen, PlacementClass::DeveloperEquivalentFrame);
assert!(*first_preference);
assert_eq!(payload_id, "p1");
assert_eq!(payload_kind, "instruction_frame");
assert_eq!(*byte_size, "opaque-body".len() as u64);
assert_eq!(content_digest, &None);
}
other => panic!("expected Chosen, got {other:?}"),
}
assert_eq!(env.body.as_deref(), Some("opaque-body"));
}
#[test]
fn placement_chosen_pre_prompt_frame_maps_to_pre_frame_trailing() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let env = payload_with(
"p1",
"opaque",
50,
vec![(PlacementClass::PrePromptFrame, RequirementLevel::Required)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
matches!(
r.placement_decisions[0],
PayloadPlacementDecision::Chosen {
chosen: PlacementClass::PrePromptFrame,
..
}
);
}
#[test]
fn placement_chosen_side_channel_context_maps_to_manual_operator() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let env = payload_with(
"p1",
"opaque",
50,
vec![(
PlacementClass::SideChannelContext,
RequirementLevel::Optional,
)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
match &r.placement_decisions[0] {
PayloadPlacementDecision::Chosen { chosen, .. } => {
assert_eq!(*chosen, PlacementClass::SideChannelContext);
}
other => panic!("expected Chosen, got {other:?}"),
}
}
#[test]
fn placement_receipt_only_is_universally_available() {
let plan = build_plan(manifest_with(
BTreeMap::new(),
BTreeMap::new(),
SupportState::Unavailable,
None,
));
let env = payload_with(
"p_receipt",
"opaque",
10,
vec![(PlacementClass::ReceiptOnly, RequirementLevel::Required)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
assert_eq!(r.outcome, NegotiationOutcome::Satisfied);
match &r.placement_decisions[0] {
PayloadPlacementDecision::Chosen { chosen, .. } => {
assert_eq!(*chosen, PlacementClass::ReceiptOnly);
}
other => panic!("expected Chosen, got {other:?}"),
}
}
#[test]
fn placement_pre_session_class_exercised_via_developer_equivalent_with_only_pre_session() {
let mut placement = BTreeMap::new();
placement.insert(
ManifestPlacementClass::PreSession,
ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: None,
},
);
placement.insert(
ManifestPlacementClass::PreFrameTrailing,
ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: None,
},
);
let plan = build_plan(manifest_with(
placement,
BTreeMap::new(),
SupportState::Native,
None,
));
let env = payload_with(
"p_fb",
"opaque",
25,
vec![
(
PlacementClass::DeveloperEquivalentFrame,
RequirementLevel::Preferred,
),
(PlacementClass::PrePromptFrame, RequirementLevel::Required),
],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
match &r.placement_decisions[0] {
PayloadPlacementDecision::Chosen {
chosen,
first_preference,
rejected,
..
} => {
assert_eq!(*chosen, PlacementClass::PrePromptFrame);
assert!(!*first_preference);
assert_eq!(rejected.len(), 1);
assert_eq!(
rejected[0].placement(),
PlacementClass::DeveloperEquivalentFrame
);
}
other => panic!("expected fallback Chosen, got {other:?}"),
}
}
#[test]
fn placement_payload_too_large_falls_through_then_fails() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let body = "x".repeat(4096);
let env = payload_with(
"p_big",
&body,
body.len() as u64,
vec![(PlacementClass::PrePromptFrame, RequirementLevel::Required)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
assert_eq!(r.outcome, NegotiationOutcome::Unsupported);
match &r.placement_decisions[0] {
PayloadPlacementDecision::Failed {
failure_class,
rejected,
..
} => {
assert_eq!(*failure_class, FailureClass::PayloadTooLarge);
assert!(matches!(
rejected[0],
PlacementRejection::PayloadTooLarge { .. }
));
}
other => panic!("expected Failed, got {other:?}"),
}
}
#[test]
fn placement_allows_payload_at_exact_manifest_byte_limit() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let body = "x".repeat(1024);
let env = payload_with(
"p_exact",
&body,
body.len() as u64,
vec![(PlacementClass::PrePromptFrame, RequirementLevel::Required)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
assert_eq!(r.outcome, NegotiationOutcome::Satisfied);
assert_eq!(r.failure_class, None);
assert!(matches!(
r.placement_decisions[0],
PayloadPlacementDecision::Chosen {
chosen: PlacementClass::PrePromptFrame,
..
}
));
}
#[test]
fn placement_payload_too_large_uses_actual_inline_body_bytes() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let body = "x".repeat(4096);
let env = payload_with(
"p_underreported",
&body,
1,
vec![(PlacementClass::PrePromptFrame, RequirementLevel::Required)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
assert_eq!(r.failure_class, Some(FailureClass::PayloadTooLarge));
match &r.placement_decisions[0] {
PayloadPlacementDecision::Failed { rejected, .. } => {
assert!(matches!(
rejected[0],
PlacementRejection::PayloadTooLarge {
byte_size: 4096,
max_bytes: 1024,
..
}
));
}
other => panic!("expected Failed, got {other:?}"),
}
}
#[test]
fn placement_unavailable_when_no_acceptable_placement_exists() {
let mut placement = BTreeMap::new();
placement.insert(
ManifestPlacementClass::PreSession,
ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: None,
},
);
let plan = build_plan(manifest_with(
placement,
BTreeMap::new(),
SupportState::Native,
None,
));
let env = payload_with(
"p_no",
"opaque",
10,
vec![(
PlacementClass::DeveloperEquivalentFrame,
RequirementLevel::Required,
)],
);
let r = negotiate(&plan, &CapabilityRequest::new(), &[env]);
assert_eq!(r.outcome, NegotiationOutcome::Unsupported);
assert_eq!(r.failure_class, Some(FailureClass::PlacementUnavailable));
assert!(matches!(
&r.placement_decisions[0],
PayloadPlacementDecision::Failed {
failure_class: FailureClass::PlacementUnavailable,
..
}
));
}
#[test]
fn payload_placement_decision_accessors_distinguish_chosen_and_failed() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let chosen_env = payload_with(
"p_chosen",
"opaque",
6,
vec![(PlacementClass::PrePromptFrame, RequirementLevel::Required)],
);
let chosen = negotiate(&plan, &CapabilityRequest::new(), &[chosen_env]);
assert_eq!(chosen.placement_decisions[0].payload_id(), "p_chosen");
assert!(!chosen.placement_decisions[0].is_failed());
let plan = build_plan(manifest_with(
BTreeMap::new(),
BTreeMap::new(),
SupportState::Native,
None,
));
let failed_env = payload_with(
"p_failed",
"opaque",
6,
vec![(
PlacementClass::DeveloperEquivalentFrame,
RequirementLevel::Required,
)],
);
let failed = negotiate(&plan, &CapabilityRequest::new(), &[failed_env]);
assert_eq!(failed.placement_decisions[0].payload_id(), "p_failed");
assert!(failed.placement_decisions[0].is_failed());
}
#[test]
fn payload_body_bytes_pass_through_unchanged() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Native,
None,
));
let body = "{\"opaque\":\"data\",\"nested\":{\"x\":1}}".to_string();
let env = payload_with(
"p_body",
&body,
body.len() as u64,
vec![(
PlacementClass::DeveloperEquivalentFrame,
RequirementLevel::Required,
)],
);
let original_body = env.body.clone();
let r = negotiate(&plan, &CapabilityRequest::new(), std::slice::from_ref(&env));
assert_eq!(env.body, original_body);
assert_eq!(env.body.as_deref(), Some(body.as_str()));
assert!(matches!(
r.placement_decisions[0],
PayloadPlacementDecision::Chosen { .. }
));
}
#[test]
fn required_capability_failure_halts_before_dispatch_path() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::new(),
SupportState::Unavailable,
None,
));
let req = CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::LifecycleEvent(LifecycleEventKind::SessionStarting),
SupportState::Native,
));
let env = payload_with(
"p_x",
"opaque",
10,
vec![(
PlacementClass::DeveloperEquivalentFrame,
RequirementLevel::Required,
)],
);
let r = negotiate(&plan, &req, &[env]);
assert_eq!(r.outcome, NegotiationOutcome::Unsupported);
assert!(r.blocks_dispatch());
assert!(
r.placement_decisions.is_empty(),
"placement evaluation must not run after fail-closed capability"
);
assert_eq!(r.failure_class, Some(FailureClass::CapabilityUnsupported));
}
#[test]
fn negotiation_strategy_trait_returns_outcome_only_view() {
let plan = build_plan(manifest_with(
full_placement_support(),
BTreeMap::from([(
LifecycleEventKind::SessionStarting,
ManifestLifecycleEventSupport {
support: SupportState::Native,
modes: vec![IntegrationMode::NativeHook],
},
)]),
SupportState::Native,
None,
));
let strat = DefaultNegotiationStrategy::new(
CapabilityRequest::new().with(CapabilityRequirement::required(
CapabilityKind::LifecycleEvent(LifecycleEventKind::SessionStarting),
SupportState::Native,
)),
Vec::new(),
);
assert_eq!(
<DefaultNegotiationStrategy as NegotiationStrategy>::negotiate(&strat, &plan),
NegotiationOutcome::Satisfied
);
}