Merge pull request 'mesh-grants: read manifests as the indexer does and say every refused one, so claude-code counts as placed (issue 1945)' (#59) from fix/1945-grants-inputs into main
This commit was merged in pull request #59.
This commit is contained in:
@@ -414,6 +414,60 @@ func TestGrants(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("a predefined-key fallback is read with no list; every refused manifest listed", func(t *testing.T) {
|
||||||
|
// mod-p's value falls back to a predefined key, as claude-code's managed_settings does, and no list is on the bus
|
||||||
|
// (issue 1945); mod-z's manifest carries a key mesh-sdk's reader refuses, and mod-z is assigned nowhere.
|
||||||
|
const pManifest = `{"module":"mod-p","version":"1","own-secrets":{"broker":"/var/lib/p/broker"},` +
|
||||||
|
`"config":{"size":{"type":"number","description":"The size text shows in, in points.","predefined":"font-size","default":12,"runtime":true}}}`
|
||||||
|
const zManifest = `{"module":"mod-z","version":"1","no-such-key":true}`
|
||||||
|
b := fixture()
|
||||||
|
b.manifestOf("mod-p", pManifest)
|
||||||
|
b.manifestOf("mod-z", zManifest)
|
||||||
|
b.assign("b", "mod-p")
|
||||||
|
in := grants.ReadInputs(b, &grants.Manifests{})
|
||||||
|
if len(in.Refused) != 1 || in.Refused[0].Input != "manifest of mod-z" || !gapNamed(in.Gaps, "manifest of mod-z") {
|
||||||
|
t.Errorf("refused = %v: only mod-z's, and named in the gaps", in.Refused)
|
||||||
|
}
|
||||||
|
recs, _ := grants.Records(in)
|
||||||
|
users, err := grants.ComposeUsers(recs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !slices.Contains(names(users), "b.mod-p") {
|
||||||
|
t.Errorf("b.mod-p is not composed, so its module counts as placed nowhere: %v", names(users))
|
||||||
|
}
|
||||||
|
// The pass, the tool listing held memberships, and the log each say the refusal.
|
||||||
|
part := partOn(b)
|
||||||
|
res := part.Pass()
|
||||||
|
if len(res.Refused) != 1 || !strings.HasPrefix(res.Refused[0], "manifest of mod-z: ") {
|
||||||
|
t.Errorf("the pass says refused %v", res.Refused)
|
||||||
|
}
|
||||||
|
held, err := part.Held()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got, _ := held.(map[string]any)["refused"].([]string); len(got) != 1 {
|
||||||
|
t.Errorf("grants_membership lists refused %v", got)
|
||||||
|
}
|
||||||
|
var logged []string
|
||||||
|
var lmu sync.Mutex
|
||||||
|
f := &grants.Follower{Part: partOn(b), Watch: &fakeWatcher{opened: make(chan string, 16)}, Logf: func(format string, args ...any) {
|
||||||
|
lmu.Lock()
|
||||||
|
logged = append(logged, fmt.Sprintf(format, args...))
|
||||||
|
lmu.Unlock()
|
||||||
|
}}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() { f.Run(ctx); close(done) }()
|
||||||
|
waitFor(t, func() bool {
|
||||||
|
lmu.Lock()
|
||||||
|
defer lmu.Unlock()
|
||||||
|
return strings.Contains(strings.Join(logged, "\n"), "refused manifest of mod-z: ")
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
<-done
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("credentials", func(t *testing.T) {
|
t.Run("credentials", func(t *testing.T) {
|
||||||
shadow := []grants.User{{User: "a.held"}, {User: "a.new"}, {User: "a.bare"}}
|
shadow := []grants.User{{User: "a.held"}, {User: "a.new"}, {User: "a.bare"}}
|
||||||
loaded := []grants.User{{User: "a.held"}, {User: "a.bare"}, {User: "enrol.x"}, {User: "person.op"}}
|
loaded := []grants.User{{User: "a.held"}, {User: "a.bare"}, {User: "enrol.x"}, {User: "person.op"}}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type Follower struct {
|
|||||||
// held and notAsserted are what the last pass held back and could not create, said again only when they change.
|
// held and notAsserted are what the last pass held back and could not create, said again only when they change.
|
||||||
held string
|
held string
|
||||||
notAsserted string
|
notAsserted string
|
||||||
|
refusedMans string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Follower) init() {
|
func (f *Follower) init() {
|
||||||
@@ -150,6 +151,18 @@ func (f *Follower) open() {
|
|||||||
// the pass could not create, each with why (issue 1790), when the set changes from the last pass's; and every stream it
|
// the pass could not create, each with why (issue 1790), when the set changes from the last pass's; and every stream it
|
||||||
// created, one line per module.
|
// created, one line per module.
|
||||||
func (f *Follower) said(res Result) {
|
func (f *Follower) said(res Result) {
|
||||||
|
if refused := strings.Join(res.Refused, "\n"); refused != f.refusedMans {
|
||||||
|
f.refusedMans = refused
|
||||||
|
if len(res.Refused) == 0 {
|
||||||
|
f.logf("every manifest the indexer holds is read")
|
||||||
|
} else {
|
||||||
|
f.logf("%d manifests cannot be read, so their modules count as placed nowhere (grants_membership with no node and module lists them):",
|
||||||
|
len(res.Refused))
|
||||||
|
for _, r := range res.Refused {
|
||||||
|
f.logf("refused %s", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if held := strings.Join(res.NotPublished, "\n"); held != f.held {
|
if held := strings.Join(res.NotPublished, "\n"); held != f.held {
|
||||||
f.held = held
|
f.held = held
|
||||||
if len(res.NotPublished) == 0 {
|
if len(res.NotPublished) == 0 {
|
||||||
|
|||||||
@@ -114,7 +114,10 @@ type Inputs struct {
|
|||||||
Loaded *delivery.UserListWritten
|
Loaded *delivery.UserListWritten
|
||||||
// Hashes are the vault's hashes of the bus accounts, by bus user (ReadAccounts); kept in memory, never written.
|
// Hashes are the vault's hashes of the bus accounts, by bus user (ReadAccounts); kept in memory, never written.
|
||||||
Hashes map[string]HeldHash
|
Hashes map[string]HeldHash
|
||||||
Gaps []Gap
|
// Refused is every manifest the indexer holds that the part cannot read or mesh-sdk's reader refuses, assigned or
|
||||||
|
// not, each with why: its module counts as placed nowhere, so it is said loudly, never passed over (issue 1945).
|
||||||
|
Refused []Gap
|
||||||
|
Gaps []Gap
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeldHash is one key of the vault's bus-accounts: the hash, and what it is of. The hash is never said or written by
|
// HeldHash is one key of the vault's bus-accounts: the hash, and what it is of. The hash is never said or written by
|
||||||
@@ -168,7 +171,10 @@ func (c *Manifests) get(r Reader, digest string) (catalogue.Manifest, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return catalogue.Manifest{}, err
|
return catalogue.Manifest{}, err
|
||||||
}
|
}
|
||||||
m, err = catalogue.ParseManifest([]byte(v.JSON))
|
// Read for its inputs, as every delivery part reads a manifest (issue 1751): the merge gate judged its config values,
|
||||||
|
// and the part reads its seats, buckets, logs and streams, never a value; so a value falling back to a predefined key
|
||||||
|
// is taken as it declares itself, where ParseManifest refused it with no list and placed its module nowhere (issue 1945).
|
||||||
|
m, err = catalogue.ParseManifestForInputs([]byte(v.JSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return catalogue.Manifest{}, fmt.Errorf("the manifest %s is refused by mesh-sdk's reader: %w", digest, err)
|
return catalogue.Manifest{}, fmt.Errorf("the manifest %s is refused by mesh-sdk's reader: %w", digest, err)
|
||||||
}
|
}
|
||||||
@@ -269,17 +275,15 @@ func ReadInputs(r Reader, cache *Manifests) Inputs {
|
|||||||
if err == nil && v.Manifest == "" {
|
if err == nil && v.Manifest == "" {
|
||||||
err = fmt.Errorf("the indexer's value names no manifest digest")
|
err = fmt.Errorf("the indexer's value names no manifest digest")
|
||||||
}
|
}
|
||||||
if err != nil {
|
var m catalogue.Manifest
|
||||||
if wanted[module] {
|
if err == nil {
|
||||||
in.Gaps = append(in.Gaps, Gap{Input: "manifest of " + module, Why: err.Error()})
|
m, err = cache.get(r, v.Manifest)
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
m, err := cache.get(r, v.Manifest)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if wanted[module] {
|
// Every manifest, assigned or not: one refused declares no seat, so its holders compose as placed nowhere.
|
||||||
in.Gaps = append(in.Gaps, Gap{Input: "manifest of " + module, Why: err.Error()})
|
g := Gap{Input: "manifest of " + module, Why: err.Error()}
|
||||||
}
|
in.Refused = append(in.Refused, g)
|
||||||
|
in.Gaps = append(in.Gaps, g)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
in.Manifests[module] = m
|
in.Manifests[module] = m
|
||||||
|
|||||||
@@ -96,8 +96,11 @@ type Result struct {
|
|||||||
AssertionsWritten int `json:"assertions_written"`
|
AssertionsWritten int `json:"assertions_written"`
|
||||||
// Asserted is each module whose declared streams this pass created, `<module> at <digest>: created …`, and
|
// Asserted is each module whose declared streams this pass created, `<module> at <digest>: created …`, and
|
||||||
// NotAsserted each creation refused, `<module> <stream>: <why>` (issue 1790).
|
// NotAsserted each creation refused, `<module> <stream>: <why>` (issue 1790).
|
||||||
Asserted []string `json:"asserted,omitempty"`
|
Asserted []string `json:"asserted,omitempty"`
|
||||||
NotAsserted []string `json:"not_asserted,omitempty"`
|
NotAsserted []string `json:"not_asserted,omitempty"`
|
||||||
|
// Refused is every manifest the indexer holds that this pass could not read, `manifest of <module>: <why>`: its
|
||||||
|
// module counts as placed nowhere (issue 1945).
|
||||||
|
Refused []string `json:"refused,omitempty"`
|
||||||
Errors []string `json:"errors,omitempty"`
|
Errors []string `json:"errors,omitempty"`
|
||||||
PublishedOrAsserted string `json:"published_or_asserted"`
|
PublishedOrAsserted string `json:"published_or_asserted"`
|
||||||
}
|
}
|
||||||
@@ -165,6 +168,9 @@ func (p *Part) Compose() Result {
|
|||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
}()
|
}()
|
||||||
in := ReadInputs(p.Read, &p.cache)
|
in := ReadInputs(p.Read, &p.cache)
|
||||||
|
for _, g := range in.Refused {
|
||||||
|
res.Refused = append(res.Refused, g.Input+": "+g.Why)
|
||||||
|
}
|
||||||
recs, left := Records(in)
|
recs, left := Records(in)
|
||||||
st := UserListState{V: 1, Mode: Shadow, LeftOut: left, Loaded: in.Loaded, BusNode: in.BusNode,
|
st := UserListState{V: 1, Mode: Shadow, LeftOut: left, Loaded: in.Loaded, BusNode: in.BusNode,
|
||||||
Gaps: append(append([]Gap(nil), in.Gaps...), MembershipGaps...), At: p.now()}
|
Gaps: append(append([]Gap(nil), in.Gaps...), MembershipGaps...), At: p.now()}
|
||||||
|
|||||||
@@ -116,7 +116,11 @@ func (p *Part) Held() (any, error) {
|
|||||||
key, why, _ := strings.Cut(n, ": ")
|
key, why, _ := strings.Cut(n, ": ")
|
||||||
out = append(out, held{Membership: key, Why: why})
|
out = append(out, held{Membership: key, Why: why})
|
||||||
}
|
}
|
||||||
return map[string]any{"held": out, "count": len(out), "pass": rev, "published": res.Published}, nil
|
refused := res.Refused
|
||||||
|
if refused == nil {
|
||||||
|
refused = []string{}
|
||||||
|
}
|
||||||
|
return map[string]any{"held": out, "count": len(out), "pass": rev, "published": res.Published, "refused": refused}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass composes and keeps the result as the last pass, for the tools to read.
|
// Pass composes and keeps the result as the last pass, for the tools to read.
|
||||||
|
|||||||
Reference in New Issue
Block a user