F-catalog-B: registry_mirrors reads distribution's mirrors, asks one again and drops one by hand

The state mirrors had no read tool and no ask-again (seams review note 1): a copy a follower holds and
does not end could only wait for a restart. registry_mirrors lists each kept copy and its claim, asks one
again with its claim let go, and drops one, saying refused under the ask's id so its waiter ends.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 17:10:09 +01:00
parent b4ab61c0aa
commit 6cd4aedb7c
4 changed files with 128 additions and 0 deletions
@@ -230,6 +230,28 @@ func (mirrors) Claim(key string, r holder.MirrorRequest, revision uint64) (bool,
return err == nil, err
}
func (mirrors) Delete(key string) error { return stdio.State(holder.MirrorsState).Delete(key) }
func (mirrors) List() (map[string]holder.MirrorRequest, error) {
keys, err := stdio.State(holder.MirrorsState).Keys()
if err != nil {
return nil, err
}
out := map[string]holder.MirrorRequest{}
for _, k := range keys {
e, err := stdio.State(holder.MirrorsState).Get(k)
if err != nil {
return nil, err
}
var r holder.MirrorRequest
if e != nil && json.Unmarshal(e.Value, &r) == nil {
out[k] = r
}
}
return out, nil
}
func (mirrors) Put(key string, r holder.MirrorRequest) error {
_, err := stdio.State(holder.MirrorsState).Put(key, r)
return err
}
// followMirrors runs every copy `mirror` took on, on the state's watch: the asks kept when the bundle starts (a copy a
// restart cut) and each new one, one at a time. Each is claimed first (Verbs.Take), so a second follower copies none
@@ -447,6 +469,32 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
used, _ := v.Objects.Used()
return map[string]any{"transfers": all, "used": used, "cap": v.CapNow(), "dropped_idle": idle}, err
}},
stdio.Tool{Name: "registry_mirrors", Description: "The copies of outside images mirror took on and has not ended (the state mirrors): each by its key, its source, digest, who asked and the follower that claimed it (none: it waits). With again, asks one again by hand, its claim let go so a follower takes it on; with drop, drops one by hand, saying refused on the image seat's events so its waiter ends (each a hand act, why required).",
Input: obj(map[string]any{"again": s("a kept copy's key to ask again"), "drop": s("a kept copy's key to drop"), "why": s("why (a hand act)")}),
Run: func(args map[string]any) (any, error) {
by, why := stdio.CallerOf(args), str(args, "why")
switch {
case str(args, "again") != "":
if why == "" {
return nil, errors.New("asking a copy again is a hand act: say why")
}
r, err := v.MirrorAgain(str(args, "again"))
if err != nil {
return nil, err
}
logf("the copy %s asked again by hand by %s: %s", str(args, "again"), by, why)
return map[string]any{"asked_again": str(args, "again"), "copy": r}, nil
case str(args, "drop") != "":
r, err := v.MirrorDrop(str(args, "drop"), by, why)
if err != nil {
return nil, err
}
logf("the copy %s dropped by hand by %s: %s", str(args, "drop"), by, why)
return map[string]any{"dropped": str(args, "drop"), "copy": r}, nil
}
all, err := v.MirrorsNow()
return map[string]any{"mirrors": all}, err
}},
stdio.Tool{Name: "registry_holds", Description: "What is held, since when and for whom; with release, lets one go through the seat's release as the operator's session (a hand act).",
Input: obj(map[string]any{"seat": s("the seat"), "release": s("the name to release"), "digest": s("its digest"), "why": s("why")}),
Run: func(args map[string]any) (any, error) {