mesh-issues: say in reserve's answer when hq could not be read, and grant the import its records calls (review of #204)
mesh/merge-gate pass: builds mesh-issues → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered

A number handed out unchecked against hq is a failure that must be loud, so
the answer carries a warning, not only the log. The import already calls
records_status and records_read; invokes now names them.
This commit is contained in:
2026-10-11 04:30:28 +02:00
parent 350a597c47
commit 9d4d9d5cd2
5 changed files with 43 additions and 19 deletions
+2 -1
View File
@@ -209,7 +209,8 @@ sessions writing a record at once never take one number.
reserve of a kind is refused and nothing is spent.
- **Never at or below a number hq holds**: each reserve reads hq again, and a counter that stands below the
highest number there (one taken by hand) is raised to it first, written only at the revision read. When
hq cannot be read once the counter has started, the counter is used as it stands.
hq cannot be read once the counter has started, the counter is used as it stands, and the answer's
`warning` says the number was not checked against hq.
- **Open branches are the floor's job.** The tracker reads hq's checkout of `main` and cannot list a folder on
another branch with the forge tools it may call; a number taken on an open branch and not yet merged is
covered by the floor. Before the first reserve of a kind, the operator raises `decision-floor` or
@@ -97,10 +97,10 @@ func TestItDeclaresAndHoldsTheIssueTrackerSeatOnceForTheMesh(t *testing.T) {
t.Fatalf("state %+v", d)
}
// The duplicate check hears the tracker's own opens and asks the agent module on its node (ADR 0278 §1, §5);
// a reservation reads what numbers the record holds through the records module (issue 424).
// the import and a reservation read the record through the records module (issue 424).
if !reflect.DeepEqual(m.Consumes, []string{"mesh-issues." + EventOpened, "mesh-issues." + EventMoved, "mesh-delivery.transition"}) ||
!reflect.DeepEqual(m.Invokes, []string{"claude-code.claude_code_judge", "seat:mesh-delivery.checks", "seat:operator-channel.notify",
"records.records_list"}) {
"records.records_status", "records.records_list", "records.records_read"}) {
t.Fatalf("consumes %v, invokes %v", m.Consumes, m.Invokes)
}
if len(m.Build.Artifacts) != 1 || m.Build.Artifacts[0].From != "cmd/mesh-issues" ||
+25 -13
View File
@@ -117,7 +117,7 @@ func (t *Tracker) Reserve(a ReserveAsk) (*Reserved, error) {
records = busRecords{}
}
highest, rerr := highestIn(records, seq)
n, err := t.nextOf(seq, highest, rerr)
n, unread, err := t.nextOf(seq, highest, rerr)
if err != nil {
return nil, err
}
@@ -126,21 +126,29 @@ func (t *Tracker) Reserve(a ReserveAsk) (*Reserved, error) {
return nil, err
}
out := &Reserved{Kind: kind, Number: n, Prefix: prefixOf(kind, n), For: purpose, By: r.By, At: r.At,
Said: fmt.Sprintf("Reserved %s number %s; it is spent, whether or not its record lands.", kind, prefixOf(kind, n))}
Said: fmt.Sprintf("Reserved %s number %s; it is spent, whether or not its record lands.", kind, prefixOf(kind, n)),
Warning: unread}
body := map[string]any{"kind": kind, "number": n, "prefix": out.Prefix, "by": r.By, "at": r.At}
if purpose != "" {
body["for"] = purpose
}
if err := t.Emit(EventReserved, body); err != nil {
t.Logf("[mesh-issues] %s %s was reserved and its reserved event could not be said: %v", kind, out.Prefix, err)
out.Warning = fmt.Sprintf("the reservation is kept, and its reserved event could not be said on the bus (%v): "+
"what watches the events did not hear of it", err)
out.Warning = joinWarnings(out.Warning, fmt.Sprintf("the reservation is kept, and its reserved event could not be said on the bus (%v): "+
"what watches the events did not hear of it", err))
} else {
out.Announced = true
}
return out, nil
}
func joinWarnings(a, b string) string {
if a == "" {
return b
}
return a + "; and " + b
}
// highestIn is the highest number the record holds in a sequence's folder, sub-folders and documents alike.
func highestIn(records Records, seq sequence) (uint64, error) {
folders, docs, err := records.List(seq.folder)
@@ -161,39 +169,43 @@ func highestIn(records Records, seq sequence) (uint64, error) {
// nextOf takes a sequence's next number. A counter not yet started is started once, above the highest the
// record holds, the operator's floor and every reservation kept; it is never started without the record
// read. A started counter behind the record (a number taken by hand) is raised to it first; a record that
// cannot be read then leaves the counter as it is.
func (t *Tracker) nextOf(seq sequence, highest uint64, recordErr error) (uint64, error) {
// cannot be read then leaves the counter as it is, and the warning answered says so.
func (t *Tracker) nextOf(seq sequence, highest uint64, recordErr error) (uint64, string, error) {
warning := ""
e, err := t.Store.Get(CountersState, seq.kind)
if err != nil {
return 0, fmt.Errorf("the %s counter cannot be read (%v); no number was taken", seq.kind, err)
return 0, "", fmt.Errorf("the %s counter cannot be read (%v); no number was taken", seq.kind, err)
}
if e == nil {
if recordErr != nil {
return 0, fmt.Errorf("the %s counter has not started, and what numbers hq's %s holds cannot be read through "+
return 0, "", fmt.Errorf("the %s counter has not started, and what numbers hq's %s holds cannot be read through "+
"the records module (%v): it starts above them, so no number was taken. Ask again when the records "+
"module answers", seq.kind, seq.folder, recordErr)
}
if err := t.startSequence(seq, highest); err != nil {
return 0, err
return 0, "", err
}
} else if recordErr != nil {
t.Logf("[mesh-issues] the record's %s could not be read (%v); the %s counter is used as it stands", seq.folder, recordErr, seq.kind)
warning = fmt.Sprintf("what numbers hq's %s holds could not be read through the records module (%v): the "+
"number comes from the %s counter as it stands, unchecked against hq. Check that hq holds no %s at or above it",
seq.folder, recordErr, seq.kind, seq.kind)
} else if err := t.raise(seq.kind, highest); err != nil {
return 0, err
return 0, "", err
}
for round := 0; round < counterRounds; round++ {
n, err := t.Store.Increment(CountersState, seq.kind, counterAttempts)
if err == nil {
return n, nil
return n, warning, nil
}
if !errors.Is(err, stdio.ErrChanged) {
t.Logf("[mesh-issues] the %s counter could not be advanced: %v", seq.kind, err)
return 0, fmt.Errorf("the %s counter could not be advanced (%v); nothing was reserved. If the bus took the "+
return 0, "", fmt.Errorf("the %s counter could not be advanced (%v); nothing was reserved. If the bus took the "+
"write before it failed, one number stays unused", seq.kind, err)
}
t.Sleep(jitter(time.Duration(round+1) * 10 * time.Millisecond))
}
return 0, fmt.Errorf("so many %s numbers are being reserved at this moment that the counter was taken by another "+
return 0, "", fmt.Errorf("so many %s numbers are being reserved at this moment that the counter was taken by another "+
"caller %d times; no number was taken. Reserve again", seq.kind, counterRounds*counterAttempts)
}
@@ -222,8 +222,17 @@ func TestAnUnreadableRecordRefusesTheFirstNumberAndNotTheNext(t *testing.T) {
r.err = nil
h.reserve(t, KindDecision) // 292
r.err = errors.New("records did not answer")
if got := h.reserve(t, KindDecision); got != 293 {
t.Fatalf("got %d, want 293 from the started counter", got)
out, err := h.Reserve(ReserveAsk{Kind: KindDecision})
if err != nil || out.Number != 293 {
t.Fatalf("got %+v (%v), want 293 from the started counter", out, err)
}
// Failure is loud: the answer says the number was not checked against hq.
if !strings.Contains(out.Warning, "records did not answer") || !strings.Contains(out.Warning, "unchecked") {
t.Fatalf("a number unchecked against hq answered warning %q", out.Warning)
}
r.err = nil
if out, err := h.Reserve(ReserveAsk{Kind: KindDecision}); err != nil || out.Warning != "" {
t.Fatalf("a reserve with hq read answered %+v (%v): no warning expected", out, err)
}
}
+3 -1
View File
@@ -61,7 +61,9 @@
"claude-code.claude_code_judge",
"seat:mesh-delivery.checks",
"seat:operator-channel.notify",
"records.records_list"
"records.records_status",
"records.records_list",
"records.records_read"
],
"state": [
{