mesh-recovery: aside sets the live data aside from the placement's entry, names as a list

Builds: design 47 G §516, §554, §566, A-123, A-124
aside takes the bus names from the catalog entry of the fingerprint the node-engine's placement runs, takes the aside whole and checked, removes the live objects, says set-aside; restore-aside puts it back.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 01:17:41 +01:00
parent 0ebabf7174
commit 2dd02da76e
5 changed files with 54 additions and 2 deletions
@@ -122,6 +122,8 @@ func (s *fakeSeats) Streams(context.Context, string, string, string) ([]string,
return s.streams, nil
}
func (s *fakeSeats) RunningFingerprint(context.Context, string) (string, error) { return "fp-run", nil }
func (s *fakeSeats) Backup(context.Context, string) (string, error) {
if s.backup == "" {
return "", errors.New("no repository snapshot holds it")
@@ -28,6 +28,8 @@ func (e *Engine) run(ctx context.Context, a *Act) {
e.expire(a.ID)
case KindRestore:
err = e.restore(ctx, a)
case KindAside:
err = e.aside(ctx, a)
case KindRestoreAside:
err = e.restoreAside(ctx, a)
case KindDiscardAside:
@@ -76,6 +76,8 @@ type Seats interface {
HandAct(caller, asker, what, why string) error
// Streams are the bus names of a module's kept data at a fingerprint, from its catalog graph entry (§554 Bus names).
Streams(ctx context.Context, module, kept, fingerprint string) ([]string, error)
// RunningFingerprint is the fingerprint the node-engine's placement runs for the module (A-123).
RunningFingerprint(ctx context.Context, module string) (string, error)
// Backup brings the points directory of the module's item back beside its path through the backup holder and
// answers where (§516); an error when no repository snapshot holds it.
Backup(ctx context.Context, module string) (dir string, err error)
@@ -35,7 +35,7 @@ type asideRecord struct {
}
func (r asideRecord) aside() seat.Aside {
return seat.Aside{ID: r.ID, Name: strings.Join(r.Streams, ","), Size: r.Size}
return seat.Aside{ID: r.ID, Names: r.Streams, Size: r.Size}
}
func (e *Engine) asideRoot(module, kept string) (string, error) {
@@ -279,6 +279,29 @@ func (e *Engine) readAside(module, kept, id string) (*asideRecord, error) {
return &rec, nil
}
// aside is the hand verb `aside` (§516, §554, A-123): it sets the live kept data aside without restoring it, taking the bus
// names from the catalog entry of the fingerprint the node-engine's placement runs for the module, and removes the live
// objects once the aside is whole and checked (a move, not a removal: the data stays in the aside until it is discarded or
// put back by `restore-aside`); it ends with `set-aside`.
func (e *Engine) aside(ctx context.Context, a *Act) error {
fp, err := e.Seats.RunningFingerprint(ctx, a.Module)
if err != nil {
return err
}
a.Fingerprint = fp
names, err := e.Seats.Streams(ctx, a.Module, a.Kept, fp)
if err != nil {
return err
}
rec, err := e.setAside(ctx, a, names, true)
if err != nil {
return err
}
x := rec.aside()
e.say(seat.EventSetAside, seat.SetAsideEvent{Act: a.ID, At: e.now(), ID: x.ID, Names: x.Names, Size: x.Size}, a.Module, a.Kept)
return nil
}
// restoreAside is `restore-aside` (§566): it puts an aside whose record ends at `removed` back under the live names it
// was taken from, refusing while a live object of those names exists, and ends with `restored`.
func (e *Engine) restoreAside(ctx context.Context, a *Act) error {
@@ -312,7 +335,7 @@ func (e *Engine) discardAside(a *Act) error {
return err
}
x := rec.aside()
e.say(seat.EventAsideDiscarded, seat.AsideDiscardedEvent{Act: a.ID, At: e.now(), ID: x.ID, Name: x.Name, Size: x.Size}, a.Module, a.Kept)
e.say(seat.EventAsideDiscarded, seat.AsideDiscardedEvent{Act: a.ID, At: e.now(), ID: x.ID, Names: x.Names, Size: x.Size}, a.Module, a.Kept)
return nil
}
@@ -232,3 +232,26 @@ func TestRestoreAsideRefusesLiveNamesAndDiscardAsideRemovesTheDirectory(t *testi
t.Errorf("asides %+v", kp.Asides)
}
}
// The hand verb aside sets the live data aside whole and removes it, naming the streams as a list, and restore-aside puts
// it back once no live name exists (§516, §554, §566, A-123, A-124).
func TestAsideMovesTheLiveDataIntoAnAsideAndRestoreAsidePutsItBack(t *testing.T) {
r := newRig(t)
ans := r.do(recovery.Act{Kind: recovery.KindAside, Module: "app", Kept: "notes", Why: "look at it", Caller: "tools", Asker: "op"})
ev := r.s.named(seat.EventSetAside)
if ans.Accepted == "" || len(ev) != 1 {
t.Fatalf("answer %+v events %+v", ans, r.s.events)
}
sa := ev[0].Body.(seat.SetAsideEvent)
if sa.Act != ans.Accepted || len(sa.Names) != 2 || sa.Size == 0 || len(r.b.streams) != 0 {
t.Errorf("set-aside %+v streams %v", sa, r.b.streams)
}
if got := r.e.Unrestored(); len(got) != 1 {
t.Errorf("unrestored %+v", got)
}
r.s.events = nil
r.do(recovery.Act{Kind: recovery.KindRestoreAside, Module: "app", Kept: "notes", AsideID: sa.ID, Why: "done looking", Caller: "tools"})
if lastRestored(t, r).Outcome != "done" || r.b.streams["app_notes"] != "hello" || len(r.e.Unrestored()) != 0 {
t.Errorf("streams %v", r.b.streams)
}
}