diff --git a/node-tools/internal/approval/approval_test.go b/node-tools/internal/approval/approval_test.go index f1cf18f..ef5907f 100644 --- a/node-tools/internal/approval/approval_test.go +++ b/node-tools/internal/approval/approval_test.go @@ -14,6 +14,7 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/seatdef" + "git.novox.be/novox/mesh-sdk/go/views" mt "github.com/novox/mesh-tools/node-tools/internal/meshtest" ) @@ -419,8 +420,20 @@ func TestTheRefusalsAndAnswers(t *testing.T) { t.Fatalf("findings %v", w.findings.raised) } w.warranted = nil + // At a start each acted, unrecorded ask is pending in the runner's findings-stated until warranted is made again + // (design 47 G §384). + pendingKey := views.FindingKey("machine", keys.EscapeName("n1."+a.ID), KindCallUnanswered) + warranted := w.g.d.Warranted + var pendingAtCall bool + w.g.d.Warranted = func(asker, ask string, open func() (bool, error)) error { + _, pendingAtCall = w.findings.pends[pendingKey] + return warranted(asker, ask, open) + } w.g.Recover() // a start, or the hand-act seat's holder heard again w.g.Recover() + if !pendingAtCall || len(w.findings.pended) != 1 || len(w.findings.pends) != 0 || w.findings.pended[0] != pendingKey { + t.Fatalf("pending at the call %v, pended %v, pending after %v", pendingAtCall, w.findings.pended, w.findings.pends) + } if len(w.recorded) != 1 || w.recorded[0] != "shell."+a.ID || len(w.findings.raised) != 0 || len(w.bounds.keys) != 0 { t.Fatalf("recorded %v, findings %v, bounds %v", w.recorded, w.findings.raised, w.bounds.keys) } diff --git a/node-tools/internal/approval/marks_test.go b/node-tools/internal/approval/marks_test.go index 39573cf..000b379 100644 --- a/node-tools/internal/approval/marks_test.go +++ b/node-tools/internal/approval/marks_test.go @@ -7,6 +7,7 @@ import ( condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" "git.novox.be/novox/mesh-sdk/go/keys" + "git.novox.be/novox/mesh-sdk/go/views" catalogseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-sdk/go/seatdef" @@ -128,7 +129,21 @@ func TestTheMarks(t *testing.T) { } // findings is the runner's held writer as a fixture: each finding raised now, by id. -type findings struct{ raised map[string]string } +type findings struct { + raised map[string]string + // pends are the keys pending in the held writer's statement; pended every key ever pended. + pends map[string]views.PendingKey + pended []string +} + +func (f *findings) Pend(p views.PendingKey) { + if f.pends == nil { + f.pends = map[string]views.PendingKey{} + } + f.pends[p.Key] = p + f.pended = append(f.pended, p.Key) +} +func (f *findings) Judged(key string) { delete(f.pends, key) } func (f *findings) Write(_ context.Context, x condseat.Finding) error { if f.raised == nil { diff --git a/node-tools/internal/approval/record.go b/node-tools/internal/approval/record.go index 20af7ab..2826bed 100644 --- a/node-tools/internal/approval/record.go +++ b/node-tools/internal/approval/record.go @@ -10,6 +10,8 @@ import ( "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/keys" + "git.novox.be/novox/mesh-sdk/go/views" ) // The bounds of a run's recording (§13, §16; lead answers A-157, A-158). @@ -84,7 +86,9 @@ func (g *Gate) recordRun(module, id string) { g.d.Logf("[mesh-tools] the run of ask %s is not recorded: the tool runner reaches no hand-act log", id) return } - if err := g.d.Warranted(module, id, func() (bool, error) { return g.unrecorded(id) }); err != nil { + err := g.d.Warranted(module, id, func() (bool, error) { return g.unrecorded(id) }) + g.judged(id) // made again: its answer, or the bound of its key, judges it from now + if err != nil { g.d.Logf("[mesh-tools] mesh-hand-acts.warranted has not answered for ask %s (%v): asked again when the hand-act seat's "+ "holder is heard again and at the next start", id, err) return @@ -181,6 +185,7 @@ func (g *Gate) Recover() { continue } module := moduleOf(k) + g.pend(id) g.d.Go(func() { g.recordRun(module, id) }) } } @@ -220,5 +225,32 @@ func (g *Gate) WatchBounds() (func(), error) { }) } +// Pender is the runner's held writer's pending (views.HeldWriter's Pend and Judged): the keys it cannot judge yet since its +// start, named in its `findings-stated` (design 47 G §384). +type Pender interface { + Pend(views.PendingKey) + Judged(key string) +} + +// pend names an acted, unrecorded ask's `call-unanswered` in the runner's `findings-stated` until its `warranted` is made +// again (§16; design 47 G §384), its bound the five minutes the finding waits. +func (g *Gate) pend(id string) { + if p, ok := g.d.Findings.(Pender); ok { + p.Pend(views.PendingKey{Key: g.unansweredFinding(id), Since: g.d.Now().UTC(), Bound: UnansweredAfter.String()}) + } +} + +// judged ends that pending. +func (g *Gate) judged(id string) { + if p, ok := g.d.Findings.(Pender); ok { + p.Judged(g.unansweredFinding(id)) + } +} + +// unansweredFinding is the key of an ask's `call-unanswered` in the runner's state findings (lead answer A-154). +func (g *Gate) unansweredFinding(id string) string { + return views.FindingKey(scopeMachine, keys.EscapeName(g.d.Node+"."+id), KindCallUnanswered) +} + // isChanged says a compare-and-set lost to another write. func isChanged(err error) bool { return errors.Is(err, stdio.ErrChanged) }