diff --git a/go/asks/asks.go b/go/asks/asks.go index efe2a66..81737ac 100644 --- a/go/asks/asks.go +++ b/go/asks/asks.go @@ -374,8 +374,16 @@ func (w Warrant) For(asker string, a Ask) (Option, error) { if w.Level != o.Level { return Option{}, fmt.Errorf("the option %s is %s, and the warrant says %s", o.ID, o.Level, w.Level) } - // A choice made after the ask expired is no answer to it, whatever the router said. - if !w.At.IsZero() && w.At.After(a.Expires) { + // A choice made after the ask expired is no answer to it, whatever the router said. An ask that says no + // expiry, or a warrant that says no time, is no answer either: neither can be shown to be in time (the + // confirmation review of 2026-10-09). + if a.Expires.IsZero() { + return Option{}, fmt.Errorf("the ask %s says no expiry, so no answer to it can be in time", a.ID) + } + if w.At.IsZero() { + return Option{}, fmt.Errorf("the warrant for the ask %s says no time it was given, so it cannot be shown to be in time", a.ID) + } + if w.At.After(a.Expires) { return Option{}, fmt.Errorf("the warrant was given at %s, after the ask %s expired at %s", w.At.UTC().Format(time.RFC3339), a.ID, a.Expires.UTC().Format(time.RFC3339)) } diff --git a/go/asks/asks_test.go b/go/asks/asks_test.go index cfa05a7..999b418 100644 --- a/go/asks/asks_test.go +++ b/go/asks/asks_test.go @@ -72,7 +72,7 @@ func TestAnAskIsRefusedInWordsForEachFault(t *testing.T) { func TestAnAskerActsOnlyOnAWarrantForItsOwnAskAndOption(t *testing.T) { w := Warrant{Ask: "d-1", Asker: "mesh-delivery", Outcome: OutcomeChosen, Option: "release", Label: "Release", Level: Approve, By: &Person{Who: Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}, - AskDigest: held().Digest()} + AskDigest: held().Digest(), At: now.Add(time.Minute)} if o, err := w.For("mesh-delivery", held()); err != nil || o.ID != "release" { t.Fatalf("a sound warrant was refused: %v", err) } @@ -88,6 +88,8 @@ func TestAnAskerActsOnlyOnAWarrantForItsOwnAskAndOption(t *testing.T) { "a lower level": func(w *Warrant) { w.Level = Acknowledge }, "another role": func(w *Warrant) { w.By = &Person{Who: "agent"} }, "no digest": func(w *Warrant) { w.AskDigest = "" }, + "no time given": func(w *Warrant) { w.At = time.Time{} }, + "after expiry": func(w *Warrant) { w.At = now.Add(2 * time.Hour) }, "another ask's digest": func(w *Warrant) { other := held() other.Options[0].Binds = bind("release", "another") @@ -242,3 +244,16 @@ func TestDigestsAreCanonicalOverNamedFields(t *testing.T) { t.Error("the expiry's zone changed the digest") } } + +// An ask that says no expiry is answered by no warrant (the confirmation review of 2026-10-09): nothing could +// show the answer was in time. +func TestAWarrantForAnAskWithNoExpiryIsRefused(t *testing.T) { + a := held() + a.Expires = time.Time{} + w := Warrant{Ask: "d-1", Asker: "mesh-delivery", Outcome: OutcomeChosen, Option: "release", Label: "Release", + Level: Approve, By: &Person{Who: Operator, Kind: "telegram", Identity: "42"}, AskDigest: a.Digest(), + At: now.Add(time.Minute)} + if _, err := w.For("mesh-delivery", a); err == nil { + t.Error("a warrant for an ask with no expiry was taken") + } +}