Files
mesh-controller/internal/link/declare.go
T
jschoubben ceaa616d01 Send files over 64 KiB by digest through the bus's object bucket to nodes that read them, grant it, and warn at 75 percent (issues 1696, 1697, 1698; ADR 0486 step 3)
A node that reports file-references gets the digest and size of each file over 64 KiB that may travel so, and the content is put in
the bucket declaration-files before the declaration is sent; others get every content inline. The controller alone writes the
bucket and node-engines read an object by name (the four grants the node-engine's own fetch makes, no list, no write). An object
goes a day after no node's latest declaration names it. The near-limit condition warns at 75 percent, and a test holds the control
node's declaration, in the reference form, under half the bus's largest message.
2026-10-21 15:16:11 +02:00

95 lines
3.7 KiB
Go

package link
import (
"context"
"encoding/json"
"fmt"
"time"
)
// Signer is whatever holds the control plane's signing key.
type Signer interface {
Sign(ctx context.Context, message []byte) ([]byte, error)
}
// ActingGate is what every send passes before it is made (novox/hq to-be 45 §6): whether this process
// may act under the controller's lease. Set by the controller; nil passes every send — a test, a tool.
var ActingGate func(ctx context.Context) error
// Declare sends a node what it should be, signed.
//
// The signature is over the declaration exactly as it is published — the same bytes the node
// verifies. Anything that re-encoded between here and there would produce a signature over
// something else, and the node would refuse a declaration that was genuinely the mesh's.
//
// Published to the node's own queue, which its account alone may read.
func Declare(ctx context.Context, bus Bus, signer Signer, node string,
declaration []byte, timeout time.Duration) error {
if !json.Valid(declaration) {
return fmt.Errorf("refusing to send %s something that is not a declaration", node)
}
// **At the send, not only where it was composed**: a lease lost between the two stops this one.
if ActingGate != nil {
if err := ActingGate(ctx); err != nil {
return fmt.Errorf("%s was not sent its declaration: %w", node, err)
}
}
signature, err := signer.Sign(ctx, declaration)
if err != nil {
return fmt.Errorf("cannot sign a declaration for %s: %w", node, err)
}
body, err := Envelope(declaration, signature)
if err != nil {
return err
}
// **Measured before it is sent** (novox/hq issue 1689): the bare bus error said only "maximum payload
// exceeded", on 2026-10-21 for the control node's declaration, 2 KB over. The limit is the connection's own.
if limit := payloadLimitOf(bus); limit > 0 {
size := int64(len(body))
if DeclarationMeasured != nil {
DeclarationMeasured(ctx, node, size, limit)
}
if size > limit {
return fmt.Errorf("%s's declaration is %d bytes on the wire (%d bytes of declaration, signed and encoded), "+
"over the bus's largest message of %d bytes by %d: it cannot be sent (novox/hq issue 1689)",
node, size, len(declaration), limit, size-limit)
}
}
publish, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return bus.PublishDeclaration(publish, node, body)
}
// Envelope is the message a declaration goes on the bus as: the declaration and its signature, each as base64 in one JSON
// document. Its length is what the bus's largest message bounds.
func Envelope(declaration, signature []byte) ([]byte, error) {
return json.Marshal(Signed{Declaration: declaration, Signature: signature})
}
// NearTheLimit is the share of the bus's largest message at which a declaration is said to be near it: three quarters, so that
// a declaration which should stay far from the limit raises the condition while there is still room to act (novox/hq ADR 0486
// §8; the first fix of issue 1689 warned at nine tenths while every file was still inline).
const NearTheLimit = 0.75
// DeclarationMeasured is told, at every send of a declaration, its size on the wire and the bus's largest message,
// so the controller raises a condition when it nears the limit and clears it when it is back under (issue 1689).
// Set by the controller; nil in a test or a tool.
var DeclarationMeasured func(ctx context.Context, node string, size, limit int64)
// payloadLimiter is a bus that knows its largest message.
type payloadLimiter interface{ MaxPayload() int64 }
// payloadLimitOf is the bus's largest message in bytes, or 0 when the bus does not say.
func payloadLimitOf(bus Bus) int64 {
if l, ok := bus.(payloadLimiter); ok {
return l.MaxPayload()
}
return 0
}