Say why the stable route proof failed: journal each distinct reason, keep it in the deadline abort
advance_routing now logs the proof failure (silent, refused with the HTTP status line, presence not active, authority disagreement) once per distinct reason, and the Routing deadline abort carries the step's last failure instead of only the clock. No persisted type or schema changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
74c04bbcc7
commit
a484a7ec17
1 changed files with 102 additions and 5 deletions
|
|
@ -6730,10 +6730,23 @@ impl Engine {
|
|||
}
|
||||
|
||||
fn clear_fault(&self, transaction_id: &str) {
|
||||
self.fault_reports
|
||||
.lock()
|
||||
.expect("fault report mutex")
|
||||
.remove(transaction_id);
|
||||
let mut reports = self.fault_reports.lock().expect("fault report mutex");
|
||||
reports.remove(transaction_id);
|
||||
reports.remove(&route_proof_report_key(transaction_id));
|
||||
}
|
||||
|
||||
/// Say why the stable route's proof failed. The Routing phase retries the
|
||||
/// proof until its deadline, and the deadline's own cause names only the
|
||||
/// clock, so this line is where an operator learns what the listener
|
||||
/// actually did. Said once per distinct reason while the fault lasts.
|
||||
fn report_route_proof_failure(&self, transaction_id: &str, error: &anyhow::Error) {
|
||||
self.report_once(
|
||||
&route_proof_report_key(transaction_id),
|
||||
format!(
|
||||
"Idunn route proof for {transaction_id} failed: {}",
|
||||
route_proof_reason(error)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Write `detail` as the record's `last_error`, touching nothing else and
|
||||
|
|
@ -8871,11 +8884,13 @@ impl Engine {
|
|||
) {
|
||||
Ok(observation) => observation,
|
||||
Err(proof_error) if !rollback_allowed => {
|
||||
self.report_route_proof_failure(&admitted.value.transaction_id, &proof_error);
|
||||
return Err(proof_error).context(
|
||||
"incumbent authority was fenced; candidate route remains installed for fail-closed retry",
|
||||
);
|
||||
}
|
||||
Err(proof_error) => {
|
||||
self.report_route_proof_failure(&admitted.value.transaction_id, &proof_error);
|
||||
return match driver.rollback(
|
||||
expected,
|
||||
&activation.runtime_instance_id,
|
||||
|
|
@ -10560,8 +10575,15 @@ impl Engine {
|
|||
.iter()
|
||||
.any(|request| request.value.command_id == transaction.command_id);
|
||||
let cause = if now >= deadline.deadline_at_unix_millis {
|
||||
// The step's latest failure is the reason the phase never
|
||||
// finished, and the abort that ends it must not drop it.
|
||||
let last_attempt = transaction
|
||||
.last_error
|
||||
.as_deref()
|
||||
.map(|error| format!("; its last attempt failed: {error}"))
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
"the {:?} phase passed its deadline at unix ms {}",
|
||||
"the {:?} phase passed its deadline at unix ms {}{last_attempt}",
|
||||
transaction.phase, deadline.deadline_at_unix_millis
|
||||
)
|
||||
} else if requested {
|
||||
|
|
@ -11426,6 +11448,22 @@ fn runner_failure_journal_line(
|
|||
}
|
||||
}
|
||||
|
||||
fn route_proof_report_key(transaction_id: &str) -> String {
|
||||
format!("route-proof:{transaction_id}")
|
||||
}
|
||||
|
||||
/// The stable route proof's failure in words: whether the listener said
|
||||
/// nothing or answered wrongly, then the specific refusal (HTTP status line,
|
||||
/// presence not active, challenge or authority disagreement). The texts it
|
||||
/// carries name states and codes, never keys or signature bytes.
|
||||
fn route_proof_reason(error: &anyhow::Error) -> String {
|
||||
match error.downcast_ref::<ChallengeFailure>() {
|
||||
Some(ChallengeFailure::Silent(_)) => format!("silent, nothing answered: {error:#}"),
|
||||
Some(ChallengeFailure::Refused(_)) => format!("refused: {error:#}"),
|
||||
None => format!("{error:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn bounded_detail(value: &str) -> String {
|
||||
let value = value.replace(' | ||||