HEAL1 (attempt-changed): the healers say each attempt they write, and every attempt kept at their start
The seat mesh-healers' attempt-changed carries the attempt whole with until (a week from its write), on the subject keyed by the condition and the attempt, each escaped as a key's name is. The event is the seat definition's; the manifest's emits stays empty as for healer-acted. Builds: design 47 G §541, §665 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -30,6 +30,7 @@ import (
|
||||
handacts "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts"
|
||||
meshhealers "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhealers"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
@@ -287,6 +288,7 @@ func (h *Healers) act(row Healer, c Condition, budget string, spent int, by stri
|
||||
}
|
||||
return "", fmt.Errorf("healer %s did not act on %s: its attempt could not be begun: %w", row.ID, c.Key, err)
|
||||
}
|
||||
h.sayAttempt(key, a, begun)
|
||||
act, said, err := row.repair(h, c)
|
||||
a.Outcome = OutcomeActed
|
||||
if err != nil {
|
||||
@@ -302,6 +304,8 @@ func (h *Healers) act(row Healer, c Condition, budget string, spent int, by stri
|
||||
a.Said, a.Finished = said+" ("+window+")", &finished
|
||||
if err := h.Store.PutTTL(AttemptsState, key, a, KeptFor); err != nil {
|
||||
h.Logf("[%s] healer %s %s %s and could not record what came of it: %v", Module, row.ID, a.Outcome, c.Key, err)
|
||||
} else {
|
||||
h.sayAttempt(key, a, finished)
|
||||
}
|
||||
h.tell(Acted{Healer: row.ID, Condition: c.Key, Kind: c.Kind, Act: a.Act, Outcome: a.Outcome, Said: said, Budget: window})
|
||||
h.wake(budget, begun.Add(row.Settle), c.Key)
|
||||
@@ -321,14 +325,55 @@ func (h *Healers) escalate(row Healer, c Condition, budget string, spent []Attem
|
||||
window := fmt.Sprintf("%d of %d within %s", len(spent), row.Budget, row.Window)
|
||||
a := Attempt{Healer: row.ID, Condition: c.Key, Kind: c.Kind, BudgetKey: budget, Act: "handed the condition to the operator",
|
||||
Outcome: OutcomeEscalated, Said: said, Begun: now, Finished: &now}
|
||||
if err := h.Store.CreateTTL(AttemptsState, attemptKey(row.ID, c.Key, now), a, KeptFor); err != nil && !errors.Is(err, ErrExists) {
|
||||
ekey := attemptKey(row.ID, c.Key, now)
|
||||
if err := h.Store.CreateTTL(AttemptsState, ekey, a, KeptFor); err != nil && !errors.Is(err, ErrExists) {
|
||||
return "", fmt.Errorf("healer %s could not record handing %s to the operator, and does not: %w", row.ID, c.Key, err)
|
||||
} else if err == nil {
|
||||
h.sayAttempt(ekey, a, now)
|
||||
}
|
||||
h.tell(Acted{Healer: row.ID, Condition: c.Key, Kind: c.Kind, Act: "handed to the operator", Outcome: OutcomeEscalated,
|
||||
Said: said, Budget: window})
|
||||
return fmt.Sprintf("healer %s handed %s to the operator: %s", row.ID, c.Key, said), nil
|
||||
}
|
||||
|
||||
// sayAttempt says an attempt as the seat's attempt-changed (design 47 G §541, §665): the attempt whole as its key in `attempts`
|
||||
// holds it, with `until`, the end of that key's lifetime (KeptFor from its write at wrote), on the subject keyed by the
|
||||
// condition and the attempt, each escaped as a key's name is (§73). Not said is said in the journal: the attempt holds it.
|
||||
func (h *Healers) sayAttempt(key string, a Attempt, wrote time.Time) {
|
||||
if h.Publish == nil {
|
||||
return
|
||||
}
|
||||
until := wrote.Add(KeptFor)
|
||||
e := meshhealers.AttemptChangedEvent{Act: a.Act, Begun: &a.Begun, BudgetKey: a.BudgetKey, Condition: a.Condition, Finished: a.Finished,
|
||||
Healer: a.Healer, Kind: a.Kind, Outcome: a.Outcome, Said: a.Said, Until: &until}
|
||||
subject := meshhealers.SubjectAttemptChanged(keys.EscapeName(a.Condition), keys.EscapeName(key))
|
||||
if err := h.Publish(subject, e, key+"."+strconv.FormatInt(wrote.UnixNano(), 10)); err != nil {
|
||||
h.Logf("[%s] the attempt %s of %s could NOT be said on the bus: %v", Module, key, a.Condition, err)
|
||||
}
|
||||
}
|
||||
|
||||
// sayOpen says every attempt still kept, at the healers' start, so a keeper that missed one hears it (design 47 G §541). The
|
||||
// end of an attempt's lifetime is a week from its last write: its finish, or its beginning while it is not finished.
|
||||
func (h *Healers) sayOpen() {
|
||||
names, err := h.Store.Keys(AttemptsState)
|
||||
if err != nil {
|
||||
h.Logf("[%s] the attempts kept cannot be read, so none is said again at the start: %v", Module, err)
|
||||
return
|
||||
}
|
||||
for _, key := range names {
|
||||
raw, found, err := h.Store.Get(AttemptsState, key)
|
||||
var a Attempt
|
||||
if err != nil || !found || json.Unmarshal(raw, &a) != nil {
|
||||
continue
|
||||
}
|
||||
wrote := a.Begun
|
||||
if a.Finished != nil {
|
||||
wrote = *a.Finished
|
||||
}
|
||||
h.sayAttempt(key, a, wrote)
|
||||
}
|
||||
}
|
||||
|
||||
// tell says a heal as healer-acted. Not said is said in the log: the attempt holds it.
|
||||
func (h *Healers) tell(e Acted) {
|
||||
e.Event, e.At, e.By = ActedEvent, h.Now().UTC(), "healer "+e.Healer
|
||||
@@ -345,6 +390,7 @@ func (h *Healers) tell(e Acted) {
|
||||
// no expiry left to hear, so each newest act whose settle ended within its window is judged at once. A settle still
|
||||
// running keeps its key on the bus and needs nothing.
|
||||
func (h *Healers) Rearm() {
|
||||
h.sayOpen()
|
||||
all, err := h.attempts()
|
||||
if err != nil {
|
||||
h.Logf("[%s] what the healers did cannot be read, so no ended settle is judged: %v", Module, err)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
meshconditions "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
|
||||
handacts "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhandacts"
|
||||
meshhealers "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhealers"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
@@ -136,6 +137,8 @@ type world struct {
|
||||
said []Acted
|
||||
// events are the subjects the finding writer said its events on.
|
||||
events []string
|
||||
// attempted are the attempt-changed events said: subject, outcome and until.
|
||||
attempted []string
|
||||
}
|
||||
|
||||
func newWorld() *world {
|
||||
@@ -157,6 +160,9 @@ func (w *world) fresh() {
|
||||
if a, ok := body.(Acted); ok && subject == "mesh.seat.mesh-healers.event.healer-acted" {
|
||||
w.said = append(w.said, a)
|
||||
}
|
||||
if a, ok := body.(meshhealers.AttemptChangedEvent); ok {
|
||||
w.attempted = append(w.attempted, subject+" "+a.Outcome+" "+a.Until.Format(time.RFC3339))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -232,6 +238,25 @@ func TestTheHealersActWithNoLease(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Design 47 G §541, §665: each attempt written is said as attempt-changed on the subject keyed by the condition and the attempt,
|
||||
// escaped as a key's name is, with its until a week from its write; and said again for every attempt kept at the healers' start.
|
||||
func TestAnAttemptIsSaidAsAttemptChangedAndAgainAtTheStart(t *testing.T) {
|
||||
w := newWorld()
|
||||
if err := w.h.Heard(body(lost("records"))); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(w.attempted) != 2 || !strings.Contains(w.attempted[0], "mesh.seat.mesh-healers.event.attempt-changed.bus") ||
|
||||
!strings.HasSuffix(w.attempted[0], " acting "+w.now.Add(KeptFor).Format(time.RFC3339)) || !strings.Contains(w.attempted[1], " acted ") {
|
||||
t.Fatalf("said %v: the begun attempt, then the finished one", w.attempted)
|
||||
}
|
||||
w.attempted = nil
|
||||
w.fresh()
|
||||
w.h.sayOpen()
|
||||
if len(w.attempted) != 1 || !strings.Contains(w.attempted[0], " acted ") {
|
||||
t.Errorf("at the start every attempt kept is said again: %v", w.attempted)
|
||||
}
|
||||
}
|
||||
|
||||
// An act waits out its settle, then its spent budget hands the condition to the operator once; an escalated condition
|
||||
// is passed over; a second instance cannot begin an act already begun; the brake stops every healer past twelve acts
|
||||
// in an hour and lets go an hour after the last (ADR 0231 §21, ADR 0564 §7).
|
||||
|
||||
Reference in New Issue
Block a user