FB4a: the forge message forms of an owed act's outcome (owed.ended)
The gate's developer needs OwedEnded and its outcomes; fields are the ones §164 and §168 name (id, outcome, words, the seat that said the act, time). Builds: design 47 G §164, §168 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
@@ -191,4 +192,37 @@ func TestForge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
// Design 47 G §164: an owed act's outcome names its seat, its id, one outcome and words for every outcome but
|
||||
// performed; an act the bridge cannot decode may name no id.
|
||||
t.Run("owed ended", func(t *testing.T) {
|
||||
at := time.Date(2026, 11, 9, 20, 0, 0, 0, time.UTC)
|
||||
ok := OwedEnded{Of: "mesh-delivery", ID: "g1.view.1", Outcome: OwedPerformed, At: at}
|
||||
raw, err := EncodeOwedEnded(ok)
|
||||
if back, derr := DecodeOwedEnded(raw); err != nil || derr != nil || back.V != Version || back.ID != ok.ID {
|
||||
t.Fatalf("round trip: %s %v %v", raw, err, derr)
|
||||
}
|
||||
if _, err := DecodeOwedEnded([]byte(`{"v":99,"of":"x","id":"a","outcome":"performed","at":"2026-11-09T20:00:00Z"}`)); err == nil {
|
||||
t.Errorf("an unknown version was read")
|
||||
}
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
edit func(*OwedEnded)
|
||||
want string
|
||||
}{
|
||||
{"no seat", func(e *OwedEnded) { e.Of = "" }, "no seat"},
|
||||
{"an outcome of no kind", func(e *OwedEnded) { e.Outcome = "done" }, "none of"},
|
||||
{"no id", func(e *OwedEnded) { e.ID = "" }, "no id"},
|
||||
{"a refusal with no words", func(e *OwedEnded) { e.Outcome = OwedRefused }, "no words"},
|
||||
{"dropped with no reason", func(e *OwedEnded) { e.Outcome = OwedDropped }, "no words"},
|
||||
{"no time", func(e *OwedEnded) { e.At = time.Time{} }, "no time"},
|
||||
{"undecodable with no id is said", func(e *OwedEnded) { e.ID, e.Outcome, e.Words = "", OwedUndecodable, "not json" }, ""},
|
||||
} {
|
||||
e := ok
|
||||
c.edit(&e)
|
||||
_, err := EncodeOwedEnded(e)
|
||||
if (c.want == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.want)) {
|
||||
t.Errorf("%s: %v, want %q", c.name, err, c.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package forgemsg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OwedEndedEvent is the name of an owed act's outcome on the forge bridge's stream `forge` (design 47 G §164): the
|
||||
// bridge says it for each act the gate or the checker said as the event `owed`. Its two parts are no repository's
|
||||
// forge event (`forge.<owner>.<name>.<kind>`).
|
||||
const OwedEndedEvent = "owed.ended"
|
||||
|
||||
// The outcomes of an owed act (design 47 G §164, §168): performed, refused (the forge's own words), could not be
|
||||
// performed (the forge did not answer within the call's bound, with the error), undecodable (an act the bridge cannot
|
||||
// read, with why) and, from the tool `bridge_owed_drop`, dropped.
|
||||
const (
|
||||
OwedPerformed = "performed"
|
||||
OwedRefused = "refused"
|
||||
OwedCouldNotBePerformed = "could not be performed"
|
||||
OwedUndecodable = "undecodable"
|
||||
OwedDropped = "dropped"
|
||||
)
|
||||
|
||||
// OwedOutcomes are the outcomes an owed.ended says, one of them.
|
||||
var OwedOutcomes = []string{OwedPerformed, OwedRefused, OwedCouldNotBePerformed, OwedUndecodable, OwedDropped}
|
||||
|
||||
// OwedEnded is an owed act's outcome (design 47 G §164): the act's id and one outcome, with the words of any outcome
|
||||
// but performed. Of is the seat that said the act (mesh-delivery for the gate's, mesh-delivery-checker for the
|
||||
// checker's), so each hears its own ids (§164). An act the bridge cannot decode may name no id.
|
||||
type OwedEnded struct {
|
||||
V int `json:"v"`
|
||||
Of string `json:"of"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Outcome string `json:"outcome"`
|
||||
Words string `json:"words,omitempty"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
func (e OwedEnded) check() error {
|
||||
switch {
|
||||
case e.Of == "":
|
||||
return fmt.Errorf("an owed act's outcome names no seat that said it")
|
||||
case !slices.Contains(OwedOutcomes, e.Outcome):
|
||||
return fmt.Errorf("the outcome %q of an owed act is none of %v", e.Outcome, OwedOutcomes)
|
||||
case e.ID == "" && e.Outcome != OwedUndecodable:
|
||||
return fmt.Errorf("the outcome %s of an owed act names no id", e.Outcome)
|
||||
case e.Outcome != OwedPerformed && e.Words == "":
|
||||
return fmt.Errorf("the outcome %s of the owed act %s says no words", e.Outcome, e.ID)
|
||||
case e.At.IsZero():
|
||||
return fmt.Errorf("the outcome of the owed act %s says no time", e.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeOwedEnded is e as JSON at Version; it refuses what check refuses.
|
||||
func EncodeOwedEnded(e OwedEnded) ([]byte, error) {
|
||||
if err := e.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.V = Version
|
||||
return json.Marshal(e)
|
||||
}
|
||||
|
||||
// DecodeOwedEnded reads an owed act's outcome; it refuses an unknown version and what EncodeOwedEnded refuses.
|
||||
func DecodeOwedEnded(data []byte) (OwedEnded, error) {
|
||||
var e OwedEnded
|
||||
if err := decode(data, &e); err != nil {
|
||||
return OwedEnded{}, err
|
||||
}
|
||||
if err := e.check(); err != nil {
|
||||
return OwedEnded{}, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
Reference in New Issue
Block a user