Send each resource's module and each module's build to a node that reads them; pin mesh-host at 0b391f49a25f (issue 1145)

This commit is contained in:
2026-10-18 19:11:10 +02:00
parent fc43238267
commit dc65fe2319
15 changed files with 407 additions and 49 deletions
+10 -5
View File
@@ -88,16 +88,21 @@ func TestTheSourceScanFindsATablePlanted(t *testing.T) {
}
}
// **The last migration drops the tables of the chain, behind its guard** (issue 1096): it names each table, the column and
// **Migration 105 drops the tables of the chain, behind its guard** (issue 1096; it was the last until issue 1145 added 106): it names each table, the column and
// the function, drops no build_gate, and raises before it drops anything.
func TestTheLastMigrationDropsTheTablesOfTheChain(t *testing.T) {
func TestMigration105DropsTheTablesOfTheChain(t *testing.T) {
migrations, err := Migrations()
if err != nil {
t.Fatal(err)
}
last := migrations[len(migrations)-1]
if last.Number < 105 {
t.Fatalf("the last migration is %d", last.Number)
var last struct{ Number int }
for _, m := range migrations {
if m.Number == 105 {
last.Number = m.Number
}
}
if last.Number != 105 {
t.Fatal("there is no migration 105")
}
raw, err := os.ReadFile(filepath.Join("migrations", migrationFile(t, last.Number)))
if err != nil {
@@ -0,0 +1,4 @@
-- Whether the machine's node-engine said it reads a module on each resource of a declaration and the build of each
-- module (its reports' `reads_modules`, novox/hq issue 1145): until it has, it is sent neither, because an older
-- node-engine refuses a key it does not know, whole. As reads_generation (migration 0094).
alter table node add column reads_modules boolean not null default false;
+15
View File
@@ -75,6 +75,21 @@ func (i *Inventory) RecordReadsGeneration(ctx context.Context, id string, reads
return err
}
// ReadsModules says a machine's node-engine said it reads a module per resource and a build per module (issue 1145).
func (i *Inventory) ReadsModules(ctx context.Context, id string) (bool, error) {
var reads bool
if err := i.store.Pool().QueryRow(ctx, `select reads_modules from node where id = $1`, id).Scan(&reads); err != nil {
return false, fmt.Errorf("reading whether %s reads modules: %w", id, err)
}
return reads, nil
}
// RecordReadsModules keeps what a machine's latest report said of reading modules.
func (i *Inventory) RecordReadsModules(ctx context.Context, id string, reads bool) error {
_, err := i.store.Pool().Exec(ctx, `update node set reads_modules = $2 where id = $1`, id, reads)
return err
}
// Send is one declaration sent to a machine, as the mesh records it.
type Send struct {
// Node is the machine's id; NodeName its name, filled where it is read back.
+17
View File
@@ -323,3 +323,20 @@ func TestAReportIsTheLastSendsAnswerOnlyWhenItIsAboutThatSend(t *testing.T) {
t.Fatalf("an ordered report about sequence 21 is not its answer: %q", a)
}
}
// 1145/AC3: whether a machine reads modules is its latest word, as whether it reads a generation is.
func TestWhetherAMachineReadsModulesIsItsLatestWord(t *testing.T) {
inv, node := aNodeWithModules(t)
record, err := inv.NodeByName(t.Context(), node)
if err != nil {
t.Fatal(err)
}
for _, reads := range []bool{false, true, false} {
if err := inv.RecordReadsModules(t.Context(), record.ID, reads); err != nil {
t.Fatal(err)
}
if got, err := inv.ReadsModules(t.Context(), record.ID); err != nil || got != reads {
t.Fatalf("1145/AC3: recorded %v, read %v (%v)", reads, got, err)
}
}
}
+4
View File
@@ -460,6 +460,10 @@ func (e Enrolment) Heard(ctx context.Context, report Report) (news bool, err err
if err := e.Inventory.RecordReadsGeneration(ctx, node.ID, report.ReadsGeneration); err != nil {
return false, err
}
// And whether it reads a module per resource and a build per module (novox/hq issue 1145), the same way.
if err := e.Inventory.RecordReadsModules(ctx, node.ID, report.ReadsModules); err != nil {
return false, err
}
}
if report.Ordered() {
account := AccountOf(report)
+15
View File
@@ -31,3 +31,18 @@ func TestTheGenerationOnTheWire(t *testing.T) {
t.Fatalf("a node-engine that never said it reads a generation reads as one that does: %+v", older)
}
}
// 1145/AC3: a report's word that the node-engine reads a module per resource and a build per module, as mesh-host's
// report writes it: `reads_modules`, on every report of a node-engine that reads them.
func TestWhetherTheNodeEngineReadsModulesIsOnItsReport(t *testing.T) {
var reads, older Report
if err := json.Unmarshal([]byte(`{"node":"anchor","applied":["a"],"report_sequence":8,"reads_modules":true}`), &reads); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(`{"node":"anchor","applied":["a"],"report_sequence":3}`), &older); err != nil {
t.Fatal(err)
}
if !reads.ReadsModules || older.ReadsModules {
t.Fatalf("1145/AC3: reads %v, an older node-engine reads %v", reads.ReadsModules, older.ReadsModules)
}
}
+4
View File
@@ -202,6 +202,10 @@ type Report struct {
// so, because an older node-engine refuses a key it does not know, whole. Mirrors mesh-host
// internal/link/messages.go.
ReadsGeneration bool `json:"reads_generation,omitempty"`
// ReadsModules says the node-engine reads a module on each resource of a declaration and the build of each module
// (novox/hq issue 1145), on every report it makes: the mesh sends them only to a machine that said so. Mirrors
// mesh-host internal/link/messages.go.
ReadsModules bool `json:"reads_modules,omitempty"`
// OlderGeneration is set on a report refusing a declaration composed from an older assignment
// generation than the machine applied (novox/hq issue 234). The refused declaration is the report's
// own Declared and Order, and its sender is read from the record of sends by that sequence.