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:
2026-10-29 23:28:21 +01:00
parent 196c5b6af2
commit bb993dd606
16 changed files with 695 additions and 51 deletions
+57 -2
View File
@@ -15,8 +15,10 @@ import (
// A log follows a bucket in every respect (ADR 0201): the module names it locally, the mesh derives
// its stream and subjects, the controller creates it from the catalogue on every raise and from
// registration, never a module, and **no path of the mesh removes one** — a log whose declaration
// is gone is reported, as a bucket is. Unlike a bucket, a log is its owner's alone: no other module
// reads it, so there is no read of a log to grant or issue.
// is gone is reported, as a bucket is. Only its owner appends to it. Another module reads it when its manifest's
// `reads` names it, `<module>.<log>` (novox/hq ADR 0571 clause 1; the lead, 2026-10-29): by direct gets, and through an
// ordered consumer of its own it creates and lets go of to read from a time or the newest per key and then follow new
// entries; never a durable consumer, never a write.
//
// Pure, but for the stream's configuration, which is the server's own type so that what is tested
// is exactly what is sent; jetstream.go is the part that asks a server.
@@ -138,6 +140,59 @@ func logGrants(module string, names []string) []string {
return out
}
// DeclaredLogs is every log a module placed anywhere declares, as a read names it: `<module>.<log>`.
func DeclaredLogs(r Records) map[string]bool {
out := map[string]bool{}
for _, ds := range r.Assigned {
for _, d := range ds {
for _, l := range d.Logs {
out[d.Module+"."+l.Name] = true
}
}
}
return out
}
// withReadLogs is d with ReadLogs set: of its reads, those naming a log of logs.
func withReadLogs(d Declared, logs map[string]bool) Declared {
d.ReadLogs = nil
for _, r := range d.Reads {
if logs[strings.TrimSpace(r)] {
d.ReadLogs = append(d.ReadLogs, strings.TrimSpace(r))
}
}
return d
}
// readLogGrants is what a reader of other modules' logs publishes, for each `<module>.<log>` read: the stream's info,
// direct gets (an entry by sequence, the last of a key), and an ordered consumer of its own (created with any name and
// filter, pulled, read and deleted). No append, no purge, no delete of an entry: the log's stream refuses those anyway.
func readLogGrants(reads []string) []string {
var out []string
for _, r := range reads {
module, name, ok := strings.Cut(r, ".")
if !ok || !safeSubject.MatchString(module) || !safeSubject.MatchString(name) {
continue
}
stream := LogStreamName(module, name)
out = append(out, "$JS.API.STREAM.INFO."+stream, "$JS.API.DIRECT.GET."+stream, "$JS.API.DIRECT.GET."+stream+".>",
"$JS.API.CONSUMER.CREATE."+stream+".>", "$JS.API.CONSUMER.MSG.NEXT."+stream+".*",
"$JS.API.CONSUMER.INFO."+stream+".*", "$JS.API.CONSUMER.DELETE."+stream+".*")
}
return out
}
// readLogsIssuedFor is every other module's log a module's code reads, as its membership lists them: by the read's
// name, never written.
func readLogsIssuedFor(d Declared) []LogIssued {
var out []LogIssued
for _, r := range d.ReadLogs {
module, name, _ := strings.Cut(r, ".")
out = append(out, LogIssued{Name: r, Stream: LogStreamName(module, name), Subject: LogSubject(module, name)})
}
return out
}
// logNames is the local names of a module's logs.
func logNames(logs []Log) []string {
out := make([]string, 0, len(logs))