use liminal_protocol::lifecycle::{
AttachSecretProof, CredentialAttachLookupResult, CredentialAttachProvenance,
CredentialAttachTokenPhase, MarkerProofDecision, MarkerProofInput, MarkerProofState,
ResolvedIdentity, select_marker_proof,
};
use liminal_protocol::wire::{
AttachEnvelope, AttemptTokenBodyConflict as WireAttemptTokenBodyConflict, BindingEpoch,
CredentialAttachRequest, CredentialAttachResponse, MarkerMismatch, MarkerNotDelivered,
MarkerProofRequest, ReceiptExpired as WireReceiptExpired, ReceiptExpiryReason, ServerValue,
};
use super::barrier::OperationFacts;
use super::facts::{self, Digest};
use super::state::{Slot, StateError};
impl Slot {
pub(super) fn attach_token_phase(
&self,
request: &CredentialAttachRequest,
now: u128,
) -> (
CredentialAttachTokenPhase<'_, Digest, Digest, Digest>,
AttachSecretProof,
) {
let identity = ResolvedIdentity::<Digest, Digest, Digest>::Live(&self.member);
let mut verifier_bytes = self.attach_secret.into_bytes();
let token_phase = match self.attach.as_ref() {
Some(attach) if attach.token == request.attach_attempt_token => {
if now < attach.receipt_expires_at {
verifier_bytes = attach.verifier;
CredentialAttachTokenPhase::LiveReceipt {
identity,
receipt: &attach.receipt,
}
} else if now < attach.provenance_expires_at {
CredentialAttachTokenPhase::Provenance {
identity,
provenance: CredentialAttachProvenance::new(
attach.result_generation,
ReceiptExpiryReason::Deadline,
),
}
} else {
CredentialAttachTokenPhase::AfterProvenance
}
}
_ => match self
.attach_provenance
.get(&request.attach_attempt_token.into_bytes())
{
Some(record) if now < record.provenance_expires_at => {
CredentialAttachTokenPhase::Provenance {
identity,
provenance: CredentialAttachProvenance::new(
record.result_generation,
record.reason,
),
}
}
Some(_) => CredentialAttachTokenPhase::AfterProvenance,
None => self.unmatched_token_phase(request, now),
},
};
let secret_proof =
if facts::constant_time_eq(&verifier_bytes, &request.attach_secret.into_bytes()) {
AttachSecretProof::Verified
} else {
AttachSecretProof::Mismatch
};
(token_phase, secret_proof)
}
fn unmatched_token_phase(
&self,
request: &CredentialAttachRequest,
now: u128,
) -> CredentialAttachTokenPhase<'_, Digest, Digest, Digest> {
let presented = request.capability_generation.get();
if presented >= self.member.generation().get() {
return CredentialAttachTokenPhase::NoMatch;
}
let Some(successor) = presented.checked_add(1) else {
return CredentialAttachTokenPhase::NoMatch;
};
let current_receipt_witnesses = self.attach.as_ref().is_some_and(|attach| {
attach.result_generation.get() == successor && now < attach.provenance_expires_at
});
let retained_record_witnesses = self.attach_provenance.values().any(|record| {
record.result_generation.get() == successor && now < record.provenance_expires_at
});
if current_receipt_witnesses || retained_record_witnesses {
CredentialAttachTokenPhase::NoMatch
} else {
CredentialAttachTokenPhase::AfterProvenance
}
}
}
pub(super) fn marker_bearing_attach_refusal(
request: &CredentialAttachRequest,
slot: &Slot,
operation_facts: &OperationFacts,
) -> Result<ServerValue, StateError> {
let Some(input) = MarkerProofInput::credential_attach(request) else {
return Err(StateError::invariant(
"marker-bearing attach classification without a presented marker",
));
};
let proof_epoch = BindingEpoch::new(
operation_facts.receiving_incarnation,
request.capability_generation,
);
let marker_state = MarkerProofState::new(slot.member.cursor(), false, None, proof_epoch, None);
let response = match select_marker_proof(&marker_state, input) {
MarkerProofDecision::MarkerMismatch(MarkerMismatch {
request: MarkerProofRequest::CredentialAttach(proof),
mismatch,
}) => CredentialAttachResponse::marker_mismatch(proof, mismatch),
MarkerProofDecision::MarkerNotDelivered(MarkerNotDelivered {
request: MarkerProofRequest::CredentialAttach(proof),
reason,
expected_marker_delivery_seq,
}) => CredentialAttachResponse::marker_not_delivered(
proof,
reason,
expected_marker_delivery_seq,
),
MarkerProofDecision::MarkerMismatch(_) | MarkerProofDecision::MarkerNotDelivered(_) => {
return Err(StateError::invariant(
"attach marker proof classified under a foreign operation envelope",
));
}
MarkerProofDecision::AckNoOp(_) => {
return Err(StateError::invariant(
"attach marker proof classified as a marker-ack no-op",
));
}
MarkerProofDecision::Permit(_) => {
return Err(StateError::invariant(
"marker proof permitted although no marker was ever delivered",
));
}
};
Ok(response.into_server_value())
}
pub(super) fn credential_attach_refusal(
lookup: &CredentialAttachLookupResult<'_, Digest>,
envelope: AttachEnvelope,
slot: &Slot,
) -> Result<ServerValue, StateError> {
let response = match lookup {
CredentialAttachLookupResult::ParticipantUnknown(_) => {
CredentialAttachResponse::participant_unknown(envelope)
}
CredentialAttachLookupResult::StaleAuthority(_) => {
CredentialAttachResponse::stale_authority(envelope, slot.member.generation())
}
CredentialAttachLookupResult::AttemptTokenBodyConflict(value) => {
let WireAttemptTokenBodyConflict::CredentialAttach { conflict, .. } = value else {
return Err(StateError::invariant(
"leave conflict row observed in the credential-attach lookup",
));
};
CredentialAttachResponse::attempt_token_body_conflict(&envelope, *conflict)
}
CredentialAttachLookupResult::Bound(_) => {
let outcome = slot
.attach
.as_ref()
.map(|attach| attach.outcome.clone())
.ok_or_else(|| {
StateError::invariant("attach receipt replay without a stored receipt")
})?;
CredentialAttachResponse::bound(outcome)
}
CredentialAttachLookupResult::UnboundReceipt(_) => {
let outcome = slot
.attach
.as_ref()
.map(|attach| attach.outcome.clone())
.ok_or_else(|| {
StateError::invariant("attach receipt replay without a stored receipt")
})?;
CredentialAttachResponse::unbound_receipt(outcome)
}
CredentialAttachLookupResult::ReceiptExpired(value) => {
let WireReceiptExpired::CredentialAttach {
result_generation,
current_generation,
reason,
..
} = value
else {
return Err(StateError::invariant(
"enrollment provenance row observed in the credential-attach lookup",
));
};
CredentialAttachResponse::receipt_expired(
&envelope,
*result_generation,
*current_generation,
*reason,
)
}
CredentialAttachLookupResult::StaleOrUnknownReceipt(value) => {
CredentialAttachResponse::stale_or_unknown_receipt(value.clone())
}
CredentialAttachLookupResult::Retired(_) => {
return Err(StateError::invariant(
"retired identity observed in a binding that mints no tombstones",
));
}
CredentialAttachLookupResult::AuthorizedFresh { .. } => {
return Err(StateError::invariant(
"authorized attach routed through the refusal mapper",
));
}
};
Ok(response.into_server_value())
}