A-8 pending: an acted, unrecorded ask is pending in the runner's findings-stated until warranted is made again

At each start (and when the hand-act seat's holder is heard again) the gate pends each acted, unrecorded
ask's call-unanswered key in its held writer, bound five minutes, and ends the pending once its warranted
is made again, so a restart neither clears nor raises the condition of a run still unrecorded.

Builds: design 46 7-approval-mark §16; design 47 G §384; lead answer A-154

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:45:53 +01:00
parent 809288da69
commit afc327392e
3 changed files with 62 additions and 2 deletions
@@ -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)
}
+16 -1
View File
@@ -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 {
+33 -1
View File
@@ -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) }