Build log: a build narrates between its start and its end (build.said)

Its module, fingerprint, line and why, as design 51 has a build narrate its publish; a start or end carries no
line (A3g's finding).

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 13:14:43 +01:00
parent 1a149d44be
commit 7efb17386d
2 changed files with 40 additions and 1 deletions
+18 -1
View File
@@ -132,6 +132,10 @@ const (
BuildLogRead = BuilderModule + "." + BuilderLog
BuildStartedEvent = "build.started"
BuildEndedEvent = "build.ended"
// BuildSaidEvent is a build's narration between its start and its end (design 51: a build narrates its publish,
// asked, waiting on a seat, stored, refused, at least every five minutes while it waits): its module, fingerprint,
// line and why.
BuildSaidEvent = "build.said"
// BuilderLogDays is how long the log keeps its entries.
BuilderLogDays = 30
// BuildsSinceTool is the builder's tool answering every ended build since a time, in order (clause 9): its
@@ -175,6 +179,9 @@ type BuildLogEntry struct {
// sorted and each once, as the build module answered them (BuildAnswer.Imports; novox/hq ADR 0593 §2): the indexer
// keeps them on the module (KeptInputs2.Imports) when the build's fingerprint is the module's.
Imports []string `json:"imports,omitempty"`
// Line is a narration's words, Why what it narrates (asked, waiting, stored, refused): a build.said entry's alone.
Line string `json:"line,omitempty"`
Why string `json:"why,omitempty"`
}
// BuildsSince is the answer of BuildsSinceTool: every ended build since a time, oldest first, and whether the log held
@@ -214,8 +221,18 @@ func EndedSince(entries []BuildLogEntry, since time.Time) []BuildLogEntry {
func EncodeBuildLogEntry(e BuildLogEntry) ([]byte, error) {
e.V = Version
switch {
case e.Event == BuildSaidEvent:
switch {
case e.Module == "" || e.FP == "" || e.At.IsZero() || e.Line == "":
return nil, fmt.Errorf("delivery: a build's narration names its module, fingerprint, line and time")
case e.Outcome != "" || len(e.Made) > 0 || e.Manifest != "" || len(e.Against) > 0 || len(e.Imports) > 0:
return nil, fmt.Errorf("delivery: a build's narration names no outcome, manifest, base, artifact or import")
}
return json.Marshal(e)
case e.Event != BuildStartedEvent && e.Event != BuildEndedEvent:
return nil, fmt.Errorf("delivery: a build log entry is %s or %s, not %q", BuildStartedEvent, BuildEndedEvent, e.Event)
return nil, fmt.Errorf("delivery: a build log entry is %s, %s or %s, not %q", BuildStartedEvent, BuildSaidEvent, BuildEndedEvent, e.Event)
case e.Line != "" || e.Why != "":
return nil, fmt.Errorf("delivery: a build's start or end carries no narration's line")
case e.Module == "" || e.FP == "" || e.Node == "" || e.At.IsZero() || e.Repository == "" || e.Commit == "":
return nil, fmt.Errorf("delivery: a build log entry names its module, fingerprint, repository, commit, node and time")
case e.Event == BuildEndedEvent && e.Outcome == "":
+22
View File
@@ -75,6 +75,28 @@ func TestPublishedPackagesAndImportsRoundTripAndRefuse(t *testing.T) {
if _, err := EncodeBuildLogEntry(end); err == nil {
t.Error("unsorted imports encoded")
}
// A build narrates between its start and its end (build.said): its module, fingerprint, line and why, nothing of an
// end; a start or end carries no line.
said := BuildLogEntry{Event: BuildSaidEvent, Module: "netcheck", FP: "src2:x", At: good.At,
Line: "waiting on container-image-registry", Why: "waiting"}
if raw, err := EncodeBuildLogEntry(said); err != nil {
t.Errorf("a narration refused: %v", err)
} else if e, err := DecodeBuildLogEntry(raw); err != nil || e.Line != said.Line || e.Why != "waiting" {
t.Errorf("a narration's round trip: %+v, %v", e, err)
}
for name, mutate := range map[string]func(e *BuildLogEntry){
"a narration with no line": func(e *BuildLogEntry) { e.Line = "" },
"a narration with an outcome": func(e *BuildLogEntry) { e.Outcome = "done" },
"an end with a line": func(e *BuildLogEntry) {
e.Event, e.Outcome, e.Node, e.Repository, e.Commit = BuildEndedEvent, "done", "ace", "novox/mesh-catalog", commit
},
} {
e := said
mutate(&e)
if _, err := EncodeBuildLogEntry(e); err == nil {
t.Errorf("%s encoded", name)
}
}
var answer BuildAnswer
if err := json.Unmarshal([]byte(`{"artifacts":[],"imports":["`+sdk+`"]}`), &answer); err != nil || len(answer.Imports) != 1 {
t.Fatalf("build answer imports: %v, %v", err, answer.Imports)