Fix 10: the catalog client imports no other seat: the key codec and the source-branches follower move into mesh-delivery's sdk
Builds: design 47 G §614, §670 (ADR 0625 §4; lead answer A-144)
This commit is contained in:
@@ -10,22 +10,19 @@ import (
|
||||
"time"
|
||||
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/sourcebranches"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// SourceVerb is the gate's verb a part reads the branches the gate keeps a source revision of by, before the first
|
||||
// registrations, as its manifest's invokes names it (design 47 G §614).
|
||||
const SourceVerb = "seat:" + delivery.Seat + "." + delivery.VerbSource
|
||||
// registrations, as its manifest's invokes names it (design 47 G §614); the follower is the SDK's sourcebranches.
|
||||
const SourceVerb = sourcebranches.SourceVerb
|
||||
|
||||
// EntryVerb is the seat verb a part reads an entry by its fingerprint by, as its manifest's invokes names it.
|
||||
const EntryVerb = "seat:" + seat.Seat + "." + seat.VerbEntry
|
||||
|
||||
// StateSourceBranches names the view of the branches the gate's source keeps in a part's reader (`mesh-delivery.source`).
|
||||
const StateSourceBranches = "source"
|
||||
|
||||
// Wiring is how Branches puts a view on its part's one reader and takes it off: ReaderWiring for a bundle's
|
||||
// views.Reader, ProcessWiring for a process's views.ProcessReader. Adopt is the reader's Adopt, so a view adopted after
|
||||
// the reader's Start takes its first view at once; Drop is its Drop, which clears every finding the view raised.
|
||||
@@ -58,96 +55,6 @@ func (w readerWiring) Rows(name string, rows func() any) { w.r.Rows(name, rows)
|
||||
// ProcessWiring wires Branches on a process's reader.
|
||||
func ProcessWiring(r *views.ProcessReader) Wiring { return processWiring{r} }
|
||||
|
||||
// SourceBranches is a part's follower of the branches the gate's `source` keeps (design 47 G §614): a first view of the
|
||||
// verb `source` given no branch, then each `source-moved` heard. Its picture is keyed by the escaped branch name.
|
||||
type SourceBranches struct {
|
||||
Follower *views.Follower
|
||||
|
||||
call Call
|
||||
}
|
||||
|
||||
// NewSourceBranches is a SourceBranches asking the gate through call. Nothing is asked before the reader starts it.
|
||||
func NewSourceBranches(call Call) *SourceBranches {
|
||||
s := &SourceBranches{call: call}
|
||||
s.Follower = &views.Follower{Owner: delivery.Seat, Tool: SourceVerb, State: StateSourceBranches, Ask: s.ask}
|
||||
return s
|
||||
}
|
||||
|
||||
// Branches is the escaped name of each branch the picture holds.
|
||||
func (s *SourceBranches) Branches() []string {
|
||||
out := make([]string, 0)
|
||||
for b := range s.Follower.Values() {
|
||||
out = append(out, b)
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Hear takes one `source-moved` event: a branch the picture does not hold asks the first view again until it does, on
|
||||
// its own goroutine, so the consumer is never held. Data that is no such event is an error.
|
||||
func (s *SourceBranches) Hear(ctx context.Context, data []byte) error {
|
||||
var ev delivery.SourceMovedEvent
|
||||
if err := json.Unmarshal(data, &ev); err != nil || ev.Branch == "" {
|
||||
return fmt.Errorf("meshcatalogclient: not a %s event: %s", delivery.EventSourceMoved, clipBytes(data))
|
||||
}
|
||||
held := func() bool { _, ok := s.Follower.Get(keys.EscapeName(ev.Branch)); return ok }
|
||||
if held() {
|
||||
return nil
|
||||
}
|
||||
go catchUp(ctx, s.Follower, held, fmt.Sprintf("source of %s moved", ev.Branch))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SourceBranches) ask(_ context.Context, _ string) (views.Answer, error) {
|
||||
raw, err := s.call(SourceVerb, delivery.SourceInput{})
|
||||
if err != nil {
|
||||
return views.Answer{}, err
|
||||
}
|
||||
answer, said, ok := buscalls.Unwrap(raw)
|
||||
if !ok {
|
||||
return views.Answer{}, fmt.Errorf("%s answered: %s", SourceVerb, said)
|
||||
}
|
||||
var a delivery.SourceAnswer
|
||||
if err := json.Unmarshal(answer, &a); err != nil {
|
||||
return views.Answer{}, fmt.Errorf("%s answered no branches: %s", SourceVerb, clipBytes(answer))
|
||||
}
|
||||
if a.Error != "" {
|
||||
return views.Answer{}, fmt.Errorf("%s answered: %s", SourceVerb, a.Error)
|
||||
}
|
||||
values := make(map[string]json.RawMessage, len(a.Branches))
|
||||
for _, b := range a.Branches {
|
||||
values[b] = json.RawMessage(`{}`)
|
||||
}
|
||||
return views.Answer{V: 1, Owner: delivery.Seat, State: StateSourceBranches, Created: registrationsCreated, Values: values, At: time.Now()}, nil
|
||||
}
|
||||
|
||||
// catchUp asks f's first view again until held says it holds what was heard, pausing between asks as the follower's Wait
|
||||
// says (doubling to a minute), or until ctx ends: an ask already running may have been answered before the change was
|
||||
// written.
|
||||
func catchUp(ctx context.Context, f *views.Follower, held func() bool, why string) {
|
||||
wait := f.Wait
|
||||
if wait == 0 {
|
||||
wait = 5 * time.Second
|
||||
}
|
||||
for !held() {
|
||||
if err := f.Gap(ctx, why); err != nil {
|
||||
return
|
||||
}
|
||||
if held() {
|
||||
return
|
||||
}
|
||||
p, cancel := context.WithTimeout(ctx, wait)
|
||||
<-p.Done()
|
||||
cancel()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if wait *= 2; wait > time.Minute {
|
||||
wait = time.Minute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Branches is the one branch follower of design 47 G §614, built once and used by every part that reads the catalog half:
|
||||
// it follows the watched branches of the registrations (the first view of `registrations` at the start, then each
|
||||
// `registration-changed`); before the first registration, the branches the gate's `source` answers (its first view at
|
||||
@@ -168,7 +75,7 @@ type Branches struct {
|
||||
w Wiring
|
||||
|
||||
mu sync.Mutex
|
||||
source *SourceBranches
|
||||
source *sourcebranches.SourceBranches
|
||||
left []string // the source's branches when it was left, kept until the registrations hold one
|
||||
catalogs map[string]*Catalog
|
||||
order []string
|
||||
@@ -180,12 +87,12 @@ func NewBranches(w Wiring, call Call) (*Branches, error) {
|
||||
b := &Branches{call: call, w: w, catalogs: map[string]*Catalog{}, Entries: NewEntries(call)}
|
||||
b.Registrations = NewRegistrations(call)
|
||||
b.Registrations.Follower.Changed = func(string, json.RawMessage, bool, time.Time) { b.reconcile() }
|
||||
b.source = NewSourceBranches(call)
|
||||
b.source = sourcebranches.New(sourcebranches.Call(call))
|
||||
b.source.Follower.Changed = func(string, json.RawMessage, bool, time.Time) { b.reconcile() }
|
||||
if err := w.Adopt(b.Registrations.Follower, seat.Seat, seat.EventRegistrationChanged, b.hearRegistration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := w.Adopt(b.source.Follower, delivery.Seat, delivery.EventSourceMoved, b.source.Hear); err != nil {
|
||||
if err := w.Adopt(b.source.Follower, sourcebranches.Seat, sourcebranches.EventSourceMoved, b.source.Hear); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rw, ok := w.(interface {
|
||||
@@ -225,7 +132,7 @@ func (b *Branches) hearRegistration(ctx context.Context, data []byte) error {
|
||||
}
|
||||
b.mu.Unlock()
|
||||
if s != nil {
|
||||
if err := b.w.Drop(delivery.Seat, StateSourceBranches); err != nil {
|
||||
if err := b.w.Drop(sourcebranches.Seat, sourcebranches.StateSourceBranches); err != nil {
|
||||
b.logf("meshcatalogclient: the gate's source is not dropped: %v", err)
|
||||
} else {
|
||||
b.logf("meshcatalogclient: the first registration-changed is heard: the gate's source is followed no more")
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/sourcebranches"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
@@ -78,8 +78,8 @@ func (f *fakeSeats) call(key string, body any) (json.RawMessage, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
switch key {
|
||||
case SourceVerb:
|
||||
return json.Marshal(delivery.SourceAnswer{Branches: f.source})
|
||||
case sourcebranches.SourceVerb:
|
||||
return json.Marshal(map[string][]string{"branches": f.source})
|
||||
case RegistrationsVerb:
|
||||
a := seat.RegistrationsAnswer{Registrations: []seat.Registration{}}
|
||||
for m, br := range f.regs {
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
package meshcatalogclient
|
||||
|
||||
import (
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
)
|
||||
|
||||
// SourcePrefix2 opens every source fingerprint of the form.
|
||||
const SourcePrefix2 = meshcatalogkey.SourcePrefix2
|
||||
const SourcePrefix2 = catalogkey.SourcePrefix2
|
||||
|
||||
// Toolchain2 is what an artifact's toolchain enters the fingerprint as (novox/hq ADR 0405 clause 7): the artifact's
|
||||
// language, the installed toolchain's version and the build module's recipe version.
|
||||
|
||||
@@ -3,9 +3,8 @@ module git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package meshcatalogclient
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ADR 0625 §4 (design 26, a client package): a client imports its own seat's contract, mesh-sdk's runtime and its domain's
|
||||
// SDK, never another seat's contract or client.
|
||||
func TestTheClientImportsNoOtherSeat(t *testing.T) {
|
||||
p, err := build.ImportDir(".", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
imports := append(append([]string{}, p.Imports...), p.TestImports...)
|
||||
for _, i := range imports {
|
||||
if strings.Contains(i, "/seats/meshdelivery") || strings.Contains(i, "/clients/") {
|
||||
t.Errorf("meshcatalogclient imports %s", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
@@ -93,7 +93,7 @@ func CheckModule(m seat.ModulesByModuleValue) error {
|
||||
return fmt.Errorf("meshcatalogclient: a refused module warns: a refusal names problems")
|
||||
}
|
||||
if m.Fp2 != "" {
|
||||
if _, err := meshcatalogkey.KeyPart(m.Fp2); err != nil {
|
||||
if _, err := catalogkey.KeyPart(m.Fp2); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/sourcebranches"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
@@ -18,11 +19,11 @@ const RegistrationsVerb = "seat:" + seat.Seat + "." + seat.VerbRegistrations
|
||||
|
||||
// Call asks one seat verb over the bus and answers what the holder said, whatever wraps it (process.Conn.Call, or
|
||||
// stdio.Ask through the tool runner).
|
||||
type Call func(key string, body any) (json.RawMessage, error)
|
||||
type Call = sourcebranches.Call
|
||||
|
||||
// registrationsCreated stands for the picture's Created in its follower: the registrations are followed by the seat's
|
||||
// event, never by an owner's views family, so one fixed time, never zero.
|
||||
var registrationsCreated = time.Unix(0, 0).UTC()
|
||||
var registrationsCreated = sourcebranches.Created
|
||||
|
||||
// Registrations is a part's typed follower of the registrations (design 47 G §353): a first view asked of the seat's verb
|
||||
// `registrations`, then each `registration-changed` heard, built on go/views' Follower, so the runtime's tools
|
||||
@@ -145,7 +146,7 @@ func (r *Registrations) holds(module string, version int64) bool {
|
||||
}
|
||||
|
||||
func (r *Registrations) catchUp(ctx context.Context, module string, version int64) {
|
||||
catchUp(ctx, r.Follower, func() bool { return r.holds(module, version) }, fmt.Sprintf("registration of %s at version %d said", module, version))
|
||||
sourcebranches.CatchUp(ctx, r.Follower, func() bool { return r.holds(module, version) }, fmt.Sprintf("registration of %s at version %d said", module, version))
|
||||
}
|
||||
|
||||
// ask is the Follower's Ask: every registration, whole in one answer.
|
||||
@@ -170,9 +171,4 @@ func (r *Registrations) ask(_ context.Context, _ string) (views.Answer, error) {
|
||||
return views.Answer{V: 1, Owner: seat.Seat, State: seat.StateRegistrations, Created: registrationsCreated, Values: values, At: time.Now()}, nil
|
||||
}
|
||||
|
||||
func clipBytes(b []byte) string {
|
||||
if len(b) > 200 {
|
||||
return string(b[:200]) + "…"
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
func clipBytes(b []byte) string { return sourcebranches.ClipBytes(b) }
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/kept"
|
||||
@@ -108,7 +108,7 @@ func OutsideAbout(key string) string { return strings.TrimPrefix(key, AskKindOut
|
||||
// BackupAskKey is the key of the ask about a module's failed backup on a node before it applies a fingerprint:
|
||||
// `backup.<node>.<module>.<hex>`.
|
||||
func BackupAskKey(node, module, fingerprint string) (string, error) {
|
||||
part, err := meshcatalogkey.KeyPart(fingerprint)
|
||||
part, err := catalogkey.KeyPart(fingerprint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -185,7 +185,7 @@ func ParseAnyAskKey(key string) (AskKey, error) {
|
||||
if err != nil || isNode {
|
||||
return refused(fmt.Errorf("a backup ask is backup.<node>.<module>.<fingerprint hex>"))
|
||||
}
|
||||
fp, err := meshcatalogkey.ParseKeyPart(part)
|
||||
fp, err := catalogkey.ParseKeyPart(part)
|
||||
if err != nil {
|
||||
return refused(err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
)
|
||||
|
||||
// Every node's published compositions, read only to compare (novox/hq ADR 0593 §8): each node's engine writes its own
|
||||
@@ -44,7 +44,7 @@ func published(values map[string][]byte) map[string][]publishedRow {
|
||||
_, module, part, isNode, err := nodeengineclient.ParseComposedKey(key)
|
||||
var fp string
|
||||
if err == nil && !isNode {
|
||||
fp, err = meshcatalogkey.ParseKeyPart(part)
|
||||
fp, err = catalogkey.ParseKeyPart(part)
|
||||
}
|
||||
switch {
|
||||
case err != nil:
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
mcseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
meshwants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
@@ -209,7 +209,7 @@ func (p *passer) outsideReview(ctx context.Context, module, fp string, nodes []s
|
||||
if o == nil || fp == "" || p.kvs[buckets.Waits] == nil {
|
||||
return n
|
||||
}
|
||||
hex, err := meshcatalogkey.KeyPart(fp)
|
||||
hex, err := catalogkey.KeyPart(fp)
|
||||
if err != nil {
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatlist"
|
||||
@@ -542,7 +542,7 @@ func wantsVerbs(t *testing.T) {
|
||||
indexer := &fakeIndexer{views: map[string]map[string][]byte{"mesh-delivery-indexer.modules": {}, "mesh-delivery-indexer.manifests": {}}}
|
||||
// ADR 0593 §6, §8: what each node published, every node's bucket as one state: anchor's own composition and its web at
|
||||
// a fingerprint, and laptop's web refused there.
|
||||
partNew, _ := meshcatalogkey.KeyPart(fpNew)
|
||||
partNew, _ := catalogkey.KeyPart(fpNew)
|
||||
webKey, _ := nodeengineclient.ComposedKey("anchor", "web", partNew)
|
||||
refusedKey, _ := nodeengineclient.ComposedKey("laptop", "web", partNew)
|
||||
st[buckets.ComposedRead].values["anchor.node"], _ = nodeengineclient.EncodeComposition(nodeengineclient.Composition{Node: "anchor"})
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
@@ -423,7 +423,7 @@ func serveTools() error {
|
||||
|
||||
// shortFP is a fingerprint's first twelve hex digits.
|
||||
func shortFP(fp string) string {
|
||||
part, err := meshcatalogkey.KeyPart(fp)
|
||||
part, err := catalogkey.KeyPart(fp)
|
||||
if err != nil || len(part) < 12 {
|
||||
return fp
|
||||
}
|
||||
|
||||
@@ -51,10 +51,8 @@ require git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdo
|
||||
require git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient v0.0.0-00010101000000-000000000000
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
require git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey v0.0.0-00010101000000-000000000000
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient => ../../contracts/go/clients/meshcatalogclient
|
||||
replace git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey => ../../contracts/go/clients/meshcatalogkey
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
mwc "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||
"maps"
|
||||
@@ -783,7 +783,7 @@ func normal(w mwc.Want) mwc.Want {
|
||||
}
|
||||
|
||||
func short(fp string) string {
|
||||
part, err := meshcatalogkey.KeyPart(fp)
|
||||
part, err := catalogkey.KeyPart(fp)
|
||||
if err != nil || len(part) < 12 {
|
||||
return fp
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@ func (p Put) Said(at time.Time, step, words string) Put {
|
||||
var digestForm = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
|
||||
|
||||
func (p Put) check() error {
|
||||
if _, err := meshcatalogkey.KeyPart(p.FP); err != nil {
|
||||
if _, err := catalogkey.KeyPart(p.FP); err != nil {
|
||||
return fmt.Errorf("puts: a put's fingerprint: %w", err)
|
||||
}
|
||||
if p.Why == "" {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds/forms"
|
||||
@@ -63,13 +63,13 @@ const (
|
||||
)
|
||||
|
||||
// BuilderBuildKey is the key, in BuilderBuildsBucket, of a module's build at a fingerprint: `<module>.<hex>`, the hex
|
||||
// being the fingerprint's key part (meshcatalogkey.KeyPart), since a bus key may not hold ':'. A module holding a dot or a
|
||||
// being the fingerprint's key part (catalogkey.KeyPart), since a bus key may not hold ':'. A module holding a dot or a
|
||||
// character a key refuses, and a fingerprint not of the new form, are refused, naming them.
|
||||
func BuilderBuildKey(module, fp string) (string, error) {
|
||||
if err := keys.CheckName("module", module); err != nil {
|
||||
return "", err
|
||||
}
|
||||
part, err := meshcatalogkey.KeyPart(fp)
|
||||
part, err := catalogkey.KeyPart(fp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func ParseBuilderBuildKey(key string) (module, fp string, err error) {
|
||||
if err := keys.CheckName("module", module); err != nil {
|
||||
return "", "", fmt.Errorf("builds: %q is not a builder build key: %w", key, err)
|
||||
}
|
||||
if fp, err = meshcatalogkey.ParseKeyPart(part); err != nil {
|
||||
if fp, err = catalogkey.ParseKeyPart(part); err != nil {
|
||||
return "", "", fmt.Errorf("builds: %q is not a builder build key: %w", key, err)
|
||||
}
|
||||
return module, fp, nil
|
||||
|
||||
@@ -4,7 +4,6 @@ go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
|
||||
@@ -8,7 +8,6 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/git v0.0.0-00010101000000-000000000000
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
@@ -52,7 +52,7 @@ func checkCandidateModule(m meshcatalog.CandidatesByGroupValuePullsItemFpsValue)
|
||||
return err
|
||||
}
|
||||
if m.Fp2 != "" {
|
||||
if _, err := meshcatalogkey.KeyPart(m.Fp2); err != nil {
|
||||
if _, err := catalogkey.KeyPart(m.Fp2); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package meshcatalogkey is the codec of a catalog key's parts (design 47 G §670): a fingerprint as the part of a bus
|
||||
// Package catalogkey is the codec of a catalog key's parts (design 47 G §670): a fingerprint as the part of a bus
|
||||
// key, and back. It imports neither mesh-sdk's views nor the bus client, so a package that only reads and writes keys
|
||||
// (the builder's builds package) reaches no bus; meshcatalogclient imports it.
|
||||
package meshcatalogkey
|
||||
package catalogkey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package meshcatalogkey
|
||||
package catalogkey
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
@@ -33,7 +33,7 @@ func TestThePackageImportsNoBus(t *testing.T) {
|
||||
}
|
||||
for _, i := range p.Imports {
|
||||
if strings.Contains(i, "nats") || strings.Contains(i, "/views") {
|
||||
t.Errorf("meshcatalogkey imports %s", i)
|
||||
t.Errorf("catalogkey imports %s", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Package sourcebranches is the follower of the branches the seat mesh-delivery's verb source keeps (design 47 G §614),
|
||||
// in the Delivery domain's SDK so that a seat's client holds no other seat's types (ADR 0625 §4): the first view of the
|
||||
// verb given no branch, then each source-moved heard, and the asking again until a change heard is held.
|
||||
package sourcebranches
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
)
|
||||
|
||||
// Seat is the seat whose verb source and event source-moved are followed.
|
||||
const Seat = delivery.Seat
|
||||
|
||||
// EventSourceMoved is the event the seat says when a branch's source moved.
|
||||
const EventSourceMoved = delivery.EventSourceMoved
|
||||
|
||||
// SourceVerb is the gate's verb a part reads the branches the gate keeps a source revision of by, before the first
|
||||
// registrations, as its manifest's invokes names it (design 47 G §614).
|
||||
const SourceVerb = "seat:" + delivery.Seat + "." + delivery.VerbSource
|
||||
|
||||
// StateSourceBranches names the view of the branches the gate's source keeps in a part's reader (`mesh-delivery.source`).
|
||||
const StateSourceBranches = "source"
|
||||
|
||||
// Call asks one seat verb over the bus and answers what the holder said, whatever wraps it (process.Conn.Call, or
|
||||
// stdio.Ask through the tool runner).
|
||||
type Call func(key string, body any) (json.RawMessage, error)
|
||||
|
||||
// Created stands for the picture's Created in a follower: followed by the seat's event, never by an owner's views family,
|
||||
// so one fixed time, never zero.
|
||||
var Created = time.Unix(0, 0).UTC()
|
||||
|
||||
// ClipBytes is data cut to 200 bytes for a message.
|
||||
func ClipBytes(b []byte) string {
|
||||
if len(b) > 200 {
|
||||
return string(b[:200]) + "…"
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// SourceBranches is a part's follower of the branches the gate's `source` keeps (design 47 G §614): a first view of the
|
||||
// verb `source` given no branch, then each `source-moved` heard. Its picture is keyed by the escaped branch name.
|
||||
type SourceBranches struct {
|
||||
Follower *views.Follower
|
||||
|
||||
call Call
|
||||
}
|
||||
|
||||
// New is a SourceBranches asking the gate through call. Nothing is asked before the reader starts it.
|
||||
func New(call Call) *SourceBranches {
|
||||
s := &SourceBranches{call: call}
|
||||
s.Follower = &views.Follower{Owner: Seat, Tool: SourceVerb, State: StateSourceBranches, Ask: s.ask}
|
||||
return s
|
||||
}
|
||||
|
||||
// Branches is the escaped name of each branch the picture holds.
|
||||
func (s *SourceBranches) Branches() []string {
|
||||
out := make([]string, 0)
|
||||
for b := range s.Follower.Values() {
|
||||
out = append(out, b)
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Hear takes one `source-moved` event: a branch the picture does not hold asks the first view again until it does, on
|
||||
// its own goroutine, so the consumer is never held. Data that is no such event is an error.
|
||||
func (s *SourceBranches) Hear(ctx context.Context, data []byte) error {
|
||||
var ev delivery.SourceMovedEvent
|
||||
if err := json.Unmarshal(data, &ev); err != nil || ev.Branch == "" {
|
||||
return fmt.Errorf("sourcebranches: not a %s event: %s", EventSourceMoved, ClipBytes(data))
|
||||
}
|
||||
held := func() bool { _, ok := s.Follower.Get(keys.EscapeName(ev.Branch)); return ok }
|
||||
if held() {
|
||||
return nil
|
||||
}
|
||||
go CatchUp(ctx, s.Follower, held, fmt.Sprintf("source of %s moved", ev.Branch))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SourceBranches) ask(_ context.Context, _ string) (views.Answer, error) {
|
||||
raw, err := s.call(SourceVerb, delivery.SourceInput{})
|
||||
if err != nil {
|
||||
return views.Answer{}, err
|
||||
}
|
||||
answer, said, ok := buscalls.Unwrap(raw)
|
||||
if !ok {
|
||||
return views.Answer{}, fmt.Errorf("%s answered: %s", SourceVerb, said)
|
||||
}
|
||||
var a delivery.SourceAnswer
|
||||
if err := json.Unmarshal(answer, &a); err != nil {
|
||||
return views.Answer{}, fmt.Errorf("%s answered no branches: %s", SourceVerb, ClipBytes(answer))
|
||||
}
|
||||
if a.Error != "" {
|
||||
return views.Answer{}, fmt.Errorf("%s answered: %s", SourceVerb, a.Error)
|
||||
}
|
||||
values := make(map[string]json.RawMessage, len(a.Branches))
|
||||
for _, b := range a.Branches {
|
||||
values[b] = json.RawMessage(`{}`)
|
||||
}
|
||||
return views.Answer{V: 1, Owner: Seat, State: StateSourceBranches, Created: Created, Values: values, At: time.Now()}, nil
|
||||
}
|
||||
|
||||
// CatchUp asks f's first view again until held says it holds what was heard, pausing between asks as the follower's Wait
|
||||
// says (doubling to a minute), or until ctx ends: an ask already running may have been answered before the change was
|
||||
// written.
|
||||
func CatchUp(ctx context.Context, f *views.Follower, held func() bool, why string) {
|
||||
wait := f.Wait
|
||||
if wait == 0 {
|
||||
wait = 5 * time.Second
|
||||
}
|
||||
for !held() {
|
||||
if err := f.Gap(ctx, why); err != nil {
|
||||
return
|
||||
}
|
||||
if held() {
|
||||
return
|
||||
}
|
||||
p, cancel := context.WithTimeout(ctx, wait)
|
||||
<-p.Done()
|
||||
cancel()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if wait *= 2; wait > time.Minute {
|
||||
wait = time.Minute
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user