From b46da94f5003f874a7a2f4dcaef63bc565184a03 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 22:46:36 +0100 Subject: [PATCH] indexer, mesh-catalog contract: a branch named candidate or entry is refused as reserved branch name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §704 (branch named candidate or entry) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- contracts/go/seats/meshcatalog/contract.go | 9 ++++--- .../go/seats/meshcatalog/contract_test.go | 2 ++ .../seats/mesh-catalog/fixtures/verbs.json | 9 +++++++ contracts/seats/mesh-catalog/seat.json | 1 + contracts/ts/seats/meshcatalog/index.d.ts | 2 +- .../internal/index/registrations.go | 25 +++++++++++-------- .../internal/index/registrations_test.go | 11 +++----- 7 files changed, 36 insertions(+), 23 deletions(-) diff --git a/contracts/go/seats/meshcatalog/contract.go b/contracts/go/seats/meshcatalog/contract.go index f5925f4a..9b77bf06 100644 --- a/contracts/go/seats/meshcatalog/contract.go +++ b/contracts/go/seats/meshcatalog/contract.go @@ -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. diff --git a/contracts/go/seats/meshcatalog/contract_test.go b/contracts/go/seats/meshcatalog/contract_test.go index 570a3914..37c240b1 100644 --- a/contracts/go/seats/meshcatalog/contract_test.go +++ b/contracts/go/seats/meshcatalog/contract_test.go @@ -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\"}", diff --git a/contracts/seats/mesh-catalog/fixtures/verbs.json b/contracts/seats/mesh-catalog/fixtures/verbs.json index 2d712038..3c0c7d5a 100644 --- a/contracts/seats/mesh-catalog/fixtures/verbs.json +++ b/contracts/seats/mesh-catalog/fixtures/verbs.json @@ -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", diff --git a/contracts/seats/mesh-catalog/seat.json b/contracts/seats/mesh-catalog/seat.json index 0c4e1c88..7eaf9bc7 100644 --- a/contracts/seats/mesh-catalog/seat.json +++ b/contracts/seats/mesh-catalog/seat.json @@ -2424,6 +2424,7 @@ "type": "string", "enum": [ "unprotected branch", + "reserved branch name", "review required", "unknown module", "nothing moves" diff --git a/contracts/ts/seats/meshcatalog/index.d.ts b/contracts/ts/seats/meshcatalog/index.d.ts index 4f20e01d..795018b6 100644 --- a/contracts/ts/seats/meshcatalog/index.d.ts +++ b/contracts/ts/seats/meshcatalog/index.d.ts @@ -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; } diff --git a/modules/mesh-delivery-indexer/internal/index/registrations.go b/modules/mesh-delivery-indexer/internal/index/registrations.go index f4f4dc40..3c0f3eae 100644 --- a/modules/mesh-delivery-indexer/internal/index/registrations.go +++ b/modules/mesh-delivery-indexer/internal/index/registrations.go @@ -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 . 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 { diff --git a/modules/mesh-delivery-indexer/internal/index/registrations_test.go b/modules/mesh-delivery-indexer/internal/index/registrations_test.go index 4b0b91a0..64a20d0a 100644 --- a/modules/mesh-delivery-indexer/internal/index/registrations_test.go +++ b/modules/mesh-delivery-indexer/internal/index/registrations_test.go @@ -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 {