use std::time::Duration;
use crate::forked_participant::{
ForkedParticipantAttachmentAssociation, ForkedParticipantOperationScope, ForkedParticipantRef,
ForkedParticipantRequestId, ForkedParticipantReusePolicy, bridge_ref,
};
use crate::ids::AgentIdentity;
use crate::machines::mob_machine::HostId;
use super::bridge_protocol::{
BridgeCreateForkedParticipantPayload, BridgeForkedParticipantAttachment,
BridgeForkedParticipantReuse, BridgeForkedParticipantScope, BridgeMemberIncarnation,
BridgePeerSpec, BridgeProtocolVersion, BridgeRevokeForkedParticipantPayload,
};
pub(super) const FORKED_PARTICIPANT_PROTOCOL_VERSION: BridgeProtocolVersion =
BridgeProtocolVersion::V6;
pub(super) const FORKED_PARTICIPANT_BRIDGE_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug, Clone, PartialEq)]
pub struct ForkedParticipantCreateRequest {
pub source_identity: AgentIdentity,
pub expected_profile: Option<super::handle::MemberExecutionProfileWitness>,
pub request_id: ForkedParticipantRequestId,
pub prefix_message_count: Option<usize>,
pub scope: ForkedParticipantOperationScope,
pub reuse: ForkedParticipantReusePolicy,
pub ttl: Duration,
}
pub(super) fn wire_scope(scope: ForkedParticipantOperationScope) -> BridgeForkedParticipantScope {
match scope {
ForkedParticipantOperationScope::Invoke => BridgeForkedParticipantScope::Invoke,
ForkedParticipantOperationScope::Observe => BridgeForkedParticipantScope::Observe,
ForkedParticipantOperationScope::InvokeAndObserve => {
BridgeForkedParticipantScope::InvokeAndObserve
}
}
}
pub(super) fn wire_reuse(reuse: ForkedParticipantReusePolicy) -> BridgeForkedParticipantReuse {
match reuse {
ForkedParticipantReusePolicy::OneShot => BridgeForkedParticipantReuse::OneShot,
ForkedParticipantReusePolicy::BoundedReuse { max_uses } => {
BridgeForkedParticipantReuse::BoundedReuse { max_uses }
}
}
}
pub(super) fn create_payload(
supervisor: BridgePeerSpec,
epoch: u64,
binding_generation: u64,
source_member: BridgeMemberIncarnation,
request: &ForkedParticipantCreateRequest,
) -> Option<BridgeCreateForkedParticipantPayload> {
Some(BridgeCreateForkedParticipantPayload {
supervisor,
epoch,
binding_generation,
protocol_version: FORKED_PARTICIPANT_PROTOCOL_VERSION,
source_member,
request_id: request.request_id.as_str().to_string(),
prefix_message_count: request
.prefix_message_count
.map(u64::try_from)
.transpose()
.ok()?,
scope: wire_scope(request.scope),
reuse: wire_reuse(request.reuse),
ttl_millis: u64::try_from(request.ttl.as_millis()).ok()?,
})
}
pub(super) fn revoke_payload(
supervisor: BridgePeerSpec,
epoch: u64,
mob_id: &crate::MobId,
host_id: &str,
binding_generation: u64,
capability: &ForkedParticipantRef,
) -> BridgeRevokeForkedParticipantPayload {
BridgeRevokeForkedParticipantPayload {
supervisor,
epoch,
binding_generation,
protocol_version: FORKED_PARTICIPANT_PROTOCOL_VERSION,
source_member: BridgeMemberIncarnation {
mob_id: mob_id.to_string(),
agent_identity: capability.source_identity().as_str().to_string(),
host_id: host_id.to_string(),
binding_generation,
member_session_id: capability.provenance().source_session_id.to_string(),
generation: 0,
fence_token: 0,
},
capability: crate::forked_participant::bridge_ref(capability),
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct AttachedForkedParticipantSpawn {
pub spawn: super::handle::SpawnResult,
pub capability: ForkedParticipantRef,
pub attachment_id: crate::forked_participant::ForkedParticipantAttachmentId,
pub lease: AttachedForkedParticipantLease,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AttachedForkedParticipantLease {
Local {
grant: crate::forked_participant::ForkedParticipantGrant,
},
HostOwned {
host_id: HostId,
},
}
impl AttachedForkedParticipantLease {
#[must_use]
pub fn grant(&self) -> Option<&crate::forked_participant::ForkedParticipantGrant> {
match self {
Self::Local { grant } => Some(grant),
Self::HostOwned { .. } => None,
}
}
#[must_use]
pub fn host_id(&self) -> Option<&HostId> {
match self {
Self::Local { .. } => None,
Self::HostOwned { host_id } => Some(host_id),
}
}
}
pub(super) fn wire_attachment(
association: &ForkedParticipantAttachmentAssociation,
) -> BridgeForkedParticipantAttachment {
BridgeForkedParticipantAttachment {
attachment_id: association.attachment_id.as_str().to_string(),
capability: bridge_ref(&association.capability),
}
}
pub(super) fn validate_attached_spawn_spec(
spec: &super::handle::SpawnMemberSpec,
capability: &ForkedParticipantRef,
owner_host: Option<&HostId>,
) -> Result<(), crate::MobError> {
let reject = |detail: String| {
Err(crate::MobError::ForkedParticipantAttachedSpawnSpecRejected { detail })
};
match &spec.launch_mode {
crate::launch::MemberLaunchMode::Fresh => {}
crate::launch::MemberLaunchMode::Resume {
bridge_session_id,
resume_from_role,
} => {
if bridge_session_id != capability.fork_session_id() {
return reject(
"the declared resume session is not this capability's fork session".to_string(),
);
}
if resume_from_role.is_some() {
return reject(
"a capability-aware attached spawn may not also restamp durable member role \
identity"
.to_string(),
);
}
}
crate::launch::MemberLaunchMode::Fork { .. } => {
return reject(
"a capability-aware attached spawn resumes the capability's own fork session; it \
may not declare a second fork"
.to_string(),
);
}
}
match (owner_host, spec.placement.as_ref()) {
(None, None) => {}
(None, Some(_)) => {
return reject(
"a LOCAL-owned capability may not be seated on a member host".to_string(),
);
}
(Some(owner_host), None) => {
let _ = owner_host;
}
(Some(owner_host), Some(declared)) if declared == owner_host => {}
(Some(owner_host), Some(declared)) => {
return reject(format!(
"this capability is owned by host '{}'; it may not be seated on host '{}'",
owner_host.as_str(),
declared.as_str()
));
}
}
match (owner_host, spec.binding.as_ref()) {
(_, None) => {}
(None, Some(crate::RuntimeBinding::Session)) => {}
(_, Some(crate::RuntimeBinding::External { .. })) => {
return reject(
"an unmanaged external runtime cannot seat a forked-participant capability"
.to_string(),
);
}
(None, Some(_)) => {
return reject(
"a capability fork session is a controller-local session binding".to_string(),
);
}
(Some(_), Some(_)) => {
return reject(
"a host-owned capability's residency is declared by its owner route, not by an \
explicit runtime binding"
.to_string(),
);
}
}
match (owner_host, spec.backend) {
(_, None) => {}
(None, Some(crate::MobBackendKind::Session)) => {}
(None, Some(_)) => {
return reject(
"a capability fork session is a controller-local session backend".to_string(),
);
}
(Some(_), Some(crate::MobBackendKind::Session)) => {
return reject(
"a host-owned capability is materialized on its owning host, not as a \
controller-local session"
.to_string(),
);
}
(Some(_), Some(_)) => {
return reject(
"a host-owned capability's residency is declared by its owner route, not by an \
explicit backend"
.to_string(),
);
}
}
if spec.tool_access_policy.is_some() {
return reject("the branch inherits the source's tool access policy".to_string());
}
if spec.tool_dispatch_admission.is_some() {
return reject("the branch inherits the source's tool dispatch admission".to_string());
}
if spec.tool_category_overrides != meerkat_core::ToolCategoryOverrides::default() {
return reject("the branch inherits the source's tool categories".to_string());
}
if spec.inherited_tool_filter.is_some() {
return reject("the branch inherits the source's tool visibility".to_string());
}
if spec.external_tools.is_some() {
return reject("the branch inherits the source's tool surface".to_string());
}
if spec.override_profile.is_some() {
return reject("the branch inherits the source's resolved profile".to_string());
}
if spec.auth_binding.is_some() {
return reject("the branch inherits the source's auth binding".to_string());
}
if spec.system_prompt_override.is_some() {
return reject("the branch inherits the source's system prompt".to_string());
}
if spec.additional_instructions.is_some() {
return reject("the branch inherits the source's instruction sections".to_string());
}
if spec.shell_env.is_some() {
return reject("the branch inherits the source's shell environment".to_string());
}
Ok(())
}