G-catalog-B 8: the retire removes only the teams the npm pair provisioner made

A person who later makes a team named packages or readers would have lost it at the next start. A team
counts as the provisioner's only in its organisation and when every member is one of its accounts (it
put its own accounts there alone, and they go with the first run), so a team made later stays.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 18:13:44 +01:00
parent e97167c58f
commit 2b8b47d862
2 changed files with 30 additions and 10 deletions
+14 -7
View File
@@ -19,10 +19,17 @@ import (
// seats came, made each consumer of npm-package-registry an account under the mesh's derived login
// (mesh_<node>_<consumer>) and put it in the organisation's team packages (read and write) or readers (read only).
// Reads of the packages are anonymous and every write goes through the seats, so both teams go, and each account of
// that form in no team but them goes with them. Nothing else is touched: a team of another name, an account of
// another form or one still in a team the module does not retire stays as it is.
// that form in no team but them goes with them. Only what the provisioner made goes: a team of those names in its
// organisation (RetiredOrg) is the provisioner's when every member of it is an account of the provisioner's form and
// it has one at least, since the provisioner put its own accounts in them alone and nobody else ever made one. A team
// a person makes later under such a name holds no such account (they are gone with the first run), so it stays, and
// so does a team of those names with a person in it or with no member. Nothing else is touched: a team of another
// name or organisation, an account of another form or one still in a team the module does not retire stays as it is.
var RetiredTeams = []string{"packages", "readers"}
// RetiredOrg is the organisation the npm pair provisioner made its teams in (its ORG, the npm registry's owner).
const RetiredOrg = "novox"
// retiredAccount is the login form the npm pair provisioner made its accounts under.
var retiredAccount = regexp.MustCompile(`^mesh_[a-z0-9-]+_[a-z0-9_]+$`)
@@ -37,6 +44,7 @@ func (s *Syncer) Retire(ctx context.Context, orgs []string) error {
team forgeTeam
}
var gone []retired
retiring := map[string]bool{} // org/team
for _, org := range orgs {
teams, err := s.teams(ctx, org)
if err != nil {
@@ -50,15 +58,14 @@ func (s *Syncer) Retire(ctx context.Context, orgs []string) error {
for _, m := range members {
teamsOf[m.Login] = append(teamsOf[m.Login], org+"/"+t.Name)
}
if slices.Contains(RetiredTeams, t.Name) {
made := len(members) > 0 && !slices.ContainsFunc(members, func(m forgeMember) bool { return !retiredAccount.MatchString(m.Login) })
if org == RetiredOrg && slices.Contains(RetiredTeams, t.Name) && made {
gone = append(gone, retired{org, t})
retiring[org+"/"+t.Name] = true
}
}
}
isRetired := func(orgTeam string) bool {
_, team, _ := strings.Cut(orgTeam, "/")
return slices.Contains(RetiredTeams, team)
}
isRetired := func(orgTeam string) bool { return retiring[orgTeam] }
var accounts []string
for login, in := range teamsOf {
if retiredAccount.MatchString(login) && slices.ContainsFunc(in, isRetired) && !slices.ContainsFunc(in, func(t string) bool { return !isRetired(t) }) {
+16 -3
View File
@@ -365,11 +365,15 @@ func TestReadValue(t *testing.T) {
// form still in another team, an account of another form and every other team stay; a second run changes nothing.
func TestTheNpmPairsTeamsAndAccountsAreRetired(t *testing.T) {
f := newFake([]string{"novox"}, "mesh_ace_mesh_build_node", "mesh_shanks_lab", "mesh_g14_agent", "mesh_novox_owner",
"mesh-packages", "agent-ace", "jschoubben")
"mesh-packages", "agent-ace", "jschoubben", "mesh_ace_agent")
f.owners = map[string]bool{"mesh_novox_owner": true}
f.add("novox", "packages", "none", nil, []string{"mesh_ace_mesh_build_node", "mesh_g14_agent", "mesh_novox_owner", "jschoubben"})
f.add("novox", "packages", "none", nil, []string{"mesh_ace_mesh_build_node", "mesh_g14_agent", "mesh_novox_owner"})
f.add("novox", "readers", "none", nil, []string{"mesh_ace_mesh_build_node", "mesh_shanks_lab"})
f.add("novox", "agents", "none", nil, []string{"mesh_g14_agent", "agent-ace"})
// Catalog note: a team of a retired name the provisioner did not make (a person in it, or in another
// organisation) stays.
f.orgs = append(f.orgs, "other")
f.add("other", "readers", "none", nil, []string{"mesh_ace_agent"})
f.add("novox", "mesh-packages", "none", nil, []string{"mesh-packages"})
s, said := syncer(t, f)
if err := s.Sync(context.Background(), Value{}); err != nil {
@@ -382,7 +386,7 @@ func TestTheNpmPairsTeamsAndAccountsAreRetired(t *testing.T) {
t.Errorf("a team the module did not retire was removed: %+v", f.teams)
}
for u, want := range map[string]bool{"mesh_ace_mesh_build_node": false, "mesh_shanks_lab": false, "mesh_g14_agent": true,
"mesh_novox_owner": true, "mesh-packages": true, "agent-ace": true, "jschoubben": true} {
"mesh_novox_owner": true, "mesh-packages": true, "agent-ace": true, "jschoubben": true, "mesh_ace_agent": true} {
if f.users[u] != want {
t.Errorf("account %s kept %v, want %v", u, f.users[u], want)
}
@@ -390,8 +394,17 @@ func TestTheNpmPairsTeamsAndAccountsAreRetired(t *testing.T) {
if !f.prohibited["mesh_novox_owner"] || len(f.prohibited) != 1 {
t.Errorf("prohibited %v, want mesh_novox_owner alone", f.prohibited)
}
if f.byName("other", "readers") == nil {
t.Errorf("a readers team in another organisation was removed")
}
// A team a person makes after the first run under a retired name, with persons in it or none, stays.
f.add("novox", "packages", "none", nil, []string{"jschoubben"})
f.add("novox", "readers", "none", nil, nil)
before := len(*said)
if err := s.Sync(context.Background(), Value{}); err != nil || len(*said) != before {
t.Errorf("a second run: %v, said %v; want nothing", err, (*said)[before:])
}
if f.byName("novox", "packages") == nil || f.byName("novox", "readers") == nil || !f.users["jschoubben"] {
t.Errorf("a team a person made later under a retired name was removed: %+v", f.teams)
}
}