K24 (mesh-recovery's hand verbs): take, restore, retry, aside, discard-aside and expire-points, asker and why on every hand verb, one answer form with cancel

Builds: design 47 G §682, §691 (§516, §554, §566, §656, §681)

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 20:45:56 +01:00
parent 0ccac1d42a
commit b76e52d30a
6 changed files with 823 additions and 12 deletions
+153 -3
View File
@@ -14,6 +14,12 @@ const (
VerbKeptPoints = "kept-points"
VerbRestoreAside = "restore-aside"
VerbCancel = "cancel"
VerbTake = "take"
VerbRestore = "restore"
VerbRetry = "retry"
VerbAside = "aside"
VerbDiscardAside = "discard-aside"
VerbExpirePoints = "expire-points"
)
const (
@@ -28,7 +34,7 @@ const (
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbSnapshotKept, VerbRestoreKept, VerbKeptPoints, VerbRestoreAside, VerbCancel}
return []string{VerbSnapshotKept, VerbRestoreKept, VerbKeptPoints, VerbRestoreAside, VerbCancel, VerbTake, VerbRestore, VerbRetry, VerbAside, VerbDiscardAside, VerbExpirePoints}
}
// Events is every event published on the seat's subject: a new copy each call.
@@ -244,6 +250,8 @@ type KeptPointsAnswer struct {
// RestoreAsideInput is what restore-aside takes.
type RestoreAsideInput struct {
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// the aside's id
ID string `json:"id"`
// the kept data, its local name in the module's manifest
@@ -268,18 +276,160 @@ type RestoreAsideAnswer struct {
type CancelInput struct {
// the act's id
Act string `json:"act"`
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// CancelAnswer is what cancel answers.
type CancelAnswer struct {
// the act ended cancelled
Cancelled bool `json:"cancelled,omitempty"`
// accepted: the id of the act it cancelled, whose ending event follows under that id
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why not, naming the step an act has begun; set alone
Error string `json:"error,omitempty"`
}
// TakeInput is what take takes.
type TakeInput struct {
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// the fingerprint whose form the point is taken at
Fingerprint string `json:"fingerprint"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// TakeAnswer is what take answers.
type TakeAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// RestoreInput is what restore takes.
type RestoreInput struct {
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// ask the backup holder for the points directory when the point is missing there; only with snapshot
FromBackup bool `json:"from-backup,omitempty"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// a keeper's snapshot by its request (design 47 G 681)
Request string `json:"request,omitempty"`
// a restore point's name
Snapshot string `json:"snapshot,omitempty"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// RestoreAnswer is what restore answers.
type RestoreAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// RetryInput is what retry takes.
type RetryInput struct {
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// RetryAnswer is what retry answers.
type RetryAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// AsideInput is what aside takes.
type AsideInput struct {
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// AsideAnswer is what aside answers.
type AsideAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// DiscardAsideInput is what discard-aside takes.
type DiscardAsideInput struct {
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// the aside's id
ID string `json:"id"`
// the kept data, its local name in the module's manifest
Kept string `json:"kept"`
// the module
Module string `json:"module"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// DiscardAsideAnswer is what discard-aside answers.
type DiscardAsideAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// ExpirePointsInput is what expire-points takes.
type ExpirePointsInput struct {
// who asked: the caller the tool runner proved and recorded for the call (who@node, or operator), which the tools bundle's recovery tool passes and a module's process leaves out; the hand-act log names it beside the account the bus proved (design 47 G 682)
Asker string `json:"asker,omitempty"`
// why, which the hand-act log keeps
Why string `json:"why"`
}
// ExpirePointsAnswer is what expire-points answers.
type ExpirePointsAnswer struct {
// accepted: the act's id, which the ending event carries
Accepted string `json:"accepted,omitempty"`
// busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event
Busy string `json:"busy,omitempty"`
// why it was not taken; set alone
Error string `json:"error,omitempty"`
}
// EndedEvent is the body of the event ended.
type EndedEvent struct {
// the id the act was answered accepted with, or a hand act's own id (design 47 G 560)
+32 -2
View File
@@ -45,12 +45,42 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v RestoreKeptAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "kept-points lists a point, an aside and the act that runs", subject: "", given: "{\"asides\":[{\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}],\"points\":[{\"at\":\"2026-11-11T10:00:00Z\",\"catch-up\":{\"tool\":\"knowledge_catch_up\"},\"consumers\":[{\"name\":\"c1\",\"sequence\":42,\"stream\":\"KV_x\"}],\"digests\":[{\"digest\":\"sha256:00\",\"name\":\"KV_x\"}],\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"size\":2048,\"snapshot\":\"2-0123456789ab\"}],\"running\":{\"act\":\"a-1\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"waiting on the dump\",\"waits-on\":\"item-ended of the bus item\"}}", wire: "{\"asides\":[{\"id\":\"20261111T121200Z\",\"name\":\"mesh-knowledge_ledger\",\"size\":4096}],\"points\":[{\"at\":\"2026-11-11T10:00:00Z\",\"catch-up\":{\"tool\":\"knowledge_catch_up\"},\"consumers\":[{\"name\":\"c1\",\"sequence\":42,\"stream\":\"KV_x\"}],\"digests\":[{\"digest\":\"sha256:00\",\"name\":\"KV_x\"}],\"fingerprint\":\"0123456789ab\",\"form\":2,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"size\":2048,\"snapshot\":\"2-0123456789ab\"}],\"running\":{\"act\":\"a-1\",\"since\":\"2026-11-11T10:00:00Z\",\"step\":\"waiting on the dump\",\"waits-on\":\"item-ended of the bus item\"}}",
decode: func(b []byte) (any, error) { var v KeptPointsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore-aside names the aside", subject: "", given: "{\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"an aside left at removed\"}", wire: "{\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"an aside left at removed\"}",
{name: "restore-aside names the aside", subject: "", given: "{\"asker\":\"jochen@shanks\",\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"an aside left at removed\"}", wire: "{\"asker\":\"jochen@shanks\",\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"an aside left at removed\"}",
decode: func(b []byte) (any, error) { var v RestoreAsideInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "cancel names the act", subject: "", given: "{\"act\":\"a-1\",\"why\":\"the dump waits too long\"}", wire: "{\"act\":\"a-1\",\"why\":\"the dump waits too long\"}",
{name: "cancel names the act", subject: "", given: "{\"act\":\"a-1\",\"asker\":\"jochen@shanks\",\"why\":\"the dump waits too long\"}", wire: "{\"act\":\"a-1\",\"asker\":\"jochen@shanks\",\"why\":\"the dump waits too long\"}",
decode: func(b []byte) (any, error) { var v CancelInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "take names the fingerprint whose form the point is taken at", subject: "", given: "{\"asker\":\"jochen@shanks\",\"fingerprint\":\"0123456789ab\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"the operator's hand act\"}", wire: "{\"asker\":\"jochen@shanks\",\"fingerprint\":\"0123456789ab\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"the operator's hand act\"}",
decode: func(b []byte) (any, error) { var v TakeInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "take answers accepted with the act", subject: "", given: "{\"accepted\":\"a-20261111t100000\"}", wire: "{\"accepted\":\"a-20261111t100000\"}",
decode: func(b []byte) (any, error) { var v TakeAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore takes a point by its name", subject: "", given: "{\"asker\":\"jochen@shanks\",\"from-backup\":true,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"2-0123456789ab\",\"why\":\"the operator's hand act\"}", wire: "{\"asker\":\"jochen@shanks\",\"from-backup\":true,\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"snapshot\":\"2-0123456789ab\",\"why\":\"the operator's hand act\"}",
decode: func(b []byte) (any, error) { var v RestoreInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore takes a keeper's snapshot by its request", subject: "", given: "{\"asker\":\"jochen@shanks\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"request\":\"r-20261111t100000\",\"why\":\"the operator's hand act\"}", wire: "{\"asker\":\"jochen@shanks\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"request\":\"r-20261111t100000\",\"why\":\"the operator's hand act\"}",
decode: func(b []byte) (any, error) { var v RestoreInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "restore answers busy with the act that runs", subject: "", given: "{\"busy\":\"a-20261111t100000\"}", wire: "{\"busy\":\"a-20261111t100000\"}",
decode: func(b []byte) (any, error) { var v RestoreAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "retry names the module and its kept data", subject: "", given: "{\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"a failed take\"}", wire: "{\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"a failed take\"}",
decode: func(b []byte) (any, error) { var v RetryInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "retry answers an error when nothing failed", subject: "", given: "{\"error\":\"nothing to retry\"}", wire: "{\"error\":\"nothing to retry\"}",
decode: func(b []byte) (any, error) { var v RetryAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "aside sets the live kept data aside", subject: "", given: "{\"asker\":\"jochen@shanks\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"the operator's hand act\"}", wire: "{\"asker\":\"jochen@shanks\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"the operator's hand act\"}",
decode: func(b []byte) (any, error) { var v AsideInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "aside answers accepted with the act", subject: "", given: "{\"accepted\":\"a-20261111t100000\"}", wire: "{\"accepted\":\"a-20261111t100000\"}",
decode: func(b []byte) (any, error) { var v AsideAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "discard-aside names the aside", subject: "", given: "{\"asker\":\"jochen@shanks\",\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"the operator's hand act\"}", wire: "{\"asker\":\"jochen@shanks\",\"id\":\"20261111T121200Z\",\"kept\":\"ledger\",\"module\":\"mesh-knowledge\",\"why\":\"the operator's hand act\"}",
decode: func(b []byte) (any, error) { var v DiscardAsideInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "discard-aside answers an error", subject: "", given: "{\"error\":\"no such aside\"}", wire: "{\"error\":\"no such aside\"}",
decode: func(b []byte) (any, error) { var v DiscardAsideAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "expire-points has no field of its own", subject: "", given: "{\"asker\":\"operator\",\"why\":\"the points past 90 days\"}", wire: "{\"asker\":\"operator\",\"why\":\"the points past 90 days\"}",
decode: func(b []byte) (any, error) { var v ExpirePointsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "expire-points answers accepted with the act", subject: "", given: "{\"accepted\":\"a-20261111t100000\"}", wire: "{\"accepted\":\"a-20261111t100000\"}",
decode: func(b []byte) (any, error) { var v ExpirePointsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "cancel answers accepted with the act it cancelled", subject: "", given: "{\"accepted\":\"a-1\"}", wire: "{\"accepted\":\"a-1\"}",
decode: func(b []byte) (any, error) { var v CancelAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "cancel answers an error naming the step an act has begun", subject: "", given: "{\"error\":\"the act has begun: step restoring\"}", wire: "{\"error\":\"the act has begun: step restoring\"}",
decode: func(b []byte) (any, error) { var v CancelAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "cancel answers busy with the act that runs", subject: "", given: "{\"busy\":\"a-20261111t100000\"}", wire: "{\"busy\":\"a-20261111t100000\"}",
decode: func(b []byte) (any, error) { var v CancelAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))