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 := json.Marshal(Signed{Declaration: declaration, Signature: signature}) if err != nil { return err } publish, cancel := context.WithTimeout(ctx, timeout) defer cancel() return bus.PublishDeclaration(publish, node, body) }