Raise S20 for every generation refusal, ask for a push when the counter was behind, and write a send's generation with its digest (hq issue 234 review)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheResolverAndWhatAsksItComposeOnOneMachine (0.03s)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheResolverAndWhatAsksItComposeOnOneMachine (0.03s)
mesh/delivery superseded: a newer head of the same pull request
A refusal of a send the mesh never recorded went only to stderr; it is now kept with its sender said to be unknown, and raised. A counter behind the machine (a store put back) is raised and the condition names push, since the receive loop must not push. The node's digest and generation are one transaction, and a send record that fails is said loudly without failing the send. The trigger ignores an update that changes nothing, and the put-back mark is dropped: a put-back is composed afresh with the current generation.
This commit is contained in:
@@ -515,7 +515,7 @@ func composedAndValidated(ctx context.Context, open *stores, node string, gens m
|
||||
return sendable{}, nil, err
|
||||
}
|
||||
// And the generation it was last sent, as the would-send is (novox/hq issue 234).
|
||||
if declared.Generation, declared.PutBack, err = open.inventory.SentGeneration(ctx, record.ID); err != nil {
|
||||
if declared.Generation, err = open.inventory.SentGeneration(ctx, record.ID); err != nil {
|
||||
return sendable{}, nil, err
|
||||
}
|
||||
body, err := declared.Body()
|
||||
|
||||
@@ -994,8 +994,7 @@ func gateFailed(ctx context.Context, open *stores, p *inventory.Plan, module str
|
||||
}
|
||||
return
|
||||
}
|
||||
sent, err := sendRollout(withScope(ctx, sendScope{modules: map[string]bool{module: true}, putBack: true}), open,
|
||||
g.Machines)
|
||||
sent, err := sendRollout(withScope(ctx, sendScope{modules: map[string]bool{module: true}}), open, g.Machines)
|
||||
if err != nil {
|
||||
notBack(fmt.Sprintf("its registered build is back at %s, and sending it to %s was refused: %v — `push %s` "+
|
||||
"sends it", short(previous.Commit), strings.Join(g.Machines, ", "), err, g.Machines[0]))
|
||||
@@ -1088,7 +1087,7 @@ func sendRollbacks(ctx context.Context, open *stores, p *inventory.Plan, b *roll
|
||||
}
|
||||
inv := open.inventory
|
||||
sort.Strings(b.machines)
|
||||
sent, err := sendRollout(withScope(ctx, sendScope{modules: b.modules, putBack: true}), open, b.machines)
|
||||
sent, err := sendRollout(withScope(ctx, sendScope{modules: b.modules}), open, b.machines)
|
||||
var back []string
|
||||
for _, r := range b.pending {
|
||||
if err != nil {
|
||||
|
||||
@@ -33,15 +33,20 @@ const kindOlderGeneration = "older-generation"
|
||||
// generationRefusalsSaid is how long a refused send is said after its refusal: an hour, as an advisory is.
|
||||
const generationRefusalsSaid = advisoryQuiet
|
||||
|
||||
// stamp gives a composed declaration the order allotted to it before it was composed: its sequence, the
|
||||
// epoch and generation when the machine reads them, and the put-back mark beside a generation — and keeps
|
||||
// the generation and acting epoch for the record of the send whether or not the machine is sent them.
|
||||
// stamp gives a composed declaration the order allotted to it before it was composed: its sequence, and
|
||||
// the epoch and generation when the machine reads them — and keeps the generation and acting epoch for the
|
||||
// record of the send whether or not the machine is sent them.
|
||||
//
|
||||
// **No put-back mark is sent.** A gate puts a machine back by composing it again (sendRollout), so its
|
||||
// declaration is allotted here like any other and carries the generation as it stands — never older than
|
||||
// what the machine applied. The node-engine reads a `put_back` key (mesh-host#82); this controller never
|
||||
// needs to send one.
|
||||
func (o order) stamp(d *sendable) {
|
||||
d.Sequence, d.Epoch = o.sequence, o.epoch
|
||||
d.composedFrom, d.actingEpoch, d.putBackSend = o.generation, o.acting, o.putBack
|
||||
d.Generation, d.PutBack = 0, false
|
||||
d.composedFrom, d.actingEpoch = o.generation, o.acting
|
||||
d.Generation = 0
|
||||
if o.readsGeneration && o.generation > 0 {
|
||||
d.Generation, d.PutBack = o.generation, o.putBack
|
||||
d.Generation = o.generation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,18 +55,17 @@ type sentRecord struct {
|
||||
sequence int64
|
||||
epoch uint64
|
||||
generation int64
|
||||
putBack bool
|
||||
// told is the generation and put-back mark on the wire, which the would-send is stamped with.
|
||||
// toldGeneration is the generation on the wire, which the would-send is stamped with: zero for a machine
|
||||
// whose node-engine has not said it reads one.
|
||||
toldGeneration int64
|
||||
toldPutBack bool
|
||||
sender string
|
||||
modules []string
|
||||
}
|
||||
|
||||
// sentRecordOf is a composed send's record: its sender is the caller this process acts for.
|
||||
func sentRecordOf(ctx context.Context, d sendable) sentRecord {
|
||||
return sentRecord{sequence: d.Sequence, epoch: d.actingEpoch, generation: d.composedFrom, putBack: d.putBackSend,
|
||||
toldGeneration: d.Generation, toldPutBack: d.PutBack, sender: senderOf(callerOf(ctx)), modules: d.modules}
|
||||
return sentRecord{sequence: d.Sequence, epoch: d.actingEpoch, generation: d.composedFrom,
|
||||
toldGeneration: d.Generation, sender: senderOf(callerOf(ctx)), modules: d.modules}
|
||||
}
|
||||
|
||||
// callerOf is who asked for what this process does: the seat call's caller when ctx belongs to one, the
|
||||
@@ -92,13 +96,19 @@ func namedModules(plan catalogue.Resolution, leftOut map[string]string) []string
|
||||
return out
|
||||
}
|
||||
|
||||
// heardGenerationRefusal keeps a machine's refusal of a send for its generation on the send it refused,
|
||||
// so S20 names its sender, and says it (novox/hq issue 234).
|
||||
// heardGenerationRefusal keeps a machine's refusal of a send for its generation, so S20 names its sender
|
||||
// and says it (novox/hq issue 234): on the send it refused or — when the mesh has no record of sending that
|
||||
// sequence — as a refusal of its own, naming the sender as unknown. Either way S20 is raised: a refusal the
|
||||
// mesh cannot attribute is the louder fact, not a quieter one.
|
||||
//
|
||||
// **And raises the mesh's counter past what the machine applied, when the refused send was composed from
|
||||
// the counter as it stands**: then the sender's view was not stale — the counter is behind the machine,
|
||||
// which is a store put back from a backup — and every send after would be refused for ever. A stale
|
||||
// sender's generation is below the counter, and the counter is left alone for it.
|
||||
// the counter as it stands** (counterBehind): then the sender's view was not stale — the counter is behind
|
||||
// the machine, which is a store put back from a backup — and every send after would be refused for ever. A
|
||||
// stale sender's generation is below the counter, and the counter is left alone for it.
|
||||
//
|
||||
// Nothing is sent from here. This runs in the controller's receive loop, and a push from it would hold that
|
||||
// loop, and the machine's hold, for as long as the push takes — the deaf controller of issues 184 and 185.
|
||||
// S20 names `push <node>` for that case instead.
|
||||
func heardGenerationRefusal(ctx context.Context, inv *inventory.Inventory, report link.Report) {
|
||||
r := report.OlderGeneration
|
||||
if r == nil || inv == nil || report.Node == "" {
|
||||
@@ -106,35 +116,44 @@ func heardGenerationRefusal(ctx context.Context, inv *inventory.Inventory, repor
|
||||
}
|
||||
node, err := inv.NodeByName(ctx, report.Node)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused a send for its generation, and the machine cannot be read: %v\n",
|
||||
report.Node, err)
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused a send for its generation, and the machine cannot be "+
|
||||
"read, so it is not raised: %v\n", report.Node, err)
|
||||
return
|
||||
}
|
||||
send, found, err := inv.RefusedSend(ctx, node.ID, report.Sequence, r.Applied)
|
||||
switch {
|
||||
case err != nil:
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused send %d for its generation, and the refusal could not be "+
|
||||
"kept: %v\n", report.Node, report.Sequence, err)
|
||||
case !found:
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused send %d (generation %d; it applied %d), which the mesh "+
|
||||
"has no record of sending\n", report.Node, report.Sequence, r.Generation, r.Applied)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused send %d from %s: composed from assignment generation %d, "+
|
||||
"and it applied %d\n", report.Node, report.Sequence, send.Sender, r.Generation, r.Applied)
|
||||
var raised int64
|
||||
if now, err := inv.AssignmentGeneration(ctx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused a send for its generation, and the mesh's own cannot be "+
|
||||
"read: %v\n", report.Node, err)
|
||||
} else if counterBehind(*r, now) {
|
||||
if raised, err = inv.RaiseAssignmentGeneration(ctx, r.Applied); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: the assignment generation (%d) is behind what %s applied (%d), "+
|
||||
"and could not be raised: %v\n", now, report.Node, r.Applied, err)
|
||||
raised = 0
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: the assignment generation was %d, behind what %s applied (%d) — "+
|
||||
"a store put back from a backup — and is raised to %d; `push %s` sends it what the mesh holds now\n",
|
||||
now, report.Node, r.Applied, raised, report.Node)
|
||||
}
|
||||
}
|
||||
now, err := inv.AssignmentGeneration(ctx)
|
||||
if err != nil || r.Generation < now || r.Applied < now {
|
||||
return
|
||||
send, found, err := inv.RefusedSend(ctx, node.ID, report.Sequence, r.Applied, raised)
|
||||
if err == nil && !found {
|
||||
send, err = inv.RecordUnrecordedRefusal(ctx, inventory.Send{Node: node.ID, Sequence: report.Sequence,
|
||||
Epoch: report.Epoch, Generation: r.Generation, Digest: report.Declared, RefusedApplied: r.Applied,
|
||||
CounterRaisedTo: raised})
|
||||
}
|
||||
raised, err := inv.RaiseAssignmentGeneration(ctx, r.Applied)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: the assignment generation (%d) is behind what %s applied (%d), and "+
|
||||
"could not be raised: %v\n", now, report.Node, r.Applied, err)
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused send %d for its generation, and the refusal could not be "+
|
||||
"kept, so it is not raised: %v\n", report.Node, report.Sequence, err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: the assignment generation was %d, behind what %s applied (%d) — a store "+
|
||||
"put back from a backup — and is raised to %d, so the next send is not refused\n", now, report.Node, r.Applied,
|
||||
raised)
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: %s refused send %d from %s: composed from assignment generation %d, "+
|
||||
"and it applied %d\n", report.Node, report.Sequence, send.Sender, r.Generation, r.Applied)
|
||||
}
|
||||
|
||||
// counterBehind says a refusal shows the mesh's counter behind the machine rather than a stale sender: the
|
||||
// refused send carried the counter as it stands now, and the machine applied more than that.
|
||||
func counterBehind(r link.GenerationRefusal, now int64) bool {
|
||||
return r.Generation >= now && r.Applied > r.Generation
|
||||
}
|
||||
|
||||
// watchGenerationRefusals is S20: every send a machine refused within the hour for the generation it was
|
||||
@@ -147,7 +166,7 @@ func watchGenerationRefusals(f *signalFacts) []conditions.Observation {
|
||||
continue
|
||||
}
|
||||
seen[s.NodeName] = true
|
||||
out = append(out, conditions.Observation{Scope: conditions.ScopeMachine, ID: s.NodeName, Kind: kindOlderGeneration,
|
||||
o := conditions.Observation{Scope: conditions.ScopeMachine, ID: s.NodeName, Kind: kindOlderGeneration,
|
||||
Machine: s.NodeName, Severity: conditions.Warning,
|
||||
Summary: fmt.Sprintf("%s refused sequence %d from %s: it was composed from assignment generation %d, and "+
|
||||
"%s applied generation %d — a sender composing from a view of the assignments the mesh has moved "+
|
||||
@@ -157,7 +176,20 @@ func watchGenerationRefusals(f *signalFacts) []conditions.Observation {
|
||||
Headline: fmt.Sprintf("%s refused an out-of-date update", s.NodeName),
|
||||
Explanation: fmt.Sprintf("Something sent %s an update made from an older list of what runs there. %s "+
|
||||
"refused it, so nothing was removed. Nothing for you to do unless it repeats.", s.NodeName, s.NodeName),
|
||||
Resolved: fmt.Sprintf("%s has had no out-of-date update for an hour", s.NodeName)})
|
||||
Resolved: fmt.Sprintf("%s has had no out-of-date update for an hour", s.NodeName)}
|
||||
if s.CounterRaisedTo > 0 {
|
||||
// Not a stale sender: the mesh's counter was behind the machine (a store put back from a backup) and
|
||||
// was raised; nothing has sent the machine its declaration since, so a person is asked to.
|
||||
o.Summary = fmt.Sprintf("%s refused sequence %d from %s: it was composed from assignment generation %d, "+
|
||||
"the mesh's own, and %s applied generation %d — the mesh's counter was behind the machine (a store "+
|
||||
"put back from a backup?) and is raised to %d; `push %s` sends it what the mesh holds now",
|
||||
s.NodeName, s.Sequence, s.Sender, s.Generation, s.NodeName, s.RefusedApplied, s.CounterRaisedTo,
|
||||
s.NodeName)
|
||||
o.Explanation = fmt.Sprintf("Needs you: push %s. The mesh's record was older than %s, so %s refused its "+
|
||||
"update and kept what it had. The record is repaired; a push sends the update again.",
|
||||
s.NodeName, s.NodeName, s.NodeName)
|
||||
}
|
||||
out = append(out, o)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -181,12 +213,7 @@ func writeLastSend(ctx context.Context, w io.Writer, inv *inventory.Inventory, n
|
||||
if s.Generation > 0 {
|
||||
generation = fmt.Sprintf("assignment generation %d", s.Generation)
|
||||
}
|
||||
kind := "sent"
|
||||
if s.PutBack {
|
||||
kind = "put back"
|
||||
}
|
||||
fmt.Fprintf(w, "%s was last %s sequence %d at %s by %s, composed from %s, naming %s\n", node, kind,
|
||||
s.Sequence, s.SentAt.Local().Format("2006-01-02 15:04:05"), s.Sender, generation, modulesWords(s.Modules))
|
||||
fmt.Fprintf(w, "%s was last sent sequence %d at %s by %s, composed from %s, naming %s\n", node, s.Sequence, s.SentAt.Local().Format("2006-01-02 15:04:05"), s.Sender, generation, modulesWords(s.Modules))
|
||||
if s.RefusedAt != nil {
|
||||
fmt.Fprintf(w, " and refused it at %s: it had applied generation %d\n",
|
||||
s.RefusedAt.Local().Format("2006-01-02 15:04:05"), s.RefusedApplied)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
// The assignment generation a declaration was composed from (novox/hq issue 234).
|
||||
@@ -25,45 +26,35 @@ func bodyKeys(t *testing.T, s sendable) map[string]any {
|
||||
}
|
||||
|
||||
// **The generation goes on the wire only to a machine whose node-engine said it reads one**: an older
|
||||
// node-engine decodes strictly and refuses an unknown key, whole. And the put-back mark only with it.
|
||||
// node-engine decodes strictly and refuses an unknown key, whole. No put-back mark is ever sent: a gate
|
||||
// composes its put-back afresh, with the generation as it stands.
|
||||
func TestTheGenerationIsSentOnlyToAMachineThatReadsOne(t *testing.T) {
|
||||
resources := []map[string]any{{"id": "a", "type": "file", "path": "/etc/a", "content": "x\n"}}
|
||||
reads := order{sequence: 12, epoch: 57, generation: 40, readsGeneration: true, acting: 57, putBack: true}
|
||||
reads := order{sequence: 12, epoch: 57, generation: 40, readsGeneration: true, acting: 57}
|
||||
var told sendable
|
||||
told.Resources = resources
|
||||
reads.stamp(&told)
|
||||
keys := bodyKeys(t, told)
|
||||
if keys["generation"] != float64(40) || keys["put_back"] != true || keys["sequence"] != float64(12) {
|
||||
if keys["generation"] != float64(40) || keys["sequence"] != float64(12) {
|
||||
t.Fatalf("a machine that reads a generation was sent %v", keys)
|
||||
}
|
||||
if _, there := keys["put_back"]; there {
|
||||
t.Fatalf("a put-back mark was sent: %v", keys)
|
||||
}
|
||||
if told.composedFrom != 40 || told.actingEpoch != 57 {
|
||||
t.Errorf("the send does not keep what it was composed from for its record: %+v", told)
|
||||
}
|
||||
|
||||
older := order{sequence: 12, epoch: 57, generation: 40, readsGeneration: false, acting: 57, putBack: true}
|
||||
older := order{sequence: 12, epoch: 57, generation: 40, readsGeneration: false, acting: 57}
|
||||
var untold sendable
|
||||
untold.Resources = resources
|
||||
older.stamp(&untold)
|
||||
keys = bodyKeys(t, untold)
|
||||
if _, there := keys["generation"]; there {
|
||||
t.Fatalf("a machine whose node-engine never said it reads a generation was sent one: %v", keys)
|
||||
}
|
||||
if _, there := keys["put_back"]; there {
|
||||
t.Fatalf("a machine whose node-engine never said it reads a generation was sent a put-back mark: %v", keys)
|
||||
if _, there := bodyKeys(t, untold)["generation"]; there {
|
||||
t.Fatal("a machine whose node-engine never said it reads a generation was sent one")
|
||||
}
|
||||
if untold.composedFrom != 40 {
|
||||
t.Errorf("the generation it was composed from is recorded whether or not it was sent: %+v", untold)
|
||||
}
|
||||
|
||||
// An ordinary send carries no put-back mark at all: the body is what it was apart from the generation.
|
||||
var ordinary sendable
|
||||
ordinary.Resources = resources
|
||||
ordinaryOrder := reads
|
||||
ordinaryOrder.putBack = false
|
||||
ordinaryOrder.stamp(&ordinary)
|
||||
if _, there := bodyKeys(t, ordinary)["put_back"]; there {
|
||||
t.Fatal("an ordinary send says it is a put-back")
|
||||
}
|
||||
}
|
||||
|
||||
// **The sender is named**: the caller of the seat call when there is one, else the shell's account, and the
|
||||
@@ -79,7 +70,7 @@ func TestASendNamesItsSender(t *testing.T) {
|
||||
func refusedSend(at time.Time) inventory.Send {
|
||||
return inventory.Send{NodeName: "anchor", Sequence: 12, Epoch: 57, Generation: 38, RefusedApplied: 40,
|
||||
Sender: "a one-shot push by jochen at a shell on anchor (pid 4242 on anchor, build 2026.10.11)",
|
||||
Modules: []string{"docker"}, SentAt: at.Add(-time.Second), RefusedAt: &at}
|
||||
Modules: []string{"docker"}, SentAt: at.Add(-time.Second), RefusedAt: &at, Recorded: true}
|
||||
}
|
||||
|
||||
// **A refused send is raised naming its sender** (novox/hq issue 234): who sent it, from which generation,
|
||||
@@ -105,3 +96,53 @@ func TestARefusedSendIsRaisedNamingItsSender(t *testing.T) {
|
||||
t.Errorf("the condition is not worded for the operator: %+v", o)
|
||||
}
|
||||
}
|
||||
|
||||
// **A refusal of a send the mesh has no record of is raised too, its sender said to be unknown** — never
|
||||
// only a line on stderr.
|
||||
func TestARefusalOfAnUnrecordedSendIsRaisedSayingTheSenderIsUnknown(t *testing.T) {
|
||||
now := time.Date(2026, 10, 11, 12, 0, 0, 0, time.UTC)
|
||||
f := calm(now)
|
||||
s := refusedSend(now.Add(-time.Minute))
|
||||
s.Recorded, s.Sender, s.Modules = false, "a sender the mesh has no record of (no send of this sequence was recorded)", nil
|
||||
f.refusedSends = []inventory.Send{s}
|
||||
got := watchGenerationRefusals(f)
|
||||
if len(got) != 1 || !strings.Contains(got[0].Summary, "no record of") {
|
||||
t.Fatalf("an unattributed refusal was not raised as one: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// **When the refusal showed the mesh's counter behind the machine, the condition asks for a push** — it was
|
||||
// raised, and nothing has sent the machine its declaration since — and does not say there is nothing to do.
|
||||
func TestACounterBehindTheMachineAsksForAPush(t *testing.T) {
|
||||
now := time.Date(2026, 10, 11, 12, 0, 0, 0, time.UTC)
|
||||
f := calm(now)
|
||||
s := refusedSend(now.Add(-time.Minute))
|
||||
s.Generation, s.RefusedApplied, s.CounterRaisedTo = 12, 40, 41
|
||||
f.refusedSends = []inventory.Send{s}
|
||||
got := watchGenerationRefusals(f)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("%+v", got)
|
||||
}
|
||||
if !strings.Contains(got[0].Summary, "`push anchor`") || !strings.Contains(got[0].Explanation, "push anchor") ||
|
||||
strings.Contains(got[0].Explanation, "Nothing for you to do") {
|
||||
t.Errorf("a counter behind the machine does not ask for a push: %s / %s", got[0].Summary, got[0].Explanation)
|
||||
}
|
||||
}
|
||||
|
||||
// **Only a refusal of the counter as it stands is the counter behind**: a stale sender's generation is below
|
||||
// it, and the counter is left alone for that.
|
||||
func TestOnlyARefusalOfTheCounterAsItStandsRaisesIt(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
refused, applied, now int64
|
||||
behind bool
|
||||
}{
|
||||
{refused: 12, applied: 40, now: 12, behind: true}, // the store was put back: the mesh says 12, the machine had 40
|
||||
{refused: 38, applied: 40, now: 41, behind: false}, // a stale sender: the counter is already past
|
||||
{refused: 38, applied: 40, now: 40, behind: false}, // a stale sender: the counter is where the machine is
|
||||
} {
|
||||
r := link.GenerationRefusal{Generation: c.refused, Applied: c.applied}
|
||||
if got := counterBehind(r, c.now); got != c.behind {
|
||||
t.Errorf("refused %d, applied %d, counter %d: behind %v, want %v", c.refused, c.applied, c.now, got, c.behind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-18
@@ -359,14 +359,12 @@ func declare(ctx context.Context, args []string) error {
|
||||
Epoch uint64 `json:"epoch"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Generation int64 `json:"generation"`
|
||||
PutBack bool `json:"put_back"`
|
||||
}
|
||||
_ = json.Unmarshal(raw, &carried)
|
||||
// And recorded as every send is (novox/hq issue 234): by hand, from what it carried; the modules it
|
||||
// named are not known, since the mesh did not compose it.
|
||||
if _, err := recordSent(ctx, inv, node, raw, nil, carried.Epoch, sentRecord{sequence: carried.Sequence,
|
||||
epoch: carried.Epoch, generation: carried.Generation, putBack: carried.PutBack,
|
||||
toldGeneration: carried.Generation, toldPutBack: carried.PutBack,
|
||||
epoch: carried.Epoch, generation: carried.Generation, toldGeneration: carried.Generation,
|
||||
sender: senderOf(callerOf(ctx)) + ", a declaration sent by hand"}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1402,9 +1400,9 @@ func wouldSendFrom(ctx context.Context, open *stores,
|
||||
if declared.Epoch, err = open.inventory.SentEpoch(ctx, n.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// And the generation and put-back mark it was last sent, for the same reason (novox/hq issue 234):
|
||||
// an assignment elsewhere in the mesh is not a change of this machine.
|
||||
if declared.Generation, declared.PutBack, err = open.inventory.SentGeneration(ctx, n.ID); err != nil {
|
||||
// And the generation it was last sent, for the same reason (novox/hq issue 234): an assignment
|
||||
// elsewhere in the mesh is not a change of this machine.
|
||||
if declared.Generation, err = open.inventory.SentGeneration(ctx, n.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := declared.Body()
|
||||
@@ -1551,8 +1549,6 @@ type order struct {
|
||||
generation int64
|
||||
readsGeneration bool
|
||||
acting uint64
|
||||
// putBack is a gate's put-back (sendScope.putBack), read from the send's context.
|
||||
putBack bool
|
||||
}
|
||||
|
||||
// allot takes the next sequence for a machine — the number its next declaration carries — under the
|
||||
@@ -1593,7 +1589,7 @@ func allot(ctx context.Context, inv *inventory.Inventory, node string) (order, e
|
||||
return order{}, err
|
||||
}
|
||||
return order{sequence: seq, epoch: epoch, generation: generation, readsGeneration: readsGeneration,
|
||||
acting: acting, putBack: scopeOf(ctx).putBack}, nil
|
||||
acting: acting}, nil
|
||||
}
|
||||
|
||||
// recordSent writes down what a machine was just sent, and returns the digest.
|
||||
@@ -1616,17 +1612,23 @@ func recordSent(ctx context.Context, inv *inventory.Inventory, node string, body
|
||||
return "", err
|
||||
}
|
||||
digest := digestOf(body)
|
||||
if err := inv.RecordSentUnder(kept, record.ID, digest, builds, epoch); err != nil {
|
||||
// The digest and the generation it carried are written in one transaction (novox/hq issue 234), so the
|
||||
// would-send is never stamped with a generation the machine was not sent; and the send itself, who sent
|
||||
// it and from which generation, beside them. A record of the send that cannot be written does not make a
|
||||
// send that is away look failed: it is said, loudly, and the machine's own record stands.
|
||||
err = inv.RecordSentWith(kept, record.ID, digest, builds, epoch, sent.toldGeneration, inventory.Send{
|
||||
Sequence: sent.sequence, Epoch: int64(sent.epoch), Sender: sent.sender, Generation: sent.generation,
|
||||
Digest: digest, Modules: sent.modules})
|
||||
var unrecorded *inventory.SendNotRecordedError
|
||||
switch {
|
||||
case errors.As(err, &unrecorded):
|
||||
fmt.Fprintf(os.Stderr, "mesh-controller: SEND NOT RECORDED: %s was sent declaration %s (sequence %d), and who "+
|
||||
"sent it and from which generation could not be written down, so a refusal of it will name no sender "+
|
||||
"(novox/hq issue 234): %v\n", node, short(digest), sent.sequence, unrecorded.Err)
|
||||
fmt.Printf("%s: sent, and the record of who sent it could NOT be written: %v\n", node, unrecorded.Err)
|
||||
case err != nil:
|
||||
return "", err
|
||||
}
|
||||
// And the send itself, who sent it and from which generation (novox/hq issue 234): what the machine
|
||||
// was last told, by whom, is read back from here — and what the would-send is stamped with.
|
||||
if err := inv.RecordSend(kept, inventory.Send{Node: record.ID, Sequence: sent.sequence,
|
||||
Epoch: int64(sent.epoch), Sender: sent.sender, Generation: sent.generation, PutBack: sent.putBack,
|
||||
ToldGeneration: sent.toldGeneration, ToldPutBack: sent.toldPutBack, Digest: digest,
|
||||
Modules: sent.modules}); err != nil {
|
||||
return "", fmt.Errorf("%s was sent its declaration, and the send could not be recorded: %w", node, err)
|
||||
}
|
||||
// And what it was, summarised, so the next push can be compared with it (novox/hq ADR 0217).
|
||||
// Never a reason to fail a send that is already away: a summary that cannot be kept means the
|
||||
// next comparison has nothing to hold against, which is how every machine starts.
|
||||
|
||||
@@ -50,9 +50,6 @@ type sendScope struct {
|
||||
modules map[string]bool
|
||||
// person is a person's act: it carries what it carries.
|
||||
person bool
|
||||
// putBack is a gate putting machines back after a failed send (novox/hq issue 234): its declarations
|
||||
// say so, and a machine does not refuse them for the generation they carry.
|
||||
putBack bool
|
||||
}
|
||||
|
||||
type sendScopeKey struct{}
|
||||
|
||||
@@ -33,17 +33,11 @@ type sendable struct {
|
||||
// node-engine has not said it reads one is sent none, for the reason the epoch is not (an older
|
||||
// node-engine refuses a key it does not know, whole).
|
||||
Generation int64
|
||||
// PutBack marks a gate's put-back (novox/hq issue 234): it carries the generation of what it puts
|
||||
// back and is not refused for it. Sent only beside a generation.
|
||||
PutBack bool
|
||||
// composedFrom is the generation read before this declaration was composed, and actingEpoch the
|
||||
// lease epoch its sender acted under — whether or not the machine is sent either — for the record of
|
||||
// the send; never on the wire.
|
||||
composedFrom int64
|
||||
actingEpoch uint64
|
||||
// putBackSend is whether a gate sent it to put the machine back, for the record; PutBack is the mark
|
||||
// on the wire, only beside a generation.
|
||||
putBackSend bool
|
||||
// modules are the modules this declaration names, for the record of the send; never on the wire.
|
||||
modules []string
|
||||
// Adoption is nil for a converged node, and then the body is byte for byte what it was before
|
||||
@@ -115,9 +109,6 @@ func (s sendable) Body() ([]byte, error) {
|
||||
}
|
||||
if s.Generation > 0 {
|
||||
envelope["generation"] = s.Generation
|
||||
if s.PutBack {
|
||||
envelope["put_back"] = true
|
||||
}
|
||||
}
|
||||
// An empty declaration is deliberate here — the node owns nothing the mesh put there
|
||||
// (novox/hq issue 127) — and the host refuses an empty body unless it is told the emptiness
|
||||
|
||||
Reference in New Issue
Block a user