mesh-vault: the retirement tools take the controller's consumers, confirm, by and via; vault_status; the export's fingerprint (review 255)
mesh/merge-gate head 8c5d70a18677 merged into trunk 8aa37e8b835d — pass: the change touches no module of the mesh's graph
mesh/repo-check head 8c5d70a18677 merged into trunk 8aa37e8b835d — fail: its merge-check.sh failed: files asking for a setting without saying whether it is…
mesh/delivery superseded: a newer head of the same pull request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 01:39:15 +00:00
co-authored by Claude Opus 5.5
parent 958018e696
commit 8c5d70a186
+121 -34
View File
@@ -3,15 +3,19 @@ package main
// What the vault serves: the seat's verb `get`, and the three tools the TypeScript vault offered —
// secret_export, secret_holders and secret_verify — answering as they did; beside them secret_handed and
// secret_vault_export over the vault's own store, and ADR 0230's four retirement tools over the custody
// ledger. None returns a value: `get` returns one sealed to the asker's key, the others fingerprints and
// ciphertext only.
// ledger, and vault_status. None returns a value: `get` returns one sealed to the asker's key, the others
// fingerprints and ciphertext only.
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
stdio "git.novox.be/novox/mesh-sdk/go"
)
@@ -24,10 +28,25 @@ type Env struct {
Receives string
Root string
Custody *Custody
// refused is why the operator key in the mesh's export was not taken, or empty.
mu sync.Mutex
refused string
// Broken is why the vault could not be built, said by `get`.
Broken error
}
func (e *Env) keyRefused() string {
e.mu.Lock()
defer e.mu.Unlock()
return e.refused
}
func (e *Env) refuseKey(why string) {
e.mu.Lock()
defer e.mu.Unlock()
e.refused = why
}
// toolDef is a tool with its caller given apart from its arguments, so a test can be the runtime.
type toolDef struct {
Name string
@@ -135,8 +154,9 @@ func definitions(env *Env) []toolDef {
return vaultExport(env.Vault)
}},
{Name: "provisioner_retirement",
Description: "The custody ledger's holders: active, retired (with when and why), the set counted toward " +
"retirement, and any set waiting for a person or rejected (novox/hq ADR 0230).",
Description: "This provider's retirement (novox/hq ADR 0230): the holders it keeps active, a retirement waiting " +
"for a person, one a person rejected, and every holder retired with when and why. Mark-only: a retired " +
"holder keeps what it has until a person deletes it.",
run: func(string, map[string]any) (any, error) {
if env.Custody == nil {
return nil, errors.New("the vault has no custody ledger")
@@ -144,45 +164,102 @@ func definitions(env *Env) []toolDef {
return env.Custody.Retirement(), nil
}},
{Name: "provisioner_retire_approve",
Description: "Retire exactly the set of holders waiting for a person (novox/hq ADR 0230). Marks them retired; deletes nothing.",
Input: map[string]any{"why": str("why they are retired")},
Description: "Retire exactly the set waiting for a person (or rejected earlier). Repeat the set in consumers; why is required.",
Input: actInput(true, false),
run: func(caller string, a map[string]any) (any, error) {
if env.Custody == nil {
return nil, errors.New("the vault has no custody ledger")
}
why, _ := a["why"].(string)
set, err := env.Custody.Approve(caller, why)
return map[string]any{"retired": set}, err
}},
{Name: "provisioner_retire_reject",
Description: "Keep the set of holders waiting for a person active; not asked about again while it stays the same set (novox/hq ADR 0230).",
Input: map[string]any{"why": str("why they are kept")},
run: func(caller string, a map[string]any) (any, error) {
if env.Custody == nil {
return nil, errors.New("the vault has no custody ledger")
}
why, _ := a["why"].(string)
set, err := env.Custody.Reject(caller, why)
return map[string]any{"kept": set}, err
}},
{Name: "provisioner_delete",
Description: "Delete one retired holder from the custody ledger, its rotation history with it — a person's act " +
"(novox/hq ADR 0230). Refused for a holder that is active or asked for, and without a why.",
Input: map[string]any{"consumer": str("the holder's login"), "why": str("why it is deleted")},
run: func(caller string, a map[string]any) (any, error) {
if env.Custody == nil {
return nil, errors.New("the vault has no custody ledger")
}
as, _ := a["consumer"].(string)
why, _ := a["why"].(string)
if err := env.Custody.Delete(as, caller, why); err != nil {
done, err := env.Custody.Approve(stringsOf(a["consumers"]), actOf(caller, a))
if err != nil {
return nil, err
}
return map[string]any{"deleted": as}, nil
return map[string]any{"retired": done}, nil
}},
{Name: "provisioner_retire_reject",
Description: "Keep the set waiting for a person active, though the mesh no longer asks for it. Repeat the set in consumers; why is required.",
Input: actInput(true, false),
run: func(caller string, a map[string]any) (any, error) {
if env.Custody == nil {
return nil, errors.New("the vault has no custody ledger")
}
kept, err := env.Custody.Reject(stringsOf(a["consumers"]), actOf(caller, a))
if err != nil {
return nil, err
}
return map[string]any{"kept": kept}, nil
}},
{Name: "provisioner_delete",
Description: "Delete one RETIRED holder from the custody ledger, its rotation history with it — never an active one. " +
"Repeat its name in confirm; why is required.",
Input: actInput(false, true),
run: func(caller string, a map[string]any) (any, error) {
if env.Custody == nil {
return nil, errors.New("the vault has no custody ledger")
}
consumer, _ := a["consumer"].(string)
confirm, _ := a["confirm"].(string)
if err := env.Custody.Delete(consumer, confirm, actOf(caller, a)); err != nil {
return nil, err
}
return map[string]any{"deleted": consumer, "freed_bytes": -1}, nil
}},
{Name: "vault_status",
Description: "What needs a person's eye in the vault: operator copies still sealed to an earlier key, the operator " +
"key the mesh's export holds if the vault refused it, and how often the record of hand-outs was rolled.",
run: func(string, map[string]any) (any, error) {
if env.Vault == nil {
return map[string]any{"ready": false, "error": fmt.Sprint(env.Broken)}, nil
}
left, err := env.Vault.StillSealedToAnother()
if err != nil {
return nil, err
}
key, _ := env.Vault.Store.OperatorKey()
return map[string]any{"ready": true, "operator_key": key, "copies_on_an_earlier_key": left,
"operator_key_refused": env.keyRefused(), "handed_rolled": env.Vault.Store.Rolled(),
"ok": left == 0 && env.keyRefused() == "" && key != ""}, nil
}},
}
}
// actInput is the arguments of an act on retirement, as the SDK's harness takes them.
func actInput(set, one bool) map[string]any {
text := map[string]any{"type": "string"}
in := map[string]any{"why": text, "by": text, "via": text}
if set {
in["consumers"] = map[string]any{"type": "array", "items": text}
}
if one {
in["consumer"], in["confirm"] = text, text
}
return in
}
// actOf is an act's why, by and via. `by` is the person the controller names; with none, the caller the
// runtime proved.
func actOf(caller string, a map[string]any) Act {
act := Act{}
act.Why, _ = a["why"].(string)
act.By, _ = a["by"].(string)
act.Via, _ = a["via"].(string)
if act.By == "" {
act.By = caller
}
return act
}
func stringsOf(v any) []string {
list, _ := v.([]any)
out := []string{}
for _, x := range list {
if s, ok := x.(string); ok {
out = append(out, s)
}
}
return out
}
func limitOf(a map[string]any) int {
if v, ok := a["limit"].(float64); ok && v > 0 {
return int(v)
@@ -190,6 +267,16 @@ func limitOf(a map[string]any) int {
return 50
}
// keyFingerprint is a key's fingerprint as the controller's secrets.Fingerprint says it: sha256 of the key,
// its first eight bytes in hex.
func keyFingerprint(key string) string {
if key == "" {
return ""
}
sum := sha256.Sum256([]byte(strings.TrimSpace(key)))
return "sha256:" + hex.EncodeToString(sum[:8])
}
// vaultExport is the vault's operator copies in the shape `secret export` writes and `secret recover
// --from-export` reads: each as node "vault", module "mesh-vault" and the secret's name.
func vaultExport(v *Vault) (map[string]any, error) {
@@ -211,7 +298,7 @@ func vaultExport(v *Vault) (map[string]any, error) {
earlier = append(earlier, k)
}
}
return map[string]any{"export": 1, "operator-key": key, "fingerprint": "", "kept": kept,
return map[string]any{"export": 1, "operator-key": key, "fingerprint": keyFingerprint(key), "kept": kept,
"sealed-to-earlier-key": earlier}, nil
}