Take the ask back on the router when withdrawing a record (issue 1081, review round 1)
mesh/merge-gate head 684e8801c420 merged into trunk 3395af96bc34 — pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change…
mesh/repo-check head 684e8801c420 merged into trunk 3395af96bc34 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
mesh/merge-gate head 684e8801c420 merged into trunk 3395af96bc34 — pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change…
mesh/repo-check head 684e8801c420 merged into trunk 3395af96bc34 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
WithdrawRecord only ended the waiting ask locally; the router kept it open, so the operator still saw Approve/Decline for a withdrawn record and an answer to it was dropped silently. It now also publishes a cancel on the router's subject once the withdraw is saved, as the controller's asker does; a cancel the router cannot be told of is logged and the withdraw still stands. The module's existing "uses": ["operator-channel"] already grants this; no manifest change needed, since the ask and cancel subjects are both under that seat.
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||
)
|
||||
|
||||
// Unproven is who acted, as recorded, where the runtime proves no caller (hq issue 365): the SDK's word. The caller
|
||||
@@ -650,9 +651,10 @@ type Withdrawn struct {
|
||||
|
||||
// WithdrawRecord withdraws a reserved or proposed record on the caller's decision, with why in one line: the
|
||||
// number stays spent (novox/hq ADR 0290 rule 5), as a new version by the caller. A pending review is cleared, and
|
||||
// a waiting operator's ask is ended, both as part of that one version. Refused: a record this holds none of, an
|
||||
// empty why, and a record already accepted (a research effort accepted at any status), superseded or withdrawn —
|
||||
// only a reserved or a proposed record takes it.
|
||||
// a waiting operator's ask is ended here and taken back on the router (a cancel on its subject, novox/hq ADR
|
||||
// 0259), both as part of that one version; a cancel the router cannot be told of is logged, and the withdraw
|
||||
// still stands. Refused: a record this holds none of, an empty why, and a record already accepted (a research
|
||||
// effort accepted at any status), superseded or withdrawn — only a reserved or a proposed record takes it.
|
||||
func (k *KB) WithdrawRecord(caller string, key Key, why string) (*Withdrawn, error) {
|
||||
why = strings.TrimSpace(why)
|
||||
if why == "" {
|
||||
@@ -675,7 +677,8 @@ func (k *KB) WithdrawRecord(caller string, key Key, why string) (*Withdrawn, err
|
||||
r := cur.Clone()
|
||||
r.Status = StatusWithdrawn
|
||||
r.Judging = nil
|
||||
if r.Ask != nil && r.Ask.Said == AskWaiting {
|
||||
waiting := r.Ask != nil && r.Ask.Said == AskWaiting
|
||||
if waiting {
|
||||
r.Ask.Said = "ended: the record was withdrawn"
|
||||
}
|
||||
full := fmt.Sprintf("withdrawn by %s: %s", caller, why)
|
||||
@@ -683,6 +686,13 @@ func (k *KB) WithdrawRecord(caller string, key Key, why string) (*Withdrawn, err
|
||||
return nil, fmt.Errorf("%s could not be written (%v); nothing was withdrawn", key, err)
|
||||
}
|
||||
k.emit(EventChanged, r, caller, full, e.Versions+1)
|
||||
if waiting && k.Asks != nil {
|
||||
id := cur.Ask.ID
|
||||
if err := k.Asks.Publish(asks.CancelSubject(Module), map[string]string{"id": id}, "cancel."+id); err != nil {
|
||||
k.logf("[mesh-knowledge] the ask %s for %s is taken back here, and the router could not be told (%v): it expires there, "+
|
||||
"and no answer to it is acted on", id, key, err)
|
||||
}
|
||||
}
|
||||
return &Withdrawn{Record: key.String(), Status: StatusWithdrawn,
|
||||
Says: fmt.Sprintf("%s is withdrawn: %s. Its number stays spent, and is never handed out again.", key, why)}, nil
|
||||
}
|
||||
|
||||
@@ -320,11 +320,13 @@ func (j *judge) judge(question string, schema map[string]any, model string) (jso
|
||||
return raw, "the agent on test (" + model + ")", nil
|
||||
}
|
||||
|
||||
// router is the operator channel, played: the asks published, and its own record of each, which only it writes.
|
||||
// router is the operator channel, played: the asks published, its own record of each, which only it writes, and
|
||||
// the cancels taken back on its subject.
|
||||
type router struct {
|
||||
mu sync.Mutex
|
||||
asked []asks.Ask
|
||||
records map[string]routerRecord
|
||||
mu sync.Mutex
|
||||
asked []asks.Ask
|
||||
records map[string]routerRecord
|
||||
cancelled []string
|
||||
}
|
||||
|
||||
func newRouter() *router { return &router{records: map[string]routerRecord{}} }
|
||||
@@ -332,14 +334,19 @@ func newRouter() *router { return &router{records: map[string]routerRecord{}} }
|
||||
func (r *router) Publish(subject string, body any, id string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if subject != asks.AskSubject(Module) {
|
||||
return fmt.Errorf("published on %s", subject)
|
||||
switch subject {
|
||||
case asks.AskSubject(Module):
|
||||
a := body.(asks.Ask)
|
||||
r.asked = append(r.asked, a)
|
||||
cp := a
|
||||
r.records[a.ID] = routerRecord{State: "open", Ask: &cp}
|
||||
return nil
|
||||
case asks.CancelSubject(Module):
|
||||
m := body.(map[string]string)
|
||||
r.cancelled = append(r.cancelled, m["id"])
|
||||
return nil
|
||||
}
|
||||
a := body.(asks.Ask)
|
||||
r.asked = append(r.asked, a)
|
||||
cp := a
|
||||
r.records[a.ID] = routerRecord{State: "open", Ask: &cp}
|
||||
return nil
|
||||
return fmt.Errorf("published on %s", subject)
|
||||
}
|
||||
|
||||
func (r *router) Record(id string) (*stdio.StateEntry, error) {
|
||||
|
||||
@@ -62,6 +62,9 @@ func TestAProposedRecordNeedingTheOperatorIsWithdrawnWithItsAskEndedAndItsReview
|
||||
if after.Current.Ask == nil || after.Current.Ask.Said == AskWaiting {
|
||||
t.Fatalf("the waiting ask was not ended: %+v", after.Current.Ask)
|
||||
}
|
||||
if !contains(w.router.cancelled, before.Current.Ask.ID) {
|
||||
t.Fatalf("the router was not told to take back the ask %s: cancelled %v", before.Current.Ask.ID, w.router.cancelled)
|
||||
}
|
||||
if after.Current.Judging != nil {
|
||||
t.Fatalf("a pending review was not cleared: %+v", after.Current.Judging)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user