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
This commit is contained in:
2026-11-04 13:45:09 +01:00
parent f16efbef4f
commit 0b2260d9fb
3 changed files with 22 additions and 1 deletions
@@ -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
@@ -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)
+4 -1
View File
@@ -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 <seat>.<verb>.