Judge the store's mirror copies by what the builder's log says each build stood on, and the images a manifest pins by itself, so mirrors are recorded and image-missing covers what the controller judged (issue 1955, ADR 0571 clause 2)

This commit is contained in:
2026-10-29 23:49:58 +01:00
parent c2a6a6f6dd
commit 63023ca5fe
6 changed files with 357 additions and 64 deletions
@@ -66,6 +66,19 @@ func (runtimeBus) Get(state, key string) ([]byte, bool, error) {
func (runtimeBus) History(state, key string) ([]stdio.StateRevision, error) {
return stdio.State(state).History(key)
}
func (runtimeBus) LogSince(read string, at time.Time) ([]stdio.LogEntry, error) {
var out []stdio.LogEntry
page, err := stdio.Log(read).Since(at, "", 0)
for err == nil {
out = append(out, page.Entries...)
if page.Next == 0 {
return out, nil
}
page, err = stdio.Log(read).Read(page.Next, "", 0)
}
return nil, err
}
func (runtimeBus) Put(state, key string, value any) error {
_, err := stdio.State(state).Put(key, value)
return err
@@ -38,7 +38,9 @@ const (
WantsRead = delivery.AssignerModule + ".wants"
BuildsRead = delivery.BuilderModule + ".builds"
HavesRead = "node-engine.have"
LeaseRead = buscompose.ControllerLease
// ManifestsRead is the indexer's manifests: the images a manifest pins by itself.
ManifestsRead = delivery.IndexerModule + ".manifests"
LeaseRead = buscompose.ControllerLease
// FindingsState is the holder's own findings (health.FindingsState): image-missing.
FindingsState = health.FindingsState
// Module is this module, the observer its findings name.
@@ -62,6 +64,8 @@ type Bus interface {
Keys(state string) ([]string, error)
Get(state, key string) ([]byte, bool, error)
History(state, key string) ([]stdio.StateRevision, error)
// LogSince is every entry of a read log from a time on (stdio.Log(read).Since).
LogSince(read string, at time.Time) ([]stdio.LogEntry, error)
Put(state, key string, value any) error
Delete(state, key string) error
Publish(subject string, body any) error
@@ -98,6 +102,8 @@ type Build struct {
Module, FP, Key string
At time.Time
Artifacts []delivery.BuiltArtifact
// Manifest is the digest of the manifest it built, in the indexer's `manifests`.
Manifest string
}
// LeaseAway says the controller's lease names a holder that is no controller: then the seat sweeps.
@@ -134,7 +140,7 @@ func (h *Holder) Builds() ([]Build, error) {
if err != nil {
return nil, err
}
out = append(out, Build{Module: module, FP: fp, Key: k, At: at, Artifacts: b.Artifacts})
out = append(out, Build{Module: module, FP: fp, Key: k, At: at, Artifacts: b.Artifacts, Manifest: b.Manifest})
}
return out, nil
}
@@ -216,6 +222,44 @@ type Ref struct {
// KeepSet judges every artifact the done builds name in the store: kept, and why, or eligible. Packages and
// references into no store of the mesh's are no business of the store's.
func KeepSet(builds []Build, wants []Want) map[string]*Ref {
keeps := keptBuilds(builds, wants)
out := map[string]*Ref{}
for _, b := range builds {
for _, a := range b.Artifacts {
reference := catalogue.Recorded(a.Reference)
if _, ours := catalogue.InArtifactStore(reference); !ours || a.Kind == "package" {
continue
}
r := out[reference]
if r == nil {
_, _, kind := splitReference(reference)
r = &Ref{Reference: reference, Kind: kind}
out[reference] = r
}
r.Builds = append(r.Builds, b)
r.keptBy(b, keeps[b.Key])
}
}
return out
}
// keptBy adds a build naming r, kept for why (none: eligible).
func (r *Ref) keptBy(b Build, why []string) {
if b.At.After(r.At) {
r.At = b.At
}
if len(why) > 0 {
r.Kept = true
for _, w := range why {
if !contains(r.Why, w) {
r.Why = append(r.Why, w)
}
}
}
}
// keptBuilds is, by build key, why each kept build is kept: one of its module's five newest, or a want names it.
func keptBuilds(builds []Build, wants []Want) map[string][]string {
wanted := map[string][]string{}
for _, w := range wants {
wanted[w.Module+" "+w.FP] = append(wanted[w.Module+" "+w.FP], w.Node)
@@ -242,34 +286,7 @@ func KeepSet(builds []Build, wants []Want) map[string]*Ref {
}
}
}
out := map[string]*Ref{}
for _, b := range builds {
for _, a := range b.Artifacts {
reference := catalogue.Recorded(a.Reference)
if _, ours := catalogue.InArtifactStore(reference); !ours || a.Kind == "package" {
continue
}
r := out[reference]
if r == nil {
_, _, kind := splitReference(reference)
r = &Ref{Reference: reference, Kind: kind}
out[reference] = r
}
r.Builds = append(r.Builds, b)
if b.At.After(r.At) {
r.At = b.At
}
if why := keeps[b.Key]; len(why) > 0 {
r.Kept = true
for _, w := range why {
if !contains(r.Why, w) {
r.Why = append(r.Why, w)
}
}
}
}
}
return out
return keeps
}
func contains(s []string, v string) bool {
@@ -304,6 +321,10 @@ type judged struct {
view *View
wants []Want
built []Build
// mirrors are the copies in the store's mirror repositories, by reference: recorded ones also in refs.
mirrors map[string]*Ref
// mirrorsHeld says why no copy is eligible now, when one is not: a kept build whose bases the log does not name.
mirrorsHeld string
}
func (h *Holder) judge(ctx context.Context) (*judged, error) {
@@ -322,9 +343,89 @@ func (h *Holder) judge(ctx context.Context) (*judged, error) {
j.view = nil
}
}
if j.view != nil {
against, err := h.against()
if err != nil {
h.logf("the builder's log could not be read, so every copy is kept as unrecorded: %v", err)
against = nil
}
j.mirrors, j.mirrorsHeld = Mirrors(j.view, builds, keptBuilds(builds, wants), against)
for r, m := range j.mirrors {
if len(m.Builds) > 0 {
j.refs[r] = m
}
}
}
return j, nil
}
// against is what each build the builder's log says ended stood on, by build key: the bases' references.
func (h *Holder) against() (map[string][]string, error) {
entries, err := h.Bus.LogSince(delivery.BuildLogRead, h.now().Add(-delivery.BuilderLogDays*24*time.Hour))
if err != nil {
return nil, err
}
out := map[string][]string{}
for _, raw := range entries {
e, err := delivery.DecodeBuildLogEntry(raw.Value)
if err != nil || e.Event != delivery.BuildEndedEvent {
continue
}
key, err := delivery.BuilderBuildKey(e.Module, e.FP)
if err != nil {
continue
}
out[key] = e.Against
}
return out, nil
}
// Mirrors judges each copy in the store's mirror repositories (upstream/<host>/<path>, <module>/on-<argument>; ADR
// 0257) by what the builder's log says each build stood on (ADR 0571 clause 1): named by a kept build, kept; named
// only by builds no longer kept, eligible; named by no build the log holds, unrecorded and never removed (ADR 0189
// §10). While a kept build's bases are not in the log (it ended before the log's 30 days), no copy is eligible: what
// it stood on cannot be told, and held says so.
func Mirrors(v *View, builds []Build, kept map[string][]string, against map[string][]string) (map[string]*Ref, string) {
out := map[string]*Ref{}
for _, name := range v.L.RepoNames() {
if !mirrorRepository.MatchString(name) {
continue
}
for d := range v.L.Repos[name].Revisions {
r := catalogue.ArtifactStoreScheme + name + "@" + d
out[r] = &Ref{Reference: r, Kind: "image"}
}
}
held := ""
for _, b := range builds {
bases, logged := against[b.Key]
if !logged {
if len(kept[b.Key]) > 0 && held == "" {
held = "the builder's log does not say what the kept build " + b.Key + " stood on, so every copy is kept"
}
continue
}
for _, base := range bases {
if m := out[catalogue.Recorded(base)]; m != nil {
m.Builds = append(m.Builds, b)
why := kept[b.Key]
if len(why) > 0 {
why = []string{"a kept build stood on it: " + b.Key}
}
m.keptBy(b, why)
}
}
}
if held != "" {
for _, m := range out {
if len(m.Builds) > 0 && !m.Kept {
m.Kept, m.Why = true, []string{held}
}
}
}
return out, held
}
// Artifacts is the seat's `artifacts`, in the controller's answer's shape (Records): every reference the mesh made that
// lives in the store, kept with why or eligible, and with collected the ones the store no longer holds.
func (h *Holder) Artifacts(repository string) (*Records, error) {
@@ -651,11 +752,17 @@ func (h *Holder) ImagesOf(node string, address string) (Images, error) {
if err != nil {
return nil
}
images := []string{}
for _, a := range b.Artifacts {
if a.Kind != "image" && a.Kind != "upstream" || a.Reference == "" {
continue
if (a.Kind == "image" || a.Kind == "upstream") && a.Reference != "" {
images = append(images, catalogue.Routed(catalogue.Recorded(a.Reference), address))
}
image := catalogue.Routed(catalogue.Recorded(a.Reference), address)
}
pinned, err := h.Pinned(b.Manifest)
if err != nil {
return err
}
for _, image := range append(images, pinned...) {
i := byImage[image]
if i == nil {
i = &Image{Image: image}
@@ -708,6 +815,54 @@ func (h *Holder) ImagesOf(node string, address string) (Images, error) {
return out, nil
}
// Pinned is every container image a manifest names by itself, by digest (`<registry>/<path>@sha256:…`), read from the
// indexer's manifests: what a definition pins outright rather than through a build's artifact. A placeholder is not one.
func (h *Holder) Pinned(manifest string) ([]string, error) {
if manifest == "" {
return nil, nil
}
key, err := delivery.ManifestsKey(manifest)
if err != nil {
return nil, nil
}
raw, found, err := h.Bus.Get(ManifestsRead, key)
if err != nil || !found {
return nil, err
}
m, err := delivery.DecodeManifest(raw)
if err != nil {
return nil, nil
}
var parsed struct {
Resources []map[string]any `json:"resources"`
}
if json.Unmarshal([]byte(m.JSON), &parsed) != nil {
return nil, nil
}
var out []string
for _, r := range parsed.Resources {
image, _ := r["image"].(string)
if fmt.Sprint(r["type"]) == "container" && strings.Contains(image, "@sha256:") && !strings.Contains(image, "${") &&
!contains(out, image) {
out = append(out, image)
}
}
return out, nil
}
// inStoreAt is the store's reference of an image its address names, "" for an image of another registry (the
// controller's imageInStore).
func inStoreAt(image, address string) string {
if address == "" || !strings.HasPrefix(image, address+"/") || !strings.Contains(image, "@sha256:") {
return ""
}
r := catalogue.Recorded(image)
if _, ours := catalogue.InArtifactStore(r); !ours {
return ""
}
return r
}
// Observe writes S25's findings (image-missing): each want whose build names an image the store does not hold, on the
// want's machine, and deletes each finding that no longer holds. Nothing is said without a listing.
func (h *Holder) Observe(ctx context.Context) error {
@@ -718,6 +873,10 @@ func (h *Holder) Observe(ctx context.Context) error {
if j.view == nil {
return nil
}
address := ""
if d, err := h.Door(); err == nil {
address = d.Address()
}
builds := map[string]Build{}
for _, b := range j.built {
builds[b.Module+" "+b.FP] = b
@@ -728,11 +887,21 @@ func (h *Holder) Observe(ctx context.Context) error {
if !ok {
continue
}
var refs []string
for _, a := range b.Artifacts {
reference := catalogue.Recorded(a.Reference)
if a.Kind != "image" {
continue
if a.Kind == "image" {
refs = append(refs, catalogue.Recorded(a.Reference))
}
}
// And what its manifest pins by itself under the store's address, as the controller judged S25.
if pinned, err := h.Pinned(b.Manifest); err == nil && address != "" {
for _, image := range pinned {
if r := inStoreAt(image, address); r != "" {
refs = append(refs, r)
}
}
}
for _, reference := range refs {
if held, known := inStore(j.view, reference); !known || held {
continue
}
@@ -808,3 +977,44 @@ func (h *Holder) logf(format string, a ...any) {
h.Logf(format, a...)
}
}
// Mirror is one copy as the seat's `mirrors` answers it.
type Mirror struct {
Reference string `json:"reference"`
State string `json:"state"`
Why []string `json:"why,omitempty"`
StoodOn []string `json:"stood_on_by,omitempty"`
}
// MirrorsAnswer is the seat's `mirrors`: every copy, and why none is eligible when a kept build's bases are unknown.
type MirrorsAnswer struct {
Copies []Mirror `json:"copies"`
Held string `json:"held,omitempty"`
}
// MirrorsNow is every copy in the store's mirror repositories as the keep set judges it now.
func (h *Holder) MirrorsNow() (MirrorsAnswer, error) {
j, err := h.judge(context.Background())
if err != nil {
return MirrorsAnswer{}, err
}
if j.view == nil {
return MirrorsAnswer{}, errors.New("the store's listing could not be read, so no copy can be named")
}
out := MirrorsAnswer{Copies: []Mirror{}, Held: j.mirrorsHeld}
for _, r := range j.mirrors {
m := Mirror{Reference: r.Reference, State: "unrecorded", Why: r.Why}
switch {
case r.Kept:
m.State = "kept"
case len(r.Builds) > 0:
m.State = "eligible"
}
for _, b := range r.Builds {
m.StoodOn = append(m.StoodOn, b.Key)
}
out.Copies = append(out.Copies, m)
}
sort.Slice(out.Copies, func(a, b int) bool { return out.Copies[a].Reference < out.Copies[b].Reference })
return out, nil
}
@@ -30,6 +30,19 @@ type realBus struct {
ctx context.Context
nc *nats.Conn
js jetstream.JetStream
// log is the builder's log as the runtime's log verbs would read it (mesh-tools adds them; the sdk's own read of a
// log is tested in mesh-sdk's process package).
log []stdio.LogEntry
}
func (b realBus) LogSince(_ string, at time.Time) ([]stdio.LogEntry, error) {
var out []stdio.LogEntry
for _, e := range b.log {
if !e.At.Before(at) {
out = append(out, e)
}
}
return out, nil
}
func bucketOf(state string) string { return strings.Replace(state, ".", "_", 1) }
@@ -116,7 +129,7 @@ func TestTheSeatSweepsAtABuildsEndOnARealBusOnceTheLeaseIsAway(t *testing.T) {
if err != nil {
t.Fatal(err)
}
for _, s := range []string{BuildsRead, WantsRead, HavesRead, LeaseRead, "artifact-store-tools." + FindingsState} {
for _, s := range []string{BuildsRead, WantsRead, HavesRead, LeaseRead, ManifestsRead, "artifact-store-tools." + FindingsState} {
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: bucketOf(s), History: 5}); err != nil {
t.Fatal(err)
}
@@ -125,7 +138,8 @@ func TestTheSeatSweepsAtABuildsEndOnARealBusOnceTheLeaseIsAway(t *testing.T) {
// The mesh as aMesh lays it, copied onto the real buckets: letta's seven builds, a want, the store's listing.
mem, memb, door, _ := aMesh(t, &buscompose.LeaseHolder{Instance: "c1", Host: "novox", Epoch: 3})
for _, s := range []string{BuildsRead, WantsRead, LeaseRead} {
bus.log = memb.log
for _, s := range []string{BuildsRead, WantsRead, LeaseRead, ManifestsRead} {
keys, _ := memb.Keys(s)
for _, k := range keys[:min(len(keys), 6)] { // the seventh build ends later, on the bus
raw, _, _ := memb.Get(s, k)
@@ -207,7 +221,8 @@ func TestTheSeatSweepsAtABuildsEndOnARealBusOnceTheLeaseIsAway(t *testing.T) {
}
mu.Lock()
defer mu.Unlock()
if len(acts) != 2 || !strings.Contains(acts[0], archiveRef("letta", 2)) || !strings.Contains(acts[1], imageRef("letta", 2)) {
if len(acts) != 3 || !strings.Contains(acts[0], archiveRef("letta", 2)) || !strings.Contains(acts[1], imageRef("letta", 2)) ||
!strings.Contains(acts[2], mirror(1)) {
t.Fatalf("the hand acts recorded are %v; the store was asked %v", acts, door.asked)
}
if len(gone) != 1 || !strings.Contains(gone[0], `"fingerprint":"`+fp(2)+`"`) {
@@ -24,9 +24,20 @@ type memBus struct {
mu sync.Mutex
states map[string]map[string][]byte
history map[string][]stdio.StateRevision
log []stdio.LogEntry
published []string
}
func (b *memBus) LogSince(read string, at time.Time) ([]stdio.LogEntry, error) {
var out []stdio.LogEntry
for _, e := range b.log {
if read == delivery.BuildLogRead && !e.At.Before(at) {
out = append(out, e)
}
}
return out, nil
}
func (b *memBus) Keys(state string) ([]string, error) {
b.mu.Lock()
defer b.mu.Unlock()
@@ -107,6 +118,14 @@ func (d *memDoor) SpareKept(context.Context, []string, []string) error {
return nil
}
// mirror is a copy of an upstream base in the store, the n-th.
func mirror(n int) string {
return "artifact-store://upstream/docker.io/library/alpine@sha256:" + strings.Repeat(fmt.Sprintf("%x", n%16), 64)
}
// pinnedImage is the image letta's manifest names by itself, at the store's address.
const pinnedImage = "10.0.0.5:5000/letta/pinned@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
func digestOf(ref string) string { _, d, _ := splitReference(ref); return d }
func fp(n int) string { return "src2:" + strings.Repeat(fmt.Sprintf("%x", n%16), 64) }
@@ -149,6 +168,30 @@ func aMesh(t *testing.T, lease *buscompose.LeaseHolder) (*Holder, *memBus, *memD
}
}
listing.repo("letta/image").Revisions["sha256:"+strings.Repeat("e", 64)] = true // no build names it
listing.repo("letta/pinned").Revisions["sha256:"+strings.Repeat("c", 64)] = true
// Three copies of a base: letta's 2nd stood on the first, its 7th on the second, and no build on the third.
for n := 1; n <= 3; n++ {
repo, digest, _ := splitReference(mirror(n))
listing.repo(repo).Revisions[digest] = true
door.holds[mirror(n)] = true
}
for n := 1; n <= 7; n++ {
e := delivery.BuildLogEntry{Event: delivery.BuildEndedEvent, Module: "letta", FP: fp(n), Repository: "novox/x", Commit: "c",
Node: "ace", At: day.Add(time.Duration(n) * time.Hour), Outcome: "done"}
switch n {
case 2:
e.Against = []string{mirror(1)}
case 7:
e.Against = []string{mirror(2)}
}
raw, _ := delivery.EncodeBuildLogEntry(e)
b.log = append(b.log, stdio.LogEntry{Sequence: uint64(n), Key: "letta", Value: raw, At: e.At})
}
manifest, _ := delivery.EncodeManifest(delivery.Manifest{Module: "letta",
JSON: `{"module":"letta","resources":[{"id":"app","type":"container","image":"` + pinnedImage + `"},` +
`{"id":"built","type":"container","image":"${artifact:image}"}]}`})
mk, _ := delivery.ManifestsKey(strings.Repeat("d", 64))
_ = b.Put(ManifestsRead, mk, json.RawMessage(manifest))
want, _ := json.Marshal(delivery.Want{V: delivery.Version, FP: fp(1), Self: true})
_ = b.Put(WantsRead, "want.ace.letta", json.RawMessage(want))
if lease != nil {
@@ -190,7 +233,14 @@ func TestTheSeatKeepsTheFiveNewestAndTheWantedAndSweepsOnlyOnceTheLeaseIsAway(t
t.Errorf("letta's build %d is %s and %s, not %s", n, states[imageRef("letta", n)], states[archiveRef("letta", n)], want)
}
}
if len(r.References) != 14 || r.Counts["kept"] != 12 || r.Counts["eligible"] != 2 || r.Store != "10.0.0.5:5000" {
// And the copies: the one letta's 2nd stood on is eligible with it, the 7th's kept; the third no build names.
if states[mirror(1)] != "eligible" || states[mirror(2)] != "kept" || states[mirror(3)] != "" {
t.Errorf("the copies are %s, %s and %q", states[mirror(1)], states[mirror(2)], states[mirror(3)])
}
if m, err := h.MirrorsNow(); err != nil || len(m.Copies) != 3 || m.Copies[2].State != "unrecorded" || m.Held != "" {
t.Errorf("mirrors answered %+v: %v", m, err)
}
if len(r.References) != 16 || r.Counts["kept"] != 13 || r.Counts["eligible"] != 3 || r.Store != "10.0.0.5:5000" {
t.Errorf("the references are %d, counted %v, at %q", len(r.References), r.Counts, r.Store)
}
@@ -202,16 +252,22 @@ func TestTheSeatKeepsTheFiveNewestAndTheWantedAndSweepsOnlyOnceTheLeaseIsAway(t
letGo []string
left int
skipped int
unlog int
}{
{name: "the lease names a controller", lease: controller},
{name: "no lease key", lease: nil},
{name: "handed away", lease: handed, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), imageRef("letta", 2)}},
{name: "an index waits", lease: handed, index: true, most: MostPerSweep, letGo: []string{archiveRef("letta", 2)}, skipped: 1},
{name: "bounded", lease: handed, most: 1, letGo: []string{archiveRef("letta", 2)}, left: 1},
{name: "handed away", lease: handed, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), imageRef("letta", 2), mirror(1)}},
{name: "an index waits", lease: handed, index: true, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), mirror(1)}, skipped: 1},
{name: "bounded", lease: handed, most: 1, letGo: []string{archiveRef("letta", 2)}, left: 2},
// A kept build the log no longer holds: what it stood on is unknown, so no copy goes.
{name: "a kept build's bases unknown", lease: handed, most: MostPerSweep, letGo: []string{archiveRef("letta", 2), imageRef("letta", 2)}, unlog: 3},
} {
t.Run(c.name, func(t *testing.T) {
h, b, door, acts := aMesh(t, c.lease)
door.indexes[imageRef("letta", 2)] = c.index
if c.unlog > 0 {
b.log = append(b.log[:c.unlog-1:c.unlog-1], b.log[c.unlog:]...)
}
if c.most == 0 {
h.AfterBuild("letta.x")
if _, err := h.Collect("tidy", true, 0); err == nil {
@@ -275,12 +331,21 @@ func TestAWantedImageTheStoreLacksIsAFindingAndImagesNamesWantsAndHaves(t *testi
if _, found, _ := b.Get(FindingsState, key); found {
t.Fatal("the finding stayed once the store held the image again")
}
// An image the manifest pins by itself, under the store's address, is judged as the controller judged it.
delete(view.L.Repos["letta/pinned"].Revisions, "sha256:"+strings.Repeat("c", 64))
if err := h.Observe(context.Background()); err != nil {
t.Fatal(err)
}
if raw, found, _ := b.Get(FindingsState, key); !found || !strings.Contains(string(raw), "letta/pinned@sha256:") {
t.Fatalf("the pinnedImage image missing is %s", raw)
}
have, _ := json.Marshal(delivery.Have{V: delivery.Version, FP: fp(7), Phase: "running"})
_ = b.Put(HavesRead, "ace.letta", json.RawMessage(have))
images, err := h.ImagesOf("ace", "10.0.0.5:5000")
if err != nil || len(images.Images) != 2 || !images.SentKnown || images.Images[0].Image != "10.0.0.5:5000/letta/image@sha256:"+strings.Repeat("1", 64) ||
fmt.Sprint(images.Images[0].In) != "[declared]" || fmt.Sprint(images.Images[1].In) != "[sent]" {
if err != nil || len(images.Images) != 3 || !images.SentKnown || images.Images[0].Image != "10.0.0.5:5000/letta/image@sha256:"+strings.Repeat("1", 64) ||
fmt.Sprint(images.Images[0].In) != "[declared]" || fmt.Sprint(images.Images[1].In) != "[sent]" ||
images.Images[2].Image != pinnedImage || fmt.Sprint(images.Images[2].In) != "[declared sent]" {
t.Fatalf("the images of ace are %+v: %v", images, err)
}
}
@@ -5,9 +5,7 @@ package main
// ok, answer}.
import (
"context"
"fmt"
"sort"
"strings"
stdio "git.novox.be/novox/mesh-sdk/go"
@@ -67,29 +65,19 @@ func SeatTools(h *Holder, s *Store) []stdio.Tool {
return verbAnswer{Output: fmt.Sprintf("let go of %d, would let go of %d, %d left\n", len(a.LetGo),
len(a.WouldLetGo), a.Left), OK: true, Answer: a}, nil
}},
{Name: seat + "mirrors", Description: "The bases the mesh copied into its store, as the store lists them.",
{Name: seat + "mirrors", Description: "The bases the mesh copied into its store, each kept (a kept build stood on " +
"it), eligible (only builds no longer kept stood on it) or unrecorded (no build the builder's log holds stood " +
"on it: never removed).",
Run: func(args map[string]any) (any, error) {
if text(args, "record") != "" {
return nil, fmt.Errorf("mirrors records nothing on this seat: a copy is kept or let go of by the builds " +
"the builder's log says stood on it (novox/hq ADR 0571 clause 1), and until that log names them every " +
"copy is unrecorded and never removed")
return nil, fmt.Errorf("mirrors records nothing by hand on this seat: a copy is recorded by the builds the " +
"builder's log says stood on it (novox/hq ADR 0571 clause 1)")
}
v, err := s.View(context.Background())
m, err := h.MirrorsNow()
if err != nil {
return nil, err
}
var copies []string
for _, name := range v.L.RepoNames() {
if !mirrorRepository.MatchString(name) {
continue
}
for d := range v.L.Repos[name].Revisions {
copies = append(copies, catalogue.ArtifactStoreScheme+name+"@"+d)
}
}
sort.Strings(copies)
return verbAnswer{Output: fmt.Sprintf("%d copies, each unrecorded and never removed\n", len(copies)), OK: true,
Answer: map[string]any{"unrecorded": nonNil(copies)}}, nil
return verbAnswer{Output: fmt.Sprintf("%d copies\n", len(m.Copies)), OK: true, Answer: m}, nil
}},
{Name: seat + "images", Description: "Every container image one machine's wants and haves name.",
Run: func(args map[string]any) (any, error) {
+2
View File
@@ -25,6 +25,8 @@
"reads": [
"mesh-delivery-assigner.wants",
"mesh-delivery-builder.builds",
"mesh-delivery-builder.build-log",
"mesh-delivery-indexer.manifests",
"node-engine.have",
"mesh-controller.lease"
],