J3 (A2i finding 2): a build's end names the generated files its test step found out of step

delivery.BuildLogEntry gains out-of-step, an end's alone, as the build module's test step answers it, so the indexer
reads the field from the entry, not the raw message, and its trunk finding can be raised once the builder writes it.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 14:26:26 +01:00
parent 1a3ef8101f
commit 635aed57fd
2 changed files with 20 additions and 8 deletions
+9 -4
View File
@@ -179,6 +179,10 @@ 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"`
// OutOfStep are, at a build's end, the generated files its test step found out of step with what their generator
// writes, as the build module answered them (its test step's `out-of-step`): the indexer raises a finding naming them
// for a trunk build, and only a clean build of the same module clears it. A start or a narration names none.
OutOfStep []string `json:"out-of-step,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"`
@@ -225,8 +229,8 @@ func EncodeBuildLogEntry(e BuildLogEntry) ([]byte, error) {
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")
case e.Outcome != "" || len(e.Made) > 0 || e.Manifest != "" || len(e.Against) > 0 || len(e.Imports) > 0 || len(e.OutOfStep) > 0:
return nil, fmt.Errorf("delivery: a build's narration names no outcome, manifest, base, artifact, import or file out of step")
}
return json.Marshal(e)
case e.Event != BuildStartedEvent && e.Event != BuildEndedEvent:
@@ -237,8 +241,9 @@ func EncodeBuildLogEntry(e BuildLogEntry) ([]byte, error) {
return nil, fmt.Errorf("delivery: a build log entry names its module, fingerprint, repository, commit, node and time")
case e.Event == BuildEndedEvent && e.Outcome == "":
return nil, fmt.Errorf("delivery: a build's end names its outcome")
case e.Event == BuildStartedEvent && (e.Outcome != "" || len(e.Made) > 0 || e.Manifest != "" || len(e.Against) > 0 || len(e.Imports) > 0):
return nil, fmt.Errorf("delivery: a build's start names no outcome, manifest, base, artifact or import")
case e.Event == BuildStartedEvent && (e.Outcome != "" || len(e.Made) > 0 || e.Manifest != "" || len(e.Against) > 0 || len(e.Imports) > 0 ||
len(e.OutOfStep) > 0):
return nil, fmt.Errorf("delivery: a build's start names no outcome, manifest, base, artifact, import or file out of step")
}
if err := CheckImports(e.Imports); err != nil {
return nil, err
+11 -4
View File
@@ -57,17 +57,23 @@ func TestPublishedPackagesAndImportsRoundTripAndRefuse(t *testing.T) {
}
imports := []string{sdk, sdk + "/announce"}
outOfStep := []string{"go/seats/meshbroker/contract.go", "ts/seats/meshbroker/src/index.ts"}
end := BuildLogEntry{Event: BuildEndedEvent, Module: "netcheck", FP: "src2:x", Repository: "novox/mesh-catalog", Commit: commit,
Node: "ace", At: good.At, Outcome: "done", Imports: imports}
Node: "ace", At: good.At, Outcome: "done", Imports: imports, OutOfStep: outOfStep}
raw, err = EncodeBuildLogEntry(end)
if err != nil {
t.Fatal(err)
}
if e, err := DecodeBuildLogEntry(raw); err != nil || !reflect.DeepEqual(e.Imports, imports) {
t.Fatalf("build end round trip: %v, %v", err, e.Imports)
if e, err := DecodeBuildLogEntry(raw); err != nil || !reflect.DeepEqual(e.Imports, imports) || !reflect.DeepEqual(e.OutOfStep, outOfStep) ||
!strings.Contains(string(raw), `"out-of-step":["go/seats/meshbroker/contract.go",`) {
t.Fatalf("build end round trip: %v, %v, %v in %s", err, e.Imports, e.OutOfStep, raw)
}
start := end
start.Event, start.Outcome = BuildStartedEvent, ""
start.Event, start.Outcome, start.Imports = BuildStartedEvent, "", nil
if _, err := EncodeBuildLogEntry(start); err == nil {
t.Error("a build's start with files out of step encoded")
}
start.Imports, start.OutOfStep = imports, nil
if _, err := EncodeBuildLogEntry(start); err == nil {
t.Error("a build's start with imports encoded")
}
@@ -87,6 +93,7 @@ func TestPublishedPackagesAndImportsRoundTripAndRefuse(t *testing.T) {
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" },
"a narration out of step": func(e *BuildLogEntry) { e.OutOfStep = outOfStep },
"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
},