indexer, mesh-catalog contract: a branch named candidate or entry is refused as reserved branch name

Builds: design 47 G §704 (branch named candidate or entry)

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-11 22:46:36 +01:00
parent 0dec003660
commit b46da94f50
7 changed files with 36 additions and 23 deletions
+5 -4
View File
@@ -948,10 +948,11 @@ type RegistrationAsked struct {
// The values of RegistrationAsked's refused.
const (
RegistrationAskedRefusedUnprotectedBranch = "unprotected branch"
RegistrationAskedRefusedReviewRequired = "review required"
RegistrationAskedRefusedUnknownModule = "unknown module"
RegistrationAskedRefusedNothingMoves = "nothing moves"
RegistrationAskedRefusedUnprotectedBranch = "unprotected branch"
RegistrationAskedRefusedReservedBranchName = "reserved branch name"
RegistrationAskedRefusedReviewRequired = "review required"
RegistrationAskedRefusedUnknownModule = "unknown module"
RegistrationAskedRefusedNothingMoves = "nothing moves"
)
// StepStatuses is the status of each pipeline step (design 47 *Pipeline steps and the policy*); a step left out keeps the module's.
@@ -151,6 +151,8 @@ func TestFixtures(t *testing.T) {
decode: func(b []byte) (any, error) { var v RegisterInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "register refused for an unprotected branch", subject: "", given: "{\"module\":\"nats\",\"refused\":\"unprotected branch\",\"words\":\"the branch beta of novox/mesh-catalog admits a direct push\"}", wire: "{\"module\":\"nats\",\"refused\":\"unprotected branch\",\"words\":\"the branch beta of novox/mesh-catalog admits a direct push\"}",
decode: func(b []byte) (any, error) { var v RegisterAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "register refused for a reserved branch name", subject: "", given: "{\"module\":\"nats\",\"refused\":\"reserved branch name\",\"words\":\"the branch candidate of novox/mesh-catalog is a name the catalog keeps for its own revisions\"}", wire: "{\"module\":\"nats\",\"refused\":\"reserved branch name\",\"words\":\"the branch candidate of novox/mesh-catalog is a name the catalog keeps for its own revisions\"}",
decode: func(b []byte) (any, error) { var v RegisterAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "policy moves one artifact's unit step", subject: "", given: "{\"artifact\":\"program\",\"module\":\"nats\",\"steps\":{\"unit\":\"bypass\"},\"why\":\"its tests need hardware\"}", wire: "{\"artifact\":\"program\",\"module\":\"nats\",\"steps\":{\"unit\":\"bypass\"},\"why\":\"its tests need hardware\"}",
decode: func(b []byte) (any, error) { var v PolicyInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "policy asked, naming the moved status", subject: "", given: "{\"ask\":\"ask-m2kx9a1b3c4d5e\",\"lines\":[\"nats:program unit: required -\\u003e bypass\"],\"module\":\"nats\"}", wire: "{\"ask\":\"ask-m2kx9a1b3c4d5e\",\"lines\":[\"nats:program unit: required -\\u003e bypass\"],\"module\":\"nats\"}",
@@ -296,6 +296,15 @@
"words": "the branch beta of novox/mesh-catalog admits a direct push"
}
},
{
"name": "register refused for a reserved branch name",
"of": "verb:register.answer",
"given": {
"module": "nats",
"refused": "reserved branch name",
"words": "the branch candidate of novox/mesh-catalog is a name the catalog keeps for its own revisions"
}
},
{
"name": "policy moves one artifact's unit step",
"of": "verb:policy.input",
+1
View File
@@ -2424,6 +2424,7 @@
"type": "string",
"enum": [
"unprotected branch",
"reserved branch name",
"review required",
"unknown module",
"nothing moves"
+1 -1
View File
@@ -448,7 +448,7 @@ export interface RegistrationAsked {
"lines"?: string[];
"module": string;
/** why no ask was raised */
"refused"?: "unprotected branch" | "review required" | "unknown module" | "nothing moves";
"refused"?: "unprotected branch" | "reserved branch name" | "review required" | "unknown module" | "nothing moves";
/** the refusal in words, naming the branch, the step or the module */
"words"?: string;
}
@@ -205,7 +205,8 @@ func refused(module, reason, words string) meshcatalog.RegistrationAsked {
return meshcatalog.RegistrationAsked{Module: module, Refused: reason, Words: words}
}
// forbiddenBranch refuses the two names the catalog's keys use (§343).
// forbiddenBranch refuses the two names the catalog's keys use (§343); the refusal's reason is `reserved branch name`
// (design 47 G §704).
func forbiddenBranch(b string) string {
if b == "candidate" || b == "entry" {
return fmt.Sprintf("the branch %q cannot be watched: the catalog keeps its revisions under <branch>.<revision> beside the key forms candidate and entry", b)
@@ -218,24 +219,26 @@ func reviewRequired(s meshcatalog.StepStatuses) bool {
return s.Review == meshcatalog.StepStatusesApprovalRequired
}
// protect refuses a watched branch the forge does not protect (§5), naming it.
func (r *Registry) protect(repository string, branches []string) (string, error) {
// protect refuses a watched branch that is named as the catalog's keys are (reason `reserved branch name`, §704) or that
// the forge does not protect (reason `unprotected branch`, §5), naming it; both empty when every branch is accepted.
func (r *Registry) protect(repository string, branches []string) (reason, words string, err error) {
for _, b := range branches {
if w := forbiddenBranch(b); w != "" {
return w, nil
return meshcatalog.RegistrationAskedRefusedReservedBranchName, w, nil
}
a, err := r.Branch(repository, b)
if err != nil {
return "", fmt.Errorf("the seat git does not answer for %s %s: %w", repository, b, err)
return "", "", fmt.Errorf("the seat git does not answer for %s %s: %w", repository, b, err)
}
if a.Error != "" {
return "", fmt.Errorf("the seat git cannot read %s %s: %s", repository, b, a.Error)
return "", "", fmt.Errorf("the seat git cannot read %s %s: %s", repository, b, a.Error)
}
if !a.Protected {
return fmt.Sprintf("the branch %s of %s is not protected: no rule refuses a direct push and admits only merges", b, repository), nil
return meshcatalog.RegistrationAskedRefusedUnprotectedBranch,
fmt.Sprintf("the branch %s of %s is not protected: no rule refuses a direct push and admits only merges", b, repository), nil
}
}
return "", nil
return "", "", nil
}
// stepLines names each step status a policy moves, one line each.
@@ -467,12 +470,12 @@ func (r *Registry) Propose(ctx context.Context, verb, caller string, change json
branches = []string{branch}
}
if len(branches) > 0 {
words, err := r.protect(next.Origin, branches)
reason, words, err := r.protect(next.Origin, branches)
if err != nil {
return meshcatalog.RegistrationAsked{}, err
}
if words != "" {
return refused(module, meshcatalog.RegistrationAskedRefusedUnprotectedBranch, words), nil
if reason != "" {
return refused(module, reason, words), nil
}
}
if len(lines) == 0 {
@@ -101,15 +101,12 @@ func TestRegistrationsAreWrittenOnTheirWarrantOnly(t *testing.T) {
}{
{"review", meshcatalog.RegistrationAskedRefusedReviewRequired, reg("a", meshcatalog.StepStatuses{Review: "required"}, "main")},
{"unprotected", meshcatalog.RegistrationAskedRefusedUnprotectedBranch, reg("a", meshcatalog.StepStatuses{}, "beta")},
{"candidate", meshcatalog.RegistrationAskedRefusedUnprotectedBranch, reg("a", meshcatalog.StepStatuses{}, "candidate")},
{"candidate", meshcatalog.RegistrationAskedRefusedReservedBranchName, reg("a", meshcatalog.StepStatuses{}, "candidate")},
{"entry", meshcatalog.RegistrationAskedRefusedReservedBranchName, reg("a", meshcatalog.StepStatuses{}, "entry")},
} {
if c.name == "candidate" {
// the name is refused whatever the forge says of it
c.why = meshcatalog.RegistrationAskedRefusedUnprotectedBranch
}
got, err := g.r.Propose(ctx, "register", "session@node", c.change)
if err != nil || got.Refused == "" || got.Ask != "" || got.Words == "" {
t.Fatalf("%s: answer %+v, %v: want a refusal naming why and no ask", c.name, got, err)
if err != nil || got.Refused == "" || got.Ask != "" || got.Words == "" || got.Refused != c.why {
t.Fatalf("%s: answer %+v, %v: want a refusal for %q naming why and no ask (design 47 G §704)", c.name, got, err, c.why)
}
}
if len(g.asks) != 0 {