Revert L3 (mesh-delivery, merge d827420): its node-engine removed every held module on novox, the bus server among them, at 08:54 CET on 2026-10-29 (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-delivery@c1b4533201c8
mesh/delivery-group group revert/774-l3 merged: every member is merged
mesh/check being carried to 6771db22ae69 on d8274204fbed | from mesh-catalog 1555241393fe | novox/mesh-delivery#109 in group revert/774-l3

L3 lands again with two fixes: a held module is never removed, and the grants cover L3's new subjects.
This commit is contained in:
2026-10-29 09:42:29 +01:00
parent d8274204fb
commit 6771db22ae
74 changed files with 1574 additions and 2550 deletions
@@ -214,10 +214,6 @@ func (s ownStore) Put(state, key string, value []byte) (uint64, error) {
return s.c.State(state).Put(key, json.RawMessage(value))
}
func (s ownStore) Keys(state string) ([]string, error) { return s.c.State(state).Keys() }
func (s ownStore) Delete(state, key string) error { return s.c.State(state).Delete(key) }
// emitting is the process connection as the part's emitter: its events, with no options.
type emitting struct{ c *process.Conn }
@@ -18,41 +18,6 @@ type creating struct {
js jetstream.JetStream
}
// CreateConsumer creates one durable consumer an account needs, by its stream and name (ADR 0561 §6): the create alone;
// one the bus holds answers ErrHeld and is left as it is.
func (c creating) CreateConsumer(stream string, cfg jetstream.ConsumerConfig) error {
ctx, cancel := context.WithTimeout(c.ctx, requestWithin)
defer cancel()
if _, err := c.js.Consumer(ctx, stream, cfg.Durable); err == nil {
return grants.ErrHeld
}
_, err := c.js.CreateConsumer(ctx, stream, cfg)
switch {
case err == nil:
return nil
case errors.Is(err, jetstream.ErrConsumerExists):
return grants.ErrHeld
case errors.Is(err, context.DeadlineExceeded):
return fmt.Errorf("no answer in %s: a create the bus refuses is answered by none (%w)", requestWithin, err)
}
return err
}
// MatchConsumer brings a held consumer to cfg on a person's ask (grants_consumer_match), keeping where it delivers from:
// the server refuses a changed deliver policy, and a consumer moved would replay or lose what it holds.
func (c creating) MatchConsumer(stream string, cfg jetstream.ConsumerConfig) error {
ctx, cancel := context.WithTimeout(c.ctx, requestWithin)
defer cancel()
held, err := c.js.Consumer(ctx, stream, cfg.Durable)
if err != nil {
return err
}
have := held.CachedInfo().Config
cfg.DeliverPolicy, cfg.OptStartSeq, cfg.OptStartTime = have.DeliverPolicy, have.OptStartSeq, have.OptStartTime
_, err = c.js.UpdateConsumer(ctx, stream, cfg)
return err
}
func (c creating) CreateDeclared(cfg jetstream.StreamConfig) error {
ctx, cancel := context.WithTimeout(c.ctx, requestWithin)
defer cancel()
+12 -9
View File
@@ -62,9 +62,14 @@ func main() {
}
}
// keptStates is what the part keeps (ADR 0495 §2-§3): each readable, for the view and a session, but the wanted accounts,
// by the vault alone (ADR 0561 §2).
func keptStates() []announce.State { return grants.Announced() }
// keptStates is what the part keeps, each readable (ADR 0495 §2-§3): the view and a session read them.
func keptStates() []announce.State {
var out []announce.State
for _, s := range grants.States {
out = append(out, announce.State{Name: s, Readable: true})
}
return out
}
// run is the process: it returns when ctx ends, or when it has no credential or cannot dial the bus at all. A bucket or
// read the bus refuses never ends it.
@@ -112,9 +117,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
}
bus := newBusReader(ctx, js)
f.Part = &grants.Part{Read: bus, Call: conn, Store: ownStore{conn}, Emit: emitting{conn},
Publish: memberships{ctx: ctx, nc: nc, js: js}, Create: creating{ctx: ctx, js: js}, Consumers: creating{ctx: ctx, js: js},
Objects: sealedObjects{ctx: ctx, js: js}, Logf: logger.Printf,
Bus: grants.BusAddress{URL: account.URL, Fingerprint: account.Fingerprint}}
Publish: memberships{ctx: ctx, nc: nc, js: js}, Create: creating{ctx: ctx, js: js},
Objects: sealedObjects{ctx: ctx, js: js}, Logf: logger.Printf}
f.Watch = bus
// The operator's word on take-over and hand-back is their yes on their channel: the part asks it itself, and acts on
// the warrant once (mesh-sdk asker), after its refusals.
@@ -134,9 +138,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
})
}
// The controller makes this module's durable consumer on EVENTS from the manifest's consumes (filtered on the two
// condition subjects and the vault's account-issued; ADR 0561 §6 gives that to the part from the lease write); it is
// pulled and every message acknowledged, so its pending never grows. Where it is not there yet the pass says so and
// hears again at the next change. The vault's bus-accounts is watched beside it, so a hash is composed either way.
// condition subjects); it is pulled and every message acknowledged, so its pending never grows. Where it is not there
// yet the pass says so and hears again at the next change.
f.Hear = func(handle func(string, []byte)) error {
cons, err := js.Consumer(ctx, "EVENTS", account.Node+"_"+account.Module)
if err != nil {
@@ -2,7 +2,6 @@ package main
import (
"encoding/json"
"strings"
stdio "git.novox.be/novox/mesh-sdk/go"
@@ -40,10 +39,6 @@ func (runtimeStore) Put(state, key string, value []byte) (uint64, error) {
return stdio.State(state).Put(key, json.RawMessage(value))
}
func (runtimeStore) Keys(state string) ([]string, error) { return stdio.State(state).Keys() }
func (runtimeStore) Delete(state, key string) error { return stdio.State(state).Delete(key) }
// runtimeBus calls tools and says events through the runtime.
type runtimeBus struct{}
@@ -118,43 +113,6 @@ func toolsOf(p *grants.Part) []stdio.Tool {
Run: func(a map[string]any) (any, error) {
return p.HandBack(stdio.CallerOf(a), grants.Answer(a, "why"))
}},
{Name: grants.ToolAccounts, Description: "The bus accounts the mesh wants (novox/hq ADR 0561 §2, §9): every account the vault " +
"mints that placement composes (a module's, a process's, a machine's provisioner client's), as the grants part last " +
"wrote it to its state accounts, which the vault alone reads: user, kind, node, module, process, the bus's address and " +
"fingerprint, a module's claims with the verbs each seat promises, and when the key was written; one user's or all; " +
"what the last pass wrote, deleted or kept; and the durable consumers the accounts need as the last pass found them " +
"(missing, differing and why, created; whether the lease is handed away). Never a password. Read-only: " +
"grants_compose_now writes them again.",
Input: schema(map[string]string{"user": "one bus user, e.g. ace.mesh-build-go"}),
Run: func(a map[string]any) (any, error) { return p.Accounts(grants.Answer(a, "user")) }},
{Name: grants.ToolConsumerMatch, Description: "Bring one durable consumer an account needs (a module's on EVENTS, a process's on " +
"its work queue; novox/hq ADR 0561 §6) to the configuration the bus composer composes, by hand: the recovery when " +
"the condition mesh-grants.consumer-differs names it. Asks the part's process, which alone may, and keeps where the " +
"consumer delivers from. Refused while the controller holds its lease (the consumers are the controller's then) and " +
"for a consumer the records do not compose. grants_user_list's last pass says what became of it.",
Input: schema(map[string]string{"stream": "the stream, e.g. EVENTS", "consumer": "the consumer, e.g. ace_mesh-build-go"}, "stream", "consumer"),
Run: func(a map[string]any) (any, error) {
return p.MatchConsumer(grants.Answer(a, "stream"), grants.Answer(a, "consumer"))
}},
{Name: grants.ToolAcceptHashes, Description: "Release the hold of a user list that changes several loaded users' hashes at " +
"once (novox/hq ADR 0561 §7; the condition mesh-grants.user-list-held names them), by a person's act with why: names " +
"exactly the users held, bound to the held list's digest, so the part's process writes that list once at its next pass " +
"and says hashes-accepted. It never releases another hold (a failed read, a user dropped for want of a hash, a lost " +
"right), and a list composed otherwise since is held again for a new acceptance.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"users": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "the users held, as the condition names them"},
"why": map[string]any{"type": "string", "description": "why their hashes change"}}, "required": []string{"users", "why"}},
Run: func(a map[string]any) (any, error) {
var users []string
if list, ok := a["users"].([]any); ok {
for _, u := range list {
if s, ok := u.(string); ok {
users = append(users, strings.TrimSpace(s))
}
}
}
return p.AcceptHashes(stdio.CallerOf(a), users, grants.Answer(a, "why"))
}},
{Name: grants.ToolTakeOverState, Description: "Where the switch stands: the list the bus node follows, the ask open now, the request, and " +
"every act with what became of it (asked, switched, declined, expired, refused and why).",
Input: schema(nil),