diff --git a/module.json b/module.json index bfc775d..4631d72 100644 --- a/module.json +++ b/module.json @@ -29,6 +29,15 @@ ], "generated-from": "." }, + { + "name": "go-meshrecovery", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/meshrecovery" + ], + "generated-from": "." + }, { "name": "go-meshconfig", "kind": "package", @@ -56,6 +65,15 @@ ], "generated-from": "." }, + { + "name": "ts-meshrecovery", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/meshrecovery" + ], + "generated-from": "." + }, { "name": "ts-meshconfig", "kind": "package", diff --git a/seats/index.json b/seats/index.json index 1b83c4b..7c54fd8 100644 --- a/seats/index.json +++ b/seats/index.json @@ -3,6 +3,7 @@ "seats": [ "mesh-store", "mesh-broker", + "mesh-recovery", "mesh-config", "node-login-shell", "node-power", diff --git a/seats/mesh-recovery/fixtures/events.json b/seats/mesh-recovery/fixtures/events.json new file mode 100644 index 0000000..2b13fb8 --- /dev/null +++ b/seats/mesh-recovery/fixtures/events.json @@ -0,0 +1,121 @@ +[ + { + "name": "ended is said", + "of": "event:ended", + "subject": "mesh.seat.mesh-recovery.event.ended.mesh-knowledge.ledger", + "given": { + "act": "a-1", + "at": "2026-11-11T10:00:00Z", + "module": "mesh-knowledge", + "kept": "ledger", + "form": 2, + "fingerprint": "0123456789ab", + "snapshot": "2-0123456789ab", + "outcome": "done", + "consumers": [ + { + "stream": "KV_x", + "name": "c1", + "sequence": 42 + } + ], + "size": 2048, + "catch-up": { + "tool": "knowledge_catch_up" + }, + "digests": [ + { + "name": "KV_x", + "digest": "sha256:00" + } + ] + } + }, + { + "name": "ended is said", + "of": "event:ended", + "subject": "mesh.seat.mesh-recovery.event.ended.mesh-knowledge.ledger", + "given": { + "act": "a-2", + "at": "2026-11-11T10:00:00Z", + "module": "mesh-knowledge", + "kept": "ledger", + "outcome": "failed", + "error": "stopped" + } + }, + { + "name": "restore-started is said", + "of": "event:restore-started", + "subject": "mesh.seat.mesh-recovery.event.restore-started.mesh-knowledge.ledger", + "given": { + "act": "a-3", + "at": "2026-11-11T10:00:00Z", + "module": "mesh-knowledge", + "kept": "ledger", + "form": 2, + "fingerprint": "0123456789ab", + "snapshot": "2-0123456789ab" + } + }, + { + "name": "restored is said", + "of": "event:restored", + "subject": "mesh.seat.mesh-recovery.event.restored.mesh-knowledge.ledger", + "given": { + "act": "a-3", + "at": "2026-11-11T10:00:00Z", + "module": "mesh-knowledge", + "kept": "ledger", + "outcome": "failed", + "error": "restore point missing", + "missing": true + } + }, + { + "name": "set-aside is said", + "of": "event:set-aside", + "subject": "mesh.seat.mesh-recovery.event.set-aside.mesh-knowledge.ledger", + "given": { + "act": "a-4", + "at": "2026-11-11T10:00:00Z", + "id": "20261111T121200Z", + "name": "mesh-knowledge_ledger", + "size": 4096 + } + }, + { + "name": "aside-discarded is said", + "of": "event:aside-discarded", + "subject": "mesh.seat.mesh-recovery.event.aside-discarded.mesh-knowledge.ledger", + "given": { + "act": "a-5", + "at": "2026-11-11T10:00:00Z", + "id": "20261111T121200Z", + "name": "mesh-knowledge_ledger", + "size": 4096 + } + }, + { + "name": "point-removed is said", + "of": "event:point-removed", + "subject": "mesh.seat.mesh-recovery.event.point-removed.mesh-knowledge.ledger", + "given": { + "act": "a-6", + "at": "2026-11-11T10:00:00Z", + "module": "mesh-knowledge", + "kept": "ledger", + "snapshot": "1-aaaaaaaaaaaa" + } + }, + { + "name": "expiry-ended says 0 when it removed nothing", + "of": "event:expiry-ended", + "subject": "mesh.seat.mesh-recovery.event.expiry-ended", + "given": { + "act": "a-7", + "at": "2026-11-11T10:00:00Z", + "removed": 0 + } + } +] diff --git a/seats/mesh-recovery/fixtures/verbs.json b/seats/mesh-recovery/fixtures/verbs.json new file mode 100644 index 0000000..71fbe83 --- /dev/null +++ b/seats/mesh-recovery/fixtures/verbs.json @@ -0,0 +1,124 @@ +[ + { + "name": "snapshot-kept takes the point of a kept data before its form changes", + "of": "verb:snapshot-kept.input", + "given": { + "module": "mesh-knowledge", + "kept": "ledger", + "form": 2, + "fingerprint": "0123456789ab", + "why": "form change of mesh-knowledge's ledger to 0123456789ab", + "catch-up": { + "tool": "knowledge_catch_up", + "arguments": { + "since": "0" + } + } + } + }, + { + "name": "snapshot-kept answers accepted with the act", + "of": "verb:snapshot-kept.answer", + "given": { + "accepted": "a-20261111t100000" + } + }, + { + "name": "restore-kept answers busy with the act that runs", + "of": "verb:restore-kept.answer", + "given": { + "busy": "a-20261111t100000" + } + }, + { + "name": "restore-kept takes a point and asks the backup for its directory", + "of": "verb:restore-kept.input", + "given": { + "module": "mesh-knowledge", + "kept": "ledger", + "form": 2, + "fingerprint": "0123456789ab", + "why": "revert of mesh-knowledge's ledger from 0123456789ab", + "from-backup": true, + "snapshot": "2-0123456789ab" + } + }, + { + "name": "restore-kept answers an error naming why", + "of": "verb:restore-kept.answer", + "given": { + "error": "aside-only is the operator's hand tool alone" + } + }, + { + "name": "kept-points lists a point, an aside and the act that runs", + "of": "verb:kept-points.answer", + "given": { + "points": [ + { + "module": "mesh-knowledge", + "kept": "ledger", + "form": 2, + "fingerprint": "0123456789ab", + "snapshot": "2-0123456789ab", + "consumers": [ + { + "stream": "KV_x", + "name": "c1", + "sequence": 42 + } + ], + "size": 2048, + "at": "2026-11-11T10:00:00Z", + "catch-up": { + "tool": "knowledge_catch_up" + }, + "digests": [ + { + "name": "KV_x", + "digest": "sha256:00" + } + ] + } + ], + "asides": [ + { + "id": "20261111T121200Z", + "name": "mesh-knowledge_ledger", + "size": 4096 + } + ], + "running": { + "act": "a-1", + "step": "waiting on the dump", + "since": "2026-11-11T10:00:00Z", + "waits-on": "item-ended of the bus item" + } + } + }, + { + "name": "restore-aside names the aside", + "of": "verb:restore-aside.input", + "given": { + "module": "mesh-knowledge", + "kept": "ledger", + "id": "20261111T121200Z", + "why": "an aside left at removed" + } + }, + { + "name": "cancel names the act", + "of": "verb:cancel.input", + "given": { + "act": "a-1", + "why": "the dump waits too long" + } + }, + { + "name": "cancel answers an error naming the step an act has begun", + "of": "verb:cancel.answer", + "given": { + "error": "the act has begun: step restoring" + } + } +] diff --git a/seats/mesh-recovery/seat.json b/seats/mesh-recovery/seat.json new file mode 100644 index 0000000..92ca793 --- /dev/null +++ b/seats/mesh-recovery/seat.json @@ -0,0 +1,788 @@ +{ + "format": 1, + "seat": "mesh-recovery", + "scope": "mesh", + "grain": "whole", + "domain": "Core", + "decision": "novox/hq ADR 0672", + "description": "Takes, sets aside and restores a module's kept data on the live bus, each act in the hand-act log (design 47 G 554). Its verbs answer at once and the result is the event.", + "verbs": [ + { + "name": "snapshot-kept", + "description": "Take the restore point of one module's kept data on the bus before it moves across a form change: the data whole and the position of each durable consumer. Answers accepted, busy or error at once; the point comes on the event ended (design 47 G 516, 554, 560).", + "by-caller": true, + "class": "act", + "input": { + "type": "object", + "properties": { + "module": { + "type": "string", + "description": "the module", + "minLength": 1 + }, + "kept": { + "type": "string", + "description": "the kept data, its local name in the module's manifest", + "minLength": 1 + }, + "form": { + "type": "integer", + "description": "its form, the number the holder raises when it changes how it writes it (0 when undeclared)" + }, + "fingerprint": { + "type": "string", + "description": "the fingerprint the restore point is taken before, or reverted from", + "minLength": 1 + }, + "why": { + "type": "string", + "description": "why, which the hand-act log keeps" + }, + "catch-up": { + "$ref": "#/defs/CatchUp" + } + }, + "required": [ + "module", + "kept", + "form", + "fingerprint", + "why" + ], + "additionalProperties": false + }, + "answer": { + "type": "object", + "description": "the act accepted, or busy with the act that runs, or the error; one of the three is set (design 47 G 554)", + "properties": { + "accepted": { + "type": "string", + "description": "accepted: the act's id, which the ending event carries" + }, + "busy": { + "type": "string", + "description": "busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event" + }, + "error": { + "type": "string", + "description": "why it was not taken; set alone" + } + } + } + }, + { + "name": "restore-kept", + "description": "Restore one module's kept data from its restore point, the live data set aside first and never over it. Answers accepted, busy or error at once; the result comes on the event restored. It refuses aside-only and discard-aside: those are hand acts alone (design 47 G 516, 554, 560).", + "by-caller": true, + "class": "act", + "input": { + "type": "object", + "properties": { + "module": { + "type": "string", + "description": "the module", + "minLength": 1 + }, + "kept": { + "type": "string", + "description": "the kept data, its local name in the module's manifest", + "minLength": 1 + }, + "form": { + "type": "integer", + "description": "its form, the number the holder raises when it changes how it writes it (0 when undeclared)" + }, + "fingerprint": { + "type": "string", + "description": "the fingerprint the restore point is taken before, or reverted from", + "minLength": 1 + }, + "why": { + "type": "string", + "description": "why, which the hand-act log keeps" + }, + "from-backup": { + "type": "boolean", + "description": "ask the backup holder for the points directory when the point is missing there (design 47 G 516)" + }, + "snapshot": { + "type": "string", + "description": "the restore point's name", + "minLength": 1 + } + }, + "required": [ + "module", + "kept", + "form", + "fingerprint", + "why" + ], + "additionalProperties": false + }, + "answer": { + "type": "object", + "description": "the act accepted, or busy with the act that runs, or the error; one of the three is set (design 47 G 554)", + "properties": { + "accepted": { + "type": "string", + "description": "accepted: the act's id, which the ending event carries" + }, + "busy": { + "type": "string", + "description": "busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event" + }, + "error": { + "type": "string", + "description": "why it was not taken; set alone" + } + } + } + }, + { + "name": "kept-points", + "description": "Every restore point and aside the recovery module holds, with its module, kept data, form, fingerprint, size and when, and the act that runs, with since when and what it waits on (design 47 G 517, 554).", + "class": "read", + "input": { + "type": "object", + "properties": { + "module": { + "type": "string", + "description": "one module (optional)" + }, + "kept": { + "type": "string", + "description": "one kept data (optional)" + } + }, + "additionalProperties": false + }, + "answer": { + "type": "object", + "properties": { + "points": { + "type": "array", + "items": { + "$ref": "#/defs/Point" + } + }, + "asides": { + "type": "array", + "items": { + "$ref": "#/defs/Aside" + } + }, + "running": { + "$ref": "#/defs/Running" + } + }, + "required": [ + "points", + "asides" + ] + } + }, + { + "name": "restore-aside", + "description": "Restore an aside under the live names it was taken from, for an aside whose record ends at removed that no running act holds; it refuses while live objects of those names exist. Ends with restored (design 47 G 566).", + "by-caller": true, + "class": "act", + "input": { + "type": "object", + "properties": { + "module": { + "type": "string", + "description": "the module", + "minLength": 1 + }, + "kept": { + "type": "string", + "description": "the kept data, its local name in the module's manifest", + "minLength": 1 + }, + "id": { + "type": "string", + "description": "the aside's id", + "minLength": 1 + }, + "why": { + "type": "string", + "description": "why, which the hand-act log keeps" + } + }, + "required": [ + "module", + "kept", + "id", + "why" + ], + "additionalProperties": false + }, + "answer": { + "type": "object", + "description": "the act accepted, or busy with the act that runs, or the error; one of the three is set (design 47 G 554)", + "properties": { + "accepted": { + "type": "string", + "description": "accepted: the act's id, which the ending event carries" + }, + "busy": { + "type": "string", + "description": "busy: the id of the act that runs; it keeps no queue, so the caller asks again on that act's ending event" + }, + "error": { + "type": "string", + "description": "why it was not taken; set alone" + } + } + } + }, + { + "name": "cancel", + "description": "End an accepted act that has not begun its work, with its ending event, outcome failed and error cancelled; it refuses an act that has begun, naming its step (design 47 G 566).", + "by-caller": true, + "class": "act", + "input": { + "type": "object", + "properties": { + "act": { + "type": "string", + "description": "the act's id", + "minLength": 1 + }, + "why": { + "type": "string", + "description": "why, which the hand-act log keeps" + } + }, + "required": [ + "act", + "why" + ], + "additionalProperties": false + }, + "answer": { + "type": "object", + "properties": { + "cancelled": { + "type": "boolean", + "description": "the act ended cancelled" + }, + "error": { + "type": "string", + "description": "why not, naming the step an act has begun; set alone" + } + } + } + } + ], + "events": [ + { + "name": "ended", + "subject": "ended..", + "keep": { + "max-age-days": 90, + "last-per-subject": true + }, + "body": { + "type": "object", + "description": "a point taken, or the act failed; on done the point whole, on failed the error (design 47 G 560)", + "properties": { + "act": { + "type": "string", + "description": "the id the act was answered accepted with, or a hand act's own id (design 47 G 560)" + }, + "at": { + "type": "string", + "description": "when it was said, RFC 3339", + "format": "date-time" + }, + "module": { + "type": "string" + }, + "kept": { + "type": "string" + }, + "form": { + "type": "integer" + }, + "fingerprint": { + "type": "string" + }, + "snapshot": { + "type": "string" + }, + "outcome": { + "type": "string", + "description": "how the act ended", + "enum": [ + "done", + "failed" + ] + }, + "error": { + "type": "string", + "description": "when failed" + }, + "consumers": { + "type": "array", + "items": { + "$ref": "#/defs/Consumer" + } + }, + "size": { + "type": "integer", + "minimum": 0 + }, + "catch-up": { + "$ref": "#/defs/CatchUp" + }, + "digests": { + "type": "array", + "items": { + "$ref": "#/defs/Digest" + } + } + }, + "required": [ + "act", + "at", + "module", + "kept", + "outcome" + ] + } + }, + { + "name": "restore-started", + "subject": "restore-started..", + "keep": { + "max-age-days": 90, + "last-per-subject": true + }, + "body": { + "type": "object", + "description": "a restore begins, said before it reads the dump's item events and before any aside is taken (design 47 G 560, 563)", + "properties": { + "act": { + "type": "string", + "description": "the id the act was answered accepted with, or a hand act's own id (design 47 G 560)" + }, + "at": { + "type": "string", + "description": "when it was said, RFC 3339", + "format": "date-time" + }, + "module": { + "type": "string" + }, + "kept": { + "type": "string" + }, + "form": { + "type": "integer" + }, + "fingerprint": { + "type": "string" + }, + "snapshot": { + "type": "string" + } + }, + "required": [ + "act", + "at", + "module", + "kept" + ] + } + }, + { + "name": "restored", + "subject": "restored..", + "keep": { + "max-age-days": 90, + "last-per-subject": true + }, + "body": { + "type": "object", + "description": "a restore ended (design 47 G 560)", + "properties": { + "act": { + "type": "string", + "description": "the id the act was answered accepted with, or a hand act's own id (design 47 G 560)" + }, + "at": { + "type": "string", + "description": "when it was said, RFC 3339", + "format": "date-time" + }, + "module": { + "type": "string" + }, + "kept": { + "type": "string" + }, + "form": { + "type": "integer" + }, + "fingerprint": { + "type": "string" + }, + "snapshot": { + "type": "string" + }, + "outcome": { + "type": "string", + "description": "how the act ended", + "enum": [ + "done", + "failed" + ] + }, + "error": { + "type": "string" + }, + "aside": { + "$ref": "#/defs/Aside" + }, + "missing": { + "type": "boolean", + "description": "the point is not in the recovery module's directory" + } + }, + "required": [ + "act", + "at", + "module", + "kept", + "outcome" + ] + } + }, + { + "name": "set-aside", + "subject": "set-aside..", + "keep": { + "max-age-days": 90, + "last-per-subject": true + }, + "body": { + "type": "object", + "description": "live data set aside by the hand act (design 47 G 560)", + "properties": { + "act": { + "type": "string", + "description": "the id the act was answered accepted with, or a hand act's own id (design 47 G 560)" + }, + "at": { + "type": "string", + "description": "when it was said, RFC 3339", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "act", + "at", + "id", + "name", + "size" + ] + } + }, + { + "name": "aside-discarded", + "subject": "aside-discarded..", + "keep": { + "max-age-days": 90, + "last-per-subject": true + }, + "body": { + "type": "object", + "description": "an aside discarded by the hand act (design 47 G 560)", + "properties": { + "act": { + "type": "string", + "description": "the id the act was answered accepted with, or a hand act's own id (design 47 G 560)" + }, + "at": { + "type": "string", + "description": "when it was said, RFC 3339", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "act", + "at", + "id", + "name", + "size" + ] + } + }, + { + "name": "point-removed", + "subject": "point-removed..", + "keep": { + "max-age-days": 90, + "last-per-subject": true + }, + "body": { + "type": "object", + "description": "a restore point removed after its days, never the newest (design 47 G 551, 560)", + "properties": { + "act": { + "type": "string", + "description": "the id the act was answered accepted with, or a hand act's own id (design 47 G 560)" + }, + "at": { + "type": "string", + "description": "when it was said, RFC 3339", + "format": "date-time" + }, + "module": { + "type": "string" + }, + "kept": { + "type": "string" + }, + "form": { + "type": "integer" + }, + "fingerprint": { + "type": "string" + }, + "snapshot": { + "type": "string" + } + }, + "required": [ + "act", + "at", + "module", + "kept" + ] + } + }, + { + "name": "expiry-ended", + "keep": { + "max-age-days": 90 + }, + "body": { + "type": "object", + "description": "a run of the removal ended, also when it removed nothing (design 47 G 560)", + "properties": { + "act": { + "type": "string", + "description": "the id the act was answered accepted with, or a hand act's own id (design 47 G 560)" + }, + "at": { + "type": "string", + "description": "when it was said, RFC 3339", + "format": "date-time" + }, + "removed": { + "type": "integer", + "description": "the number of points the run removed, 0 included", + "minimum": 0 + } + }, + "required": [ + "act", + "at", + "removed" + ] + } + } + ], + "family": { + "name": "mesh-recovery.events", + "max-age-days": 7 + }, + "defs": { + "Consumer": { + "type": "object", + "description": "one durable consumer of the kept data and the sequence it had acknowledged", + "properties": { + "stream": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sequence": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "stream", + "name", + "sequence" + ] + }, + "Digest": { + "type": "object", + "description": "the digest of one object of a point's bytes", + "properties": { + "name": { + "type": "string" + }, + "digest": { + "type": "string" + } + }, + "required": [ + "name", + "digest" + ] + }, + "Point": { + "type": "object", + "description": "a restore point: the kept data whole and its consumers' positions, in the recovery module's points directory (design 47 G 516, 560)", + "properties": { + "module": { + "type": "string", + "description": "the module", + "minLength": 1 + }, + "kept": { + "type": "string", + "description": "the kept data, its local name in the module's manifest", + "minLength": 1 + }, + "form": { + "type": "integer", + "description": "its form, the number the holder raises when it changes how it writes it (0 when undeclared)" + }, + "fingerprint": { + "type": "string", + "description": "the fingerprint it was taken before" + }, + "snapshot": { + "type": "string", + "description": "the point's name, also its directory's" + }, + "consumers": { + "type": "array", + "items": { + "$ref": "#/defs/Consumer" + } + }, + "size": { + "type": "integer", + "description": "its bytes", + "minimum": 0 + }, + "at": { + "type": "string", + "description": "when it was taken, RFC 3339", + "format": "date-time" + }, + "catch-up": { + "$ref": "#/defs/CatchUp" + }, + "digests": { + "type": "array", + "items": { + "$ref": "#/defs/Digest" + }, + "description": "the digest of each object's bytes, checked before a restore (design 47 G 567)" + } + }, + "required": [ + "module", + "kept", + "form", + "fingerprint", + "snapshot", + "size", + "at" + ] + }, + "Aside": { + "type": "object", + "description": "the live data set aside before a restore: a directory under aside/ in the recovery module's directory, nothing of it on the bus (design 47 G 516)", + "properties": { + "id": { + "type": "string", + "description": "the directory's name" + }, + "name": { + "type": "string", + "description": "the bus name it was live under" + }, + "size": { + "type": "integer", + "description": "its bytes on disk", + "minimum": 0 + } + }, + "required": [ + "id", + "name", + "size" + ] + }, + "Running": { + "type": "object", + "description": "the act that runs, since when and what it waits on (design 47 G 517, 554)", + "properties": { + "act": { + "type": "string" + }, + "step": { + "type": "string" + }, + "since": { + "type": "string", + "format": "date-time" + }, + "waits-on": { + "type": "string", + "description": "what it waits on, when it waits" + } + }, + "required": [ + "act", + "since" + ] + }, + "CatchUp": { + "type": "object", + "description": "the catch-up of the newer manifest: a tool and its arguments, never text (design 47 G 451, 516, 631)", + "properties": { + "tool": { + "type": "string" + }, + "arguments": { + "type": "object" + } + }, + "required": [ + "tool" + ] + } + } +}