mesh-vault: tests of the bound, the retry's real path and a hand-out without its record; the README corrected (review 255)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head dadf824c3e33 merged into trunk 8aa37e8b835d — pass: the change touches no module of the mesh's graph
mesh/repo-check head dadf824c3e33 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 dadf824c3e33 merged into trunk 8aa37e8b835d — pass: the change touches no module of the mesh's graph
mesh/repo-check head dadf824c3e33 merged into trunk 8aa37e8b835d — fail: its merge-check.sh failed: files asking for a setting without saying whether it is…
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,7 +96,16 @@ the bus user the runtime proved — never an argument.
|
||||
|
||||
Every answer and every refusal is recorded in the vault's own store (`handed.jsonl`: who, which secret,
|
||||
which generation, when, and why a refusal), synced before the answer is given — as ADR 0277 §25 records
|
||||
asks. `secret_handed` reads it.
|
||||
asks; a secret whose hand-out cannot be recorded is not handed out. A refusal is recorded only for a caller
|
||||
of a bus user's shape and a well-formed secret name, so an arbitrary argument fills nothing. The record is
|
||||
bounded: rolled at 1 MiB into `handed.jsonl.1` … `.3`, the oldest dropped, each roll said loudly in the
|
||||
journal and counted in `vault_status`. `secret_handed` reads it.
|
||||
|
||||
`vault_status` says what needs a person: operator copies still sealed to an earlier key, an operator key in
|
||||
the export the vault refused, and how often the record was rolled. **It is not yet a condition**: a health
|
||||
check is declared on a long-running resource (ADR 0240), and the vault is a bundle the tool runner loads, so
|
||||
nothing asks it on its own; until it runs as a resource of its own (V3's review) these reach a person
|
||||
through the journal and this tool only.
|
||||
|
||||
**Who may ask, today: nobody.** The rule (ADR 0349 §11) is a node's provisioner client under a bus account
|
||||
of its own, for what its node's declaration names, and a module under its own account (issue 569). Neither
|
||||
@@ -126,9 +135,19 @@ ten minutes; more than three at once, or more than half of those held, waits for
|
||||
only by `provisioner_delete`, a person's act with a why. `provisioner_retirement` shows it all. The vault is
|
||||
mark-only: it holds no access to switch off.
|
||||
|
||||
What the TypeScript vault did and this one does not: it consumed its own events only to print an audit
|
||||
line, which the pass now prints itself; and it withdrew a holder on the first pass that missed it, with its
|
||||
history, and said `deprovisioned` — that is gone for ADR 0230's retirement.
|
||||
The words are the controller's: the `provisioner.retirement` event is mesh-controller's `link.Retirement`
|
||||
(`provider-node`, `consumers` as objects) and the `provisioner_retirement` answer its `link.RetirementState`
|
||||
(`held`, `bound`, `retires_by: mark-only`), so `retire approve`, `retire reject` and `cleanup list` read the
|
||||
vault and a set waiting for a person raises the controller's urgent condition. The acts take what the SDK's
|
||||
harness takes: approve and reject repeat the set in `consumers` and refuse any other set; delete repeats the
|
||||
name in `confirm`; each records the `by` and `via` the controller sends. `testdata/retirement-*.json` are
|
||||
those types marshalled by the controller (feat/57 at dc36536), and the tests hold the vault's words to them.
|
||||
|
||||
What the TypeScript vault did and this one does not: it consumed its own events only to print an audit line,
|
||||
which the pass now prints itself. Its retirement ran on the SDK's harness, which this one follows: mark-only,
|
||||
and a holder's ledger file removed only through a person's delete, as here. The difference is where the mark
|
||||
lives: the harness kept it in its process, so a restart forgot it (ADR 0230 §13); here it is in the ledger
|
||||
file.
|
||||
|
||||
## What comes later
|
||||
|
||||
|
||||
@@ -560,11 +560,12 @@ func TestANewOperatorKeyFailingPartwayIsFinished(t *testing.T) {
|
||||
t.Fatalf("%d copies left on the earlier key, not 1", left)
|
||||
}
|
||||
v.Store.failWrite = nil
|
||||
if n, err := v.Reseal(); err != nil || n != 1 {
|
||||
t.Fatalf("the retry sealed %d again (%v), not the 1 left", n, err)
|
||||
// The path the minute takes while the export holds the key: the same key again.
|
||||
if n, err := v.SetOperatorKey(asPEM(t, pub), "the test"); err != nil || n != 1 {
|
||||
t.Fatalf("the same key again sealed %d again (%v), not the 1 left", n, err)
|
||||
}
|
||||
if n, err := v.SetOperatorKey(asPEM(t, pub), "the test"); err != nil || n != 0 {
|
||||
t.Fatalf("the same key again sealed %d (%v)", n, err)
|
||||
t.Fatalf("the same key a third time sealed %d (%v)", n, err)
|
||||
}
|
||||
all, _ := v.Store.List()
|
||||
for _, rec := range all {
|
||||
@@ -651,3 +652,79 @@ func TestTheOperatorRecoversAVaultSecretFromItsExport(t *testing.T) {
|
||||
t.Error("the export holds the value in the clear")
|
||||
}
|
||||
}
|
||||
|
||||
// With no key in the export the minute calls Reseal, which finishes what is pending too.
|
||||
func TestResealFinishesWhatIsPending(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
if _, err := v.Mint("a/one", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pub, _ := operatorKey(t)
|
||||
v.Store.failWrite = func(Record) error { return os.ErrPermission }
|
||||
if _, err := v.SetOperatorKey(asPEM(t, pub), "the test"); err == nil {
|
||||
t.Fatal("a failing write was not said")
|
||||
}
|
||||
v.Store.failWrite = nil
|
||||
if n, err := v.Reseal(); err != nil || n != 1 {
|
||||
t.Fatalf("%d %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
// A secret is not handed out when who was handed it cannot be recorded.
|
||||
func TestNoHandOutWithoutItsRecord(t *testing.T) {
|
||||
v := newVault(t, AllowList{"g14.node-provisioner": {"pair/postgres/gitea"}})
|
||||
if _, err := v.Put("pair/postgres/gitea", "x", "the test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(v.dir, "store", "handed.jsonl"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
if a, err := v.Get("g14.node-provisioner", "pair/postgres/gitea", asker.PublicKey); err == nil {
|
||||
t.Fatalf("handed out with no record of it: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// What an asker cannot shape fills no record: a name that is no secret's name, a caller that is no bus user.
|
||||
func TestAnArbitraryAskFillsNoRecord(t *testing.T) {
|
||||
v := newVault(t, nil)
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for _, ask := range [][2]string{{"g14.x", strings.Repeat("junk ", 1000)}, {"g14.x", "../../etc"},
|
||||
{"not a bus user!", "pair/a"}, {strings.Repeat("a", 300), "pair/a"}} {
|
||||
_, _ = v.Get(ask[0], ask[1], asker.PublicKey)
|
||||
}
|
||||
if h, _ := v.Store.Handed(10); len(h) != 0 {
|
||||
t.Errorf("recorded %d arbitrary asks: %+v", len(h), h)
|
||||
}
|
||||
}
|
||||
|
||||
// The record of hand-outs is bounded: rolled at its size into a fixed number of files, the oldest dropped.
|
||||
func TestTheRecordOfHandOutsIsBounded(t *testing.T) {
|
||||
v := newVault(t, nil) // refuses everyone, as the running vault does
|
||||
v.Store.HandedMax = 2000
|
||||
asker, _ := seal.GenerateKeyPair()
|
||||
for i := 0; i < 500; i++ {
|
||||
_, _ = v.Get("g14.flood", "pair/postgres/gitea", asker.PublicKey)
|
||||
}
|
||||
var total int64
|
||||
files, _ := filepath.Glob(filepath.Join(v.dir, "store", "handed.jsonl*"))
|
||||
if len(files) != HandedKept+1 {
|
||||
t.Errorf("%d files of the record, not %d: %v", len(files), HandedKept+1, files)
|
||||
}
|
||||
for _, f := range files {
|
||||
info, _ := os.Stat(f)
|
||||
if info.Size() > 2000 {
|
||||
t.Errorf("%s is %d bytes, over its bound", f, info.Size())
|
||||
}
|
||||
total += info.Size()
|
||||
}
|
||||
if total > int64(HandedKept+1)*2000 {
|
||||
t.Errorf("the record holds %d bytes", total)
|
||||
}
|
||||
if v.Store.Rolled() == 0 {
|
||||
t.Error("rolling was not counted")
|
||||
}
|
||||
if h, _ := v.Store.Handed(5); len(h) != 5 || h[0].To != "g14.flood" {
|
||||
t.Errorf("the newest are not read back: %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user