Files
mesh-sdk/go/delivery/composed_seals.go
T
jschoubben 16b8d8a1ee Compose as the controller does, so a node's shadow can answer same (issue 1126, C1 C2 C3 C5 C6, T1)
Seals are reused and compared by slot (resource, field), never across resources;
one rule says which resolved config values are laid; a seat path that steps back
is owned by the seat it steps back for, whatever the seat order; a folded module
of an old-form build is composed from what runs of it; the operator export's file
is carried as it runs; a differing resource names its differing JSON paths, found
before its bodies are cut, and an answer is fitted to the node's key.
2026-10-28 09:36:36 +01:00

151 lines
4.7 KiB
Go

package delivery
import (
"bytes"
"encoding/json"
"strconv"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Seals is, by slot, each value one composition carries sealed to its node's key (issue 1126, C1): a file's `sealed`
// and a vault box (catalogue.VaultSealedField), as their JSON text (a box is carried as its JSON, a sealed value as
// the string it is). A slot is one field of one resource of the composition's module (SealSlot): a box is reused only
// at the slot that holds it, never for another resource whose value opens to the same (a provider's grant and its
// consumer's secret on one node hold one value).
type Seals map[string]string
// SealedFields are the fields of a resource that carry a value sealed to the node's key.
var SealedFields = []string{"sealed", catalogue.VaultSealedField}
// SealSlot is the slot of one sealed field of one resource, by the resource's id (its place, `#<i>`, when it has none).
// The module is the composition's: a slot is never compared across compositions.
func SealSlot(resource, field string) string { return resource + "#" + field }
// SealsIn is every sealed value c carries, by slot; nil when it carries none.
func SealsIn(c Composed) Seals {
var out Seals
for i, raw := range c.Resources {
id, fields, ok := sealedFieldsOf(raw, i)
if !ok {
continue
}
for _, field := range SealedFields {
f, has := fields[field]
if !has {
continue
}
if text := sealedText(f); text != "" {
if out == nil {
out = Seals{}
}
out[SealSlot(id, field)] = text
}
}
}
return out
}
// KeepSeals is c with each sealed value replaced by the one kept at the same slot, where same says the two open to the
// same value (known and same): a value the vault sealed again is then the box that runs, so the composition is the
// one that runs (ADR 0547 §3, issue 1126). A slot kept holds nothing for, or whose value differs or cannot be opened,
// keeps c's. Every other byte of a resource is kept as it is; c itself is not changed.
func KeepSeals(c Composed, kept Seals, same func(a, b string) (same, known bool)) Composed {
if len(kept) == 0 || same == nil {
return c
}
out := c
out.Resources = nil
for i, raw := range c.Resources {
id, fields, ok := sealedFieldsOf(raw, i)
if ok {
for _, field := range SealedFields {
f, has := fields[field]
if !has {
continue
}
ours := sealedText(f)
theirs, held := kept[SealSlot(id, field)]
if !held || ours == "" || theirs == ours {
continue
}
if s, known := same(ours, theirs); !known || !s {
continue
}
value := json.RawMessage(theirs)
if f[0] == '"' {
value, _ = json.Marshal(theirs)
}
if swapped, ok := withField(raw, field, value); ok {
raw = swapped
}
}
}
out.Resources = append(out.Resources, raw)
}
return out
}
// sealedFieldsOf is a resource's id (`#<i>` when it has none) and its top-level fields, false when it is no object.
func sealedFieldsOf(raw json.RawMessage, i int) (string, map[string]json.RawMessage, bool) {
var fields map[string]json.RawMessage
if json.Unmarshal(raw, &fields) != nil {
return "", nil, false
}
var id string
if f, ok := fields["id"]; !ok || json.Unmarshal(f, &id) != nil || id == "" {
id = "#" + strconv.Itoa(i)
}
return id, fields, true
}
// sealedText is a sealed field as its text: a string as it is, a box as its JSON.
func sealedText(f json.RawMessage) string {
var text string
if json.Unmarshal(f, &text) == nil {
return text
}
var compact bytes.Buffer
if json.Compact(&compact, f) != nil {
return string(f)
}
return compact.String()
}
// withField is the object raw with its top-level field replaced by value, every other byte as it was (so a composition
// whose sealed values are those that run is the one that runs, byte for byte); false when raw is no object holding it.
func withField(raw json.RawMessage, field string, value json.RawMessage) (json.RawMessage, bool) {
d := json.NewDecoder(bytes.NewReader(raw))
if t, err := d.Token(); err != nil || t != json.Delim('{') {
return nil, false
}
for d.More() {
t, err := d.Token()
if err != nil {
return nil, false
}
key, _ := t.(string)
before := d.InputOffset()
var skip json.RawMessage
if err := d.Decode(&skip); err != nil {
return nil, false
}
if key != field {
continue
}
after := d.InputOffset()
// The span holds the colon, any space and the value: kept up to the colon, the value replaced.
span := raw[before:after]
colon := bytes.IndexByte(span, ':')
if colon < 0 {
return nil, false
}
out := make([]byte, 0, len(raw)-len(span)+colon+1+len(value))
out = append(out, raw[:int(before)+colon+1]...)
out = append(out, value...)
out = append(out, raw[after:]...)
return out, true
}
return nil, false
}