nats: a warrant's spend record in the keeper's own state spent; the fill pass while the consumer binds; partial says kept

Review round 2 of the registry milestone:
- security F2: the spend record leaves requests, which the tools write as the tool runner's account, for state spent,
  named in the keeper's bus.state and declared read-only (the lead's 18:1x ruling: the tools read it, never write);
  nats_requests lists the warrants spent from it;
- running F1: the fill pass also runs while the manifest-accepted consumer is bound again;
- health note 3: an event said on the seat's subject and refused by its history is kept with its one id and said again
  on the history alone, never again with a new id;
- seams note 3, health note 4: words unsaid with a status that cannot be written raise the keeper's own condition
  status-unwritten, cleared when it is written; the health names what a restart would lose.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 18:13:36 +01:00
parent ccb7eaff13
commit 0d6d7acaae
10 changed files with 236 additions and 58 deletions
+18 -5
View File
@@ -128,6 +128,7 @@ func streamEmitter(js jetstream.JetStream, a process.Account) func(*keeper.Sayin
if subjects == nil {
subjects = EventSubjects(a, s.Event)
}
s.Said = nil
for i, subject := range subjects {
msg := nats.NewMsg(subject)
msg.Header.Set("x-event-id", s.ID)
@@ -143,6 +144,7 @@ func streamEmitter(js jetstream.JetStream, a process.Account) func(*keeper.Sayin
s.Left = append([]string(nil), subjects[i:]...)
return fmt.Errorf("saying %s on %s: %w", s.Event, subject, err)
}
s.Said = append(s.Said, subject)
}
s.Left = nil
return nil
@@ -267,6 +269,9 @@ func hearer(js jetstream.JetStream, a process.Account, logf func(string, ...any)
}
}
// FillEvery is how often the keeper's fill pass runs: a minute (a test's shorter bound beside).
var FillEvery = time.Minute
// DecidedSubject is where the operator channel says the operator's answer to one of the bus module's own asks (ADR
// 0259 §6; ADR 0620 §2), heard on the keeper's durable consumer: the warrant a recreate or a drop waits for.
var DecidedSubject = oc.SubjectDecided(keeper.Module)
@@ -278,7 +283,7 @@ var DecidedSubject = oc.SubjectDecided(keeper.Module)
// the persons' requests acted on as they are written or answered, never on a timer. It returns when ctx ends.
func Keep(ctx context.Context, k *keeper.Keeper, hear func(context.Context, func(string, []byte) error) error,
requests func(context.Context, func()) error, logf func(string, ...any)) error {
tick := time.NewTicker(time.Minute)
tick := time.NewTicker(FillEvery)
defer tick.Stop()
k.FillPass()
var wait time.Duration
@@ -335,10 +340,18 @@ func Keep(ctx context.Context, k *keeper.Keeper, hear func(context.Context, func
break
}
logf("manifest-accepted cannot be heard yet (%v): asked again in %s", err, backoff)
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
// The fill pass runs on while the consumer is bound again (review round 2, running F1).
bound := time.After(backoff)
binding:
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-tick.C:
k.FillPass()
case <-bound:
break binding
}
}
}
if requests != nil {
@@ -153,7 +153,7 @@ func TestTheBusModuleRunsWhole(t *testing.T) {
t.Fatal(err)
}
defer pconn.Close()
for _, st := range []string{keeper.StateAsked, keeper.StateRequests} {
for _, st := range []string{keeper.StateAsked, keeper.StateRequests, keeper.StateSpent} {
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "nats_" + st, History: 5}); err != nil {
t.Fatal(err)
}
@@ -252,6 +252,22 @@ func TestTheBusModuleRunsWhole(t *testing.T) {
stopDown := run(down)
eventually(t, "a fill pass with no first view", func() bool { return !down.Status().Pass.IsZero() && down.Status().FirstView.Answered.IsZero() })
stopDown()
// And while its consumer of manifest-accepted is bound again on its backoff (review round 2, running F1).
was := FillEvery
FillEvery = 20 * time.Millisecond
defer func() { FillEvery = was }()
deaf := newKeeper()
kctx, stopDeaf := context.WithCancel(ctx)
done := make(chan struct{})
go func() {
_ = Keep(kctx, deaf, func(context.Context, func(string, []byte) error) error { return errors.New("no consumer yet") }, nil, t.Logf)
close(done)
}()
eventually(t, "the first view answered", func() bool { return !deaf.Status().FirstView.Answered.IsZero() })
first := deaf.Status().Pass
eventually(t, "a fill pass while the consumer is bound again", func() bool { return deaf.Status().Pass.After(first) })
stopDeaf()
<-done
})
k := newKeeper()
stop := run(k)
+12 -6
View File
@@ -174,7 +174,8 @@ func TestTheBussManifestMountsItsProgramsIntoTheUpstreamImage(t *testing.T) {
MaxMiB int `json:"max-mib"`
} `json:"streams"`
State []struct {
Name string `json:"name"`
Name string `json:"name"`
ReadOnly bool `json:"read-only"`
} `json:"state"`
}
if err := json.Unmarshal(raw, &nm); err != nil {
@@ -203,12 +204,17 @@ func TestTheBussManifestMountsItsProgramsIntoTheUpstreamImage(t *testing.T) {
t.Errorf("emits lists %s, which its stream carries: an event is named in one place (ADR 0363)", e)
}
}
var states []string
var states, readOnly []string
for _, s := range nm.State {
states = append(states, s.Name)
if s.ReadOnly {
readOnly = append(readOnly, s.Name)
}
}
if !slices.Equal(states, []string{"asked", "requests"}) {
t.Errorf("state is %v: asked and requests, old sections (ADR 0595 §10)", states)
// spent, the warrants the keeper spent, is written by the keeper alone and only read by the tools (review round 2,
// security F2: the tool runner's account, which writes requests, may never delete a spend record).
if !slices.Equal(states, []string{"asked", "requests", "spent"}) || !slices.Equal(readOnly, []string{"spent"}) {
t.Errorf("state is %v, read-only %v: asked and requests, old sections (ADR 0595 §10), and spent, read-only", states, readOnly)
}
if _, ok := nm.OwnSecrets["broker-keeper"]; !ok {
t.Error("own-secrets lack broker-keeper, the keeper's own account (ADR 0436 §1)")
@@ -217,9 +223,9 @@ func TestTheBussManifestMountsItsProgramsIntoTheUpstreamImage(t *testing.T) {
bus, _ := keeper["bus"].(map[string]any)
health, _ := keeper["health"].(map[string]any)
if keeper["type"] != "process" || keeper["artifact"] != "tools" || fmt.Sprint(keeper["run"]) != "[./nats-tools keep]" ||
fmt.Sprint(bus["state"]) != "[asked requests]" ||
fmt.Sprint(bus["state"]) != "[asked requests spent]" ||
health["kind"] != "tool" || health["tool"] != "nats_keeper_health" {
t.Errorf("the keeper is %v: a process of the tools bundle, run as keep, keeping asked and requests, judged by nats_keeper_health", keeper)
t.Errorf("the keeper is %v: a process of the tools bundle, run as keep, keeping asked, requests and spent, judged by nats_keeper_health", keeper)
}
// Its bus declares keeps, the one key more ADR 0626 §1 allows it, read through the SDK as the grant composition reads it.
var whole catalogue.Manifest
+22 -15
View File
@@ -31,10 +31,10 @@ type requestStore struct {
Get func(key string) (*keeper.Request, error)
Keys func() ([]string, error)
Delete func(key string) error
// Raw reads one key's value as it is (an acted key is no request).
Raw func(key string) ([]byte, error)
Ask func(a oc.Ask) error
Now func() time.Time
// Spent reads the keeper's spend records, its state spent, which this bundle reads and never writes.
Spent func() ([]keeper.Acted, error)
Ask func(a oc.Ask) error
Now func() time.Time
// Ended answers the request's end once the keeper wrote it, or nil once within passed: heard on the state's watch,
// never read again on a timer.
Ended func(id string, within time.Duration) *keeper.Request
@@ -64,12 +64,21 @@ func RequestsInState() requestStore {
},
Keys: s.Keys,
Delete: s.Delete,
Raw: func(key string) ([]byte, error) {
e, err := s.Get(key)
if err != nil || e == nil {
Spent: func() ([]keeper.Acted, error) {
spent := stdio.State(keeper.StateSpent)
keys, err := spent.Keys()
if err != nil {
return nil, err
}
return e.Value, nil
sort.Strings(keys)
out := []keeper.Acted{}
for _, key := range keys {
var a keeper.Acted
if e, err := spent.Get(key); err == nil && e != nil && json.Unmarshal(e.Value, &a) == nil {
out = append(out, a)
}
}
return out, nil
},
Ask: func(a oc.Ask) error { return stdio.SeatPublish(oc.AcceptSubjectAsk(keeper.Module), a, a.ID) },
Now: func() time.Time { return time.Now().UTC() },
@@ -249,7 +258,7 @@ func awaitEnd(rs requestStore, r keeper.Request, tool string) any {
// so a request nobody will answer leaves by a person's act and never only by its key ageing out.
func requestsTool(rs requestStore) stdio.Tool {
return stdio.Tool{Name: "nats_requests",
Description: "The persons' requests the bus module's tools handed its keeper (nats_create, nats_apply, nats_recreate, nats_drop, nats_first_view_again): each one waiting (and for what: the operator's warrant on its ask, or the keeper's first view) or ended, with its outcome or why not, and each warrant spent (acted.<ask id>, a warrant acts once). With drop and why, removes one request not ended yet, recorded as a hand act; the operator's ask it raised then acts on nothing.",
Description: "The persons' requests the bus module's tools handed its keeper (nats_create, nats_apply, nats_recreate, nats_drop, nats_first_view_again): each one waiting (and for what: the operator's warrant on its ask, or the keeper's first view) or ended, with its outcome or why not, and each warrant spent (the keeper's state spent, which only it writes: a warrant acts once). With drop and why, removes one request not ended yet, recorded as a hand act; the operator's ask it raised then acts on nothing.",
Input: map[string]any{"drop": str("a request not ended yet, to remove by hand"), "why": str("why, in plain words: kept with the act")},
Run: func(a map[string]any) (any, error) {
if id := strings.TrimSpace(arg(a, "drop")); id != "" {
@@ -279,14 +288,12 @@ func requestsTool(rs requestStore) stdio.Tool {
}
sort.Strings(keys)
var waiting, ended []keeper.Request
spent := []keeper.Acted{}
spent, err := rs.Spent()
if err != nil {
return nil, fmt.Errorf("the warrants spent cannot be read: %w", err)
}
for _, key := range keys {
if strings.HasPrefix(key, keeper.ActedPrefix) {
raw, err := rs.Raw(key)
var acted keeper.Acted
if err == nil && json.Unmarshal(raw, &acted) == nil {
spent = append(spent, acted)
}
continue
}
r, err := rs.Get(key)
+4 -2
View File
@@ -43,10 +43,12 @@ func TestAPersonsRequestIsHandedToTheKeeperAsTheProvedCaller(t *testing.T) {
for k := range held {
out = append(out, k)
}
return append(out, keeper.ActedKey("bus-old")), nil
return out, nil
},
Delete: func(key string) error { delete(held, key); return nil },
Raw: func(string) ([]byte, error) { return []byte(`{"ask":"bus-old","request":"r0","verb":"drop"}`), nil },
Spent: func() ([]keeper.Acted, error) {
return []keeper.Acted{{Ask: "bus-old", Request: "r0", Verb: "drop"}}, nil
},
Ask: func(a oc.Ask) error { raised = append(raised, a); return nil },
Now: func() time.Time { return time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC) },
Record: func(q handacts.Request) error { acts = append(acts, q); return nil },