Trust a trunk whose whitelist adds the operator's own login, never an agent's (ADR 0534 §2, §6)
The gitea module keeps the operator's login beside mesh-tools on every main so the operator can merge by hand; an agent's account there is still refused.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -206,8 +207,8 @@ type forgeFacts struct {
|
||||
// ID is the forge's own id of the repository: what tells it from one deleted and made again by its name.
|
||||
ID int64
|
||||
// Guarded is whether the branch is protected as a module's trunk must be: no direct push, a merge whitelist naming
|
||||
// the tools' account alone, and no administrator merging past the rule (novox/hq ADR 0534 §6; no required status,
|
||||
// which the forge no longer asks, §1). Why says what is missing when it is not.
|
||||
// the tools' account and no agent's account, and no administrator merging past the rule (novox/hq ADR 0534 §6; no
|
||||
// required status, which the forge no longer asks, §1). Why says what is missing when it is not.
|
||||
Guarded bool
|
||||
Why string
|
||||
}
|
||||
@@ -362,10 +363,15 @@ func askTheForgeOnce(ctx context.Context, owner, repo, branch string) (forgeFact
|
||||
return facts, nil
|
||||
}
|
||||
|
||||
// forgeToolsAccount is the forge tools' account, the only one main's merge whitelist names (novox/hq ADR 0398 §2, the
|
||||
// gitea module's tools-account.ts TOOLS_ACCOUNT): only mesh-delivery, through the merge tool, lands a commit on a trunk.
|
||||
// forgeToolsAccount is the forge tools' account, which main's merge whitelist names for mesh-delivery (novox/hq ADR 0398
|
||||
// §2, the gitea module's tools-account.ts TOOLS_ACCOUNT); beside it the whitelist names only people's own logins, the
|
||||
// operator's (ADR 0534 §2), never an agent's account.
|
||||
const forgeToolsAccount = "mesh-tools"
|
||||
|
||||
// isAgentAccount is whether a forge login is a node's agent's account, agent-<node> (novox/hq ADR 0398 §1): one that
|
||||
// may never merge into a module's trunk by itself.
|
||||
func isAgentAccount(login string) bool { return strings.HasPrefix(login, "agent-") }
|
||||
|
||||
// protectionRule is a branch protection rule as the forge's tool summarises it.
|
||||
type protectionRule struct {
|
||||
Rule string `json:"rule"`
|
||||
@@ -379,9 +385,10 @@ type protectionRule struct {
|
||||
// judgedRule judges the one rule the forge applies to a branch, as the forge picks it: the rule named for the
|
||||
// branch, else the first glob rule, in the forge's order, that covers it (gitea's first matching rule). Never a
|
||||
// later rule that happens to be stronger: the forge does not read it. A trunk is trusted for who can land a commit on
|
||||
// it, not for a status it requires (novox/hq ADR 0534 §6): no push, only the tools' account merges, and no administrator
|
||||
// merging past the rule; then only mesh-delivery lands a commit there, through the merge tool, on a passing mesh/check
|
||||
// and the operator's Approve (ADR 0534 §4).
|
||||
// it, not for a status it requires (novox/hq ADR 0534 §6): no push, a merge whitelist naming the tools' account and
|
||||
// beside it only people's own logins, never an agent's, and no administrator merging past the rule; then a commit lands
|
||||
// there through mesh-delivery, on a passing mesh/check and the operator's Approve (ADR 0534 §4), or by the operator's
|
||||
// own hand at the forge (§2).
|
||||
func judgedRule(rules []protectionRule, branch string) forgeFacts {
|
||||
switch r := appliedRule(rules, branch); {
|
||||
case r == nil:
|
||||
@@ -390,9 +397,12 @@ func judgedRule(rules []protectionRule, branch string) forgeFacts {
|
||||
return forgeFacts{Why: fmt.Sprintf("the rule %s lets a person push to %s directly", r.Rule, branch)}
|
||||
case r.MergeWhitelist == nil || len(r.MergeWhitelist) == 0:
|
||||
return forgeFacts{Why: fmt.Sprintf("the rule %s keeps no merge whitelist naming %s alone for %s", r.Rule, forgeToolsAccount, branch)}
|
||||
case len(r.MergeWhitelist) != 1 || r.MergeWhitelist[0] != forgeToolsAccount:
|
||||
return forgeFacts{Why: fmt.Sprintf("the rule %s lets %s merge into %s, not %s alone", r.Rule,
|
||||
case !slices.Contains(r.MergeWhitelist, forgeToolsAccount):
|
||||
return forgeFacts{Why: fmt.Sprintf("the rule %s lets %s merge into %s, and not %s", r.Rule,
|
||||
strings.Join(r.MergeWhitelist, ", "), branch, forgeToolsAccount)}
|
||||
case slices.ContainsFunc(r.MergeWhitelist, isAgentAccount):
|
||||
return forgeFacts{Why: fmt.Sprintf("the rule %s lets an agent's account merge into %s: %s", r.Rule, branch,
|
||||
strings.Join(r.MergeWhitelist, ", "))}
|
||||
case r.AdminMayOverride:
|
||||
return forgeFacts{Why: fmt.Sprintf("the rule %s lets an administrator merge into %s past it", r.Rule, branch)}
|
||||
}
|
||||
|
||||
@@ -120,9 +120,10 @@ func TestTheRuleJudgedIsTheOneTheForgeApplies(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// **A module's trunk is trusted when it takes no push, only the tools' account merges into it, and no administrator
|
||||
// merges past its rule** (novox/hq ADR 0534 §6): the forge requires no status to merge (§1), so a rule requiring none is
|
||||
// trusted; one with no merge whitelist, another name in it, a push allowed or an administrator's override is not.
|
||||
// **A module's trunk is trusted when it takes no push, only the tools' account and people's own logins merge into it,
|
||||
// and no administrator merges past its rule** (novox/hq ADR 0534 §2, §6): the forge requires no status to merge (§1), so
|
||||
// a rule requiring none is trusted; one with no merge whitelist, no tools' account in it, an agent's account in it, a
|
||||
// push allowed or an administrator's override is not.
|
||||
func TestATrunkIsTrustedForItsMergersNotForAStatus(t *testing.T) {
|
||||
tools := []string{forgeToolsAccount}
|
||||
for _, c := range []struct {
|
||||
@@ -135,8 +136,10 @@ func TestATrunkIsTrustedForItsMergersNotForAStatus(t *testing.T) {
|
||||
{"a status required as well", protectionRule{Rule: "main", MergeWhitelist: tools, RequiredStatuses: []string{"mesh/check"}}, true, ""},
|
||||
{"no merge whitelist", protectionRule{Rule: "main"}, false, "merge whitelist"},
|
||||
{"an empty merge whitelist", protectionRule{Rule: "main", MergeWhitelist: []string{}}, false, "merge whitelist"},
|
||||
{"another name in the whitelist", protectionRule{Rule: "main", MergeWhitelist: []string{forgeToolsAccount, "agent-ace"}}, false, "agent-ace"},
|
||||
{"another name alone", protectionRule{Rule: "main", MergeWhitelist: []string{"agent-ace"}}, false, "agent-ace"},
|
||||
{"the operator's own login beside the tools", protectionRule{Rule: "main", MergeWhitelist: []string{forgeToolsAccount, "jschoubben"}}, true, ""},
|
||||
{"an agent's account in the whitelist", protectionRule{Rule: "main", MergeWhitelist: []string{forgeToolsAccount, "agent-ace"}}, false, "agent-ace"},
|
||||
{"an agent's account alone", protectionRule{Rule: "main", MergeWhitelist: []string{"agent-ace"}}, false, "agent-ace"},
|
||||
{"a person's login without the tools", protectionRule{Rule: "main", MergeWhitelist: []string{"jschoubben"}}, false, forgeToolsAccount},
|
||||
{"a push allowed", protectionRule{Rule: "main", Push: true, MergeWhitelist: tools}, false, "push"},
|
||||
{"an administrator's override", protectionRule{Rule: "main", MergeWhitelist: tools, AdminMayOverride: true}, false, "administrator"},
|
||||
} {
|
||||
|
||||
Reference in New Issue
Block a user