Correct a mirror written behind the writer's back, and write none without the lease
mesh/merge-gate out of date: judged against trunk 617881c3093d, the trunk is now 71362c196dbc; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 617881c3093d, the trunk is now 71362c196dbc; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate out of date: judged against trunk 617881c3093d, the trunk is now 71362c196dbc; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 617881c3093d, the trunk is now 71362c196dbc; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
Review of mesh-controller#315 (hq issue 652): the writer trusted the bucket as it read it once, so a former lease holder's write stayed for good; it wrote without the lease, asking only after a refused compare-and-set; and the declared config carried a manifest's declarations whole, so a field the manifest gains would reach the bus unreviewed (ADR 0289 §9, §10, §12). Every pass now reads the bucket and writes back the store's value where it differs, asks for the lease before the pass and before every write, and a declaration is mirrored as its listed fields (kind, default, why), with a test that fails on a declaration field the list does not name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -25,14 +26,27 @@ type AssignmentMirrored struct {
|
||||
}
|
||||
|
||||
// DeclaredConfigMirrored is one registered module's config declarations as the declared-config mirror holds
|
||||
// them: the module, and its manifest's `settings` exactly as the manifest holds them (each key's kind,
|
||||
// default and why today), carried whole so a declaration that gains fields (ADR 0348 clause 4) is mirrored
|
||||
// without a change here. An empty object when the module declares none: registered, declaring nothing.
|
||||
// them: the module, and each key its manifest's `settings` declares with that declaration's listed fields.
|
||||
// An empty object when the module declares none: registered, declaring nothing.
|
||||
type DeclaredConfigMirrored struct {
|
||||
Module string `json:"module"`
|
||||
Settings json.RawMessage `json:"settings"`
|
||||
Module string `json:"module"`
|
||||
Settings map[string]DeclaredMirrored `json:"settings"`
|
||||
}
|
||||
|
||||
// DeclaredMirrored is one key's declaration as the mirror holds it: **its listed fields and no other**
|
||||
// (ADR 0289 §12). Today a declaration is kind, default and why (catalogue.SettingDeclaration); a field the
|
||||
// manifest gains reaches the bus only once it is added here, deliberately, and a test fails until it is
|
||||
// listed or refused (ADR 0348 clause 4's fields arrive with package K1). A default is never a secret: the
|
||||
// catalogue refuses a default for a key whose name says secret.
|
||||
type DeclaredMirrored struct {
|
||||
Kind string `json:"kind"`
|
||||
Default json.RawMessage `json:"default"`
|
||||
Why string `json:"why"`
|
||||
}
|
||||
|
||||
// DeclaredMirroredFields are the fields of a declaration the mirror carries, by their manifest names.
|
||||
var DeclaredMirroredFields = []string{"kind", "default", "why"}
|
||||
|
||||
// AssignmentsMirror is every assignment's mirrored value, keyed `<node>.<module>`.
|
||||
func (i *Inventory) AssignmentsMirror(ctx context.Context) (map[string][]byte, error) {
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
@@ -74,11 +88,19 @@ func (i *Inventory) DeclaredConfigMirror(ctx context.Context) (map[string][]byte
|
||||
if err := rows.Scan(&d.Module, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings, err := canonical(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var declared map[string]DeclaredMirrored
|
||||
if err := json.Unmarshal(raw, &declared); err != nil {
|
||||
return nil, fmt.Errorf("the declarations of %s cannot be read: %w", d.Module, err)
|
||||
}
|
||||
d.Settings = map[string]DeclaredMirrored{}
|
||||
for key, decl := range declared {
|
||||
if len(decl.Default) > 0 {
|
||||
if decl.Default, err = canonical(decl.Default); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d.Settings[key] = decl
|
||||
}
|
||||
d.Settings = settings
|
||||
value, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
)
|
||||
|
||||
// jsonFields is the names a struct's fields are encoded under.
|
||||
func jsonFields(t reflect.Type) []string {
|
||||
var out []string
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
name, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ",")
|
||||
if name != "" && name != "-" {
|
||||
out = append(out, name)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// **A declaration reaches the bus with its listed fields and no other** (ADR 0289 §12): the mirror's value
|
||||
// carries exactly DeclaredMirroredFields, and every field a manifest's declaration has is on that list. A
|
||||
// field a declaration gains (package K1 widens it, ADR 0348 clause 4) fails here until it is listed — read,
|
||||
// judged not a secret and carried — so nothing reaches the declared-config mirror unreviewed.
|
||||
func TestTheDeclaredConfigMirrorCarriesOnlyItsListedFields(t *testing.T) {
|
||||
if got := jsonFields(reflect.TypeOf(DeclaredMirrored{})); !slices.Equal(got, DeclaredMirroredFields) {
|
||||
t.Errorf("the mirrored declaration carries %v, its list is %v", got, DeclaredMirroredFields)
|
||||
}
|
||||
for _, field := range jsonFields(reflect.TypeOf(catalogue.SettingDeclaration{})) {
|
||||
if !slices.Contains(DeclaredMirroredFields, field) {
|
||||
t.Errorf("a declaration has the field %q, which the declared-config mirror does not list: list it "+
|
||||
"in DeclaredMirrored (and say why it is no secret), or say here why it stays off the bus", field)
|
||||
}
|
||||
}
|
||||
if got := jsonFields(reflect.TypeOf(DeclaredConfigMirrored{})); !slices.Equal(got, []string{"module", "settings"}) {
|
||||
t.Errorf("the mirrored module carries %v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user