indexer, mesh-catalog contract: an unwatch of a branch a placement names is refused as placed
The indexer asks the seat mesh-module-assigner's verb placement (input {module}, answer {placements: [{branch, nodes}]}, A-62)
before an unwatch is proposed and refuses one whose branch a placement names, with the reason placed (new in the seat
mesh-catalog's contract) naming the placement; its manifest invokes the verb.
Builds: design 47 G §704 (unwatch), A-62
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -98,6 +98,17 @@ func newRegistry(js jetstream.JetStream, conn *nats.Conn, cred process.Account,
|
||||
Say: func(_ context.Context, e meshcatalog.RegistrationChangedEvent) error {
|
||||
return publishAs(conn, cred, seatEventSubject(meshcatalog.EventRegistrationChanged), fmt.Sprintf("%s-%s-%d", meshcatalog.EventRegistrationChanged, e.Module, e.Version), e)
|
||||
},
|
||||
Placement: func(module string) ([]mma.ModulePlacement, error) {
|
||||
raw, err := call("seat:"+mma.Seat+"."+mma.VerbPlacement, mma.PlacementInput{Module: module})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var a mma.PlacementAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return nil, fmt.Errorf("the answer of %s.%s is not its seat's: %w", mma.Seat, mma.VerbPlacement, err)
|
||||
}
|
||||
return a.Placements, nil
|
||||
},
|
||||
Branch: func(repository, branch string) (gitseat.BranchAnswer, error) {
|
||||
var a gitseat.BranchAnswer
|
||||
raw, err := call("seat:"+gitseat.Seat+"."+gitseat.VerbBranch, gitseat.BranchInput{Repository: repository, Branch: branch})
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
gitseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/git"
|
||||
@@ -123,6 +124,9 @@ type Registry struct {
|
||||
Say func(ctx context.Context, e meshcatalog.RegistrationChangedEvent) error
|
||||
// Branch reads a branch through the seat git; an empty branch asks the repository's default branch.
|
||||
Branch func(repository, branch string) (gitseat.BranchAnswer, error)
|
||||
// Placement reads a module's placements through the seat mesh-module-assigner's verb placement: one per watched branch a row
|
||||
// of its assignments names, with the nodes that run the module from it (design 47 G §704, A-62).
|
||||
Placement func(module string) ([]mma.ModulePlacement, error)
|
||||
// Ask publishes an ask under the indexer's own name, Cancel takes it back, Where reads the router's own record of an
|
||||
// ask, read under the indexer's module's name (state, and the warrant when it holds one; none when it holds no ask).
|
||||
Ask func(a oc.Ask) error
|
||||
@@ -417,6 +421,22 @@ func (r *Registry) Propose(ctx context.Context, verb, caller string, change json
|
||||
if verb != verbRegister && !found {
|
||||
return refused(module, meshcatalog.RegistrationAskedRefusedUnknownModule, module+" is not registered: register it first"), nil
|
||||
}
|
||||
// An unwatch of a branch a placement names is refused, naming the placement (§704, A-62).
|
||||
if verb == verbUnwatch {
|
||||
if r.Placement == nil {
|
||||
return meshcatalog.RegistrationAsked{}, errors.New("the placements of a module are not asked of the seat mesh-module-assigner, so no branch is unwatched")
|
||||
}
|
||||
placed, err := r.Placement(module)
|
||||
if err != nil {
|
||||
return meshcatalog.RegistrationAsked{}, fmt.Errorf("the placements of %s are not read: %w", module, err)
|
||||
}
|
||||
for _, p := range placed {
|
||||
if p.Branch == branch {
|
||||
return refused(module, meshcatalog.RegistrationAskedRefusedPlaced,
|
||||
fmt.Sprintf("the branch %s of %s is placed: %s run it from that branch", branch, module, strings.Join(p.Nodes, ", "))), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// register with no branch takes the repository's default branch (§342).
|
||||
if verb == verbRegister {
|
||||
var in meshcatalog.RegisterInput
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
|
||||
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
|
||||
gitseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/git"
|
||||
@@ -26,6 +27,7 @@ type regRig struct {
|
||||
whereErr error
|
||||
chosen map[string]string // ask id -> option the router's record holds
|
||||
missing map[string]bool
|
||||
placed map[string][]mma.ModulePlacement // module -> its placements, as the seat mesh-module-assigner's placement answers
|
||||
}
|
||||
|
||||
func newRegRig() *regRig {
|
||||
@@ -45,8 +47,9 @@ func newRegRig() *regRig {
|
||||
}
|
||||
return gitseat.BranchAnswer{Branch: branch, Repository: repo, Protected: !g.unprotect[branch]}, nil
|
||||
},
|
||||
Ask: func(a oc.Ask) error { g.asks[a.ID] = a; return nil },
|
||||
Cancel: func(id string) error { g.cancelled = append(g.cancelled, id); return nil },
|
||||
Placement: func(module string) ([]mma.ModulePlacement, error) { return g.placed[module], nil },
|
||||
Ask: func(a oc.Ask) error { g.asks[a.ID] = a; return nil },
|
||||
Cancel: func(id string) error { g.cancelled = append(g.cancelled, id); return nil },
|
||||
Where: func(id string) (string, *oc.Warrant, error) {
|
||||
if g.whereErr != nil {
|
||||
return "", nil, g.whereErr
|
||||
@@ -176,6 +179,13 @@ func TestRegistrationsAreWrittenOnTheirWarrantOnly(t *testing.T) {
|
||||
delete(g.unprotect, "beta")
|
||||
w, _ = g.r.Propose(ctx, "watch", "s@n", mustJSON(meshcatalog.WatchInput{Module: "a", Branch: "beta"}))
|
||||
g.decide(t, w.Ask, "approve")
|
||||
// A branch a placement names is not unwatched: refused as placed, naming the placement, and no ask is raised (§704, A-62).
|
||||
g.placed = map[string][]mma.ModulePlacement{"a": {{Branch: "beta", Nodes: []string{"n1", "n2"}}}}
|
||||
if p, err := g.r.Propose(ctx, "unwatch", "s@n", mustJSON(meshcatalog.WatchInput{Module: "a", Branch: "beta"})); err != nil || p.Refused != meshcatalog.RegistrationAskedRefusedPlaced ||
|
||||
p.Ask != "" || !strings.Contains(p.Words, "beta") || !strings.Contains(p.Words, "n1, n2") {
|
||||
t.Fatalf("unwatch of a placed branch: %+v, %v; want refused placed naming the placement", p, err)
|
||||
}
|
||||
g.placed = nil
|
||||
u, _ := g.r.Propose(ctx, "unwatch", "s@n", mustJSON(meshcatalog.WatchInput{Module: "a", Branch: "beta"}))
|
||||
g.decide(t, u.Ask, "approve")
|
||||
if now, _, _ := g.r.Registration(ctx, "a"); now.Version != 4 || len(now.Branches) != 1 {
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
}
|
||||
// Design 47 G §700, A-74: the router's record of an ask is read through the seat operator-channel, which the indexer uses,
|
||||
// and its holder is asked of the seat mesh-module-assigner.
|
||||
if !slices.Equal(m.Uses, []string{"operator-channel"}) || !slices.Contains(m.Invokes, "seat:mesh-module-assigner.assignments") {
|
||||
if !slices.Equal(m.Uses, []string{"operator-channel"}) || !slices.Contains(m.Invokes, "seat:mesh-module-assigner.assignments") || !slices.Contains(m.Invokes, "seat:mesh-module-assigner.placement") {
|
||||
t.Errorf("uses = %v, invokes = %v, want the seat operator-channel used and seat:mesh-module-assigner.assignments invoked", m.Uses, m.Invokes)
|
||||
}
|
||||
// 1126 note 76: heard keeps the last 64 FORGE messages the indexer handled, for indexer_heard.
|
||||
@@ -178,7 +178,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
// Three invokes: the file of a registered module's manifest and lock files (registered.go), the forge's trees (the named paths' objects, since the indexer is the one reader of a manifest's paths:
|
||||
// ADR 0499 clauses 2 and 3, ADR 0495 clause 1; and the module directories of the trunk, issue 1900) and whether a
|
||||
// commit is in another (issue 1900).
|
||||
if want := []string{"mesh-delivery-assigner.assignments", "seat:git.trees", "seat:git.file", "seat:git.contains", "seat:git.open-pulls", "seat:git.branch", "mesh-delivery-checker.checker_tries", "mesh-delivery-forge-bridge.bridge_merged_message", "seat:node-engine.composition", "seat:mesh-delivery.source", "seat:mesh-module-assigner.assignments", "seat:mesh-hand-acts.hand-act", "seat:mesh-watchdogs.presence"}; !reflect.DeepEqual(m.Invokes, want) {
|
||||
if want := []string{"mesh-delivery-assigner.assignments", "seat:git.trees", "seat:git.file", "seat:git.contains", "seat:git.open-pulls", "seat:git.branch", "mesh-delivery-checker.checker_tries", "mesh-delivery-forge-bridge.bridge_merged_message", "seat:node-engine.composition", "seat:mesh-delivery.source", "seat:mesh-module-assigner.assignments", "seat:mesh-module-assigner.placement", "seat:mesh-hand-acts.hand-act", "seat:mesh-watchdogs.presence"}; !reflect.DeepEqual(m.Invokes, want) {
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
// 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
"seat:node-engine.composition",
|
||||
"seat:mesh-delivery.source",
|
||||
"seat:mesh-module-assigner.assignments",
|
||||
"seat:mesh-module-assigner.placement",
|
||||
"seat:mesh-hand-acts.hand-act",
|
||||
"seat:mesh-watchdogs.presence"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user