From 8db2734a5fd5c6bfd373e027f405cf6468754308 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 3 Nov 2026 22:55:19 -0500 Subject: [PATCH 1/3] fix(accounts): the three role vocabularies are one table the matrix reads from its sources The "Three vocabularies" section of AUTHZ_MATRIX.md was prose typed into the generator. It is now rendered from RoleVocabularies(), one table whose words come from the code that defines each vocabulary: the organisation roles from the OrgRole enum, the catalogue built-ins from the roles the permission vocabulary grants, the installation roles from the shared authority core's declaration rule. A test holds each word to its source and the rendered section to the matrix; the generated file is unchanged. Co-Authored-By: Claude Fable 5.1 --- .../code/pkg/business/role_vocabularies.go | 134 ++++++++++++++++++ .../pkg/business/role_vocabularies_test.go | 58 ++++++++ .../code/pkg/business/rpc_policy_matrix.go | 28 +--- 3 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 module/services/accounts/code/pkg/business/role_vocabularies.go create mode 100644 module/services/accounts/code/pkg/business/role_vocabularies_test.go diff --git a/module/services/accounts/code/pkg/business/role_vocabularies.go b/module/services/accounts/code/pkg/business/role_vocabularies.go new file mode 100644 index 000000000..7c7e488a8 --- /dev/null +++ b/module/services/accounts/code/pkg/business/role_vocabularies.go @@ -0,0 +1,134 @@ +package business + +import ( + "slices" + "strings" + + gen "accounts/pkg/gen/saas/accounts/v1" + + "github.com/codefly-dev/authority/libraries/authority-core/go/domain" +) + +// RoleVocabulary is one of the three sets of role words on this host. Two of +// them share words (editor, viewer) and none stands in for another, so each +// says what it decides and where its words are declared. The matrix renders +// this table; nothing types the words a second time. +type RoleVocabulary struct { + Name string + // Words are read from the code that defines each vocabulary, never typed + // here: the organisation roles from the OrgRole enum, the catalogue + // built-ins from the roles the permission vocabulary grants, the + // installation roles from the shared authority core's declaration rule. + Words []string + Decides string + DeclaredIn string +} + +// RoleVocabularies is the one table of the three role vocabularies, in the +// order a reader meets them: who administers the organisation, what a role +// assignment carries on this host's own resources, and what a person holds +// through one installation of a solution or a module page. +func RoleVocabularies() []RoleVocabulary { + return []RoleVocabulary{ + { + Name: "Organisation role", + Words: organizationRoleWords(), + Decides: "Who administers the organisation. The `org_admin` tier admits `owner` and `admin` (and a platform `super_admin`); " + + "`org_member` admits any member. The `/admin` pages gate on `roles:write`, which admits the same people " + + "(`owner`, `admin`, a platform `super_admin`) as the API's org-admin check.", + DeclaredIn: "the organisation membership; the session's `or` claim", + }, + { + Name: "Catalogue built-in role", + Words: catalogueBuiltInRoleWords(), + Decides: "Which `:` permissions (the Permission / scope column above) a role assignment carries " + + "on this host's own resources.", + DeclaredIn: "`servicePermissionVocabulary` in `pkg/business/service_vocabulary.go`", + }, + { + Name: "Installation role", + Words: installationRoleWords(), + Decides: "What a person holds through one installation of a solution or of a module page that declares roles: " + + "its page, its navigation entry, its Setup tab, and what a Work Context at its audience carries. " + + "The shared authority core requires every solution, and every module page that declares roles, to declare " + + "`installer`; `editor` and `viewer` are the standard an open handbook proposal sets, checked by nothing yet. " + + "The installation's owner holds `installer`.", + DeclaredIn: "the signed authority document of the solution or module page; granted per installation to a person or a team", + }, + } +} + +// organizationRoleWords are the OrgRole enum's names, highest first, as the +// session's `or` claim and the authorisation tiers spell them. +func organizationRoleWords() []string { + var words []string + for value := range gen.OrgRole_name { + role := gen.OrgRole(value) + if role == gen.OrgRole_ORG_ROLE_UNSPECIFIED { + continue + } + words = append(words, strings.ToLower(strings.TrimPrefix(role.String(), "ORG_ROLE_"))) + } + slices.SortFunc(words, func(a, b string) int { + return int(gen.OrgRole_value["ORG_ROLE_"+strings.ToUpper(b)]) - int(gen.OrgRole_value["ORG_ROLE_"+strings.ToUpper(a)]) + }) + return words +} + +// catalogueBuiltInRoleWords are the built-in roles the permission vocabulary +// grants at least one permission to, in the order the vocabulary first names +// them. A platform role that reaches a permission through the `*:*` root is +// not a catalogue role and is left out. +func catalogueBuiltInRoleWords() []string { + var words []string + for _, definition := range servicePermissionVocabulary { + for _, holder := range definition.BuiltInRoles { + name, _, _ := strings.Cut(holder, " (") + if strings.HasPrefix(name, "platform ") || slices.Contains(words, name) { + continue + } + words = append(words, name) + } + } + return words +} + +// installationRoleWords are the roles the shared authority core requires an +// installation target to declare: the installer alone, today; whatever else +// the target declares is its own. +func installationRoleWords() []string { + return []string{domain.InstallerRole} +} + +// renderRoleVocabularies is the matrix's "Three vocabularies" section, rendered +// from RoleVocabularies. +func renderRoleVocabularies() string { + var b strings.Builder + b.WriteString("\n## Three vocabularies\n\n") + b.WriteString("Three sets of role words appear on this host. They answer different questions,\n") + b.WriteString("and a word in one never means the same-spelled word in another.\n\n") + b.WriteString("| Vocabulary | Words | What it decides | Declared in |\n|---|---|---|---|\n") + for _, v := range RoleVocabularies() { + words := make([]string, 0, len(v.Words)) + for _, w := range v.Words { + words = append(words, "`"+w+"`") + } + spelled := strings.Join(words, ", ") + if v.Name == "Installation role" { + spelled += ", and whatever else a solution or a module page declares (the proposed standard adds `editor`, `viewer`)" + } + b.WriteString("| " + v.Name + " | " + spelled + " | " + escapeCell(v.Decides) + " | " + v.DeclaredIn + " |\n") + } + b.WriteString(` +The ` + "`/s/`" + ` page, the solution navigation and the Setup tab speak only +installation roles. A module page (a module whose signed manifest declares a +frontend) may declare installation roles too: then they gate its page, its +navigation entry and its audience exactly as a solution's, and the +organisation's roles no longer apply there. A module that declares no roles, +which every module without a page is, keeps the organisation's roles at its +audience, and its page is reached through the organisation's installation of +it and a team grant at that installation. The shared authority core refuses +roles on a module without a page. +`) + return b.String() +} diff --git a/module/services/accounts/code/pkg/business/role_vocabularies_test.go b/module/services/accounts/code/pkg/business/role_vocabularies_test.go new file mode 100644 index 000000000..e62e74afe --- /dev/null +++ b/module/services/accounts/code/pkg/business/role_vocabularies_test.go @@ -0,0 +1,58 @@ +package business + +import ( + "strings" + "testing" + + gen "accounts/pkg/gen/saas/accounts/v1" + + "github.com/codefly-dev/authority/libraries/authority-core/go/domain" + "github.com/stretchr/testify/require" +) + +// The three vocabularies are read from the code that defines each, never +// typed: every word names something the source declares, and nothing the +// source declares is missing from the table. +func TestRoleVocabulariesAreReadFromTheirSources(t *testing.T) { + byName := map[string]RoleVocabulary{} + for _, v := range RoleVocabularies() { + byName[v.Name] = v + } + require.Len(t, byName, 3) + + organisation := byName["Organisation role"].Words + require.Equal(t, []string{"owner", "admin", "member"}, organisation) + for _, word := range organisation { + _, declared := gen.OrgRole_value["ORG_ROLE_"+strings.ToUpper(word)] + require.True(t, declared, "%q is not an OrgRole", word) + } + require.Len(t, organisation, len(gen.OrgRole_name)-1, "every OrgRole but UNSPECIFIED is a word") + + catalogue := byName["Catalogue built-in role"].Words + require.Equal(t, []string{"admin", "editor", "viewer"}, catalogue) + for _, word := range catalogue { + granted := false + for _, definition := range servicePermissionVocabulary { + for _, holder := range definition.BuiltInRoles { + if name, _, _ := strings.Cut(holder, " ("); name == word { + granted = true + } + } + } + require.True(t, granted, "%q holds no permission in the vocabulary", word) + } + for _, v := range RoleVocabularies() { + require.NotContains(t, v.Words, "super_admin", "a platform role is none of the three vocabularies") + } + + require.Equal(t, []string{domain.InstallerRole}, byName["Installation role"].Words) + + rendered := renderRoleVocabularies() + for _, v := range RoleVocabularies() { + require.Contains(t, rendered, "| "+v.Name+" | ") + for _, word := range v.Words { + require.Contains(t, rendered, "`"+word+"`") + } + } + require.Contains(t, string(RenderRPCPolicyMatrix()), rendered) +} diff --git a/module/services/accounts/code/pkg/business/rpc_policy_matrix.go b/module/services/accounts/code/pkg/business/rpc_policy_matrix.go index f9dcf5bff..cfccef165 100644 --- a/module/services/accounts/code/pkg/business/rpc_policy_matrix.go +++ b/module/services/accounts/code/pkg/business/rpc_policy_matrix.go @@ -57,7 +57,7 @@ func RenderRPCPolicyMatrix() []byte { for _, tier := range tiers { fmt.Fprintf(&b, "- `%s`: %d\n", tier, counts[RPCPolicyTier(tier)]) } - b.WriteString(threeVocabularies) + b.WriteString(renderRoleVocabularies()) return []byte(b.String()) } @@ -161,29 +161,3 @@ func serviceCount(policies []RPCPolicy) int { func escapeCell(value string) string { return strings.ReplaceAll(value, "|", "\\|") } - -// threeVocabularies names the three sets of role words a reader of this matrix -// meets, because two of them share words (editor, viewer) and none stands in -// for another. -const threeVocabularies = ` -## Three vocabularies - -Three sets of role words appear on this host. They answer different questions, -and a word in one never means the same-spelled word in another. - -| Vocabulary | Words | What it decides | Declared in | -|---|---|---|---| -| Organisation role | ` + "`owner`, `admin`, `member`" + ` | Who administers the organisation. The ` + "`org_admin`" + ` tier admits ` + "`owner`" + ` and ` + "`admin`" + ` (and a platform ` + "`super_admin`" + `); ` + "`org_member`" + ` admits any member. The ` + "`/admin`" + ` pages gate on ` + "`roles:write`" + `, which admits the same people (` + "`owner`, `admin`" + `, a platform ` + "`super_admin`" + `) as the API's org-admin check. | the organisation membership; the session's ` + "`or`" + ` claim | -| Catalogue built-in role | ` + "`admin`, `editor`, `viewer`" + ` | Which ` + "`:`" + ` permissions (the Permission / scope column above) a role assignment carries on this host's own resources. | ` + "`servicePermissionVocabulary`" + ` in ` + "`pkg/business/service_vocabulary.go`" + ` | -| Installation role | ` + "`installer`" + `, and whatever else a solution or a module page declares (the proposed standard adds ` + "`editor`, `viewer`" + `) | What a person holds through one installation of a solution or of a module page that declares roles: its page, its navigation entry, its Setup tab, and what a Work Context at its audience carries. The shared authority core requires every solution, and every module page that declares roles, to declare ` + "`installer`" + `; ` + "`editor`" + ` and ` + "`viewer`" + ` are the standard an open handbook proposal sets, checked by nothing yet. The installation's owner holds ` + "`installer`" + `. | the signed authority document of the solution or module page; granted per installation to a person or a team | - -The ` + "`/s/`" + ` page, the solution navigation and the Setup tab speak only -installation roles. A module page (a module whose signed manifest declares a -frontend) may declare installation roles too: then they gate its page, its -navigation entry and its audience exactly as a solution's, and the -organisation's roles no longer apply there. A module that declares no roles, -which every module without a page is, keeps the organisation's roles at its -audience, and its page is reached through the organisation's installation of -it and a team grant at that installation. The shared authority core refuses -roles on a module without a page. -` From 141766c104b1d4da0c79a9108d9d76478b544467 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 3 Nov 2026 23:30:08 -0500 Subject: [PATCH 2/3] fix(accounts): a fixture takes a fresh approval only once the core no longer holds its installation After an uninstall, the fixture kept presenting the stored plan's approval, whose receipt the core refuses for good once the installation's revision moved, so it never installed again (module-saas-starter#1275, item 7). The stored approval now continues an installation the core holds pending or active (a same-operation replay is idempotent, and the host's plan guard refuses a fresh operation id against an active record); a revoked or absent record takes the fresh review's approval. Co-Authored-By: Claude Fable 5.1 --- .../accounts/code/fixtures/installations.go | 26 ++++++++++++++-- .../code/fixtures/installations_test.go | 31 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 module/services/accounts/code/fixtures/installations_test.go diff --git a/module/services/accounts/code/fixtures/installations.go b/module/services/accounts/code/fixtures/installations.go index ad53aba2b..42ec68cba 100644 --- a/module/services/accounts/code/fixtures/installations.go +++ b/module/services/accounts/code/fixtures/installations.go @@ -218,9 +218,19 @@ func (i *Installations) coreInstall(ctx context.Context, row pendingInstallation return false, err } approval.OperationID = business.NewIDString() + // An installation the core holds pending or active is continued with the + // approval it was opened under: the core replays an active same-operation + // install idempotently, and the host's plan guard refuses a fresh + // operation id against it. A revoked or absent record takes the fresh + // review's approval: a revoked installation's receipt names an operation + // the core refuses for good (its revision moved), so reusing it parked a + // fixture on that refusal on every pass after an uninstall. if store, ok := i.service.Store().(business.CanonicalInstallationSetupStore); ok { - if plan, err := store.SolutionInstallationPlan(ctx, row.orgID, authority.StableInstallationID(row.orgID, row.declared.Target)); err == nil { - approval = plan.Approval + id := authority.StableInstallationID(row.orgID, row.declared.Target) + if all, err := core.Installations(ctx, caller, row.orgID); err == nil && attemptContinues(all, id) { + if plan, err := store.SolutionInstallationPlan(ctx, row.orgID, id); err == nil { + approval = plan.Approval + } } } inst, err := i.service.InstallAuthoritySolution(ctx, caller, row.orgID, row.declared.Target, row.declared.Role, row.ownerID, &approval) @@ -267,3 +277,15 @@ func (i *Installations) ConvergeAuthority(ctx context.Context) error { } return errors.Join(failures...) } + +// attemptContinues reports whether the core holds installation id pending or +// active: the states in which a stored plan's approval still names an +// operation the core finishes or replays. A revoked or absent record does not. +func attemptContinues(all []domain.Installation, id string) bool { + for _, inst := range all { + if inst.ID == id { + return inst.Status == domain.Pending || inst.Status == domain.Active + } + } + return false +} diff --git a/module/services/accounts/code/fixtures/installations_test.go b/module/services/accounts/code/fixtures/installations_test.go new file mode 100644 index 000000000..cf89cc011 --- /dev/null +++ b/module/services/accounts/code/fixtures/installations_test.go @@ -0,0 +1,31 @@ +package fixtures + +import ( + "testing" + + "github.com/codefly-dev/authority/libraries/authority-core/go/domain" +) + +// A stored approval is reused while the core holds the installation pending +// or active (a replay of the same operation is idempotent, and a fresh +// operation id against an active record is refused by the host's plan +// guard). After an uninstall the receipt is refused for good, and a fixture +// that kept presenting it never installed again. +func TestAStoredApprovalContinuesAPendingOrActiveAttempt(t *testing.T) { + for name, tc := range map[string]struct { + all []domain.Installation + want bool + }{ + "pending": {all: []domain.Installation{{ID: "i", Status: domain.Pending}}, want: true}, + "active": {all: []domain.Installation{{ID: "i", Status: domain.Active}}, want: true}, + "revoked": {all: []domain.Installation{{ID: "i", Status: domain.Revoked}}, want: false}, + "another's": {all: []domain.Installation{{ID: "other", Status: domain.Pending}}, want: false}, + "not installed": {want: false}, + } { + t.Run(name, func(t *testing.T) { + if got := attemptContinues(tc.all, "i"); got != tc.want { + t.Fatalf("attemptContinues = %v, want %v", got, tc.want) + } + }) + } +} From cb8d0b1c1150840ac7600bf5da484f4352155fd1 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Tue, 3 Nov 2026 23:18:15 -0500 Subject: [PATCH 3/3] chore(accounts): pin the authority libraries at fab9ea74 (authority#69) authority-client, authority-core, conformance, delivery and resources move from dea37afac (authority#65) to fab9ea74a58b, the merge commit of codefly-dev/authority#69: a dropped role drops its grants, a module page's roles name what it serves, review finds the installation issuance decides on, and one installation per target. Co-Authored-By: Claude Fable 5.1 --- module/services/accounts/code/go.mod | 10 +++++----- module/services/accounts/code/go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index ba96b70f4..985e4c507 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -7,11 +7,11 @@ require ( buf.build/go/protovalidate v1.4.0 connectrpc.com/connect v1.21.1 connectrpc.com/otelconnect v0.9.0 - github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261030222015-dea37afac9ad - github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261030222015-dea37afac9ad - github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261030222015-dea37afac9ad - github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261030222015-dea37afac9ad - github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261030222015-dea37afac9ad + github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261104040014-fab9ea74a58b + github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261104040014-fab9ea74a58b + github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261104040014-fab9ea74a58b + github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261104040014-fab9ea74a58b + github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261104040014-fab9ea74a58b github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc github.com/codefly-dev/core v0.19.17-0.20261029043538-2aa2e9aaf950 github.com/codefly-dev/interface-cache/go/cache v0.4.1 diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index 9788b4693..dd058c4c5 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -56,18 +56,18 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261030222015-dea37afac9ad h1:fFpOC3bErcM55ouVmz4B+ehyhP6jDQtYBWI2sVsLiG0= -github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261030222015-dea37afac9ad/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg= -github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261030222015-dea37afac9ad h1:evJ/d2Zi4nBXVc2NvMmWPphR7DN1cABwsfcy5LHjNjw= -github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261030222015-dea37afac9ad/go.mod h1:Cdk+10ebRh0tZA9ze2dDIrfQC34E+Y7LMi6UjSQ2dQU= -github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261030222015-dea37afac9ad h1:DZt68NLXwaGYWXo1RUM0+JrvuXt/za/EyeHMGYFSix8= -github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261030222015-dea37afac9ad/go.mod h1:or3dd9bqacXWMjke/n81l/QxRaLwq2lq/hL4V2qtAZo= -github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261030222015-dea37afac9ad h1:gq8WatMLdoqsfwafxj2ihYSl0jIEw/d7ZMTGRWu9qpo= -github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261030222015-dea37afac9ad/go.mod h1:XsOFnO2MQdr37vM3ULa0Uwi8ZWptyVx7hRSUyIdaZ1Q= +github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261104040014-fab9ea74a58b h1:68rIZ6V5GPFyyIr0bhaCdTjiiap1+ObTiVUNEdwLuD4= +github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261104040014-fab9ea74a58b/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg= +github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261104040014-fab9ea74a58b h1:1VKjvHbrskPXeiM05+bXzncU4B/F08TbDiiHQlEoAOQ= +github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261104040014-fab9ea74a58b/go.mod h1:Cdk+10ebRh0tZA9ze2dDIrfQC34E+Y7LMi6UjSQ2dQU= +github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261104040014-fab9ea74a58b h1:AmncS7mb1ZstFYXq0uoQ2MWKUjOJN8kjwIl6N8YJV5o= +github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261104040014-fab9ea74a58b/go.mod h1:or3dd9bqacXWMjke/n81l/QxRaLwq2lq/hL4V2qtAZo= +github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261104040014-fab9ea74a58b h1:VqGBJ+ILqFnin9fmjAl2gOopWGUhu+zuURppLdWxeOA= +github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261104040014-fab9ea74a58b/go.mod h1:XsOFnO2MQdr37vM3ULa0Uwi8ZWptyVx7hRSUyIdaZ1Q= github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027154629-b57068f61a0f h1:llYwKOBeCB6kS4faxICrR5YYlwnhiV+0qXjuT3IwBd4= github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027154629-b57068f61a0f/go.mod h1:/SZab4/ll1nAKTucP3h30rpcXo56BHmJWLkvs2ayaMs= -github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261030222015-dea37afac9ad h1:ZuYv+9r6U08xLQ5c+AOSBzhkF7h3lqUj6kMa0S2OKKE= -github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261030222015-dea37afac9ad/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As= +github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261104040014-fab9ea74a58b h1:rGi94JiLGtCZUmJtwRaTkmvSHjDmIgJUAxTFSiVAW0I= +github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261104040014-fab9ea74a58b/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As= github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc h1:olDnVJ1A1Retn0z9A59YMwDRmDLgkV2y1lbl+HzNrj8= github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc/go.mod h1:VWPYY3FH4te7SzdN2xl9odNpWidJ1Grl/Hjs1SNoEF8= github.com/codefly-dev/core v0.19.17-0.20261029043538-2aa2e9aaf950 h1:U5HeCDUfftwVFbMYN1HGPUL+9HO+ske3nkExu1C2kYY=