diff --git a/go/views/held_writer.go b/go/views/held_writer.go index c785f03e..58cbc5d9 100644 --- a/go/views/held_writer.go +++ b/go/views/held_writer.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "sort" "sync" ) @@ -20,6 +21,9 @@ type HeldWriter[F, C, S, A any] struct { started bool pending map[string]F // written before Start, written through the writer at Start cleared map[string][4]string // cleared before Start, cleared through the writer at Start + // pends are the keys the part cannot judge yet since its start (design 47 G §384): a kept call it has not yet made + // again, named in every statement until the part judges it. + pends map[string]PendingKey } // NewHeldWriter makes the writer for cfg, whose Observe is none (a given Observe is refused): the findings that stand in @@ -32,7 +36,7 @@ func NewHeldWriter[F, C, S, A any](cfg WriterConfig[F, C, S, A]) (*HeldWriter[F, if cfg.State == nil { return nil, errors.New("a finding writer needs the state `findings`") } - h := &HeldWriter[F, C, S, A]{held: map[string]F{}} + h := &HeldWriter[F, C, S, A]{held: map[string]F{}, pends: map[string]PendingKey{}} h.state, h.pending, h.cleared = cfg.State, map[string]F{}, map[string][4]string{} cfg.Observe = func(context.Context) (Observed[F], error) { h.mu.Lock() @@ -41,6 +45,10 @@ func NewHeldWriter[F, C, S, A any](cfg WriterConfig[F, C, S, A]) (*HeldWriter[F, for _, f := range h.held { o.Findings = append(o.Findings, f) } + for _, p := range h.pends { + o.Pending = append(o.Pending, p) + } + sort.Slice(o.Pending, func(i, j int) bool { return o.Pending[i].Key < o.Pending[j].Key }) return o, nil } w, err := NewWriter(cfg) @@ -100,6 +108,25 @@ func (h *HeldWriter[F, C, S, A]) WriteWire(ctx context.Context, finding map[stri return h.Write(ctx, f) } +// Pend names a key the part cannot judge yet since its start, with its since and its bound, in every statement until Judged +// (design 47 G §384; design 46 7-approval-mark §16: an acted ask whose hand-act is not yet asked again). A key pended again +// keeps its first since. +func (h *HeldWriter[F, C, S, A]) Pend(p PendingKey) { + h.mu.Lock() + defer h.mu.Unlock() + if was, ok := h.pends[p.Key]; ok { + p.Since = was.Since + } + h.pends[p.Key] = p +} + +// Judged ends a key's pending: the part judged it, raising or clearing its finding as it found. +func (h *HeldWriter[F, C, S, A]) Judged(key string) { + h.mu.Lock() + defer h.mu.Unlock() + delete(h.pends, key) +} + // Held answers the findings the writer holds, a copy taken under its lock (design 47 G A-38), so a part reads its open findings // from the writer. func (h *HeldWriter[F, C, S, A]) Held() []F { diff --git a/go/views/writer_test.go b/go/views/writer_test.go index f75abe55..ed5d4929 100644 --- a/go/views/writer_test.go +++ b/go/views/writer_test.go @@ -505,6 +505,12 @@ func subHeldWriterStatesWhatItHoldsFromItsOwnState(t *testing.T) { if err := h.Clear(ctx, "module", "seeded", "k", "no longer"); err != nil { t.Fatal(err) } + // a key the part cannot judge yet is named in the statement's pending until judged (design 47 G §384), keeping its since + since := time.Date(2026, 11, 12, 9, 0, 0, 0, time.UTC) + h.Pend(views.PendingKey{Key: "machine.n1.a2.call-unanswered", Since: since, Bound: "5m"}) + h.Pend(views.PendingKey{Key: "machine.n1.a2.call-unanswered", Since: since.Add(time.Hour), Bound: "5m"}) + h.Pend(views.PendingKey{Key: "machine.n1.a3.call-unanswered", Since: since, Bound: "5m"}) + h.Judged("machine.n1.a3.call-unanswered") sent = nil if err := h.State(ctx, "a1"); err != nil { t.Fatal(err) @@ -514,6 +520,9 @@ func subHeldWriterStatesWhatItHoldsFromItsOwnState(t *testing.T) { len(stated.Findings) != 2 || stated.Asked != "a1" { t.Fatalf("the statement says %+v after %d events", stated, len(sent)) } + if p := stated.Pending; len(p) != 1 || p[0].Key != "machine.n1.a2.call-unanswered" || !p[0].Since.Equal(since) || p[0].Bound != "5m" { + t.Fatalf("the statement's pending is %+v", p) + } if held := h.Held(); len(held) != 2 { t.Fatalf("Held answers %v", held) } else if held[0].ID = "mutated"; len(h.Held()) != 2 {