From 0036455e63805ddb6134517776e781795d8426d4 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 10:50:51 +0100 Subject: [PATCH] RR-2: the reader reads an accepted kind's stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check or delivery; the same kind name may ride both. Builds: design 47 G §409, §415 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- go/seatdef/seatdef.go | 19 +++++++++++++++++-- go/seatdef/seatdef_test.go | 7 +++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/go/seatdef/seatdef.go b/go/seatdef/seatdef.go index 7b09c9b..d283899 100644 --- a/go/seatdef/seatdef.go +++ b/go/seatdef/seatdef.go @@ -223,6 +223,12 @@ type Verb struct { NeedsApproval bool `json:"needs-approval,omitempty"` } +// The streams an accepted kind may ride (design 47 G §415). +const ( + StreamCheck = "check" + StreamDelivery = "delivery" +) + // Message is one accepted message or one event of the seat, its body a JSON Schema. type Message struct { // Name is the message's name; an event's may be lower-case words joined by dots (pull.updated), as a module's @@ -245,6 +251,9 @@ type Message struct { Keep *Keep `json:"keep,omitempty"` // Callers says who may send an accepted kind, as a verb's (design 47 G §338). Callers *Callers `json:"callers,omitempty"` + // Stream is the stream an accepted kind rides, StreamCheck or StreamDelivery; its subject is `.`, and + // the same kind name may ride more than one stream (design 47 G §415). + Stream string `json:"stream,omitempty"` // MaxAgeDays and MaxMiB bound an event kept KeepStream: how long a message is kept and how much the stream holds // (design 47 *What the gate owes the forge*: the stream `owed`, 7 days and at most 64 MiB). Given with KeepStream // alone. @@ -814,10 +823,16 @@ func checkMessages(field string, ms []Message, whole bool) Problems { } if !named.MatchString(m.Name) { ps = append(ps, Problem{where + ".name", "missing, or not a lower-case name"}) - } else if seen[m.Name] { + } else if key := m.Stream + "/" + m.Name; seen[key] { ps = append(ps, Problem{where + ".name", fmt.Sprintf("%q twice", m.Name)}) } - seen[m.Name] = true + seen[m.Stream+"/"+m.Name] = true + switch { + case m.Stream != "" && field != "accepts": + ps = append(ps, Problem{where + ".stream", "only an accepted kind rides a stream"}) + case m.Stream != "" && m.Stream != StreamCheck && m.Stream != StreamDelivery: + ps = append(ps, Problem{where + ".stream", fmt.Sprintf("%q is not %s or %s", m.Stream, StreamCheck, StreamDelivery)}) + } mode := m.legacyMode() switch { case m.Keep != nil && field != "events": diff --git a/go/seatdef/seatdef_test.go b/go/seatdef/seatdef_test.go index 6fb8b8b..abaa972 100644 --- a/go/seatdef/seatdef_test.go +++ b/go/seatdef/seatdef_test.go @@ -143,6 +143,13 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) { {"a verb marked for approval", strings.Replace(whole, `"name":"list",`, `"name":"list","needs-approval":true,`, 1), ""}, {"a verb marked with a word that is no boolean", strings.Replace(whole, `"name":"list",`, `"name":"list","needs-approval":"yes",`, 1), "not a seat definition's JSON"}, {"a bodies section", strings.Replace(whole, `"family":`, `"bodies":{"Finding":{"type":"object"}},"family":`, 1), ""}, + {"one kind on two streams", strings.Replace(whole, `"events":`, + `"accepts":[{"name":"step","body":{},"stream":"check"},{"name":"step","body":{},"stream":"delivery"}],"events":`, 1), ""}, + {"one kind twice on a stream", strings.Replace(whole, `"events":`, + `"accepts":[{"name":"step","body":{},"stream":"check"},{"name":"step","body":{},"stream":"check"}],"events":`, 1), `"step" twice`}, + {"a stream that is none", strings.Replace(whole, `"events":`, `"accepts":[{"name":"step","body":{},"stream":"mail"}],"events":`, 1), + `accepts[0].stream: "mail" is not check or delivery`}, + {"an event on a stream", strings.Replace(whole, `"name":"created"`, `"name":"created","stream":"check"`, 1), "only an accepted kind rides a stream"}, {"over 256 KiB", listGrain[:len(listGrain)-1] + `,"description":"` + strings.Repeat("x", MaxSize) + `"}`, ErrTooLarge.Error()}, }