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
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user