messenger: run a walk's bound from its started_at, as the controller's walk events say it (hq ADR 0308 §5)
mesh/merge-gate pass: builds messenger → 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 superseded: a newer head of the same pull request
mesh/merge-gate pass: builds messenger → 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 superseded: a newer head of the same pull request
This commit is contained in:
@@ -14,8 +14,12 @@ package main
|
||||
// - `bound_seconds`: twice the largest delivery budget among its deliveries, or 900 when none has one (§5),
|
||||
// on `started`; missing, 900 is taken;
|
||||
// - `budget_class`: `core`, `leaf` or `none`;
|
||||
// - `at` when the controller gives it, else the plan's `updated`: when it happened. A start older than its
|
||||
// bound is history and opens nothing; an end older than FreshFor with nothing held closes quietly.
|
||||
// - `started_at` on the start and `ended_at` on the end (else `at`, else the plan's `updated`): when it
|
||||
// happened. A start older than its bound is history and opens nothing; an end older than FreshFor with
|
||||
// nothing held closes quietly.
|
||||
//
|
||||
// A touch may name `carried` beside `modules` (what a moved tool runner or node-engine carries); the
|
||||
// controller lists those in `modules` too, which is what is read.
|
||||
//
|
||||
// An event of its own named `walk`, with the same body, is read the same way, should the controller say it
|
||||
// there instead.
|
||||
@@ -87,6 +91,9 @@ type WalkEvent struct {
|
||||
Touches []Touch
|
||||
BoundSeconds int
|
||||
At time.Time
|
||||
// StartedAt is the walk's own start, on both events (`started_at`): the bound runs from it, so a router
|
||||
// that hears a start late, or starts again, ends the hold on time.
|
||||
StartedAt time.Time
|
||||
}
|
||||
|
||||
// HeldMessage is one message a walk holds: when it was first held and what was to be said.
|
||||
@@ -220,7 +227,16 @@ func DecodeWalk(body []byte) (WalkEvent, bool, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, n := range []string{"at", "updated"} {
|
||||
if s := str("started_at"); s != "" {
|
||||
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
|
||||
ev.StartedAt = t
|
||||
}
|
||||
}
|
||||
when := []string{"started_at", "at", "updated"}
|
||||
if ev.Phase == WalkEnded {
|
||||
when = []string{"ended_at", "at", "updated"}
|
||||
}
|
||||
for _, n := range when {
|
||||
if s := str(n); s != "" {
|
||||
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
|
||||
ev.At = t
|
||||
@@ -297,6 +313,9 @@ func (h *Holder) Walked(ev WalkEvent) {
|
||||
if w != nil {
|
||||
delete(h.walks, ev.Walk)
|
||||
w.Touches = mergeTouches(w.Touches, ev.Touches)
|
||||
if !ev.StartedAt.IsZero() {
|
||||
w.Started = ev.StartedAt
|
||||
}
|
||||
}
|
||||
h.mu.Unlock()
|
||||
if w == nil {
|
||||
|
||||
@@ -10,11 +10,16 @@ import (
|
||||
// The tests of novox/hq ADR 0308 "How it is checked" §2–§6, with the controller's walk events given by a fake
|
||||
// source: the body the controller's plan-moved carries at a walk's start and end (ADR 0308 §1).
|
||||
|
||||
// walkBody is a plan-moved body as the controller says a walk's start or end.
|
||||
// walkBody is a plan-moved body as the controller says a walk's start or end (mesh-controller pull request 247).
|
||||
func walkBody(t *testing.T, id, phase, outcome string, at time.Time, bound int, touches ...Touch) []byte {
|
||||
t.Helper()
|
||||
b := map[string]any{"id": id, "state": "walking", "walk": id, "phase": phase, "touches": touches,
|
||||
"updated": at.Format(time.RFC3339Nano), "budget_class": "leaf"}
|
||||
if phase == WalkStarted {
|
||||
b["started_at"] = at.Format(time.RFC3339Nano)
|
||||
} else {
|
||||
b["ended_at"] = at.Format(time.RFC3339Nano)
|
||||
}
|
||||
if outcome != "" {
|
||||
b["outcome"] = outcome
|
||||
}
|
||||
@@ -413,6 +418,13 @@ func TestTheWalkEventsAreReadAsTheControllerSaysThem(t *testing.T) {
|
||||
len(ev.Touches) != 1 || ev.Touches[0].Machine != "novox" || len(ev.Touches[0].Modules) != 2 {
|
||||
t.Fatalf("%+v %v %v", ev, ok, err)
|
||||
}
|
||||
// As mesh-controller's walk events (its pull request 247) say an end: ended_at, and carried beside modules.
|
||||
end, ok, err := DecodeWalk([]byte(`{"id":"plan-1","walk":"plan-1","phase":"ended","outcome":"stopped",` +
|
||||
`"touches":[{"machine":"novox","modules":["node-tools","messenger"],"carried":["messenger"]}],` +
|
||||
`"started_at":"2026-10-12T11:12:00Z","ended_at":"2026-10-12T11:19:00Z","updated":"2026-10-12T11:19:01Z"}`))
|
||||
if err != nil || !ok || end.Outcome != WalkStopped || !end.At.Equal(at.Add(7*time.Minute)) || len(end.Touches[0].Modules) != 2 {
|
||||
t.Fatalf("%+v %v %v", end, ok, err)
|
||||
}
|
||||
if _, ok, err := DecodeWalk([]byte(`{"id":"plan-1","state":"walking","tier":1}`)); ok || err != nil {
|
||||
t.Fatalf("a move inside a walk read as its start or end: %v %v", ok, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user