carlos / platform Public

svcrole: Self, outside the catalogue, and AllowSelf (#155)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev bc81665939a7a56ae44e5824d042f992580ba7e0 parent a14da1b
2 files changed, +77 −0
  • internal/svcrole/svcrole.go +23 −0
  • internal/svcrole/svcrole_test.go +54 −0
diff --git a/internal/svcrole/svcrole.go b/internal/svcrole/svcrole.go
index 9c0820a95..9df952fea 100644
--- a/internal/svcrole/svcrole.go
+++ b/internal/svcrole/svcrole.go
@@ -64,6 +64,29 @@ const (
// catalogue is the whole vocabulary, in the order All returns it.
var catalogue = []Role{Publish, Deploy, Instance, Operate, Service, Admin}
+// Self is what a kind='instance' credential resolves to: the authority of
+// one instance over itself (spec 2026-10-16-instance-bound-tokens §3.3).
+//
+// It is deliberately NOT in the catalogue. Parse refuses it, so no service
+// row can be minted with it, and All omits it, so no mint door or form can
+// offer it. Allow refuses it too, because Allow adds Admin to every set and
+// Admin is app-wide: a door opened to Self through Allow would open to every
+// Admin credential, which has no host to be checked against. AllowSelf is
+// the only way a door admits it.
+const Self Role = "self"
+
+// AllowSelf returns base with Self added and nothing else: AllowSelf(Set{})
+// admits Self alone (not Admin), and AllowSelf(Allow(Instance)) admits
+// Instance, Admin and Self. base is not modified.
+func AllowSelf(base Set) Set {
+ s := Set{roles: make(map[Role]bool, len(base.roles)+1)}
+ for r := range base.roles {
+ s.roles[r] = true
+ }
+ s.roles[Self] = true
+ return s
+}
+
// All returns the catalogue. The slice is copied so a caller cannot edit the
// vocabulary by writing through it.
func All() []Role {
diff --git a/internal/svcrole/svcrole_test.go b/internal/svcrole/svcrole_test.go
index a5775141f..03e84f4ed 100644
--- a/internal/svcrole/svcrole_test.go
+++ b/internal/svcrole/svcrole_test.go
@@ -133,3 +133,57 @@ func TestDeployIsMintableAndNarrow(t *testing.T) {
t.Fatal("Allow adds Admin to every set; that changed")
}
}
+
+// Self is what an instance-bound credential resolves to (spec
+// 2026-10-16-instance-bound-tokens §3.3). It must never be storable on a
+// service row, nor offered by a mint door or form.
+func TestSelfIsNotMintable(t *testing.T) {
+ if _, ok := Parse("self"); ok {
+ t.Fatal(`Parse("self") succeeded; Self must never be stored on a service row`)
+ }
+ for _, r := range All() {
+ if r == Self {
+ t.Fatal("Self is in All(); the mint door and form would offer it")
+ }
+ }
+}
+
+// Allow adds Admin to every set, so a door opened to Self through Allow
+// would open to every Admin credential too, which has no host to check.
+func TestAllowRefusesSelf(t *testing.T) {
+ defer func() {
+ if recover() == nil {
+ t.Fatal("Allow(Self) did not panic; it would admit Admin with it")
+ }
+ }()
+ Allow(Self)
+}
+
+func TestAllowSelfAddsOnlySelf(t *testing.T) {
+ closed := AllowSelf(Set{})
+ if !closed.Permits(Self) {
+ t.Fatal("AllowSelf(Set{}) refuses Self")
+ }
+ for _, r := range All() {
+ if closed.Permits(r) {
+ t.Fatalf("AllowSelf(Set{}) admits %q", r)
+ }
+ }
+ if closed.Empty() {
+ t.Fatal("AllowSelf(Set{}) reads as a closed door")
+ }
+ open := AllowSelf(Allow(Instance))
+ for _, r := range []Role{Self, Instance, Admin} {
+ if !open.Permits(r) {
+ t.Fatalf("AllowSelf(Allow(Instance)) refuses %q", r)
+ }
+ }
+ if open.Permits(Publish) {
+ t.Fatal("AllowSelf widened the base set")
+ }
+ base := Allow(Instance)
+ _ = AllowSelf(base)
+ if base.Permits(Self) {
+ t.Fatal("AllowSelf mutated its argument")
+ }
+}