mesh/merge-gate pass: builds mesh-sdk, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group feat/asks-answered-on-any-channel rejected: a member's own check failed
mesh/delivery superseded: a newer head of the same pull request
154 lines
5.1 KiB
Go
154 lines
5.1 KiB
Go
package stdio
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Seats that name their caller or their kind (novox/hq ADR 0259 §3): a module submits an accept or says an
|
|
// event under its own name or kind, takes a held seat's work, asks a proof and answers one, and reads a
|
|
// record a seat keeps for it. The runtime does each as the module, only as far as its membership lists,
|
|
// and refuses the rest with the reason.
|
|
|
|
// Work is one piece of a seat's work this bundle takes: the subject it was submitted on, its body, and the
|
|
// headers the submitter's runtime set (x-source is the submitting module, x-event-id its id).
|
|
type Work struct {
|
|
Worker string `json:"worker,omitempty"`
|
|
Subject string `json:"subject"`
|
|
Body json.RawMessage `json:"body"`
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
}
|
|
|
|
const seatTimeout = 30 * time.Second
|
|
|
|
// SeatPublish submits an accept, or says an event, on a seat, de-duplicated by id: the same id twice is
|
|
// one message. An empty id is the runtime's to make.
|
|
func SeatPublish(subject string, body any, id string) error {
|
|
c, err := live()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = c.ask("mesh/seat.publish", map[string]any{"subject": subject, "body": body, "id": id}, seatTimeout)
|
|
return err
|
|
}
|
|
|
|
// SeatProve asks a proof — core request and reply, never kept — and returns its answer.
|
|
func SeatProve(subject string, body any) (json.RawMessage, error) {
|
|
c, err := live()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.ask("mesh/seat.prove", map[string]any{"subject": subject, "body": body}, seatTimeout)
|
|
}
|
|
|
|
// SeatAnswer answers every proof on a subject (a pattern, `*` one token) with what handle returns.
|
|
func SeatAnswer(subject string, handle func(subject string, body json.RawMessage) (any, error)) error {
|
|
c, err := live()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.mu.Lock()
|
|
c.provers[subject] = handle
|
|
c.mu.Unlock()
|
|
_, err = c.ask("mesh/seat.answer", map[string]any{"subject": subject}, seatTimeout)
|
|
return err
|
|
}
|
|
|
|
// SeatTake takes a held seat's work from a worker, handing each piece to handle: taken when it returns
|
|
// nil, offered again after an error. Make the work safe to do twice, or skip an x-event-id already done.
|
|
func SeatTake(worker string, handle func(Work) error) error {
|
|
c, err := live()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.mu.Lock()
|
|
c.takers[worker] = handle
|
|
c.mu.Unlock()
|
|
_, err = c.ask("mesh/seat.take", map[string]any{"worker": worker}, seatTimeout)
|
|
return err
|
|
}
|
|
|
|
// SeatRecord reads one key of a record a seat keeps for this module, under its own name, or nil.
|
|
func SeatRecord(bucket, key string) (*StateEntry, error) {
|
|
c, err := live()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
raw, err := c.ask("mesh/seat.record", map[string]any{"bucket": bucket, "key": key}, seatTimeout)
|
|
if err != nil || len(raw) == 0 || string(raw) == "null" {
|
|
return nil, err
|
|
}
|
|
var e StateEntry
|
|
return &e, json.Unmarshal(raw, &e)
|
|
}
|
|
|
|
// KindHeld is one kind of a kinded bench and who holds it, with what it promises (novox/hq ADR 0259 §5).
|
|
type KindHeld struct {
|
|
Seat string `json:"seat"`
|
|
Kind string `json:"kind"`
|
|
Module string `json:"module"`
|
|
Node string `json:"node"`
|
|
Capabilities []string `json:"capabilities,omitempty"`
|
|
}
|
|
|
|
// Promises says whether this kind's claim declares a capability.
|
|
func (k KindHeld) Promises(capability string) bool {
|
|
for _, c := range k.Capabilities {
|
|
if c == capability {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SeatKinds is every kind of the kinded benches this module uses or watches, as the controller issued
|
|
// them in its membership: the one account of which channel is which, never a channel's own word.
|
|
func SeatKinds() ([]KindHeld, error) {
|
|
c, err := live()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
raw, err := c.ask("mesh/seat.kinds", map[string]any{}, seatTimeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out []KindHeld
|
|
return out, json.Unmarshal(raw, &out)
|
|
}
|
|
|
|
// Create writes a key only when it has none, and returns the revision; a key that has one is refused.
|
|
func (s KeptState) Create(key string, value any) (uint64, error) {
|
|
return s.revisionOf(s.ask("create", map[string]any{"key": key, "value": value}))
|
|
}
|
|
|
|
// Update writes a key only when it is still at revision, and returns the new one; a key changed
|
|
// meanwhile is refused, so of two writers that read one value only the first writes.
|
|
func (s KeptState) Update(key string, value any, revision uint64) (uint64, error) {
|
|
return s.revisionOf(s.ask("update", map[string]any{"key": key, "value": value, "revision": revision}))
|
|
}
|
|
|
|
func (s KeptState) revisionOf(raw json.RawMessage, err error) (uint64, error) {
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var put struct {
|
|
Revision uint64 `json:"revision"`
|
|
}
|
|
return put.Revision, json.Unmarshal(raw, &put)
|
|
}
|
|
|
|
// subjectMatches is a subject against a pattern in the bus's wildcards: `*` one token, `>` the rest.
|
|
func subjectMatches(pattern, subject string) bool {
|
|
p, s := strings.Split(pattern, "."), strings.Split(subject, ".")
|
|
for i, part := range p {
|
|
if part == ">" {
|
|
return len(s) > i
|
|
}
|
|
if i >= len(s) || (part != "*" && part != s[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return len(p) == len(s)
|
|
}
|