Files
mesh-delivery/modules/mesh-delivery-builder/internal/versions/again.go
T
jschoubben bd3727a900 BL10: tagged versions and version.numbered go from the builder
The state tag-asks and its bounds, builder_tag, builder_tag_asks and builder_untag, the checker's tag-wanted
consumer, the branch end's removal of tagged versions, the numbering of a check's build under a tag and the
event version.numbered (its contract in builds, its emit, its manifest line) are removed; a forge message still
wakes a floor. builder_publish_again refuses a held build, since nothing says a version numbered.

Builds: design 47 G §373, §321, §349, §413

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 20:35:04 +01:00

142 lines
6.9 KiB
Go

package versions
import (
"context"
"errors"
"fmt"
"strings"
"time"
)
// What builder_publish_again does with one fingerprint of a name (design 51 *Tools of the builder*), after a holder failed.
const (
// AgainPublish is a held build whose recorded version is published again: refused since design 47 G §349 (no version.numbered is said).
AgainPublish = "publish"
// AgainBuild builds the fingerprint again under the same number: the build's file is gone and no published was said
// for the number, so the new digest replaces the recorded one (Numberer.Built).
AgainBuild = "build"
// AgainRenumber takes a new number: a published was said for the number, so other bytes never go under it.
AgainRenumber = "renumber"
)
// Again is what builder_publish_again does, with the entry it acts on and why.
type Again struct {
Do string `json:"do"`
Entry Entry `json:"entry"`
Why string `json:"why"`
}
// PublishAgain decides builder_publish_again for fingerprint among a name's entries; built says whether the builder still
// holds the build's file (its done key). It refuses a fingerprint not numbered, and a version refused for other bytes
// than the ones recorded under it.
func PublishAgain(entries []Entry, fingerprint string, built bool) (Again, error) {
fingerprint = strings.TrimSpace(fingerprint)
if fingerprint == "" {
return Again{}, errors.New("fp is required: the fingerprint whose version is published again")
}
for _, e := range latestFirst(entries) {
if e.Tag != "" || e.Fingerprint != fingerprint {
continue
}
switch {
case e.State == Refused && e.RefusedDigest != "" && e.Digest != "" && e.RefusedDigest != e.Digest:
return Again{}, fmt.Errorf("%s@%s was refused for other bytes (%s, recorded %s): it is not published again; a new build takes a new number",
e.Name, e.Version, e.RefusedDigest, e.Digest)
case e.State == Published || e.State == Removed:
return Again{Do: AgainRenumber, Entry: e, Why: fmt.Sprintf("%s@%s was %s, so the fingerprint takes a new number", e.Name, e.Version, e.State)}, nil
case !built:
return Again{Do: AgainBuild, Entry: e, Why: fmt.Sprintf("the build's file is gone and no published was said for %s@%s: built again under the same number", e.Name, e.Version)}, nil
}
return Again{Do: AgainPublish, Entry: e, Why: fmt.Sprintf("%s@%s (%s) is held", e.Name, e.Version, e.State)}, nil
}
return Again{}, fmt.Errorf("fingerprint %s is not numbered for this name: builder_versions lists what is", fingerprint)
}
// The tools' descriptions.
const (
PublishAgainDescription = "Publish one fingerprint's version again after its holder failed (design 51): with the build's file held, the " +
"nothing says a version numbered since design 47 G §349, so a held build is refused; with the file gone, it builds the fingerprint " +
"again under the same number, the new digest replacing the recorded one, since no published was said for it; after a published (or " +
"a removal) it takes a new number. It refuses a fingerprint not numbered and a version the holder refused for other bytes. A hand " +
"act, recorded with its caller and why in the builder's log."
FloorAgainDescription = "Ask the floor of one name again now, by hand (design 51 *The floor*): the seat's versions, the forge bridge's " +
"bridge_tags and the indexer's indexer_requirements, as the builder asks them by itself when a silent source says something. " +
"Answers which sources were silent and whether the floor is now known. A name the builder's versions already hold needs no floor: " +
"refused, saying so. A hand act."
)
// Acts are the builder's hand acts on its versions, answered by its program (the tools bundle writes them to reasks).
type Acts struct {
Numberer *Numberer
// Done says whether the build key of module and fingerprint is done: the build's file is held.
Done func(ctx context.Context, module, fp string) (bool, error)
// BuildAgain builds a failed key again (builder_build_again's path): its answer, or why it refused.
BuildAgain func(ctx context.Context, module, fp, why, by string) (answer string, refused bool)
// Reconsider looks at every module again, so a claim asks a name's floor.
Reconsider func(ctx context.Context)
// DropOldForm drops a retired node from the old form's count by hand (builder_old_form): its answer, or why not.
DropOldForm func(ctx context.Context, node, by, why string, at time.Time) (string, error)
// DropReserved drops a module's reservation of one MAJOR.MINOR by hand (builder_reserved_drop): its answer, or why not.
DropReserved func(ctx context.Context, module, series, by, why string, at time.Time) (string, error)
}
// PublishAgain is builder_publish_again of name's fingerprint: what it did, the version, and its answer; an error refuses.
func (a Acts) PublishAgain(ctx context.Context, name, fp, why, by string, at time.Time) (Again, string, error) {
module, _, _ := strings.Cut(name, "/")
entries, err := a.Numberer.Versions.Of(ctx, name)
if err != nil {
return Again{}, "", err
}
done, err := a.Done(ctx, module, fp)
if err != nil {
return Again{}, "", fmt.Errorf("the build key of %s at %s cannot be read: %w", module, fp, err)
}
ag, err := PublishAgain(entries, fp, done)
if err != nil {
return Again{}, "", err
}
if ag.Do == AgainPublish || (ag.Do == AgainRenumber && done) {
return Again{}, "", errors.New("nothing says a version numbered since design 47 G §349: the version is published under its reservation (§369)")
}
e := ag.Entry
if ag.Do == AgainRenumber {
_, ne, err := a.Numberer.Versions.Number(ctx, Claim{Name: e.Name, Kind: e.Kind, Series: e.Series, Module: e.Module, Fingerprint: e.Fingerprint,
Commit: e.Commit, Revision: e.Revision, Seat: e.Seat, Merge: e.Merge, Fresh: true})
if err != nil {
return Again{}, "", err
}
ag.Entry = ne
}
answer := ag.Why
if ag.Do == AgainBuild || (ag.Do == AgainRenumber && !done) {
said, refused := a.BuildAgain(ctx, module, fp, why, by)
if refused {
return Again{}, "", fmt.Errorf("%s; and the build again was refused: %s", ag.Why, said)
}
answer += "; " + said
}
return ag, answer, nil
}
// FloorAgain is builder_floor_again of name: the floor counts only for a name the versions hold nothing of.
func (a Acts) FloorAgain(ctx context.Context, name string) (string, error) {
entries, err := a.Numberer.Versions.Of(ctx, name)
if err != nil {
return "", err
}
if len(entries) > 0 {
return "", fmt.Errorf("%s holds %d versions: its floor counts only for its first number, so nothing is asked", name, len(entries))
}
answer := name + " waits on no floor in this builder: the floor is asked at its next claim"
if a.Numberer.Waits != nil {
for _, w := range a.Numberer.Waits.Waiting() {
if w.Name == name {
answer = fmt.Sprintf("the floor of %s is asked again (%s were silent)", name, strings.Join(w.Silent, ", "))
}
}
a.Numberer.Waits.Wake(WakeByHand, "", "", name)
}
a.Reconsider(ctx)
return answer + "; builder_versions shows its version once numbered", nil
}