From 4580d80a32d9c8f6bb43fa3692d947879d6aafe8 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 19 Oct 2026 21:50:03 +0200 Subject: [PATCH] Add the forge kind open: every open pull request into trunk, so the indexer can drop the rest (issue 1279, ADR 0443) --- go/delivery/source.go | 54 +++++++++++++++++++++++++++++++++----- go/delivery/source_test.go | 36 +++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/go/delivery/source.go b/go/delivery/source.go index f4328ff..0ec2568 100644 --- a/go/delivery/source.go +++ b/go/delivery/source.go @@ -41,8 +41,19 @@ const ( ForgePull = "pull" ForgeMerged = "merged" ForgeRefused = "refused" + ForgeOpen = "open" ) +// OpenPull names an open pull request into the trunk: the repository as `/` and the number. +type OpenPull struct { + Repository string `json:"repository"` + Number int `json:"number"` +} + +// OpenEvent is the name of an open message: `forge.open`. It names no repository: it says every open pull request of +// every repository the forge bridge reads (novox/hq ADR 0443 ยง5). +func OpenEvent() string { return "forge." + ForgeOpen } + // The actions of a pull message. const ( PullOpened = "opened" @@ -106,18 +117,21 @@ func checkRepository(repository string) error { func checkForgeKind(kind string) error { switch kind { - case ForgePull, ForgeMerged, ForgeRefused: + case ForgePull, ForgeMerged, ForgeRefused, ForgeOpen: return nil } - return fmt.Errorf("delivery: the forge kind %q is not pull, merged or refused", kind) + return fmt.Errorf("delivery: the forge kind %q is not pull, merged, refused or open", kind) } // ForgeEvent is the name of a forge message of the given kind: `forge...`. func ForgeEvent(repository, kind string) (string, error) { - if err := checkRepository(repository); err != nil { + if err := checkForgeKind(kind); err != nil { return "", err } - if err := checkForgeKind(kind); err != nil { + if kind == ForgeOpen { + return "", fmt.Errorf("delivery: an open message names no repository, its event is %s", OpenEvent()) + } + if err := checkRepository(repository); err != nil { return "", err } owner, name, _ := strings.Cut(repository, "/") @@ -126,6 +140,9 @@ func ForgeEvent(repository, kind string) (string, error) { // ParseForgeEvent is the inverse of ForgeEvent. func ParseForgeEvent(event string) (repository, kind string, err error) { + if event == OpenEvent() { + return "", ForgeOpen, nil + } parts := strings.Split(event, ".") if len(parts) != 4 || parts[0] != "forge" { return "", "", fmt.Errorf("delivery: %q is not a forge event, forge...", event) @@ -259,13 +276,20 @@ type Forge struct { Root string `json:"root,omitempty"` Act string `json:"act,omitempty"` Words string `json:"words,omitempty"` + Open []OpenPull `json:"open,omitempty"` } func (f Forge) check() error { - if err := checkRepository(f.Repository); err != nil { + if err := checkForgeKind(f.Kind); err != nil { return err } - if err := checkForgeKind(f.Kind); err != nil { + if f.Kind == ForgeOpen { + return f.checkOpen() + } + if len(f.Open) > 0 { + return fmt.Errorf("delivery: only an open message names open pull requests") + } + if err := checkRepository(f.Repository); err != nil { return err } switch f.Kind { @@ -322,6 +346,24 @@ func (f Forge) check() error { return nil } +// checkOpen refuses an open message that names a repository, a branch, a head or anything of the other kinds, or +// an open pull request that is not `/` and a number. +func (f Forge) checkOpen() error { + if f.Repository != "" || f.Branch != "" || f.Number != 0 || f.Head != "" || f.Action != "" || len(f.Dirs) > 0 || f.DirsKnown || + f.Merge != "" || len(f.Trees) > 0 || f.Root != "" || f.Act != "" || f.Words != "" { + return fmt.Errorf("delivery: an open message carries only the open pull requests it names") + } + for _, p := range f.Open { + if err := checkRepository(p.Repository); err != nil { + return err + } + if p.Number <= 0 { + return fmt.Errorf("delivery: the open pull request of %s has no number", p.Repository) + } + } + return nil +} + // EncodeSource is s as JSON at Version; it refuses a source with no repository or an empty head. func EncodeSource(s Source) ([]byte, error) { if err := s.check(); err != nil { diff --git a/go/delivery/source_test.go b/go/delivery/source_test.go index 029793b..8969545 100644 --- a/go/delivery/source_test.go +++ b/go/delivery/source_test.go @@ -65,6 +65,41 @@ func TestSourceKeys(t *testing.T) { // 1200/AC1 } } +func TestForgeOpenKind(t *testing.T) { // 1279/AC5 + ev := OpenEvent() + if ev != "forge.open" { + t.Fatalf("open event = %q", ev) + } + repo, kind, err := ParseForgeEvent(ev) + if err != nil || repo != "" || kind != ForgeOpen { + t.Fatalf("parse open event = %q %q %v", repo, kind, err) + } + // An empty list is a message too: nothing is open. + data, err := EncodeForge(Forge{Kind: ForgeOpen, Open: []OpenPull{}}) + if err != nil { + t.Fatalf("empty open: %v", err) + } + if f, err := DecodeForge(data); err != nil || f.Kind != ForgeOpen || len(f.Open) != 0 { + t.Errorf("empty open round trip: %+v %v", f, err) + } + for name, f := range map[string]Forge{ + "a repository": {Kind: ForgeOpen, Repository: "a/b", Open: []OpenPull{{Repository: "a/b", Number: 1}}}, + "a bad entry": {Kind: ForgeOpen, Open: []OpenPull{{Repository: "a", Number: 1}}}, + "a zero number": {Kind: ForgeOpen, Open: []OpenPull{{Repository: "a/b"}}}, + "a head": {Kind: ForgeOpen, Head: "x", Open: []OpenPull{{Repository: "a/b", Number: 1}}}, + } { + if _, err := EncodeForge(f); err == nil { + t.Errorf("open with %s accepted", name) + } + } + if _, err := EncodeForge(Forge{Kind: ForgePull, Repository: "a/b", Number: 1, Head: "h", Action: "updated", Open: []OpenPull{{Repository: "a/b", Number: 1}}}); err == nil { + t.Error("a pull message naming open pull requests accepted") + } + if _, err := ForgeEvent("a/b", ForgeOpen); err == nil { + t.Error("an open event for a repository accepted") + } +} + func TestHeadsHash(t *testing.T) { // 1200/AC2 a := Head{Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa"} b := Head{Repository: "novox/mesh-control", Number: 7, Head: "bbbb"} @@ -113,6 +148,7 @@ func sampleForges() []Forge { {Kind: ForgeMerged, Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa", Merge: "cccc", Trees: map[string]string{"modules/a": "t1"}, Root: "t0"}, {Kind: ForgeRefused, Repository: "novox/mesh-sdk", Act: "novox/mesh-sdk.36.aaaa.view", Words: "403 forbidden"}, + {Kind: ForgeOpen, Open: []OpenPull{{Repository: "novox/mesh-sdk", Number: 36}, {Repository: "novox/mesh-control", Number: 7}}}, } }