From 0b2260d9fb8e0e537d54266af62c7d8a571ab8af Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 13:45:09 +0100 Subject: [PATCH] CAT-G2: the forge's npm retention says removed on the seat's events for every version it deletes Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- modules/gitea/cmd/package-registry/retention.go | 9 +++++++++ modules/gitea/cmd/package-registry/retention_test.go | 9 +++++++++ modules/gitea/cmd/package-registry/seats.go | 5 ++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/gitea/cmd/package-registry/retention.go b/modules/gitea/cmd/package-registry/retention.go index 4f9915db6..77915f226 100644 --- a/modules/gitea/cmd/package-registry/retention.go +++ b/modules/gitea/cmd/package-registry/retention.go @@ -9,6 +9,8 @@ import ( "strings" "sync" "time" + + "gitea/internal/packageseats" ) // Retention for the package registry (novox/hq ADR 0251 ยง4, to-be 51): a version is kept when a @@ -418,12 +420,19 @@ func retention(ctx context.Context, f *Forge, only string, keep int, dryRun bool } del, cancel := context.WithTimeout(context.Background(), budget) defer cancel() + store := seatStore{forge: func() (*Forge, error) { return f, nil }} for _, c := range p.Candidates { + digest, _, _ := store.Held(packageseats.NpmSeat, c.Package, c.Version) if err := f.Delete(del, c.Package, c.Version); err != nil && !errors.Is(err, errNotFound) { p.Refused = append(p.Refused, fmt.Sprintf("%s@%s: %v", c.Package, c.Version, err)) continue } p.Deleted = append(p.Deleted, c.Package+"@"+c.Version) + // The seat says every removal (design 51 *Retention*), so the builder's entry turns removed. + o := packageseats.Outcome{Name: c.Package, Version: c.Version, Kind: packageseats.KindPackage, Digest: digest, Reason: packageseats.ReasonRetention} + if err := say(packageseats.NpmSeat, packageseats.EventRemoved, o); err != nil { + s.Said = append(s.Said, fmt.Sprintf("%s@%s is deleted but its removed could not be said: %v", c.Package, c.Version, err)) + } } s.Said = append(s.Said, fmt.Sprintf("deleted %d, refused %d, because: %s", len(p.Deleted), len(p.Refused), p.Why)) return p, nil diff --git a/modules/gitea/cmd/package-registry/retention_test.go b/modules/gitea/cmd/package-registry/retention_test.go index a4da4f361..71c81ed86 100644 --- a/modules/gitea/cmd/package-registry/retention_test.go +++ b/modules/gitea/cmd/package-registry/retention_test.go @@ -277,10 +277,19 @@ func TestARealRunWithoutWhyIsRefusedBeforeAnythingIsRead(t *testing.T) { func TestARealRunDeletesOnlyWhatNothingKeeps(t *testing.T) { f := aRegistry(t) + var removed []string + say = func(seat, event string, o packageseats.Outcome) error { + removed = append(removed, seat+" "+event+" "+o.Name+"@"+o.Version+" "+o.Reason) + return nil + } + t.Cleanup(func() { say = sayOn }) p, err := retention(context.Background(), f.serve(), "", 3, false, "the registry keeps what is named") if err != nil { t.Fatal(err) } + if sort.Strings(removed); len(removed) != 4 || removed[0] != "npm-package-registry removed @acme/sdk@0.1.1 retention" { + t.Errorf("said %v", removed) + } sort.Strings(f.deleted) if strings.Join(f.deleted, " ") != "@acme/sdk@0.1.1 @acme/sdk@0.1.2 @acme/sdk@0.2.0 @acme/sdk@0.2.1" { t.Fatalf("deleted %v", f.deleted) diff --git a/modules/gitea/cmd/package-registry/seats.go b/modules/gitea/cmd/package-registry/seats.go index 6de3b4437..6e5b9efcd 100644 --- a/modules/gitea/cmd/package-registry/seats.go +++ b/modules/gitea/cmd/package-registry/seats.go @@ -227,6 +227,9 @@ func outcomeBody(o packageseats.Outcome) map[string]any { "reference": o.Reference, "reason": o.Reason, "module": o.Module} } +// say says a seat's event; a test hears it instead. +var say = sayOn + // sayOn says an event on the seat's family, in the seat's contract's Outcome. func sayOn(seat, event string, o packageseats.Outcome) error { var body any @@ -265,7 +268,7 @@ func busHolder(forge func() (*Forge, error)) (*packageseats.Holder, error) { } return &packageseats.Holder{Callers: callers, Objects: parts.BusObjects(packageseats.IncomingObjects), Transfers: parts.BusTransfers[packageseats.Opening](packageseats.TransfersState), - Builder: delivery.BuilderVersions{Ask: stdio.Ask}, Store: seatStore{forge}, Say: sayOn, Finding: finding}, nil + Builder: delivery.BuilderVersions{Ask: stdio.Ask}, Store: seatStore{forge}, Say: say, Finding: finding}, nil } // SeatTools are both seats' verbs as tools ..