mesh-vault: a store file that is not a record is listed by name with its error (issue 1409, V10)
mesh/delivery merged: source.main moved to novox/mesh-catalog@792a79720b30
mesh/check checking 45b0779b6801 on 792a79720b30

Store.ListReadable lists every record and, beside them, each file it cannot read
by name with its error; RecordsHeld lists those as rows with an error and goes
on. recordsError stays for a store that cannot be listed at all. Store.List and
ListPrefix keep their behaviour for their other callers.

Builds: design 39 bus-and-switch §20; design 39 parts §12. Lead answers (issue 1409): V9 (keep recordsCount and recordsError), V10.
This commit is contained in:
2026-11-12 17:41:39 +01:00
parent 52572698cb
commit 45b0779b68
3 changed files with 55 additions and 3 deletions
@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"regexp"
"sort"
"strings"
"time"
)
@@ -102,7 +103,7 @@ type RecordHeld struct {
// RecordsHeld lists every record the store keeps, by name, ordered as the store lists them. The fingerprint is the
// vault's own of the whole value, as the ledger gives it.
func (v *Vault) RecordsHeld() ([]RecordHeld, error) {
records, err := v.Store.List()
records, bad, err := v.Store.ListReadable()
if err != nil {
return nil, err
}
@@ -122,5 +123,9 @@ func (v *Vault) RecordsHeld() ([]RecordHeld, error) {
}
out = append(out, row)
}
for _, b := range bad {
out = append(out, RecordHeld{Name: b.Name, Error: "the vault could not read the record's file: " + b.Err.Error()})
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out, nil
}
@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"os"
"strings"
"testing"
@@ -138,6 +139,10 @@ func TestSecretHoldersListsEveryStoreRecordByNameAndFingerprintNeverTheValue(t *
t.Fatal(err)
}
if err := os.WriteFile(v.Store.pathOf("pair/notjson/one"), []byte("{not json"), 0o600); err != nil {
t.Fatal(err)
}
got, err := toolNamed(t, definitions(&Env{Vault: v.Vault}), "secret_holders").Run(map[string]any{})
if err != nil {
t.Fatal(err)
@@ -151,8 +156,8 @@ func TestSecretHoldersListsEveryStoreRecordByNameAndFingerprintNeverTheValue(t *
for _, r := range m["records"].([]RecordHeld) {
rows[r.Name] = r
}
if m["recordsCount"] != 4 || len(rows) != 4 {
t.Fatalf("records %v, want 4 (two licence secrets, a pair, a broken one)", rows)
if m["recordsCount"] != 5 || len(rows) != 5 {
t.Fatalf("records %v, want 5 (two licence secrets, a pair, a sealed-broken one, a file that is not JSON)", rows)
}
for part, value := range map[string]string{LicenceGrant: grant, LicenceAccess: access} {
r := rows["licence.jane_at_example.org."+part]
@@ -167,4 +172,10 @@ func TestSecretHoldersListsEveryStoreRecordByNameAndFingerprintNeverTheValue(t *
if r := rows["pair/broken/one"]; r.Error == "" || r.Fingerprint != "" {
t.Errorf("a record the vault cannot open must stay listed with an error: %+v", r)
}
if r := rows["pair/notjson/one"]; r.Error == "" || r.Fingerprint != "" {
t.Errorf("a file that is not JSON must be listed by name with its error, the listing going on (V10): %+v", r)
}
if _, bad := m["recordsError"]; bad {
t.Errorf("recordsError is for a store that cannot be listed at all (V10): %v", m["recordsError"])
}
}
@@ -327,6 +327,42 @@ func (s *Store) ListPrefix(prefix string) ([]Record, error) {
return out, nil
}
// Unreadable is a record file the store could not read, by the name its file gives.
type Unreadable struct {
Name string
Err error
}
// ListReadable is every record by name, and beside them each file that is not a record (not valid JSON, say) by name
// with its error: the listing goes on past it (issue 1409, V10). Only an unreadable directory is an error.
func (s *Store) ListReadable() ([]Record, []Unreadable, error) {
entries, err := os.ReadDir(filepath.Join(s.dir, "secrets"))
if err != nil {
return nil, nil, err
}
var out []Record
var bad []Unreadable
for _, e := range entries {
if !strings.HasSuffix(e.Name(), ".json") {
continue
}
name, err := url.PathUnescape(strings.TrimSuffix(e.Name(), ".json"))
if err != nil {
bad = append(bad, Unreadable{Name: strings.TrimSuffix(e.Name(), ".json"), Err: err})
continue
}
r, ok, err := s.Read(name)
if err != nil {
bad = append(bad, Unreadable{Name: name, Err: err})
} else if ok {
out = append(out, r)
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
sort.Slice(bad, func(i, j int) bool { return bad[i].Name < bad[j].Name })
return out, bad, nil
}
type operatorFile struct {
Key string `json:"key"`
Since time.Time `json:"since"`