J7: the gate reads the ask it keeps through operator-channel's client (occ.Carried)

askOf keeps its key step and holds the carried ask to its id and an option with
occ.Carried, the one reader the assigner, teller and view use; its own decode, id and
option checks are gone. Rows in TestTheGateRefusesAnAskWithAnotherIdOrNoOption name the
client's refusals and a value carrying no ask; they failed with the gate's own checks.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 15:49:38 +01:00
parent 5601fdb51d
commit 85d96d327f
2 changed files with 11 additions and 15 deletions
@@ -808,23 +808,13 @@ func gateAsk(group string, heads []sdk.Head, asking int, ask oc.Ask) (sdk.Ask, e
}
// askOf reads the router's ask a gate ask carries and holds it to what the gate owes: the id its group, heads and asking
// give, and at least one option. kvOperator.Ask holds every ask it writes to it, too.
// give, and at least one option (occ.Carried). kvOperator.Ask holds every ask it writes to it, too.
func askOf(a sdk.Ask) (oc.Ask, error) {
var ask oc.Ask
if err := json.Unmarshal(a.Ask, &ask); err != nil {
return oc.Ask{}, fmt.Errorf("the ask is not a router's ask: %w", err)
}
key, err := sdk.MergeAskKey(a.Group, a.Heads)
if err != nil {
return oc.Ask{}, fmt.Errorf("an ask needs its group and at least one pull request's head: %w", err)
}
if want := sdk.AskingID(key, a.Asking); ask.ID != want {
return oc.Ask{}, fmt.Errorf("the ask's id %q is not %q, the id its group, heads and asking %d give", ask.ID, want, a.Asking)
}
if len(ask.Options) == 0 {
return oc.Ask{}, fmt.Errorf("the ask has no option")
}
return ask, nil
return occ.Carried(a.Ask, sdk.AskingID(key, a.Asking))
}
// kvOperator is the operator over the runtime: the ask is a key of the gate's bucket asks, the answer a key of the teller's
@@ -495,9 +495,10 @@ func TestTheGateRefusesAnAskWithAnotherIdOrNoOption(t *testing.T) {
}{
{"the first asking's id", 1, oc.Ask{ID: sdk.AskingID(key, 1), Options: []oc.Option{option}}, ""},
{"the second asking's id", 2, oc.Ask{ID: sdk.AskingID(key, 2), Options: []oc.Option{option}}, ""},
{"another id", 1, oc.Ask{ID: "another-id", Options: []oc.Option{option}}, "is not"},
{"the first asking's id at the second asking", 2, oc.Ask{ID: sdk.AskingID(key, 1), Options: []oc.Option{option}}, "is not"},
{"no option", 1, oc.Ask{ID: sdk.AskingID(key, 1)}, "no option"},
// The refusals are operator-channel's client's (occ.Carried), held to the id the gate's key and asking give.
{"another id", 1, oc.Ask{ID: "another-id", Options: []oc.Option{option}}, "the id it was kept under"},
{"the first asking's id at the second asking", 2, oc.Ask{ID: sdk.AskingID(key, 1), Options: []oc.Option{option}}, "the id it was kept under"},
{"no option", 1, oc.Ask{ID: sdk.AskingID(key, 1)}, "offers no option"},
} {
a, err := gateAsk("feat/g", heads, c.asking, c.ask)
if c.refuse == "" {
@@ -515,4 +516,9 @@ func TestTheGateRefusesAnAskWithAnotherIdOrNoOption(t *testing.T) {
t.Errorf("%s: kvOperator wrote it (%v, %v)", c.name, err, a)
}
}
// A value that carries no router's ask is refused before it is written.
if err := (kvOperator{}).Ask(key, sdk.Ask{Group: "feat/g", Heads: heads, Asking: 1, Ask: json.RawMessage(`"an ask"`)}); err == nil ||
!strings.Contains(err.Error(), "carries no operator-channel ask") {
t.Errorf("a value carrying no ask: %v", err)
}
}