Keep the builder's history as its own log that readers declare and follow, the memberships as the grants part's state, and refuse a module's own stream, so every module's data is in the bus's predefined forms and readable by its tools (issue 1955, ADR 0571 clauses 1, 4, 5, 7)
This commit is contained in:
+51
-5
@@ -58,12 +58,13 @@ func (r *logRuntime) answer(m message) map[string]any {
|
||||
From uint64 `json:"from"`
|
||||
Keys string `json:"keys"`
|
||||
Limit int `json:"limit"`
|
||||
Since time.Time `json:"since"`
|
||||
}
|
||||
_ = json.Unmarshal(m.Params, &p)
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.asked = append(r.asked, m)
|
||||
if p.Log != "changes" {
|
||||
if p.Log != "changes" && p.Log != "builder.build-log" {
|
||||
return map[string]any{"error": map[string]any{"code": -32000, "message": "issues was issued no log " + p.Log}}
|
||||
}
|
||||
switch strings.TrimPrefix(m.Method, "mesh/log.") {
|
||||
@@ -83,7 +84,7 @@ func (r *logRuntime) answer(m message) map[string]any {
|
||||
page := map[string]any{"entries": []LogEntry{}, "next": 0, "last": len(r.entries)}
|
||||
var got []LogEntry
|
||||
for _, e := range r.entries {
|
||||
if e.Sequence < p.From || !matches(p.Keys, e.Key) {
|
||||
if e.Sequence < p.From || !matches(p.Keys, e.Key) || e.At.Before(p.Since) {
|
||||
continue
|
||||
}
|
||||
if len(got) == limit {
|
||||
@@ -96,6 +97,16 @@ func (r *logRuntime) answer(m message) map[string]any {
|
||||
page["entries"] = got
|
||||
}
|
||||
return map[string]any{"result": page}
|
||||
case "newest":
|
||||
var out []LogEntry
|
||||
for _, e := range r.entries {
|
||||
if matches(p.Keys, e.Key) && r.last(e.Key) == e.Sequence {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return map[string]any{"result": out}
|
||||
case "watch":
|
||||
return map[string]any{"result": map[string]any{}}
|
||||
case "last":
|
||||
seq := r.last(p.Key)
|
||||
if seq == 0 {
|
||||
@@ -148,6 +159,13 @@ func logging(t *testing.T, r *logRuntime, run func() (any, error)) map[string]an
|
||||
a := r.answer(m)
|
||||
a["id"] = m.ID
|
||||
send(a)
|
||||
// A watch's entries follow its answer, each named by the watch's id (ADR 0571 clause 1).
|
||||
if m.Method == "mesh/log.watch" {
|
||||
r.mu.Lock()
|
||||
e := r.entries[len(r.entries)-1]
|
||||
r.mu.Unlock()
|
||||
send(map[string]any{"id": "entry-1", "method": "mesh/log", "params": map[string]any{"watch": m.ID, "entry": e}})
|
||||
}
|
||||
}(m)
|
||||
continue
|
||||
}
|
||||
@@ -193,7 +211,9 @@ func TestABundleKeepsALogThroughTheRuntime(t *testing.T) {
|
||||
pages []*LogPage
|
||||
last, none *LogEntry
|
||||
info *LogInfo
|
||||
narrowed *LogPage
|
||||
narrowed, since *LogPage
|
||||
newest []LogEntry
|
||||
watched *LogEntry
|
||||
)
|
||||
got := logging(t, r, func() (any, error) {
|
||||
changes := Log("changes")
|
||||
@@ -232,8 +252,27 @@ func TestABundleKeepsALogThroughTheRuntime(t *testing.T) {
|
||||
if none, err = changes.Last("999"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err = changes.Info()
|
||||
return "done", err
|
||||
if info, err = changes.Info(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ADR 0571 clause 1: another module's log, newest per key, from a time, and followed.
|
||||
if newest, err = changes.Newest(""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if since, err = changes.Since(time.Date(2026, 10, 11, 18, 0, 5, 0, time.UTC), "", 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
heard := make(chan LogEntry, 1)
|
||||
if err = Log("builder.build-log").Watch("letta", LogFromSince(time.Date(2026, 10, 11, 18, 0, 0, 0, time.UTC)),
|
||||
func(e LogEntry) error { heard <- e; return nil }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
select {
|
||||
case e := <-heard:
|
||||
watched = &e
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
return "done", nil
|
||||
})
|
||||
if out, isError := text(t, got); isError || out != `"done"` {
|
||||
t.Fatalf("the tool answered %s", out)
|
||||
@@ -296,6 +335,13 @@ func TestABundleKeepsALogThroughTheRuntime(t *testing.T) {
|
||||
if none != nil {
|
||||
t.Fatalf("a key with no entry has a last: %+v", none)
|
||||
}
|
||||
if len(newest) != 5 || newest[0].Key != "402" || newest[1].Sequence != 3 || len(since.Entries) != 2 || since.Entries[0].Sequence != 5 {
|
||||
t.Fatalf("the newest per key are %+v, since a time %+v", newest, since)
|
||||
}
|
||||
if method, p = r.params(t, 15); method != "mesh/log.watch" || p["log"] != "builder.build-log" || p["keys"] != "letta" ||
|
||||
p["from"] != "since" || p["since"] != "2026-10-11T18:00:00Z" || watched == nil || watched.Sequence != 6 {
|
||||
t.Fatalf("a watch asked %s %v and heard %+v", method, p, watched)
|
||||
}
|
||||
if info == nil || info.Entries != 6 || info.MaxBytes != 1<<30 || info.First != 1 || info.Last != 6 || info.Bytes == 0 {
|
||||
t.Fatalf("the log's info is %+v", info)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user