Nine files had drifted from gofmt and nothing checked; nine staticcheck
findings were live (three dead symbols, capitalization, a redundant
Sprintf, two literal-to-conversion sites, a nil test context). Fix all
of them and make CI fail on unformatted Go so this cannot re-drift.
An out-of-band curl of the live Fill v3 endpoint contradicted two claims the
previous commit shipped and surfaced a mis-tiering:
- User-Agent is NOT enforced: fill.papermc.io/v3/projects/velocity returned
HTTP 200 to a bare curl UA. The comments claimed a generic UA "is refused"
and the API "REQUIRES" a contact UA. Reword to what is true — PaperMC's usage
policy asks for a descriptive UA and may block generic ones, but sending it is
etiquette/defensive here, not a gate Felis depends on.
- The test fixture's shape was invented, not captured: the real "versions"
object groups the entire 3.x line under a single key "3.0.0", not the
per-minor keys the fixture used. Replace it with the real body (keys and
version strings as returned). The key-agnostic parser already produced the
right answer, and an independent max-stable check confirms 3.4.0.
- Re-tier doc.go: the GitHub Releases source is verifiable-here (the same
httptest-testable shape as PaperMC), not integration remainder. It is why
3 of 4 components report "latest unknown" today and is the next verifiable
slice — the release-source work is only ~half done until it exists.
No production logic changed. WSL oracle: build + vet clean, internal/updater
10/10, full tree go test RC=0 (19 ok, 0 fail).
internal/updates is a pure, fakes-tested decision core with no production caller,
so nothing could produce its "版本号状态" report. Add internal/updater as that caller:
- topology: the fixed platform components and their user-set policies (felis-api
and cloudflared Scheduled+manageable; k3s Notify, high-blast-radius single node;
velocity Notify, off-cluster and unmanageable). Minecraft is pinned by ABSENCE,
never force-tracked here, appended from the live fleet at runtime.
- PaperMC Fill v3 release source: the v2 API (api.papermc.io) was retired
2026-07-01 and returns HTTP 410, so this targets fill.papermc.io/v3, sends the
required non-generic User-Agent, and returns the newest STABLE version, filtering
the -SNAPSHOT/rc prereleases the plan would otherwise suppress. Its test fixture
is captured from the live v3 response shape (2026-07-04).
- RoutingSource: the single ReleaseSource updates.Run requires, dispatching
velocity to PaperMC and returning errGitHubNotWired for the GitHub-backed
components so they degrade to "latest unknown" honestly, never a fabricated one.
- Runner: gather current versions (seam) -> assemble Components -> updates.Run ->
Report; report-only when notifier and applier are nil.
Verification boundary: the parse/plan/compose logic is unit-tested (httptest +
fakes, fixture grounded in the live v3 shape). Live network/TLS/User-Agent
enforcement, the GitHub Releases source, the concrete version gatherer, the
notifier and applier, and the felis update CLI/CronJob remain integration work,
enumerated in doc.go.