Make pending assignments safe to race, settle them on a tick, and say only what was checked

Review of #150: a withdrawal could land between the look and the act, a
failed ask read as a build in flight, a request kept the wrong asker, a
build being registered read as not built, build "true" could ask a build
nothing waited on, and a status read changed state. Claim a row under the
machine's hold before making it, keep a request only once asked, settle on
the controller's own tick, raise an assignment not made as a condition
until it is answered, and tie each row to its machine.
This commit is contained in:
jochen
2026-10-08 16:38:11 +02:00
parent 7d63d2e68c
commit 249d97d1c8
12 changed files with 1018 additions and 235 deletions
+8 -11
View File
@@ -4,7 +4,6 @@ import (
"context"
"flag"
"fmt"
"os"
"sort"
"strings"
"time"
@@ -469,11 +468,8 @@ func theThreeQuestions(ctx context.Context, open *stores) (answers, error) {
if out.conditions, err = openConditions(ctx); err != nil {
out.conditionsUnread = err.Error()
}
// And the assignments waiting for their module's build (novox/hq issue 325): what a missed outcome left
// waiting is settled first, so one never waits silently past its build.
for _, line := range settleStalePending(ctx, open, time.Now()) {
fmt.Fprintln(os.Stderr, line)
}
// And the assignments waiting for their module's build (novox/hq issue 325, ADR 0261), read only: the
// controller's tick settles them, never a read.
if out.pending, err = inv.Pending(ctx, time.Now().Add(-pendingShownFor)); err != nil {
out.pendingUnread = err.Error()
err = nil
@@ -610,13 +606,14 @@ func (a answers) well() bool {
return len(a.wrong) == 0 && len(a.quiet) == 0 && len(a.behind) == 0 &&
len(a.waiting) == 0 && len(a.refused) == 0 && a.network == "" && len(a.untaken) == 0 &&
len(a.filtered) == 0 && len(a.unheld) == 0 && len(a.overflowing) == 0 &&
len(a.conditions) == 0 && a.conditionsUnread == "" && a.pendingUnread == "" && !pendingNotWell(a.pending)
len(a.conditions) == 0 && a.conditionsUnread == "" && a.pendingUnread == "" && !pendingOpen(a.pending)
}
// pendingNotWell is whether any pending assignment is waiting, or ended without being made.
func pendingNotWell(pending []inventory.PendingAssignment) bool {
// pendingOpen is whether any pending assignment is still to be made. One that ended without being made is
// not counted here: it is a condition, open until it is answered (ADR 0261).
func pendingOpen(pending []inventory.PendingAssignment) bool {
for _, p := range pending {
if p.State != inventory.PendingApplied && p.State != inventory.PendingWithdrawn {
if p.Open() {
return true
}
}
@@ -632,7 +629,7 @@ func printPending(pending []inventory.PendingAssignment, unread string) {
}
var waiting, ended []inventory.PendingAssignment
for _, p := range pending {
if p.State == inventory.PendingWaiting {
if p.Open() {
waiting = append(waiting, p)
} else {
ended = append(ended, p)