delivery: the grants part's grant keys and the source the assigner reads (a1; issue 774)

Why: the grants part writes grants.<module>.<fp> in shadow beside the controller's; the assigner switches source only when the lease is handed away.
This commit is contained in:
2026-10-28 21:54:04 +01:00
parent 54b17164ef
commit a1a688b19e
3 changed files with 61 additions and 0 deletions
+6
View File
@@ -6,6 +6,7 @@ import (
"time"
"git.novox.be/novox/mesh-sdk/go/announce"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/nats-io/nats.go/jetstream"
"os"
"path/filepath"
@@ -431,6 +432,11 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
if on, why := LeaseHandedAway([]byte(row.raw), row.found, row.err); on != row.on || why == "" {
t.Errorf("%s: on %v (%s), want %v", row.name, on, why, row.on)
}
// The assigner's grants source follows it: the grants part's bucket only for a handed-away lease (issue 774).
on, _ := LeaseHandedAway([]byte(row.raw), row.found, row.err)
if bucket, _ := delivery.GrantSource(on); (bucket == delivery.PartGrantsBucket) != row.on {
t.Errorf("%s: the grants source is %s", row.name, bucket)
}
}
announced := map[string]announce.States{ControllerSeat: ControllerAnnouncement("novox")}
for reader, want := range map[string]bool{VaultSeat: true, GrantsModule: true, "mesh-delivery-view": false} {
+19
View File
@@ -298,6 +298,25 @@ func TestControllerGrant(t *testing.T) {
if _, err := EncodeControllerGrant(ControllerGrant{Accounts: []string{""}}); err == nil {
t.Errorf("a grant naming an empty account was accepted")
}
// The grants part's keys are the controller's: the same key round-trips; the source is the part's bucket only for a
// handed-away lease (issue 774).
if pk, err := PartGrantKey("mesh-delivery-assigner", fp); err != nil || pk != key {
t.Errorf("PartGrantKey = %q, %v, want the controller's %q", pk, err, key)
}
if m, f, err := ParsePartGrantKey(key); err != nil || m != "mesh-delivery-assigner" || f != fp {
t.Errorf("ParsePartGrantKey = %q, %q, %v", m, f, err)
}
for _, c := range []struct {
away bool
bucket, read string
}{
{false, "mesh-controller_grants", "mesh-controller.grants"},
{true, "mesh-grants_grants", "mesh-grants.grants"},
} {
if b, r := GrantSource(c.away); b != c.bucket || r != c.read {
t.Errorf("GrantSource(%v) = %q, %q, want %q, %q", c.away, b, r, c.bucket, c.read)
}
}
g := ControllerGrant{Accounts: []string{"ace.mesh-build-go", "shanks.mesh-mail.reader"}}
for _, c := range []struct {
g ControllerGrant
+36
View File
@@ -90,3 +90,39 @@ func (g ControllerGrant) NamesNode(node, module string) bool {
}
return false
}
// **The grants part's grants** (novox/hq ADR 0547 §4, §5, ADR 0561 §2, §6; issue 774). The grants part writes the same
// keys, with the same value, into its own state `grants` (the bucket PartGrantsBucket), beside the controller's: first in
// shadow, which nobody reads, so a tool can compare the two before anything switches. The assigner reads the part's keys
// only once the controller's lease is handed away (GrantSource), and the controller's until then.
const (
// PartGrantsState is the grants part's state of issued credentials by fingerprint, as its manifest names it.
PartGrantsState = "grants"
// PartGrantsBucket is that state's bucket, `<owner module>_<name>`.
PartGrantsBucket = "mesh-grants_" + PartGrantsState
// PartGrantsRead is that state as a reader names it in `reads`.
PartGrantsRead = "mesh-grants." + PartGrantsState
// PartGrantsHistory is the revisions the bucket keeps per key.
PartGrantsHistory = ControllerGrantsHistory
)
// PartGrantKey is the key, in PartGrantsBucket, of a module's credential issued for a fingerprint: the layout of the
// controller's key (ControllerGrantKey), so a key of one is the key of the other and a compare is by key.
func PartGrantKey(module, fingerprint string) (string, error) {
return ControllerGrantKey(module, fingerprint)
}
// ParsePartGrantKey is the inverse of PartGrantKey.
func ParsePartGrantKey(key string) (module, fingerprint string, err error) {
return ParseControllerGrantKey(key)
}
// GrantSource names the bucket the assigner reads grants from, and the `reads` name that grants it: the grants part's
// once the controller's lease is handed away (buscompose.LeaseHandedAway, which refuses every other value of the lease,
// an unreadable one included), the controller's in every other case.
func GrantSource(leaseHandedAway bool) (bucket, read string) {
if leaseHandedAway {
return PartGrantsBucket, PartGrantsRead
}
return ControllerGrantsBucket, "mesh-controller.grants"
}