Design 32 (A config value renamed): the checker invokes seat:mesh-config.renames, never the module. A change bringing renamed-from to a holder that serves no renames (nothing answers the optional verb) is refused naming it; dropping a renamed-from while its window is open is refused naming the nodes holding it open; a check that cannot ask fails, saying so. The pull request's trunk and head manifests are read once for this and for the series changes (manifestPair). Rows in TestTheIndexersJudgeJudgesTheChangesManifests with the verb faked from the seat's definition (its fixture's answer): not served, served, open on g14, closed, an ask that fails, no seat to ask; the four refusals failed first. Manifest row for the invokes. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
128 lines
5.0 KiB
Go
128 lines
5.0 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-contracts/go/seats/meshconfig"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// A config value renamed (design 32 *A config value renamed*, DLV-C5): the checker asks the seat mesh-config's verb
|
|
// renames, never the module, for every change that brings `renamed-from` or drops one. A holder that does not serve the
|
|
// verb refuses `renamed-from`, naming that; while a rename's window is open, dropping its `renamed-from` is refused,
|
|
// naming the nodes holding it open; a check that cannot ask fails, saying so.
|
|
|
|
// RenamesSeat is the verb the checker asks, as its manifest's `invokes` names it.
|
|
var RenamesSeat = "seat:" + meshconfig.Seat + "." + meshconfig.VerbRenames
|
|
|
|
// ErrRenamesNotServed is the answer of Renames when the seat's holder serves no verb renames.
|
|
var ErrRenamesNotServed = errors.New("the holder of the seat " + meshconfig.Seat + " does not serve its verb " + meshconfig.VerbRenames)
|
|
|
|
// Renames asks the seat mesh-config's verb renames for a module's open renames.
|
|
type Renames interface {
|
|
Renames(ctx context.Context, module string) (meshconfig.RenamesAnswer, error)
|
|
}
|
|
|
|
// renameChange is what a pull request's head does to a module's `renamed-from` against the trunk: the declarations it
|
|
// brings (key from old name) and the ones it drops (key, old name).
|
|
type renameChange struct {
|
|
brought, dropped map[string]string
|
|
}
|
|
|
|
// renamesOf compares a module's config declarations at the trunk and at the head.
|
|
func renamesOf(was, now catalogue.Manifest) renameChange {
|
|
rc := renameChange{brought: map[string]string{}, dropped: map[string]string{}}
|
|
for key, d := range now.Settings {
|
|
if d.RenamedFrom != "" && was.Settings[key].RenamedFrom != d.RenamedFrom {
|
|
rc.brought[key] = d.RenamedFrom
|
|
}
|
|
}
|
|
for key, d := range was.Settings {
|
|
if d.RenamedFrom != "" && now.Settings[key].RenamedFrom != d.RenamedFrom {
|
|
rc.dropped[key] = d.RenamedFrom
|
|
}
|
|
}
|
|
return rc
|
|
}
|
|
|
|
// judgeRenames fails the check for a rename the seat refuses, a window a change would drop, or an ask that cannot be made.
|
|
func (st *state) judgeRenames(ctx context.Context) *delivery.Verdict {
|
|
if st.c.Try != nil {
|
|
return nil
|
|
}
|
|
var out *delivery.Verdict
|
|
failed := func(words string) {
|
|
f := delivery.FailedFirst{Machine: meshconfig.Seat, Words: words}
|
|
if out == nil {
|
|
out = &delivery.Verdict{State: delivery.VerdictFail, Failed: f}
|
|
return
|
|
}
|
|
addFailure(out, f)
|
|
}
|
|
for _, p := range st.c.Pulls {
|
|
for _, module := range p.Modules {
|
|
was, now, ok, err := st.manifestPair(ctx, p, module)
|
|
if err != nil {
|
|
failed(fmt.Sprintf("%s (%s): its config renames cannot be judged: %v", module, pullKey(p), err))
|
|
continue
|
|
}
|
|
if !ok {
|
|
was = catalogue.Manifest{} // a module new on the pull request brings every rename it declares
|
|
if now, ok = st.headManifest(ctx, p, module); !ok {
|
|
continue
|
|
}
|
|
}
|
|
rc := renamesOf(was, now)
|
|
if len(rc.brought) == 0 && len(rc.dropped) == 0 {
|
|
continue
|
|
}
|
|
if st.j.Renames == nil {
|
|
failed(fmt.Sprintf("%s (%s) changes a renamed-from, and this checker cannot ask %s", module, pullKey(p), RenamesSeat))
|
|
continue
|
|
}
|
|
answer, err := st.j.Renames.Renames(ctx, module)
|
|
switch {
|
|
case errors.Is(err, ErrRenamesNotServed):
|
|
for _, key := range slices.Sorted(maps.Keys(rc.brought)) {
|
|
failed(fmt.Sprintf("%s (%s) renames %s from %s: refused, %v, so the value cannot be moved", module, pullKey(p), key, rc.brought[key], err))
|
|
}
|
|
for _, key := range slices.Sorted(maps.Keys(rc.dropped)) {
|
|
failed(fmt.Sprintf("%s (%s) drops renamed-from %s of %s: refused, %v, so whether its window is open is not known", module, pullKey(p), rc.dropped[key], key, err))
|
|
}
|
|
continue
|
|
case err != nil:
|
|
failed(fmt.Sprintf("%s (%s) changes a renamed-from, and the check cannot ask %s: %v", module, pullKey(p), RenamesSeat, err))
|
|
continue
|
|
}
|
|
for _, key := range slices.Sorted(maps.Keys(rc.dropped)) {
|
|
for _, r := range answer.Renames {
|
|
if r.Module == module && r.From == rc.dropped[key] && r.To == key && len(r.Open) > 0 {
|
|
open := slices.Sorted(slices.Values(r.Open))
|
|
failed(fmt.Sprintf("%s (%s) drops renamed-from %s of %s while its rename window is open on %s", module, pullKey(p), r.From, key, strings.Join(open, ", ")))
|
|
}
|
|
}
|
|
}
|
|
st.c.note(fmt.Sprintf("%s.%s answered for %s (%s)", meshconfig.Seat, meshconfig.VerbRenames, module, pullKey(p)))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// headManifest is a module's manifest at the pull request's head, new on it: by its candidate's digest, else the forge's.
|
|
func (st *state) headManifest(ctx context.Context, p Pull, module string) (catalogue.Manifest, bool) {
|
|
if digest := p.Fps[module].Manifest; digest != "" && st.j.Read != nil {
|
|
if st.j.Manifests != nil && st.j.Manifests.ManifestDigests()[module] == digest {
|
|
return catalogue.Manifest{}, false // unchanged
|
|
}
|
|
m, err := st.j.Read.Manifest(ctx, digest)
|
|
return m, err == nil
|
|
}
|
|
return catalogue.Manifest{}, false
|
|
}
|