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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user