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:
2026-11-04 12:34:59 +01:00
parent 3b61a05532
commit 68dd69f4ef
3 changed files with 57 additions and 4 deletions
+1 -1
View File
@@ -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())...)
+37 -2
View File
@@ -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
}
+19 -1
View File
@@ -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
}