H-revert: the node agent starts a held revert on the live data when its want says revert live
A want with `revert` `live` starts the reverted build with no restore, no aside taken and no catch-up, and applies as any want; without the field a revert with no restore point stays held. Builds: design 47 G §710 (G-21-provider-and-revert-ask.md, "On the answer"), §300, §567 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -1441,7 +1441,8 @@ func (a *Agent) settleOne(ctx context.Context, module string) {
|
||||
// be swallowed by "it runs what its want names" (issue 1585, the second failure). The operator's answer, again or
|
||||
// skip, in a revision after the one the hold took, is acted on whatever runs; any other new revision of the want
|
||||
// is the answer only for a move (ADR 0568 §4), as before.
|
||||
answered := w.Rev > att.AnsweredRev && (w.Want.Backup == meshwants.WantsByWantNodeModuleValueBackupAgain || w.Want.Backup == meshwants.WantsByWantNodeModuleValueBackupSkip)
|
||||
answered := w.Rev > att.AnsweredRev && (w.Want.Backup == meshwants.WantsByWantNodeModuleValueBackupAgain || w.Want.Backup == meshwants.WantsByWantNodeModuleValueBackupSkip ||
|
||||
w.Want.Revert == meshwants.WantsByWantNodeModuleValueRevertLive)
|
||||
if !answered && (runsWant || (att.AnsweredRev >= w.Rev && att.CompRev == comp.Rev)) {
|
||||
a.sayOnce("held:"+module, fmt.Sprintf("%s is held for a failed backup before %s: it waits for the operator's "+
|
||||
"answer, and its want (revision %d) carries none since the hold (revision %d)", module, w.Want.Fp, w.Rev, att.AnsweredRev))
|
||||
|
||||
@@ -212,7 +212,7 @@ func (a *Agent) askAndWait(ctx context.Context, h Have, kind string, k KeptMove,
|
||||
|
||||
// keptStep takes the restore points a move of a module needs before it applies and restores the ones a revert needs: it
|
||||
// answers whether the module is held (its have says why, and the running build is kept) and, after a restore, the catch-up
|
||||
// of the point to call once the reverted build runs. A want that says to go on without a backup asks neither.
|
||||
// of the point to call once the reverted build runs. A want that says to go on without a backup asks neither, and nor does one that answers a held revert live.
|
||||
func (a *Agent) keptStep(ctx context.Context, module string, w wantNow, comp compNow, ranBefore bool, others []compositions.Composed) (held bool, catchUps []meshrecovery.CatchUp) {
|
||||
r := a.env.Recovery
|
||||
if !ranBefore || r == nil || r.FormChanges == nil || w.Want.Backup == meshwants.WantsByWantNodeModuleValueBackupSkip {
|
||||
@@ -248,6 +248,12 @@ func (a *Agent) keptStep(ctx context.Context, module string, w wantNow, comp com
|
||||
if len(take)+len(restore) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
// The operator's answer "live" to a held revert, read from this want and from nothing else: the reverted build starts on the live
|
||||
// data as it is, with no restore, no aside taken and no catch-up, and applies as any want (design 47 G section 710).
|
||||
if len(restore) > 0 && w.Want.Revert == meshwants.WantsByWantNodeModuleValueRevertLive {
|
||||
a.env.Say(fmt.Sprintf("%s starts %s on the live data as it is: its want (revision %d) carries the operator's answer %q to the held revert", module, fp, w.Rev, w.Want.Revert))
|
||||
return false, nil
|
||||
}
|
||||
hold := func(words string, interrupted bool) {
|
||||
if interrupted || ctx.Err() != nil {
|
||||
words = BackupInterrupted
|
||||
|
||||
@@ -12,7 +12,9 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshrecovery"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
)
|
||||
|
||||
@@ -242,6 +244,34 @@ func testAMoveAcrossAFormChange(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("a held revert, then the want with revert live, starts the older build with no restore, no aside and no catch-up (design 47 G section 710)", func(t *testing.T) {
|
||||
f := &recoveryFake{changes: down}
|
||||
b := moveDB(t, f, "a", "b", heldHave)
|
||||
if h := b.haves.last("db"); !contains(h.Detail, "restore point missing") {
|
||||
t.Fatalf("the have %+v", h)
|
||||
}
|
||||
before := len(b.appliedList())
|
||||
_, _, dig2 := composition(t, "db", fpOf("b"), true, file("db", "x", "2"))
|
||||
raw, err := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fpOf("b"), Composed: dig2, Revert: meshwants.WantsByWantNodeModuleValueRevertLive,
|
||||
Words: "the operator answered \"live\" to the revert ask (asking 1)"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.wants.put(meshwants.WantsByWantNodeModuleKey(node, "db"), raw)
|
||||
eventually(t, "the older build runs on the answer", func() bool { h := b.haves.last("db"); return h.FP == fpOf("b") && h.Phase != "held" })
|
||||
if got := f.seen(); !slices.Equal(got, []string{"stop", "kept-points", "apply", "apply"}) && len(b.appliedList()) <= before {
|
||||
t.Fatalf("calls %v", got)
|
||||
}
|
||||
for _, c := range f.seen() {
|
||||
if c == "restore-kept" || c == "snapshot-kept" || c == "catch-up" {
|
||||
t.Fatalf("calls %v", f.seen())
|
||||
}
|
||||
}
|
||||
if n := strings.Count(strings.Join(f.seen(), " "), "kept-points"); n != 1 {
|
||||
t.Fatalf("the points were read again on the answer: %v", f.seen())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("a revert whose restore fails starts the newer build again and holds the move, saying why (A-113)", func(t *testing.T) {
|
||||
f := &recoveryFake{changes: down,
|
||||
points: []meshrecovery.Point{{Module: "db", Kept: "cache", Form: 1, Fingerprint: fp("a"), Snapshot: "snap-1", At: time.Now()}},
|
||||
|
||||
Reference in New Issue
Block a user