Bind each asked option to the exact act, and perform only that act on its warrant (hq ADR 0259 §6)
Every option the controller asks with carries the digest of its verb, machine, arguments and level (the SDK's Option.Binds). A warrant must name the digest of the ask the controller keeps, and before acting the controller checks that the act it is about to perform is the one the option bound: a record changed after the ask is refused, never performed. mesh-sdk moves to d4077b4.
This commit is contained in:
@@ -373,7 +373,11 @@ func askOf(id string, c conditions.Condition, now time.Time) (asks.Ask, map[stri
|
||||
approves = approves || level != asks.Acknowledge
|
||||
oid := optionID(act.Label)
|
||||
options[oid] = i
|
||||
q.Options = append(q.Options, asks.Option{ID: oid, Label: act.Label, Does: doesWords(act), Level: level})
|
||||
// Every option binds the exact act it stands for (novox/hq ADR 0259 §6): the verb, the machine and
|
||||
// every argument. The warrant then authorises that act and no other.
|
||||
binds, _ := asks.ActDigest(boundAct(act))
|
||||
q.Options = append(q.Options, asks.Option{ID: oid, Label: act.Label, Does: doesWords(act), Level: level,
|
||||
Binds: binds})
|
||||
}
|
||||
q.Expires = now.Add(askAcknowledgeFor)
|
||||
if approves {
|
||||
@@ -382,6 +386,12 @@ func askOf(id string, c conditions.Condition, now time.Time) (asks.Ask, map[stri
|
||||
return q, options
|
||||
}
|
||||
|
||||
// boundAct is what an option's Binds digests: the act exactly as the controller will perform it, with its
|
||||
// level, and never its label or words.
|
||||
func boundAct(act conditions.Action) map[string]any {
|
||||
return map[string]any{"verb": act.Verb, "machine": act.Machine, "arguments": act.Arguments, "level": act.Level}
|
||||
}
|
||||
|
||||
func newAskID() string {
|
||||
var b [8]byte
|
||||
_, _ = rand.Read(b[:])
|
||||
@@ -469,6 +479,12 @@ func (a *asker) Decided(ctx context.Context, body []byte) error {
|
||||
return nil
|
||||
}
|
||||
act := r.Actions[index]
|
||||
// The act about to be performed is the one the option bound when the controller asked: a record changed
|
||||
// since is refused, never performed.
|
||||
if err := option.Performs(boundAct(act)); err != nil {
|
||||
a.logf("REFUSED a warrant for the ask %s: %v", r.ID, err)
|
||||
return nil
|
||||
}
|
||||
r.State, r.Warrant = string(asks.OutcomeChosen), &w
|
||||
open, err := a.open(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -188,7 +188,8 @@ func (r *askerRig) warrantFor(t *testing.T, condition, label string) asks.Warran
|
||||
if o.Label == label {
|
||||
return asks.Warrant{Ask: a.ID, Asker: "mesh-controller", About: condition, Outcome: asks.OutcomeChosen,
|
||||
Option: o.ID, Label: o.Label, Level: o.Level, Channel: "telegram", Proofs: []string{"P1"}, At: r.now,
|
||||
By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}}
|
||||
AskDigest: a.Ask.Digest(),
|
||||
By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,6 +243,8 @@ func TestAWarrantThatIsNotForItsOwnAskIsRefused(t *testing.T) {
|
||||
"an option not offered": func(w *asks.Warrant) { w.Option = "delete" },
|
||||
"another level": func(w *asks.Warrant) { w.Level = asks.Acknowledge },
|
||||
"no person": func(w *asks.Warrant) { w.By = nil },
|
||||
"another ask's digest": func(w *asks.Warrant) { w.AskDigest = "sha256:0000" },
|
||||
"no ask's digest": func(w *asks.Warrant) { w.AskDigest = "" },
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := newAskerRig(t)
|
||||
@@ -474,3 +477,40 @@ func TestAWarrantIsActedOnlyForAnOpenAskItClaimsBeforeItExpired(t *testing.T) {
|
||||
t.Errorf("acted on a cancelled ask: %v", r.called)
|
||||
}
|
||||
}
|
||||
|
||||
// novox/hq ADR 0259 §6: a warrant authorises the act its option bound when the controller asked, and no
|
||||
// other. A record of the act changed after the ask — another delivery, another machine, another argument —
|
||||
// is refused and nothing is performed.
|
||||
func TestAWarrantPerformsOnlyTheActItsOptionBound(t *testing.T) {
|
||||
for name, change := range map[string]func(*conditions.Action){
|
||||
"another argument": func(a *conditions.Action) {
|
||||
a.Arguments = map[string]string{"id": "novox/mesh-controller@000000000000"}
|
||||
},
|
||||
"another verb": func(a *conditions.Action) { a.Verb = "mesh-delivery.stop" },
|
||||
"another machine": func(a *conditions.Action) { a.Machine = "anchor" },
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := newAskerRig(t)
|
||||
r.open = []conditions.Condition{heldCondition()}
|
||||
_ = r.a.reconcile(context.Background())
|
||||
w := r.warrantFor(t, heldCondition().Key, "Release")
|
||||
kept := r.store[w.Ask]
|
||||
acts := append([]conditions.Action(nil), kept.Actions...)
|
||||
i := kept.Options[w.Option]
|
||||
change(&acts[i])
|
||||
kept.Actions = acts
|
||||
r.store[w.Ask] = kept
|
||||
answerWith(t, r, w)
|
||||
if len(r.called)+len(r.acts) != 0 {
|
||||
t.Errorf("performed an act the option did not bind: %v %v", r.called, r.acts)
|
||||
}
|
||||
})
|
||||
}
|
||||
// Every option of an ask binds its act.
|
||||
q, _ := askOf("x", heldCondition(), time.Now())
|
||||
for _, o := range q.Options {
|
||||
if o.Binds == "" {
|
||||
t.Errorf("the option %s binds nothing", o.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user