From e1303f1542d8615b30f645c9481ee17d996264ef Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 10 Nov 2026 02:54:44 +0100 Subject: [PATCH] =?UTF-8?q?C2:=20the=20finding=20kind=20call-unanswered=20?= =?UTF-8?q?of=20the=20seat=20mesh-conditions=20(issue=201966,=20ADR=200668?= =?UTF-8?q?=20=C2=A73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A kept call still unanswered past five minutes is said in the caller's findings, keyed call..call-unanswered. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../meshconditionsclient/client_test.go | 5 +++++ .../meshconditionsclient/observation.go | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/health-and-repair/go/clients/meshconditionsclient/client_test.go b/health-and-repair/go/clients/meshconditionsclient/client_test.go index f96ebf8..f351fd2 100644 --- a/health-and-repair/go/clients/meshconditionsclient/client_test.go +++ b/health-and-repair/go/clients/meshconditionsclient/client_test.go @@ -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 { diff --git a/health-and-repair/go/clients/meshconditionsclient/observation.go b/health-and-repair/go/clients/meshconditionsclient/observation.go index e8bc379..9af47d6 100644 --- a/health-and-repair/go/clients/meshconditionsclient/observation.go +++ b/health-and-repair/go/clients/meshconditionsclient/observation.go @@ -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..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} +}