Files
mesh-sdk/go/catalogue/logs.go
T
jschoubben 68aaf235d4 Take the judge out of mesh-sdk: it is mesh-delivery's now (ADR 0578 §4, §7)
Issue 1585, S19: the strict parser, every manifest problem function and the judge's tables (192 declarations,
about 5,200 lines) and their 244 tests leave; composition keeps its own refusals and the helpers it uses. The
catalogue's tests decode the manifests they build. ParseManifest, ParseManifestWith, ParseManifestForInputs,
CatalogueProblems and the rest are gone; mesh-delivery-indexer/judge holds them.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 02:36:24 +01:00

90 lines
3.1 KiB
Go

package catalogue
import (
"bytes"
"encoding/json"
"fmt"
)
// What a module may call its logs (novox/hq ADR 0297).
//
// A log is a module's record of operations: entries appended under a key, each kept as long as the
// log is, in the order they came. A module names each log it owns **locally** — `changes`, never a
// stream or a subject (ADR 0201 §4) — and the mesh derives the stream from the module and the local
// name, as it derives a bucket. So the rule for a log's name is a state name's: one plain token, and
// a module that keeps a log has a name that is one plain token too.
// The mesh's caps on a log, in MiB: what a module may ask, and what it gets when it asks nothing.
const (
LogLeastMiB = 1
LogMostMiB = 8192
LogDefaultMiB = 1024
)
// LogDeclaration is one log a module owns: its local name, and how large it may grow.
type LogDeclaration struct {
Name string `json:"name"`
// MaxMiB is the log's cap in MiB; zero is LogDefaultMiB. When full, the log refuses new entries
// and never drops old ones.
MaxMiB int `json:"max-mib,omitempty"`
// MaxAgeDays is how long an entry is kept, in days; zero is as long as the log (novox/hq ADR 0297). The one way an
// entry leaves a log: the server ages it out, never a delete or a purge (novox/hq ADR 0565 §10: the hand-act log
// keeps every act 400 days; issue 1953 S-age).
MaxAgeDays int `json:"max-age-days,omitempty"`
}
// LogMostAgeDays is the longest a log may keep an entry by age: past it, a log keeps its entries as long as it is.
const LogMostAgeDays = 3650
// Cap is the log's cap in MiB, the default where none is said.
func (l LogDeclaration) Cap() int {
if l.MaxMiB == 0 {
return LogDefaultMiB
}
return l.MaxMiB
}
// UnmarshalJSON reads a log as its bare name, or as {name, max-mib, max-age-days}.
func (l *LogDeclaration) UnmarshalJSON(raw []byte) error {
trimmed := bytes.TrimSpace(raw)
if len(trimmed) > 0 && trimmed[0] == '"' {
return json.Unmarshal(trimmed, &l.Name)
}
var full struct {
Name string `json:"name"`
MaxMiB *int `json:"max-mib"`
MaxAgeDays *int `json:"max-age-days"`
}
dec := json.NewDecoder(bytes.NewReader(trimmed))
dec.DisallowUnknownFields()
if err := dec.Decode(&full); err != nil {
return fmt.Errorf("a log is either a name or {name, max-mib, max-age-days}: %w", typedUnknown(err))
}
l.Name = full.Name
l.MaxMiB = 0
if full.MaxMiB != nil {
// Said, and said as nothing: refused rather than read as the default, which it did not say.
if *full.MaxMiB == 0 {
return fmt.Errorf("log %q: max-mib is between %d and %d, not 0", full.Name, LogLeastMiB, LogMostMiB)
}
l.MaxMiB = *full.MaxMiB
}
l.MaxAgeDays = 0
if full.MaxAgeDays != nil {
if *full.MaxAgeDays < 1 || *full.MaxAgeDays > LogMostAgeDays {
return fmt.Errorf("log %q: max-age-days is between 1 and %d, not %d", full.Name, LogMostAgeDays, *full.MaxAgeDays)
}
l.MaxAgeDays = *full.MaxAgeDays
}
return nil
}
// MarshalJSON writes back the short form when there is nothing else to say.
func (l LogDeclaration) MarshalJSON() ([]byte, error) {
if l.MaxMiB == 0 && l.MaxAgeDays == 0 {
return json.Marshal(l.Name)
}
type plain LogDeclaration
return json.Marshal(plain(l))
}