CAT-N9: the old remove tools refuse an entry of the new form, naming nats_drop
The mesh deletes a module's kept data only through drop and recreate (ADR 0595 §4 as its insight has it; design 25 § 12.3): nats_stream_remove, nats_bucket_remove and nats_consumer_remove read the keeper's own state asked and refuse a stream or bucket the keeper made, before the bus is reached, naming nats_drop and nats_recreate; one that cannot be told apart is not removed. The old sections' entries are removed as before. Rows of the existing refusal test. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -179,7 +179,7 @@ func allTools() []stdio.Tool {
|
||||
defer cancel()
|
||||
return monitor(ctx, "/jsz?accounts=1&streams=1&config=1")
|
||||
}
|
||||
out := append(tools(monitor, ReadUsers, UsersWritten), removalTools(LiveBus(), SaidByTheRuntime)...)
|
||||
out := append(tools(monitor, ReadUsers, UsersWritten), removalTools(LiveBus(), SaidByTheRuntime, HeldInState)...)
|
||||
out = append(out, expiryTools(LiveBus(), SaidByTheRuntime, RecordedBySeat)...)
|
||||
out = append(out, brokerTools(HeldInState, jsz, StreamHistory(LiveBus()))...)
|
||||
return append(out, requestTools(RequestsInState())...)
|
||||
|
||||
@@ -333,9 +333,23 @@ func RemoveStream(ctx context.Context, bus Bus, said Said, a map[string]any) (an
|
||||
}
|
||||
|
||||
// removalTools are the module's three tools that change the bus; every other tool here reads.
|
||||
func removalTools(bus Bus, said Said) []stdio.Tool {
|
||||
// removalTools are the three removal tools. **They refuse an entry of the new form** (design 25 § 12.3; ADR 0595 §4 as
|
||||
// its insight of 2026-11-03 has it): the mesh deletes a module's kept data only through the bus module's drop and
|
||||
// recreate, so a stream, a bucket or a consumer of a stream the keeper made (its state asked, read through newForm) is
|
||||
// refused, naming nats_drop, before the bus is reached; the old sections' entries are removed as before. A newForm
|
||||
// that cannot be read refuses too: what cannot be told apart is not removed.
|
||||
func removalTools(bus Bus, said Said, newForm heldEntries) []stdio.Tool {
|
||||
run := func(f func(context.Context, Bus, Said, map[string]any) (any, error)) func(map[string]any) (any, error) {
|
||||
return func(a map[string]any) (any, error) { return f(context.Background(), bus, said, a) }
|
||||
return func(a map[string]any) (any, error) {
|
||||
stream := arg(a, "stream")
|
||||
if b := arg(a, "bucket"); b != "" {
|
||||
stream = "KV_" + b
|
||||
}
|
||||
if err := refuseNewForm(newForm, stream); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f(context.Background(), bus, said, a)
|
||||
}
|
||||
}
|
||||
return []stdio.Tool{
|
||||
{Name: "nats_consumer_remove",
|
||||
@@ -353,3 +367,24 @@ func removalTools(bus Bus, said Said) []stdio.Tool {
|
||||
Run: run(RemoveKeys)},
|
||||
}
|
||||
}
|
||||
|
||||
// refuseNewForm refuses a stream the keeper made for a module's bus state, naming the tool that removes it.
|
||||
func refuseNewForm(newForm heldEntries, stream string) error {
|
||||
if newForm == nil || stream == "" {
|
||||
return nil
|
||||
}
|
||||
held, err := newForm()
|
||||
if err != nil {
|
||||
return fmt.Errorf("whether %s is a module's kept data of the new form cannot be read (%v), so nothing was removed", stream, err)
|
||||
}
|
||||
for module, a := range held {
|
||||
for local, m := range a.Made {
|
||||
for _, o := range m.Plan.Objects {
|
||||
if o.Stream() == stream {
|
||||
return fmt.Errorf("%s is %s's kept data %q, which the bus module made: it is removed only by nats_drop (an orphan, on the operator's warrant) or rebuilt by nats_recreate, never by this tool; nothing was removed", stream, module, local)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"nats-tools/internal/keeper"
|
||||
)
|
||||
|
||||
const connz = `{"connections":[
|
||||
@@ -220,6 +223,17 @@ func TestTheRemovalToolsRefuseBeforeTheyReachTheBus(t *testing.T) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
said := func(string, any) error { t.Fatal("an audit event was said for a refused call"); return nil }
|
||||
newForm := func() (map[string]keeper.Asked, error) {
|
||||
made := map[string]keeper.Made{}
|
||||
for _, e := range []catalogue.BusStateEntry{{Name: "log", Storage: "stream", Template: "log"}, {Name: "drafts", Storage: "key-value", Template: "state"}} {
|
||||
p, err := keeper.PlanOf("m", e)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
made[e.Name] = keeper.Made{Plan: p, Created: true}
|
||||
}
|
||||
return map[string]keeper.Asked{"m": {Module: "m", Made: made}}, nil
|
||||
}
|
||||
for _, c := range []struct {
|
||||
tool string
|
||||
args map[string]any
|
||||
@@ -238,9 +252,13 @@ func TestTheRemovalToolsRefuseBeforeTheyReachTheBus(t *testing.T) {
|
||||
{"nats_stream_remove", map[string]any{"stream": "ASSIGNMENTS", "why": " "}, "why is required"},
|
||||
{"nats_stream_remove", map[string]any{"stream": "A.>", "why": "x"}, "stream is required"},
|
||||
{"nats_stream_remove", map[string]any{"stream": "", "why": "x"}, "stream is required"},
|
||||
// CAT-N9: an entry of the new form goes only by drop or recreate; an old section's goes as before.
|
||||
{"nats_stream_remove", map[string]any{"stream": "m_log", "why": "x"}, "removed only by nats_drop"},
|
||||
{"nats_bucket_remove", map[string]any{"bucket": "m_drafts", "node": "g14", "why": "x"}, "m's kept data \"drafts\""},
|
||||
{"nats_consumer_remove", map[string]any{"stream": "m_log", "consumer": "c", "why": "x"}, "removed only by nats_drop"},
|
||||
} {
|
||||
var run func(map[string]any) (any, error)
|
||||
for _, tl := range removalTools(bus, said) {
|
||||
for _, tl := range removalTools(bus, said, newForm) {
|
||||
if tl.Name == c.tool {
|
||||
run = tl.Run
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user