From eddb2d4bf6c22706ac5141b2984fb3a74560a12e Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 9 Nov 2026 20:22:47 +0100 Subject: [PATCH] W3.3 (format): an event may be kept in a stream of its own with an age and a cap The seat mesh-delivery's and mesh-delivery-checker's owed events are kept on each owner's stream owed, 7 days and at most 64 MiB (design 47, What the gate owes the forge); the format could only keep an event last per subject. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- go/seatdef/seatdef.go | 33 ++++++++++++++++++++++++--------- go/seatdef/seatdef_test.go | 8 +++++++- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/go/seatdef/seatdef.go b/go/seatdef/seatdef.go index 6a065c8..40de632 100644 --- a/go/seatdef/seatdef.go +++ b/go/seatdef/seatdef.go @@ -122,15 +122,26 @@ type Message struct { // still knows the event. Empty, the subject is the name. A part `` is the node that publishes it, which // the grants part grants a node's account alone (ADR 0363 ยง2). Subject string `json:"subject,omitempty"` - // Keep says how an event is kept when not as the family keeps the seat's others: KeepLastPerSubject, in a stream - // of its own named KeptStream, the last message of each subject and no age limit (design 47: the node-engine's - // running.., so the last build of every module on every node stays on the bus however long ago - // it was said, and a durable consumer starting from the last message per subject holds them all). + // Keep says how an event is kept when not as the family keeps the seat's others, in a stream of its own named + // KeptStream: KeepLastPerSubject, the last message of each subject and no age limit (design 47: the node-engine's + // running.., so the last build of every module on every node stays on the bus however long ago it + // was said, and a durable consumer starting from the last message per subject holds them all); or KeepStream, + // every message within MaxAgeDays and MaxMiB. Keep string `json:"keep,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. + MaxAgeDays int `json:"max-age-days,omitempty"` + MaxMiB int `json:"max-mib,omitempty"` } -// KeepLastPerSubject is an event kept last per subject with no age limit, in a stream of its own. -const KeepLastPerSubject = "last-per-subject" +// The ways an event is kept in a stream of its own, KeptStream, instead of its seat's family. +const ( + // KeepLastPerSubject keeps the last message of each subject, with no age limit. + KeepLastPerSubject = "last-per-subject" + // KeepStream keeps every message for MaxAgeDays, at most MaxMiB in all. + KeepStream = "stream" +) // KeptStream is the stream an event with Keep is kept in: `.`, as a family is `.events`. func KeptStream(seat string, m Message) string { return seat + "." + m.Name } @@ -677,10 +688,14 @@ func checkMessages(field string, ms []Message, whole bool) Problems { switch { case m.Keep != "" && field != "events": ps = append(ps, Problem{where + ".keep", "only an event is kept"}) - case m.Keep != "" && m.Keep != KeepLastPerSubject: - ps = append(ps, Problem{where + ".keep", fmt.Sprintf("%q is not %s", m.Keep, KeepLastPerSubject)}) - case m.Keep != "" && len(m.Parts()) == 0 && !m.ByCaller: + case m.Keep != "" && m.Keep != KeepLastPerSubject && m.Keep != KeepStream: + ps = append(ps, Problem{where + ".keep", fmt.Sprintf("%q is not %s or %s", m.Keep, KeepLastPerSubject, KeepStream)}) + case m.Keep == KeepLastPerSubject && len(m.Parts()) == 0 && !m.ByCaller: ps = append(ps, Problem{where + ".keep", "an event of one subject kept last per subject is one message: give it a subject with parts"}) + case m.Keep == KeepStream && (m.MaxAgeDays <= 0 || m.MaxMiB <= 0): + ps = append(ps, Problem{where + ".keep", "an event kept in a stream of its own gives its max-age-days and max-mib"}) + case m.Keep != KeepStream && (m.MaxAgeDays != 0 || m.MaxMiB != 0): + ps = append(ps, Problem{where + ".max-age-days", "only an event kept as a stream of its own gives max-age-days and max-mib"}) } if m.Subject != "" && field != "events" { ps = append(ps, Problem{where + ".subject", "only an event carries a subject of its own"}) diff --git a/go/seatdef/seatdef_test.go b/go/seatdef/seatdef_test.go index c406521..2cca1b5 100644 --- a/go/seatdef/seatdef_test.go +++ b/go/seatdef/seatdef_test.go @@ -66,7 +66,13 @@ func TestADefinitionIsReadStrictlyNamingWhatIsWrong(t *testing.T) { {"an event kept last per subject", strings.Replace(whole, `"name":"created"`, `"name":"running","subject":"running..","keep":"last-per-subject"`, 1), ""}, {"an event kept otherwise", strings.Replace(whole, `"name":"created"`, - `"name":"running","subject":"running.","keep":"forever"`, 1), `events[0].keep: "forever" is not last-per-subject`}, + `"name":"running","subject":"running.","keep":"forever"`, 1), `events[0].keep: "forever" is not last-per-subject or stream`}, + {"an event kept in a stream of its own, bounded", strings.Replace(whole, `"name":"created"`, + `"name":"owed","subject":"owed..","keep":"stream","max-age-days":7,"max-mib":64`, 1), ""}, + {"an event kept in a stream of its own, unbounded", strings.Replace(whole, `"name":"created"`, + `"name":"owed","subject":"owed..","keep":"stream","max-age-days":7`, 1), "gives its max-age-days and max-mib"}, + {"a bound on an event kept by its family", strings.Replace(whole, `"name":"created"`, + `"name":"owed","max-mib":64`, 1), "events[0].max-age-days: only an event kept as a stream"}, {"an event of one subject kept last per subject", strings.Replace(whole, `"name":"created"`, `"name":"running","keep":"last-per-subject"`, 1), "give it a subject with parts"}, {"an accept with dotted words", strings.Replace(whole, `"events":`,