Join item 1: the catalogue's modules take the router's ask from operator-channel's contract and client

mesh-sdk holds neither asks nor asker, nor the vault's answer types, and mesh-delivery's
sdk/views is mesh-sdk's go/views (a1-progress.md, the rewrite map for the join): thirteen
modules switch to the operatorchannel contract and client, mesh-sdk's channel package and
meshvault, with go.mod requiring each at the group pin (nats: meshbroker too). The gate
holds the checks mesh-sdk dropped (an ask's id is its key's asking, at least one option),
with a row that fails without them; claude-code refuses a router record whose times do
not parse, as its JSON decode did, with a row.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 15:33:27 +01:00
parent 60a187cfa8
commit 695ac0356d
103 changed files with 1380 additions and 1105 deletions
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
)
// novox/hq issue 639: the operator would not read the ask for ADR 0343 — thirty plain lines, every clause one per
@@ -44,13 +44,13 @@ func TestADecisionAskReadsAtAGlance(t *testing.T) {
r := adr0343()
at := time.Date(2026, 10, 13, 14, 5, 0, 0, time.UTC)
a := composeAsk(r, &OperatorAsk{ID: "kb-decision-343-0d1e2f3a", At: at, Version: 12, Form: AskGlance})
if err := a.Check(at); err != nil {
if err := occ.Check(a, at); err != nil {
t.Fatal(err)
}
if a.Headline != "Your agents' memory follows you to every machine" {
t.Errorf("the headline is not what it changes for the operator: %q", a.Headline)
}
if err := asks.Glance(a.Explanation); err != nil {
if err := occ.Glance(a.Explanation); err != nil {
t.Errorf("%v:\n%s", err, a.Explanation)
}
if n := strings.Count(a.Explanation, "\n• "); n != 3 || !strings.HasPrefix(a.Explanation, "What an agent learns") {
@@ -69,13 +69,13 @@ func TestADecisionAskReadsAtAGlance(t *testing.T) {
}
// The ask made before the brief is composed again as it was, so its warrant still holds after the change.
old := composeAsk(r, &OperatorAsk{ID: "kb-decision-343-0d1e2f3a", At: at, Version: 12})
if !strings.Contains(old.Explanation, "Its 13 clause(s):") || old.Headline != "Accept ADR 0343?" || old.Details != "" || old.Digest() == a.Digest() {
if !strings.Contains(old.Explanation, "Its 13 clause(s):") || old.Headline != "Accept ADR 0343?" || old.Details != "" || occ.Digest(old) == occ.Digest(a) {
t.Errorf("an ask made before the brief is not composed as it was asked:\n%s", old.Explanation)
}
// Without a brief (a record proposed before the rule), the glance is the title, and no clause.
r.Brief = nil
a = composeAsk(r, &OperatorAsk{ID: "kb-decision-343-0d1e2f3a", At: at, Version: 12, Form: AskGlance})
if a.Headline != "Approve ADR 0343?" || asks.Glance(a.Explanation) != nil || strings.Contains(a.Explanation, "Only the memory folders") {
if a.Headline != "Approve ADR 0343?" || occ.Glance(a.Explanation) != nil || strings.Contains(a.Explanation, "Only the memory folders") {
t.Errorf("without a brief: %q\n%s", a.Headline, a.Explanation)
}
}
@@ -17,8 +17,8 @@ import (
"sync"
"time"
oc "git.novox.be/novox/mesh-contracts/go/seats/operatorchannel"
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
@@ -816,7 +816,7 @@ func (k *KB) WithdrawRecord(caller string, key Key, why string) (*Withdrawn, 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 {
if err := k.Asks.Publish(oc.AcceptSubjectCancel(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)
}
@@ -22,8 +22,9 @@ import (
"testing"
"time"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/go/seats/operatorchannel"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/asks"
)
const bundleVar = "MESH_KNOWLEDGE_TEST_BUNDLE"
@@ -326,7 +327,7 @@ func (j *judge) judge(question string, schema map[string]any, model string) (jso
// the cancels taken back on its subject.
type router struct {
mu sync.Mutex
asked []asks.Ask
asked []oc.Ask
records map[string]routerRecord
cancelled []string
}
@@ -337,13 +338,13 @@ func (r *router) Publish(subject string, body any, id string) error {
r.mu.Lock()
defer r.mu.Unlock()
switch subject {
case asks.AskSubject(Module):
a := body.(asks.Ask)
case oc.AcceptSubjectAsk(Module):
a := body.(oc.Ask)
r.asked = append(r.asked, a)
cp := a
r.records[a.ID] = routerRecord{State: "open", Ask: &cp}
return nil
case asks.CancelSubject(Module):
case oc.AcceptSubjectCancel(Module):
m := body.(map[string]string)
r.cancelled = append(r.cancelled, m["id"])
return nil
@@ -363,14 +364,14 @@ func (r *router) Record(id string) (*stdio.StateEntry, error) {
}
// answer closes an ask as the router does when the operator taps an option on a channel that proves who answered.
func (r *router) answer(id, option string, at time.Time) asks.Warrant {
func (r *router) answer(id, option string, at time.Time) oc.Warrant {
r.mu.Lock()
defer r.mu.Unlock()
rec := r.records[id]
o, _ := rec.Ask.Option(option)
w := asks.Warrant{Ask: id, Asker: Module, About: rec.Ask.About, Outcome: asks.OutcomeChosen, Option: o.ID, Label: o.Label,
Level: o.Level, By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "1", Verified: "user id verified"},
Channel: "telegram", At: at, AskDigest: rec.Ask.Digest()}
o, _ := occ.OptionOf(*rec.Ask, option)
w := oc.Warrant{Ask: id, Asker: Module, About: rec.Ask.About, Outcome: occ.OutcomeChosen, Option: o.ID, Label: o.Label,
Level: o.Level, By: &oc.Person{Who: occ.Operator, Kind: "telegram", Identity: "1", Verified: "user id verified"},
Channel: "telegram", At: occ.FormatTime(at), AskDigest: occ.Digest(*rec.Ask)}
rec.State, rec.Warrant = "closed", &w
r.records[id] = rec
return w
@@ -29,8 +29,8 @@ import (
"sync"
"time"
oc "git.novox.be/novox/mesh-contracts/go/seats/operatorchannel"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// Seat is the role this module holds, once for the mesh.
@@ -122,8 +122,8 @@ func callerOf(args map[string]any) string { return stdio.CallerOf(args) }
// hear subscribes to the router's word on this module's asks, asked again until the runtime takes it.
func hear(k *KB) {
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
err := stdio.Subscribe(asks.Seat+".decided."+Module, func(e stdio.Envelope) error {
var w asks.Warrant
err := stdio.Subscribe(oc.Seat+".decided."+Module, func(e stdio.Envelope) error {
var w oc.Warrant
if json.Unmarshal(e.Body, &w) == nil {
k.HeardEnd(w)
}
@@ -20,8 +20,9 @@ import (
"strings"
"time"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/go/seats/operatorchannel"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// Module is this module's name: the one the bus grants its asks under.
@@ -34,7 +35,7 @@ const (
)
// AskLasts is how long the operator has to answer: a day less a margin, under the router's bound for an approval.
const AskLasts = asks.ApproveLasts - 10*time.Minute
const AskLasts = occ.ApproveLasts - 10*time.Minute
// The states of an operator's ask, as the record keeps them.
const (
@@ -58,8 +59,8 @@ func (sdkAskBus) Publish(subject string, body any, id string) error {
func (sdkAskBus) Record(id string) (*stdio.StateEntry, error) { return stdio.SeatRecord("", id) }
// act is what an answer binds: this record's version, by its digest, and the answer.
func act(r *Record, a *OperatorAsk, option string) asks.Act {
return asks.Act{"verb": "knowledge-base.accept", "option": option, "ask": a.ID, "kind": r.Kind,
func act(r *Record, a *OperatorAsk, option string) occ.Act {
return occ.Act{"verb": "knowledge-base.accept", "option": option, "ask": a.ID, "kind": r.Kind,
"number": strconv.Itoa(r.Number), "version": strconv.Itoa(a.Version), "digest": r.Digest()}
}
@@ -90,7 +91,7 @@ const AskGlance = 1
// composeAsk is the ask for a record, composed from the record and its ask alone: asked once, and composed
// again to hold the warrant to it — in the form it was asked in, so an ask made before a form changed is still
// held to the words the operator was shown.
func composeAsk(r *Record, a *OperatorAsk) asks.Ask {
func composeAsk(r *Record, a *OperatorAsk) oc.Ask {
if a.Form == AskGlance {
return glanceAsk(r, a)
}
@@ -101,10 +102,10 @@ func composeAsk(r *Record, a *OperatorAsk) asks.Ask {
// points; who proposed it, its review, its fingerprint and how to read every clause are its Details, which the
// router notes after the answers. A record without a brief (one proposed before the rule) is asked with its
// title as its summary. The clauses are never listed.
func glanceAsk(r *Record, a *OperatorAsk) asks.Ask {
func glanceAsk(r *Record, a *OperatorAsk) oc.Ask {
name := r.Key().String()
headline, explanation := "Approve "+name+"?", name+" needs your approval before it holds."
if t := strings.TrimSuffix(strings.TrimSpace(r.Title), "."); shown(t) && len([]rune(t)) < asks.SummaryLength {
if t := strings.TrimSuffix(strings.TrimSpace(r.Title), "."); shown(t) && len([]rune(t)) < occ.SummaryLength {
explanation = t + "."
}
if r.Brief != nil && len(briefProblems(r.Brief)) == 0 {
@@ -120,12 +121,12 @@ func glanceAsk(r *Record, a *OperatorAsk) asks.Ask {
}
details := fmt.Sprintf("%s, proposed by %s; the knowledge base's own review approved it. Fingerprint %s.\n%s",
name, author, fingerprint(r.Digest()), clauses)
return withOptions(r, a, asks.Ask{ID: a.ID, Headline: headline, Explanation: explanation, Details: details})
return withOptions(r, a, oc.Ask{ID: a.ID, Headline: headline, Explanation: explanation, Details: details})
}
// listedAsk is the ask as it was composed before the brief: every clause's lead listed. Composed only to hold
// the warrant of an ask made then.
func listedAsk(r *Record, a *OperatorAsk) asks.Ask {
func listedAsk(r *Record, a *OperatorAsk) oc.Ask {
name := r.Key().String()
headline := "Accept " + name + "?"
var b strings.Builder
@@ -150,21 +151,21 @@ func listedAsk(r *Record, a *OperatorAsk) asks.Ask {
}
}
fmt.Fprintf(&b, "\n\nRead it whole with the knowledge base's get, number %d; its fingerprint is %s.", r.Number, fingerprint(r.Digest()))
return withOptions(r, a, asks.Ask{ID: a.ID, Headline: headline, Explanation: b.String()})
return withOptions(r, a, oc.Ask{ID: a.ID, Headline: headline, Explanation: b.String()})
}
// withOptions is an ask with what every form of it carries: who answers, its expiry, what it is about, and its
// two options, each binding its act.
func withOptions(r *Record, a *OperatorAsk, out asks.Ask) asks.Ask {
out.Who, out.Expires = asks.Operator, a.At.Add(AskLasts)
func withOptions(r *Record, a *OperatorAsk, out oc.Ask) oc.Ask {
out.Who, out.Expires = occ.Operator, occ.FormatTime(a.At.Add(AskLasts))
out.OnExpiry = "the record stays proposed; nothing of it is in force"
out.About = "knowledge." + r.Kind + "." + strconv.Itoa(r.Number)
for _, o := range []struct{ id, label, does string }{
{OptionApprove, "Approve", "the record is accepted and its clauses are in force"},
{OptionDecline, "Decline", "the record stays proposed; nothing of it is in force"},
} {
binds, _ := asks.ActDigest(act(r, a, o.id))
out.Options = append(out.Options, asks.Option{ID: o.id, Label: o.label, Does: o.does, Level: asks.Approve, Binds: binds})
binds, _ := occ.ActDigest(act(r, a, o.id))
out.Options = append(out.Options, oc.Option{ID: o.id, Label: o.label, Does: o.does, Level: occ.Approve, Binds: binds})
}
return out
}
@@ -204,7 +205,7 @@ func (k *KB) askOperator(ctx context.Context, held *Entry, w *Record, by string,
}
oa := &OperatorAsk{ID: newAskID(w), At: k.now(), Version: version, Said: AskWaiting, Form: AskGlance}
ask := composeAsk(w, oa)
if err := ask.Check(k.now()); err != nil {
if err := occ.Check(ask, k.now()); err != nil {
return unasked(fmt.Sprintf("the ask could not be made (%v)", err))
}
w.Ask = oa
@@ -213,7 +214,7 @@ func (k *KB) askOperator(ctx context.Context, held *Entry, w *Record, by string,
return "", fmt.Errorf("%s: the review could not be kept (%v); the operator was not asked", w.Key(), err)
}
k.emit(EventReviewed, w, by, "approved; the operator is asked", held.Versions+1)
if err := k.Asks.Publish(asks.AskSubject(Module), ask, ask.ID); err != nil {
if err := k.Asks.Publish(oc.AcceptSubjectAsk(Module), ask, ask.ID); err != nil {
k.logf("[mesh-knowledge] the ask for %s could not be published: %v", w.Key(), err)
return unasked(fmt.Sprintf("the operator could not be asked (%v)", err))
}
@@ -224,9 +225,9 @@ func (k *KB) askOperator(ctx context.Context, held *Entry, w *Record, by string,
type routerRecord struct {
State string `json:"state"`
// Held is why no channel can carry the ask yet, while its state is held (ADR 0397).
Held string `json:"held"`
Ask *asks.Ask `json:"ask"`
Warrant *asks.Warrant `json:"warrant"`
Held string `json:"held"`
Ask *oc.Ask `json:"ask"`
Warrant *oc.Warrant `json:"warrant"`
}
// Reconcile decides every record waiting for the operator whose ask the router has closed: accepted on a warrant
@@ -319,10 +320,10 @@ func (k *KB) endAsk(ctx context.Context, e *Entry, w *Record, why string) string
}
// decide holds one closed ask's warrant to the record, and accepts the record when it is the operator's Approve.
func (k *KB) decide(ctx context.Context, e *Entry, w *Record, warrant asks.Warrant) string {
func (k *KB) decide(ctx context.Context, e *Entry, w *Record, warrant oc.Warrant) string {
w = w.Clone()
w.Ask.Held = "" // an ask that ended is held no longer
if warrant.Outcome != asks.OutcomeChosen {
if warrant.Outcome != occ.OutcomeChosen {
why := "ended " + string(warrant.Outcome)
if warrant.Words != "" {
why += " (" + warrant.Words + ")"
@@ -330,9 +331,9 @@ func (k *KB) decide(ctx context.Context, e *Entry, w *Record, warrant asks.Warra
return k.endAsk(ctx, e, w, why)
}
ask := composeAsk(w, w.Ask)
option, err := warrant.For(Module, ask)
option, err := occ.For(warrant, Module, ask)
if err == nil {
err = option.Performs(act(w, w.Ask, option.ID))
err = occ.Performs(option, act(w, w.Ask, option.ID))
}
if err != nil {
k.logf("[mesh-knowledge] REFUSED the answer to the ask for %s: %v", w.Key(), err)
@@ -341,15 +342,15 @@ func (k *KB) decide(ctx context.Context, e *Entry, w *Record, warrant asks.Warra
switch option.ID {
case OptionApprove:
w = w.Clone()
w.Ask.Said, w.Ask.Warrant = AskApproved, warrant.Says()
r, err := k.accept(ctx, e, w, "the operator", "accepted on the operator's Approve after the knowledge base's review", "warrant: "+warrant.Says())
w.Ask.Said, w.Ask.Warrant = AskApproved, occ.Says(warrant)
r, err := k.accept(ctx, e, w, "the operator", "accepted on the operator's Approve after the knowledge base's review", "warrant: "+occ.Says(warrant))
if err != nil {
return err.Error()
}
return fmt.Sprintf("%s: %s on the operator's warrant (%s)", r.Key(), r.Status, warrant.Says())
return fmt.Sprintf("%s: %s on the operator's warrant (%s)", r.Key(), r.Status, occ.Says(warrant))
case OptionDecline:
w = w.Clone()
w.Ask.Warrant = warrant.Says()
w.Ask.Warrant = occ.Says(warrant)
return k.endAsk(ctx, e, w, "declined by the operator")
}
return k.endAsk(ctx, e, w, "was answered with "+option.ID+", which it does not offer")
@@ -357,8 +358,8 @@ func (k *KB) decide(ctx context.Context, e *Entry, w *Record, warrant asks.Warra
// HeardEnd takes the router's word that one of this module's asks ended without a choice: nothing is accepted on
// such a word, so it needs no proof beyond its subject. A choice is only ever taken from the router's record.
func (k *KB) HeardEnd(w asks.Warrant) {
if w.Asker != Module || w.Outcome == asks.OutcomeChosen {
func (k *KB) HeardEnd(w oc.Warrant) {
if w.Asker != Module || w.Outcome == occ.OutcomeChosen {
if _, err := k.Reconcile(); err != nil {
k.logf("[mesh-knowledge] reading the router's answers: %v", err)
}
@@ -20,7 +20,8 @@ import (
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/go/seats/operatorchannel"
)
// RetractAsked is the status of a retraction asked and not yet answered.
@@ -127,7 +128,7 @@ func (k *KB) Retract(caller string, key Key, clauses []int, why string) (*Retrac
x := &Retraction{Clauses: sorted, Why: why, By: caller, At: at, Title: cur.Title, Leads: leads,
Ask: &OperatorAsk{ID: newRetractAskID(cur), At: at, Version: e.CurrentVersion, Said: AskWaiting, Form: AskGlance}}
ask := composeRetractAsk(cur, x)
if err := ask.Check(at); err != nil {
if err := occ.Check(ask, at); err != nil {
return nil, fmt.Errorf("the ask could not be made (%v); nothing was asked", err)
}
r := cur.Clone()
@@ -136,7 +137,7 @@ func (k *KB) Retract(caller string, key Key, clauses []int, why string) (*Retrac
if err := k.Store.Save(ctx, Version{By: caller, At: at, Why: what, Record: r}, true, false, e.Revision); err != nil {
return nil, fmt.Errorf("%s could not be written (%v); nothing was asked", key, err)
}
if err := k.Asks.Publish(asks.AskSubject(Module), ask, ask.ID); err != nil {
if err := k.Asks.Publish(oc.AcceptSubjectAsk(Module), ask, ask.ID); err != nil {
// Taken back: the record says it was not asked, and nothing waits on it.
k.logf("[mesh-knowledge] the retraction of %s could not be asked: %v", key, err)
if e2, gerr := k.Store.Get(ctx, key); gerr == nil && e2 != nil {
@@ -176,7 +177,7 @@ func (k *KB) WithdrawRetraction(caller string, key Key, why string) (*Withdrawn,
return nil, err
}
if k.Asks != nil {
if err := k.Asks.Publish(asks.CancelSubject(Module), map[string]string{"id": id}, "cancel."+id); err != nil {
if err := k.Asks.Publish(oc.AcceptSubjectCancel(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)
}
@@ -201,18 +202,18 @@ func newRetractAskID(r *Record) string {
}
// retractAct is what an answer to a retraction binds: this record, the clauses, why, who asked, and the answer.
func retractAct(r *Record, x *Retraction, option string) asks.Act {
return asks.Act{"verb": "knowledge-base.retract", "option": option, "ask": x.Ask.ID, "kind": r.Kind,
func retractAct(r *Record, x *Retraction, option string) occ.Act {
return occ.Act{"verb": "knowledge-base.retract", "option": option, "ask": x.Ask.ID, "kind": r.Kind,
"number": strconv.Itoa(r.Number), "clauses": clauseList(x.Clauses), "why": x.Why, "by": x.By}
}
// composeRetractAsk is the ask for a retraction, composed from the retraction alone, so it is the same when it is
// composed again to hold the warrant to it.
func composeRetractAsk(r *Record, x *Retraction) asks.Ask {
func composeRetractAsk(r *Record, x *Retraction) oc.Ask {
name := r.Key().String()
headline := "Retract " + name + "?"
if !x.Whole() {
if h := "Retract " + name + " " + clauseList(x.Clauses) + "?"; len([]rune(h)) <= asks.HeadlineLength {
if h := "Retract " + name + " " + clauseList(x.Clauses) + "?"; len([]rune(h)) <= occ.HeadlineLength {
headline = h
} else {
headline = fmt.Sprintf("Retract %d clauses of %s?", len(x.Clauses), name)
@@ -225,12 +226,12 @@ func composeRetractAsk(r *Record, x *Retraction) asks.Ask {
summary := what + " would no longer hold; nothing is deleted, its versions stay in history."
var points []string
if shown(x.Why) {
points = append(points, "Why: "+clip(x.Why, asks.PointLength-6))
points = append(points, "Why: "+clip(x.Why, occ.PointLength-6))
} else {
points = append(points, "Why: read it with the knowledge base's get.")
}
if t := strings.TrimSuffix(strings.TrimSpace(x.Title), "."); shown(t) {
points = append(points, "Record: "+clip(t, asks.PointLength-9))
points = append(points, "Record: "+clip(t, occ.PointLength-9))
}
if !x.Whole() {
var leads []string
@@ -240,7 +241,7 @@ func composeRetractAsk(r *Record, x *Retraction) asks.Ask {
}
}
if len(leads) > 0 {
points = append(points, clip("Clauses: "+strings.Join(leads, "; "), asks.PointLength))
points = append(points, clip("Clauses: "+strings.Join(leads, "; "), occ.PointLength))
}
}
explanation := summary
@@ -249,15 +250,15 @@ func composeRetractAsk(r *Record, x *Retraction) asks.Ask {
}
details := fmt.Sprintf("%s, retraction of %s asked by %s: %s\nRead it with the knowledge base's get, number %d.",
name, x.What(), x.By, x.Why, r.Number)
out := asks.Ask{ID: x.Ask.ID, Headline: headline, Explanation: explanation, Details: details,
Who: asks.Operator, Expires: x.Ask.At.Add(AskLasts), OnExpiry: "nothing is retracted; the record holds as it did",
out := oc.Ask{ID: x.Ask.ID, Headline: headline, Explanation: explanation, Details: details,
Who: occ.Operator, Expires: occ.FormatTime(x.Ask.At.Add(AskLasts)), OnExpiry: "nothing is retracted; the record holds as it did",
About: "knowledge." + r.Kind + "." + strconv.Itoa(r.Number)}
for _, o := range []struct{ id, label, does string }{
{OptionApprove, "Approve", "it is retracted and no longer holds"},
{OptionDecline, "Decline", "nothing is retracted; it holds as it did"},
} {
binds, _ := asks.ActDigest(retractAct(r, x, o.id))
out.Options = append(out.Options, asks.Option{ID: o.id, Label: o.label, Does: o.does, Level: asks.Approve, Binds: binds})
binds, _ := occ.ActDigest(retractAct(r, x, o.id))
out.Options = append(out.Options, oc.Option{ID: o.id, Label: o.label, Does: o.does, Level: occ.Approve, Binds: binds})
}
return out
}
@@ -310,30 +311,30 @@ func (k *KB) endRetractionSaid(ctx context.Context, e *Entry, by, why string) st
}
// decideRetraction holds one closed ask's warrant to the retraction, and retracts on the operator's Approve.
func (k *KB) decideRetraction(ctx context.Context, e *Entry, warrant asks.Warrant) string {
func (k *KB) decideRetraction(ctx context.Context, e *Entry, warrant oc.Warrant) string {
cur := e.Current
x := cur.Retracting
if warrant.Outcome != asks.OutcomeChosen {
if warrant.Outcome != occ.OutcomeChosen {
why := "retraction not made: the operator's ask ended " + string(warrant.Outcome)
if warrant.Words != "" {
why += " (" + warrant.Words + ")"
}
return k.endRetractionSaid(ctx, e, "the knowledge base", why)
}
option, err := warrant.For(Module, composeRetractAsk(cur, x))
option, err := occ.For(warrant, Module, composeRetractAsk(cur, x))
if err == nil {
err = option.Performs(retractAct(cur, x, option.ID))
err = occ.Performs(option, retractAct(cur, x, option.ID))
}
if err != nil {
k.logf("[mesh-knowledge] REFUSED the answer to the ask to retract %s: %v", cur.Key(), err)
return k.endRetractionSaid(ctx, e, "the knowledge base", "retraction not made: the answer does not hold here: "+err.Error())
}
if option.ID != OptionApprove {
return k.endRetractionSaid(ctx, e, "the operator", "retraction of "+x.What()+" declined by the operator ("+warrant.Says()+")")
return k.endRetractionSaid(ctx, e, "the operator", "retraction of "+x.What()+" declined by the operator ("+occ.Says(warrant)+")")
}
r := cur.Clone()
done := *r.Retracting
done.Ask.Said, done.Ask.Warrant, done.Ask.Held, done.Applied = AskApproved, warrant.Says(), "", k.now()
done.Ask.Said, done.Ask.Warrant, done.Ask.Held, done.Applied = AskApproved, occ.Says(warrant), "", k.now()
r.Retracting = nil
if done.Whole() {
done.Before, r.Status = r.Status, StatusRetracted
@@ -345,7 +346,7 @@ func (k *KB) decideRetraction(ctx context.Context, e *Entry, warrant asks.Warran
}
}
r.Retractions = append(r.Retractions, done)
why := fmt.Sprintf("retracted %s on the operator's Approve, asked by %s: %s; warrant: %s", done.What(), done.By, done.Why, warrant.Says())
why := fmt.Sprintf("retracted %s on the operator's Approve, asked by %s: %s; warrant: %s", done.What(), done.By, done.Why, occ.Says(warrant))
if err := k.Store.Save(ctx, Version{By: "the operator", At: k.now(), Why: why, Record: r}, true, false, e.Revision); err != nil {
return fmt.Sprintf("%s could not be retracted (%v); it holds as it did", r.Key(), err)
}
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
)
// to-be 55 §10: "A record is accepted only after the knowledge base's own judgement approves it, and, when it needs
@@ -248,9 +248,9 @@ func TestARecordThatNeedsTheOperatorIsAcceptedOnlyOnTheWarrant(t *testing.T) {
t.Fatalf("after the review: %s, asks %d", e.Current.Status, len(w.router.asked))
}
ask := w.router.asked[0]
if err := ask.Check(w.clock); err != nil || ask.Options[0].Level != asks.Approve || ask.Options[1].Level != asks.Approve ||
if err := occ.Check(ask, w.clock); err != nil || ask.Options[0].Level != occ.Approve || ask.Options[1].Level != occ.Approve ||
!strings.Contains(ask.Details, "the knowledge base's own review") || ask.Headline != "Merges land after ninety seconds of quiet" ||
asks.Glance(ask.Explanation) != nil || strings.Contains(ask.Explanation, "The window closes") {
occ.Glance(ask.Explanation) != nil || strings.Contains(ask.Explanation, "The window closes") {
t.Fatalf("the ask: %v %+v", err, ask)
}
// Without the warrant: nothing is accepted, however often the router's record is read or the reviews run.
@@ -275,7 +275,7 @@ func TestARecordThatNeedsTheOperatorIsAcceptedOnlyOnTheWarrant(t *testing.T) {
t.Fatal(err)
}
e := mustGet(t, s, Key{KindDecision, n})
if e.Current.Status != StatusAccepted || e.Current.Ask == nil || e.Current.Ask.Warrant != warrant.Says() ||
if e.Current.Status != StatusAccepted || e.Current.Ask == nil || e.Current.Ask.Warrant != occ.Says(warrant) ||
e.Current.Ask.Said != AskApproved || len(said) != 1 {
t.Fatalf("on the warrant: %+v, ask %+v, said %v", e.Current.Status, e.Current.Ask, said)
}
@@ -17,7 +17,7 @@ import (
"regexp"
"strings"
"git.novox.be/novox/mesh-sdk/go/asks"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
)
// Rule is one rule that judges one record: its id and what it holds.
@@ -654,7 +654,7 @@ func Holds(rule, kind, date string) bool {
// briefProblems are the ways a brief would not read at a glance on the operator's phone (novox/hq issue 639):
// missing; a headline empty or longer than an ask's; a summary or a point the router could refuse, or that
// names a path, an address or a long word (shown); more than three points, or one too long (asks.Glance).
// names a path, an address or a long word (shown); more than three points, or one too long (occ.Glance).
func briefProblems(b *Brief) []string {
if b == nil {
return []string{"a record that needs the operator carries a brief (headline, summary, points): what it changes for the operator, " +
@@ -662,8 +662,8 @@ func briefProblems(b *Brief) []string {
}
var out []string
h := strings.TrimSpace(b.Headline)
if h == "" || strings.Contains(h, "\n") || len([]rune(h)) > asks.HeadlineLength {
out = append(out, fmt.Sprintf("the brief's headline is empty, more than one line, or longer than %d characters", asks.HeadlineLength))
if h == "" || strings.Contains(h, "\n") || len([]rune(h)) > occ.HeadlineLength {
out = append(out, fmt.Sprintf("the brief's headline is empty, more than one line, or longer than %d characters", occ.HeadlineLength))
}
if s := strings.TrimSpace(b.Summary); s == "" || strings.Contains(s, "\n") {
out = append(out, "the brief's summary is empty or more than one line")
@@ -673,7 +673,7 @@ func briefProblems(b *Brief) []string {
out = append(out, fmt.Sprintf("the brief's point %d is empty or more than one line", i+1))
}
}
if err := asks.Glance(b.Explanation()); err != nil {
if err := occ.Glance(b.Explanation()); err != nil {
out = append(out, "the brief "+strings.TrimPrefix(err.Error(), "the explanation "))
}
for _, line := range append([]string{h}, strings.Split(b.Explanation(), "\n")...) {
@@ -8,7 +8,7 @@ import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/asks"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
)
func TestAReservedRecordIsWithdrawnAtOnce(t *testing.T) {
@@ -181,7 +181,7 @@ func TestARetractionTakesEffectOnlyOnTheOperatorsApprove(t *testing.T) {
t.Fatalf("asked %d times", len(w.router.asked)-asked)
}
a := w.router.asked[len(w.router.asked)-1]
if err := a.Check(w.clock); err != nil || asks.Glance(a.Explanation) != nil || len([]rune(a.Headline)) > 60 ||
if err := occ.Check(a, w.clock); err != nil || occ.Glance(a.Explanation) != nil || len([]rune(a.Headline)) > 60 ||
!strings.Contains(a.Headline, key.String()) || !strings.Contains(a.Explanation+a.Details, why) {
t.Fatalf("the ask %+v: %v", a, err)
}