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>
123 lines
4.3 KiB
Go
123 lines
4.3 KiB
Go
package inventory
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// The values of the controller's record mirrors, built from the store (novox/hq ADR 0289, design 54 §1,
|
|
// design 57 package F3).
|
|
//
|
|
// **Each value is built from a named list of fields, and nothing else** (design 54 §1): no epoch, no time
|
|
// of writing and no time of the last look, so any lease holder builds the same bytes from the same record
|
|
// and a pass over unchanged records writes nothing (ADR 0289 §1, §4). The encoding is fixed: the fields in
|
|
// their struct's order, a declaration's maps sorted.
|
|
|
|
// AssignmentMirrored is one assignment as the assignments mirror holds it: the module, the node, and since
|
|
// when it is assigned there. Design 54 §1 also lists whether it is pending, the builds sent and running and
|
|
// the seats and provisions it holds there; those join with issue 419, which builds the rest of design 54.
|
|
type AssignmentMirrored struct {
|
|
Node string `json:"node"`
|
|
Module string `json:"module"`
|
|
Assigned time.Time `json:"assigned"`
|
|
}
|
|
|
|
// DeclaredConfigMirrored is one registered module's config declarations as the declared-config mirror holds
|
|
// 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 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,
|
|
`select n.name, a.module, a.assigned from assignment a join node n on n.id = a.node`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := map[string][]byte{}
|
|
for rows.Next() {
|
|
var a AssignmentMirrored
|
|
if err := rows.Scan(&a.Node, &a.Module, &a.Assigned); err != nil {
|
|
return nil, err
|
|
}
|
|
a.Assigned = a.Assigned.UTC()
|
|
value, err := json.Marshal(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[a.Node+"."+a.Module] = value
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// DeclaredConfigMirror is every registered module's mirrored config declarations, keyed by the module.
|
|
func (i *Inventory) DeclaredConfigMirror(ctx context.Context) (map[string][]byte, error) {
|
|
rows, err := i.store.Pool().Query(ctx,
|
|
`select name, coalesce(manifest->'settings', '{}'::jsonb) from module`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := map[string][]byte{}
|
|
for rows.Next() {
|
|
var (
|
|
d DeclaredConfigMirrored
|
|
raw []byte
|
|
)
|
|
if err := rows.Scan(&d.Module, &raw); 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
|
|
}
|
|
value, err := json.Marshal(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[d.Module] = value
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// canonical is a JSON document encoded the one way: objects' keys sorted, numbers as written.
|
|
func canonical(raw []byte) ([]byte, error) {
|
|
dec := json.NewDecoder(bytes.NewReader(raw))
|
|
dec.UseNumber()
|
|
var v any
|
|
if err := dec.Decode(&v); err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(v)
|
|
}
|