use crate::{
AcceptablePlacement, AdapterManifest, CapabilityDegradation, FailureClass, LifecycleEventKind,
ManifestPlacementClass, ManifestPlacementSupport, NegotiationOutcome, PayloadEnvelope,
PlacementClass, RequirementLevel, SupportState, Warning,
};
use super::plan::RoutingPlan;
use super::seams::NegotiationStrategy;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CapabilityKind {
LifecycleEvent(LifecycleEventKind),
ContextPressure,
NativeReceipts,
LifeloopSynthesizedReceipts,
ReceiptLedger,
HarnessSessionId,
HarnessRunId,
HarnessTaskId,
SessionRename,
RenewalResetNative,
RenewalResetWrapperMediated,
RenewalResetManual,
RenewalContinuationObservation,
RenewalContinuationPayloadDelivery,
ApprovalSurface,
}
impl CapabilityKind {
pub fn name(&self) -> String {
match self {
Self::LifecycleEvent(ev) => match serde_json::to_value(ev) {
Ok(serde_json::Value::String(s)) => format!("lifecycle_event:{s}"),
_ => "lifecycle_event".to_string(),
},
Self::ContextPressure => "context_pressure".into(),
Self::NativeReceipts => "receipts.native".into(),
Self::LifeloopSynthesizedReceipts => "receipts.lifeloop_synthesized".into(),
Self::ReceiptLedger => "receipts.receipt_ledger".into(),
Self::HarnessSessionId => "session_identity.harness_session_id".into(),
Self::HarnessRunId => "session_identity.harness_run_id".into(),
Self::HarnessTaskId => "session_identity.harness_task_id".into(),
Self::SessionRename => "session_rename".into(),
Self::RenewalResetNative => "renewal.reset.native".into(),
Self::RenewalResetWrapperMediated => "renewal.reset.wrapper_mediated".into(),
Self::RenewalResetManual => "renewal.reset.manual".into(),
Self::RenewalContinuationObservation => "renewal.continuation.observation".into(),
Self::RenewalContinuationPayloadDelivery => {
"renewal.continuation.payload_delivery".into()
}
Self::ApprovalSurface => "approval_surface".into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CapabilityRequirement {
pub kind: CapabilityKind,
pub level: RequirementLevel,
pub desired: SupportState,
}
impl CapabilityRequirement {
pub fn required(kind: CapabilityKind, desired: SupportState) -> Self {
Self {
kind,
level: RequirementLevel::Required,
desired,
}
}
pub fn preferred(kind: CapabilityKind, desired: SupportState) -> Self {
Self {
kind,
level: RequirementLevel::Preferred,
desired,
}
}
pub fn optional(kind: CapabilityKind, desired: SupportState) -> Self {
Self {
kind,
level: RequirementLevel::Optional,
desired,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CapabilityRequest {
pub requirements: Vec<CapabilityRequirement>,
}
impl CapabilityRequest {
pub fn new() -> Self {
Self::default()
}
pub fn with(mut self, req: CapabilityRequirement) -> Self {
self.requirements.push(req);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlacementRejection {
Unsupported {
placement: PlacementClass,
manifest_support: SupportState,
},
PayloadTooLarge {
placement: PlacementClass,
byte_size: u64,
max_bytes: u64,
},
}
impl PlacementRejection {
pub fn placement(&self) -> PlacementClass {
match self {
Self::Unsupported { placement, .. } => *placement,
Self::PayloadTooLarge { placement, .. } => *placement,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PayloadPlacementDecision {
Chosen {
payload_id: String,
payload_kind: String,
byte_size: u64,
content_digest: Option<String>,
chosen: PlacementClass,
first_preference: bool,
rejected: Vec<PlacementRejection>,
},
Failed {
payload_id: String,
payload_kind: String,
byte_size: u64,
content_digest: Option<String>,
failure_class: FailureClass,
rejected: Vec<PlacementRejection>,
},
}
impl PayloadPlacementDecision {
pub fn payload_id(&self) -> &str {
match self {
Self::Chosen { payload_id, .. } => payload_id,
Self::Failed { payload_id, .. } => payload_id,
}
}
pub fn is_failed(&self) -> bool {
matches!(self, Self::Failed { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NegotiatedPlan {
pub plan: RoutingPlan,
pub outcome: NegotiationOutcome,
pub capability_degradations: Vec<CapabilityDegradation>,
pub placement_decisions: Vec<PayloadPlacementDecision>,
pub warnings: Vec<Warning>,
pub failure_class: Option<FailureClass>,
}
impl NegotiatedPlan {
pub fn blocks_dispatch(&self) -> bool {
matches!(
self.outcome,
NegotiationOutcome::Unsupported | NegotiationOutcome::RequiresOperator
)
}
}
#[derive(Debug, Clone, Default)]
pub struct DefaultNegotiationStrategy {
pub request: CapabilityRequest,
pub payloads: Vec<PayloadEnvelope>,
}
impl DefaultNegotiationStrategy {
pub fn new(request: CapabilityRequest, payloads: Vec<PayloadEnvelope>) -> Self {
Self { request, payloads }
}
pub fn negotiate_full(&self, plan: &RoutingPlan) -> NegotiatedPlan {
negotiate(plan, &self.request, &self.payloads)
}
}
impl NegotiationStrategy for DefaultNegotiationStrategy {
fn negotiate(&self, plan: &RoutingPlan) -> NegotiationOutcome {
self.negotiate_full(plan).outcome
}
}
pub fn negotiate(
plan: &RoutingPlan,
request: &CapabilityRequest,
payloads: &[PayloadEnvelope],
) -> NegotiatedPlan {
let manifest = &plan.adapter;
let mut outcome = NegotiationOutcome::Satisfied;
let mut capability_degradations: Vec<CapabilityDegradation> = Vec::new();
let mut warnings: Vec<Warning> = Vec::new();
for req in &request.requirements {
let manifest_support = manifest_support_for(manifest, &req.kind);
let cap_outcome = classify_capability(req, manifest_support);
match cap_outcome {
CapabilityVerdict::Satisfied => {}
CapabilityVerdict::Degraded { previous, current } => {
capability_degradations.push(CapabilityDegradation {
capability: req.kind.name(),
previous_support: previous,
current_support: current,
evidence: None,
retry_class: None,
});
warnings.push(Warning {
code: "capability_degraded".into(),
message: format!(
"preferred capability `{}` degraded from `{previous:?}` to `{current:?}`",
req.kind.name()
),
capability: Some(req.kind.name()),
});
outcome = strongest(outcome, NegotiationOutcome::Degraded);
}
CapabilityVerdict::RequiresOperator => {
warnings.push(Warning {
code: "operator_required".into(),
message: format!(
"required capability `{}` is only available through manual operator action",
req.kind.name()
),
capability: Some(req.kind.name()),
});
outcome = strongest(outcome, NegotiationOutcome::RequiresOperator);
}
CapabilityVerdict::Unsupported => {
warnings.push(Warning {
code: "capability_unsupported".into(),
message: format!(
"required capability `{}` not supported by adapter `{}`",
req.kind.name(),
manifest.adapter_id
),
capability: Some(req.kind.name()),
});
outcome = strongest(outcome, NegotiationOutcome::Unsupported);
}
}
}
let mut placement_decisions: Vec<PayloadPlacementDecision> = Vec::new();
let dispatch_blocked = matches!(
outcome,
NegotiationOutcome::Unsupported | NegotiationOutcome::RequiresOperator
);
let mut placement_failure_class: Option<FailureClass> = None;
if !dispatch_blocked {
for env in payloads {
let decision = decide_placement(env, manifest);
if let PayloadPlacementDecision::Failed {
failure_class,
payload_id,
..
} = &decision
{
outcome = strongest(outcome, NegotiationOutcome::Unsupported);
placement_failure_class = Some(*failure_class);
warnings.push(Warning {
code: "placement_unavailable".into(),
message: format!(
"payload `{}` had no acceptable placement on adapter `{}`",
payload_id, manifest.adapter_id
),
capability: None,
});
}
placement_decisions.push(decision);
}
}
let failure_class = match outcome {
NegotiationOutcome::Satisfied | NegotiationOutcome::Degraded => None,
NegotiationOutcome::Unsupported => {
Some(placement_failure_class.unwrap_or(FailureClass::CapabilityUnsupported))
}
NegotiationOutcome::RequiresOperator => Some(FailureClass::OperatorRequired),
};
NegotiatedPlan {
plan: plan.clone(),
outcome,
capability_degradations,
placement_decisions,
warnings,
failure_class,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum CapabilityVerdict {
Satisfied,
Degraded {
previous: SupportState,
current: SupportState,
},
RequiresOperator,
Unsupported,
}
fn classify_capability(
req: &CapabilityRequirement,
manifest_support: SupportState,
) -> CapabilityVerdict {
let satisfies = support_satisfies(manifest_support, req.desired);
match req.level {
RequirementLevel::Required => {
if satisfies {
CapabilityVerdict::Satisfied
} else if manifest_support == SupportState::Manual
&& req.desired != SupportState::Manual
{
CapabilityVerdict::RequiresOperator
} else {
CapabilityVerdict::Unsupported
}
}
RequirementLevel::Preferred => {
if satisfies {
CapabilityVerdict::Satisfied
} else {
CapabilityVerdict::Degraded {
previous: req.desired,
current: manifest_support,
}
}
}
RequirementLevel::Optional => {
CapabilityVerdict::Satisfied
}
}
}
fn support_rank(s: SupportState) -> u8 {
match s {
SupportState::Native => 4,
SupportState::Synthesized => 3,
SupportState::Partial => 2,
SupportState::Manual => 1,
SupportState::Unavailable => 0,
}
}
fn support_satisfies(have: SupportState, want: SupportState) -> bool {
if have == SupportState::Unavailable {
return false;
}
if want == SupportState::Manual {
return have != SupportState::Unavailable;
}
if have == SupportState::Manual {
return false;
}
support_rank(have) >= support_rank(want)
}
fn strongest(a: NegotiationOutcome, b: NegotiationOutcome) -> NegotiationOutcome {
fn rank(o: NegotiationOutcome) -> u8 {
match o {
NegotiationOutcome::Satisfied => 0,
NegotiationOutcome::Degraded => 1,
NegotiationOutcome::RequiresOperator => 2,
NegotiationOutcome::Unsupported => 3,
}
}
if rank(a) >= rank(b) { a } else { b }
}
fn manifest_support_for(manifest: &AdapterManifest, kind: &CapabilityKind) -> SupportState {
match kind {
CapabilityKind::LifecycleEvent(ev) => manifest
.lifecycle_events
.get(ev)
.map(|claim| claim.support)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::ContextPressure => manifest.context_pressure.support,
CapabilityKind::NativeReceipts => {
if manifest.receipts.native {
SupportState::Native
} else {
SupportState::Unavailable
}
}
CapabilityKind::LifeloopSynthesizedReceipts => {
if manifest.receipts.lifeloop_synthesized {
SupportState::Synthesized
} else {
SupportState::Unavailable
}
}
CapabilityKind::ReceiptLedger => manifest.receipts.receipt_ledger,
CapabilityKind::HarnessSessionId => manifest
.session_identity
.as_ref()
.map(|si| si.harness_session_id)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::HarnessRunId => manifest
.session_identity
.as_ref()
.map(|si| si.harness_run_id)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::HarnessTaskId => manifest
.session_identity
.as_ref()
.map(|si| si.harness_task_id)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::SessionRename => manifest
.session_rename
.as_ref()
.map(|s| s.support)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::RenewalResetNative => manifest
.renewal
.as_ref()
.map(|r| r.reset.native)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::RenewalResetWrapperMediated => manifest
.renewal
.as_ref()
.map(|r| r.reset.wrapper_mediated)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::RenewalResetManual => manifest
.renewal
.as_ref()
.map(|r| r.reset.manual)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::RenewalContinuationObservation => manifest
.renewal
.as_ref()
.map(|r| r.continuation.observation)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::RenewalContinuationPayloadDelivery => manifest
.renewal
.as_ref()
.map(|r| r.continuation.payload_delivery)
.unwrap_or(SupportState::Unavailable),
CapabilityKind::ApprovalSurface => manifest
.approval_surface
.as_ref()
.map(|s| s.support)
.unwrap_or(SupportState::Unavailable),
}
}
fn manifest_placement_for(p: PlacementClass) -> Option<ManifestPlacementClass> {
match p {
PlacementClass::DeveloperEquivalentFrame => Some(ManifestPlacementClass::PreFrameLeading),
PlacementClass::PrePromptFrame => Some(ManifestPlacementClass::PreFrameTrailing),
PlacementClass::SideChannelContext => Some(ManifestPlacementClass::ManualOperator),
PlacementClass::ReceiptOnly => None,
}
}
fn evaluate_one(
ap: &AcceptablePlacement,
byte_size: u64,
manifest: &AdapterManifest,
) -> Result<(), PlacementRejection> {
let Some(mfc) = manifest_placement_for(ap.placement) else {
return Ok(());
};
let support: ManifestPlacementSupport =
manifest
.placement
.get(&mfc)
.cloned()
.unwrap_or(ManifestPlacementSupport {
support: SupportState::Unavailable,
max_bytes: None,
});
if support.support == SupportState::Unavailable {
return Err(PlacementRejection::Unsupported {
placement: ap.placement,
manifest_support: support.support,
});
}
if let Some(max) = support.max_bytes
&& byte_size > max
{
return Err(PlacementRejection::PayloadTooLarge {
placement: ap.placement,
byte_size,
max_bytes: max,
});
}
Ok(())
}
fn decide_placement(env: &PayloadEnvelope, manifest: &AdapterManifest) -> PayloadPlacementDecision {
let byte_size = env.effective_byte_size();
let mut rejected: Vec<PlacementRejection> = Vec::new();
for (idx, ap) in env.acceptable_placements.iter().enumerate() {
match evaluate_one(ap, byte_size, manifest) {
Ok(()) => {
return PayloadPlacementDecision::Chosen {
payload_id: env.payload_id.clone(),
payload_kind: env.payload_kind.clone(),
byte_size,
content_digest: env.content_digest.clone(),
chosen: ap.placement,
first_preference: idx == 0,
rejected,
};
}
Err(rej) => rejected.push(rej),
}
}
let all_size_failures = !rejected.is_empty()
&& rejected
.iter()
.all(|r| matches!(r, PlacementRejection::PayloadTooLarge { .. }));
let failure_class = if all_size_failures {
FailureClass::PayloadTooLarge
} else {
FailureClass::PlacementUnavailable
};
PayloadPlacementDecision::Failed {
payload_id: env.payload_id.clone(),
payload_kind: env.payload_kind.clone(),
byte_size,
content_digest: env.content_digest.clone(),
failure_class,
rejected,
}
}