Files
zooqueenandHanzo Dev 8a53464ca7 mirror: carry all 40 repos the script did, not the 24 the port kept
The Go port took 24 entries from a Python dict that held 40. Sixteen repos were
in NEITHER sync leg afterwards -- not pull mirrors, so nothing brought them in,
and absent from this table, so nothing pushed them forward -- which is the exact
shape the port was written to eliminate, reintroduced by the port itself.

The worst of them is hanzoai/ci. Every repo in three orgs imports it as
`hanzoai/ci/...@v1`, and a stale pipeline runs GREEN, so a caller cannot tell a
current pipeline from one months old. It had already sat 18 commits behind once
for exactly this reason. The rest: console and namespace (shipping services whose
inbound leg was never written down), the five hanzo-apps surfaces, hanzo-go/sdk
and hanzo-rs/sdk (published SDKs that `go get` and `cargo` resolve against the
forge copy -- 16 and 10 commits behind when they were found), hanzo-dart,
hanzokv, hanzozt, hanzo-docs/team and luxfi/aml.

Two guards, because a table that quietly loses rows is not a thing to notice
twice. One asserts every declared org is in the allowlist -- without it an entry
in a new org is refused and the only symptom is a smaller "N of M resolved". The
other asserts the count and names five of the entries that went missing.

The allowlist gains hanzo-inc, and the live run is why. Three declared repos --
auto, blog and visor -- have been transferred there, and the run refused all
three by name rather than pushing into an org nobody had listed. That is the
guard working: it is an allowlist so that an unknown org is a question, not an
assumption. hanzo-inc is ours (created 2026-07-24, all three private and pushed
within the week), so it is listed now and the refusal goes away.

Measured against live GitHub: 40 of 40 resolve, exit 0. EIGHT only resolve
because names are RESOLVED and never matched against a listing -- three moved to
hanzo-apps, three to hanzo-inc, esign became sign, kms-v1 became kms. A listing
would have reported eight absences, and an absent name raises nothing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 02:15:58 -07:00

125 lines
4.6 KiB
Go

package mirror
import (
"context"
"fmt"
"sort"
"strings"
)
// Owned is the set of orgs this may touch. An ALLOWLIST, not a denylist, and the
// difference is the whole point.
//
// The script this replaces carried a regex of forbidden names, which meant the
// forbidden names were written down in our source — the exact thing the rule
// against them forbids. It needed that regex because it swept entire orgs, so
// anything could turn up in a listing.
//
// This never enumerates an org: it acts only on the explicitly declared table. The
// one residual risk is a declared repo being TRANSFERRED into an org we do not own,
// which Plan checks after resolution. Naming what we own answers that without
// naming anything we do not, and fails closed on an org nobody thought about —
// including ones that do not exist yet.
// Owned lists the orgs whose repos this program may touch. An ALLOWLIST, not a
// denylist: it refuses by default, so an org nobody has thought about yet is
// refused rather than swept, and a lookalike that merely contains one of our
// names is refused too.
//
// It is deliberately a SUPERSET of the orgs in repos.json. A declared repo can be
// renamed or transferred, and `owned` guards the name GitHub resolves it to — so
// an org with no entry today is still the org a repo may land in tomorrow. Three
// declared repos had already moved into hanzo-apps that way.
var Owned = map[string]bool{
"hanzo-apps": true,
"hanzo-dart": true,
"hanzo-docs": true,
"hanzo-go": true,
"hanzo-js": true,
"hanzo-ml": true,
"hanzo-rs": true,
"hanzoai": true,
"hanzo-inc": true,
"hanzobot": true,
"hanzodao": true,
"hanzoid": true,
"hanzokv": true,
"hanzozt": true,
"luxfi": true,
}
// owned reports whether a full name "org/repo" sits in an org we own.
func owned(full string) bool {
org, _, ok := strings.Cut(full, "/")
return ok && Owned[org]
}
// Entry is one declared repository: where it lives here, and which way it syncs.
//
// This is data, not code. Adding a repo used to be a source edit to a list literal
// inside the script; it is now a line of config, which is what "declared state"
// has to mean if the word is doing any work.
type Entry struct {
Org string `json:"org" yaml:"org"`
Name string `json:"name" yaml:"name"`
Direction Direction `json:"direction" yaml:"direction"`
}
func (e Entry) String() string { return e.Org + "/" + e.Name }
// Planned is one entry resolved against the remote: what we declared, and what the
// remote says that name is today.
type Planned struct {
Entry
// GitHub is the repo the entry resolves to NOW. Its full name differs from the
// entry whenever GitHub has renamed or moved the repo, which is the case the
// listing-based predecessor could not see at all.
GitHub *Repo
// Moved is true when the entry's name is no longer the remote's name.
Moved bool
}
// Plan resolves every declared entry against GitHub, in the order it will be acted
// on: smallest first, so a budget spent on one enormous repo cannot starve every
// small one behind it.
//
// Two entries resolving to ONE GitHub repo is a config bug, not a merge: both would
// push-mirror to the same remote and overwrite each other. The first wins and the
// collision is reported, because silently dropping one is how a repo stops syncing
// without anyone being told.
func Plan(ctx context.Context, gh *Client, entries []Entry) ([]Planned, []error) {
var out []Planned
var errs []error
claimed := map[string]Entry{}
for _, e := range entries {
if !Owned[e.Org] {
errs = append(errs, fmt.Errorf("%s: org %q is not one we own", e, e.Org))
continue
}
if e.Direction != Native && e.Direction != Mirror {
errs = append(errs, fmt.Errorf("%s: direction %q is neither %q nor %q", e, e.Direction, Native, Mirror))
continue
}
r, err := gh.Resolve(ctx, e.Org, e.Name)
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", e, err))
continue
}
// A transfer can move a repo anywhere, including out of our estate entirely.
// Refusing loudly beats syncing a repo that is no longer ours.
if !owned(r.FullName) {
errs = append(errs, fmt.Errorf("%s now resolves to %s, outside the orgs we own — refusing", e, r.FullName))
continue
}
if prev, dup := claimed[r.FullName]; dup {
errs = append(errs, fmt.Errorf("%s and %s both resolve to %s — one repo, one entry", e, prev, r.FullName))
continue
}
claimed[r.FullName] = e
out = append(out, Planned{Entry: e, GitHub: r, Moved: !strings.EqualFold(r.FullName, e.String())})
}
sort.SliceStable(out, func(i, j int) bool { return out[i].GitHub.Size < out[j].GitHub.Size })
return out, errs
}