C2: the finding kind call-unanswered of the seat mesh-conditions (issue 1966, ADR 0668 §3)

A kept call still unanswered past five minutes is said in the caller's findings, keyed call.<id>.call-unanswered.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-10 02:54:44 +01:00
parent 5c29bea0b3
commit e1303f1542
2 changed files with 26 additions and 0 deletions
@@ -19,6 +19,11 @@ func TestAFindingsKeyAndTheOrderOfConditions(t *testing.T) {
if k := Key("call", "a b", ""); k != "call.a_b._" {
t.Errorf("an unsafe part and an empty token: %s", k)
}
// Issue 1966, ADR 0668 §3: a kept call unanswered is a finding the keeper can make a condition of, keyed by what waits.
o := CallUnanswered("ace", "seat:node-engine.manifest-reads", "ace", "no responders", "enginereads", time.Unix(0, 0))
if f := o.Finding("mesh-delivery-assigner", "r1", time.Unix(300, 0)); o.Key() != "call.ace.call-unanswered" || CheckFinding(f) != nil {
t.Errorf("a call unanswered: key %s, %v", o.Key(), CheckFinding(f))
}
good := seat.FindingsByKeyValue{Key: "machine.g14.silent", Kind: "silent", Severity: Warning, Summary: "x", Source: "S1",
Observer: "mesh-delivery-watchman", Subject: seat.Subject{Scope: ScopeMachine, ID: "g14"}}
for _, c := range []struct {
@@ -145,3 +145,24 @@ func Clear(b FindingBucket, scope, id, token string) (cleared bool, err error) {
}
return true, b.Delete(key)
}
// KindCallUnanswered is the kind of a kept call still unanswered past CallUnansweredAfter (novox/hq ADR 0668 §3, issue
// 1966): a call whose answer the caller keeps, asked again through every transport fault, that no answer has ended. The
// caller writes it into its own findings, keeps the wait, and clears it when the call is answered.
const KindCallUnanswered = "call-unanswered"
// CallUnansweredAfter is how long a kept call stays unanswered before it is a finding (ADR 0668 §3; ADR 0564 §2's bound).
const CallUnansweredAfter = 5 * time.Minute
// CallUnanswered is the observation of a kept call to callee (a seat verb's or a tool's key, as process.Conn.Call names
// it) about id (what the caller waits on: a node, a merge, a key), unanswered since since, its last fault said. Its key
// is `call.<id>.call-unanswered`, cleared by Clear(b, ScopeCall, id, KindCallUnanswered).
func CallUnanswered(id, callee, machine, fault, source string, since time.Time) Observation {
return Observation{Scope: ScopeCall, ID: id, Kind: KindCallUnanswered, Machine: machine, Severity: Warning,
Summary: fmt.Sprintf("the call %s about %s is unanswered since %s", callee, id, since.UTC().Format(time.RFC3339)),
Said: fault,
Headline: "A call whose answer is kept is not answered",
Explanation: fmt.Sprintf("The call %s is made again on every transport fault and on the callee's event; it has "+
"not been answered since %s. Its last fault: %s.", callee, since.UTC().Format(time.RFC3339), fault),
Resolved: "the call is answered", Source: source}
}