diff --git a/modules/mesh-delivery-indexer/internal/index/builds.go b/modules/mesh-delivery-indexer/internal/index/builds.go index 02a7a63b..fb7e5d94 100644 --- a/modules/mesh-delivery-indexer/internal/index/builds.go +++ b/modules/mesh-delivery-indexer/internal/index/builds.go @@ -1,6 +1,7 @@ package index import ( + "encoding/json" "fmt" "maps" "slices" @@ -28,9 +29,13 @@ type Builds struct { func NewBuilds() *Builds { return &Builds{records: map[string]delivery.BuildRecord{}} } // Put takes one entry of the builder's log. A build's end is an outcome: done registers, failed or queued does not; its -// start is a request, which registers nothing either. An entry that does not read, or names no module or repository, is -// refused and said. +// start is a request, which registers nothing either. An entry of another kind (the old reference form's close, design 51 +// *The node-engine's own build*) is no build and is passed over, whatever its form. A build's entry that does not read, or +// names no module or repository, is refused and said. func (b *Builds) Put(value []byte) error { + if !IsBuildEntry(value) { + return nil + } e, err := delivery.DecodeBuildLogEntry(value) if err != nil { return err @@ -179,3 +184,16 @@ func (b *Builds) Registered() []Registration { } return out } + +// IsBuildEntry says whether an entry of the builder's log is a build's start or end, read from its event alone, so an +// entry of another kind is passed over before its form is read. One whose event cannot be read is taken as a build's, so +// its decoding says what is wrong with it. +func IsBuildEntry(value []byte) bool { + var e struct { + Event *string `json:"event"` + } + if json.Unmarshal(value, &e) != nil || e.Event == nil { + return true + } + return *e.Event == delivery.BuildStartedEvent || *e.Event == delivery.BuildEndedEvent +} diff --git a/modules/mesh-delivery-indexer/internal/index/imports.go b/modules/mesh-delivery-indexer/internal/index/imports.go index f659f8e4..9042c833 100644 --- a/modules/mesh-delivery-indexer/internal/index/imports.go +++ b/modules/mesh-delivery-indexer/internal/index/imports.go @@ -23,6 +23,9 @@ import ( // Imports that cannot be resolved (a package the forge cannot list) are kept with no ImportForm: the next recompute that // resolves them writes the import form, one rebuild too many, never a stale module. func (ix *Indexer) ApplyBuildEnd(ctx context.Context, value []byte) error { + if !IsBuildEntry(value) { + return nil + } e, err := delivery.DecodeBuildLogEntry(value) if err != nil { return err diff --git a/modules/mesh-delivery-indexer/internal/index/registered_test.go b/modules/mesh-delivery-indexer/internal/index/registered_test.go index d5751cd6..3f33b733 100644 --- a/modules/mesh-delivery-indexer/internal/index/registered_test.go +++ b/modules/mesh-delivery-indexer/internal/index/registered_test.go @@ -3,6 +3,7 @@ package index import ( "context" "errors" + "fmt" "strings" "testing" @@ -313,4 +314,17 @@ func TestARecordOfAPullRequestDoesNotReplaceTheRegisteredRef(t *testing.T) { // if got := b.RegistrationsOf("photos"); len(got) != 1 || got[0].Ref != "main" { t.Fatalf("registrations = %+v", got) } + // An entry of another kind in the builder's log (the old reference form's close, design 51), of this form or another, + // is no build: passed over, never refused. + for _, other := range []string{ + fmt.Sprintf(`{"v":%d,"event":"old-form-closed","module":"node-engine","at":"2026-10-04T09:00:00Z"}`, delivery.Version), + `{"v":99,"event":"old-form-closed","at":"2026-10-04T09:00:00Z","nodes":["ace"]}`, + } { + if err := b.Put([]byte(other)); err != nil { + t.Errorf("an entry of another kind is refused: %v", err) + } + } + if got := b.RegistrationsOf("photos"); len(got) != 1 || got[0].Ref != "main" { + t.Fatalf("after entries of another kind, registrations = %+v", got) + } } diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go index 144d9d30..1520dfc2 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go @@ -209,7 +209,9 @@ func TestBuilderBuildsListsKeysAsStored(t *testing.T) { r.BuildLog = func(at time.Time, keys string, limit int) (*stdio.LogPage, error) { asked = keys return &stdio.LogPage{Entries: []stdio.LogEntry{{Sequence: 1, Value: entry(delivery.BuildStartedEvent, 0)}, - {Sequence: 2, Value: entry(delivery.BuildEndedEvent, 90*time.Second)}, {Sequence: 3, Value: entry(delivery.BuildStartedEvent, time.Hour)}}}, nil + {Sequence: 2, Value: entry(delivery.BuildEndedEvent, 90*time.Second)}, {Sequence: 3, Value: entry(delivery.BuildStartedEvent, time.Hour)}, + // an entry of another kind (the old reference form's close, design 51), of a form no build has: passed over + {Sequence: 4, Value: []byte(`{"v":99,"event":"old-form-closed","at":"2026-10-29T23:00:00Z","nodes":["ace"]}`)}}}, nil } out, err = run(t, r, view.BuildHistoryTool, map[string]any{"module": "netcheck", "since": t0.Format(time.RFC3339)}) if h, _ := out.(view.HistoryAnswer); err != nil || len(h.Builds) != 1 || h.Builds[0].Seconds != 90 || h.Builds[0].Outcome != delivery.BuildDone || asked != "netcheck" || h.More { diff --git a/modules/mesh-delivery-view/internal/view/read.go b/modules/mesh-delivery-view/internal/view/read.go index 5da2b0bb..5f236fef 100644 --- a/modules/mesh-delivery-view/internal/view/read.go +++ b/modules/mesh-delivery-view/internal/view/read.go @@ -870,8 +870,9 @@ type HistoryAnswer struct { } // BuildHistory is every build of module (every module when empty) that ended at or after since, oldest first, each with its -// duration from the start the log holds of the same module and fingerprint before it (delivery.BuildDuration). An entry that -// does not read is refused, naming its sequence. +// duration from the start the log holds of the same module and fingerprint before it (delivery.BuildDuration). An entry of +// another kind than a build's start or end is passed over, whatever its form; a build's entry that does not read is +// refused, naming its sequence. func (r Reader) BuildHistory(module string, since time.Time) (HistoryAnswer, error) { out := HistoryAnswer{Since: since.UTC(), Builds: []HistoryRow{}} if r.BuildLog == nil { @@ -894,6 +895,9 @@ func (r Reader) BuildHistory(module string, since time.Time) (HistoryAnswer, err } var entries []delivery.BuildLogEntry for _, le := range page.Entries { + if !isBuildEntry(le.Value) { + continue // an entry of another kind (the old reference form's close, design 51) is no build + } e, err := delivery.DecodeBuildLogEntry(le.Value) if err != nil { return out, fmt.Errorf("the builder's log %s, entry %d: %w", delivery.BuildLogRead, le.Sequence, err) @@ -923,3 +927,16 @@ func (r Reader) BuildHistory(module string, since time.Time) (HistoryAnswer, err out.More = page.Next != 0 return out, nil } + +// isBuildEntry says whether an entry of the builder's log is a build's start or end, read from its event alone, so an entry +// of another kind is passed over before its form is read. One whose event cannot be read is taken as a build's, so its +// decoding says what is wrong with it. +func isBuildEntry(value []byte) bool { + var e struct { + Event *string `json:"event"` + } + if json.Unmarshal(value, &e) != nil || e.Event == nil { + return true + } + return *e.Event == delivery.BuildStartedEvent || *e.Event == delivery.BuildEndedEvent +}