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
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:
@@ -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" ||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user