The catalog graph entry carries what the next revision is computed from and its package versions (milestone 47/G W3.1, inputs)

The lead's ruling on finding 1: an entry gains inputs (what its fingerprint is
computed from, the toolchains used, problems, warnings, named paths, the source
revisions it was taken in and its fingerprint moved at) and packages becomes
name to version, so the indexer's view of the trunk can be read back from the
newest trunk revision alone (ModuleValueOf). A revision's entries are the values
the recompute wrote, not the values it started from.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 11:45:56 +01:00
parent 6ff7aeb5cf
commit 9ab20f14f8
3 changed files with 60 additions and 6 deletions
@@ -6,11 +6,13 @@ import (
"encoding/json"
"fmt"
"maps"
"reflect"
"slices"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/sdk/graph"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The catalog graph (novox/hq ADR 0656; design 47, *Two graphs, one source of truth*): at each revision one entry per
@@ -22,13 +24,24 @@ import (
const CatalogEntryLayout = 1
// CatalogEntryOf is the catalog graph entry of one module: the module graph's node of it as a recompute computed it
// (its build, provision and verb edges, the verb edges left out on a cycle, ADR 0614), with where the trunk holds it and
// its manifest's digest and the main package its process runs (MainOf). The verdict and the acting rules are the revision's to add (they are not the node's).
// (its build, provision and verb edges, the verb edges left out on a cycle, ADR 0614), with where the trunk holds it,
// its manifest's digest and verdict, the main package its process runs (MainOf), the package versions it builds against
// and what the indexer computes its next revision from (Inputs: the lead's ruling on milestone 47/G finding 1), so that
// ModuleValueOf gives the module's value back from the entry alone. The acting rules are the revision's to add.
func CatalogEntryOf(module string, cur meshcatalog.ModulesByModuleValue, m catalogue.Manifest, n graph.Node) meshcatalog.CatalogEntry {
e := meshcatalog.CatalogEntry{V: CatalogEntryLayout, Module: module, Repository: cur.Repository, Path: cur.Path,
Commit: cur.Commit, Manifest: cur.Manifest, Fp: n.FP, None: n.None, Bases: n.Bases, Packages: n.Packages,
Provides: n.Provides, Requires: n.Requires, Awaits: n.Awaits, Invokes: n.Invokes, Serves: n.Serves, LeftOut: n.LeftOut,
Main: MainOf(m)}
Commit: cur.Commit, Manifest: cur.Manifest, Verdict: cur.Verdict, Fp: n.FP, None: n.None, Bases: n.Bases,
Packages: maps.Clone(cur.Packages), Provides: n.Provides, Requires: n.Requires, Awaits: n.Awaits, Invokes: n.Invokes,
Serves: n.Serves, LeftOut: n.LeftOut, Main: MainOf(m)}
in := meshcatalog.EntryInputs{Toolchains: maps.Clone(cur.Toolchains), Paths: maps.Clone(cur.Paths), Problems: cur.Problems,
Warnings: cur.Warnings, Unknown: cur.Unknown, Src: cur.Src, FpSrc: cur.FpSrc}
if cur.Inputs2 != nil {
from := meshcatalog.FingerprintInputs(*cur.Inputs2)
in.From = &from
}
if !reflect.ValueOf(in).IsZero() {
e.Inputs = &in
}
for language, t := range n.Toolchains {
if e.Toolchains == nil {
e.Toolchains = map[string]meshcatalog.BuildEdge{}
@@ -38,6 +51,24 @@ func CatalogEntryOf(module string, cur meshcatalog.ModulesByModuleValue, m catal
return e
}
// ModuleValueOf is the module's value as the indexer computes with it, read back from its catalog graph entry: the
// inverse of CatalogEntryOf, by which the indexer's view of the trunk is the newest trunk revision's entries (the lead's
// ruling on milestone 47/G finding 1; ADR 0656 §6: one form of the fact). The old line's fields (the old-form
// fingerprint, the reader needs) are not kept.
func ModuleValueOf(e meshcatalog.CatalogEntry) meshcatalog.ModulesByModuleValue {
v := meshcatalog.ModulesByModuleValue{V: keys.Version, Repository: e.Repository, Path: e.Path, Commit: e.Commit, Manifest: e.Manifest,
Verdict: e.Verdict, Fp2: e.Fp, None2: e.None, Packages: maps.Clone(e.Packages)}
if in := e.Inputs; in != nil {
v.Toolchains, v.Paths, v.Problems, v.Warnings, v.Unknown = maps.Clone(in.Toolchains), maps.Clone(in.Paths), in.Problems, in.Warnings, in.Unknown
v.Src, v.FpSrc = in.Src, in.FpSrc
if in.From != nil {
from := meshcatalog.ModulesByModuleValueInputs2(*in.From)
v.Inputs2 = &from
}
}
return v
}
// EncodeEntry is an entry's bytes and their sha256 in hex, the name of its object: the same entry in two revisions is
// one object (design 47, *An unchanged entry is stored once*).
func EncodeEntry(e meshcatalog.CatalogEntry) (string, []byte, error) {
@@ -176,9 +176,19 @@ func (ix *Indexer) keepTrunkRevision(ctx context.Context, edges map[string]graph
if ix.Revisions == nil {
return nil
}
// Each entry is the module's value as this recompute left it in modules (its fingerprint, toolchains, package versions
// and inputs written), so the revision holds what the next one is computed from.
held, err := ix.heldModules(ctx)
if err != nil {
return err
}
entries := make(map[string]meshcatalog.CatalogEntry, len(edges))
for name, e := range edges {
entries[name] = CatalogEntryOf(name, e.cur, e.manifest, whole[name])
cur, ok := held[name]
if !ok {
cur = e.cur
}
entries[name] = CatalogEntryOf(name, cur, e.manifest, whole[name])
}
ix.mu.Lock()
src := int64(ix.source.rev)
@@ -308,6 +308,19 @@ func TestBasesAreComputedFirstAndNoFingerprintTakesTheBuilder(t *testing.T) { //
e.Toolchains["go"] != (meshcatalog.BuildEdge{Module: "mesh-build-go", Version: "go1.27.1", Recipe: "1"}) {
t.Errorf("the catalog entry of avahi = %+v", e)
}
// W3.1's inputs (the lead's ruling on finding 1): an entry carries what the next revision is computed from (the
// fingerprint's inputs, the toolchains used, the package versions), so the module's value read back from it is the
// value modules held, but for the old line's fields.
for name := range mods.vals {
m, _ := moduleOf(t, mods, name)
m.Fp, m.None, m.Needs = "", "", nil
if back := ModuleValueOf(entries[name]); !reflect.DeepEqual(back, m) {
t.Errorf("%s read back from its entry = %+v, want %+v", name, back, m)
}
}
if e := entries["avahi"]; e.Inputs == nil || e.Inputs.From == nil || e.Inputs.Toolchains["go"].Version != "go1.27.1" {
t.Errorf("the catalog entry of avahi carries inputs %+v, want what its fingerprint is computed from", e.Inputs)
}
rev, objects, err := CatalogRevisionOf(meshcatalog.CatalogRevision{Revision: 1, Kind: "trunk", Source: 7}, entries)
if err != nil || len(rev.Entries) != len(mods.vals) || len(objects) != len(mods.vals) || rev.V != CatalogEntryLayout {
t.Fatalf("revision %+v with %d objects (%v), want one entry per module of %d", rev, len(objects), err, len(mods.vals))