diff --git a/internal/updater/doc.go b/internal/updater/doc.go index f013643..f29cf71 100644 --- a/internal/updater/doc.go +++ b/internal/updater/doc.go @@ -11,20 +11,29 @@ // "the updater works against real infra": // // - BUILT + UNIT-VERIFIED (Go tests, WSL oracle): the topology, the PaperMC Fill -// v3 parser (its fixture is captured from the REAL live response shape on -// 2026-07-04 — a grounded contract test, not a self-referential one), the -// routing source, and the Runner's report-only composition. These prove the -// parse/plan/compose LOGIC and that the core is now wired to a caller. +// v3 parser, the routing source, and the Runner's report-only composition. The +// PaperMC fixture is captured from the live endpoint, and the live call was +// exercised out-of-band on 2026-07-04 (curl fill.papermc.io/v3/projects/velocity): +// the response shape matches the fixture and 3.4.0 is confirmed the newest stable +// (3.5.0-SNAPSHOT correctly filtered). These prove the parse/plan/compose LOGIC +// and that the core is now wired to a caller. // -// - WRITTEN, NOT LIVE-VERIFIED: the tests assert the required non-generic -// User-Agent is transmitted, but real fill.papermc.io network/TLS/UA-enforcement -// is not exercised here; the fixture proves today's shape, not its future -// stability. +// - NOT YET BUILT, but VERIFIABLE HERE (same technique as PaperMC — HTTP GET, JSON +// decode, tolerant Parse, prerelease filter, all httptest-testable): the GitHub +// Releases source. It is why 3 of the 4 components (felis-api, k3s, cloudflared) +// currently report "latest unknown" — RoutingSource returns errGitHubNotWired for +// them. This is the next VERIFIABLE slice, not integration remainder; until it +// exists the verifiable release-source work is only ~half done. // -// - REMAINING INTEGRATION (not built here): the GitHub Releases source (felis-api, -// k3s, cloudflared — RoutingSource returns errGitHubNotWired for them today), the -// concrete VersionGatherer (`k3s --version`, image-tag / jar inspection), the -// concrete Notifier (SMTP + in-game) and Applier (control-plane image bump, -// cloudflared swap), the `felis update` CLI + CronJob entry point, and the -// runtime append of the live Pinned Minecraft fleet. +// - CAVEATS on what the tests do NOT prove: they run against httptest, not the live +// host, so future upstream shape drift is not caught; and while Felis sends a +// descriptive User-Agent (PaperMC etiquette), upstream UA enforcement was not +// active on the project endpoint on 2026-07-04 (a bare UA got HTTP 200), so the UA +// is defensive, not load-bearing. +// +// - REMAINING INTEGRATION (pure I/O, no verifiable-here logic): the concrete +// VersionGatherer (`k3s --version`, image-tag / jar inspection), the concrete +// Notifier (SMTP + in-game) and Applier (control-plane image bump, cloudflared +// swap), the `felis update` CLI + CronJob entry point, and the runtime append of +// the live Pinned Minecraft fleet. package updater diff --git a/internal/updater/papermc.go b/internal/updater/papermc.go index c8c2236..dbd77cb 100644 --- a/internal/updater/papermc.go +++ b/internal/updater/papermc.go @@ -10,11 +10,15 @@ import ( "felis.lolicon.best/internal/updates" ) -// defaultUserAgent identifies Felis to the PaperMC Fill v3 API, which REQUIRES a -// non-generic User-Agent that names the software and carries a contact URL — a -// generic default (curl, wget, Go-http-client) is refused. It uses the public Felis -// module path as the contact and contains no operator-specific serving domain; a -// deployment can override it (paperMC.userAgent) with a SysAdmin contact from config. +// defaultUserAgent identifies Felis to the PaperMC Fill v3 API. PaperMC's API usage +// policy asks consumers to send a descriptive User-Agent that names the application +// and carries contact info, and warns that generic/library-default agents (curl, wget, +// Go-http-client) may be rate-limited or blocked. Enforcement was NOT active on the +// project-metadata endpoint as of 2026-07-04 — a bare UA still returned HTTP 200 — so +// sending this is documented etiquette and future-proofing, not an empirically +// confirmed hard gate. It uses the public Felis module path as the contact and +// contains no operator-specific serving domain; a deployment can override it +// (paperMC.userAgent) with a SysAdmin contact from config. const defaultUserAgent = "felis-updater/0.1 (+https://felis.lolicon.best)" // paperMC discovers the latest STABLE version of a PaperMC project (Velocity, for @@ -25,7 +29,7 @@ const defaultUserAgent = "felis-updater/0.1 (+https://felis.lolicon.best)" // network (see papermc_test.go, whose fixture is captured from the real v3 shape). type paperMC struct { baseURL string // e.g. "https://fill.papermc.io" - userAgent string // non-generic UA with a contact (Fill v3 requirement) + userAgent string // descriptive UA with a contact (PaperMC usage-policy etiquette) hc *http.Client } diff --git a/internal/updater/papermc_test.go b/internal/updater/papermc_test.go index 51d8e5e..fb48961 100644 --- a/internal/updater/papermc_test.go +++ b/internal/updater/papermc_test.go @@ -8,22 +8,32 @@ import ( "testing" ) -// velocityV3Fixture is the PaperMC Fill v3 GET /v3/projects/velocity body — its shape -// and version strings captured verbatim from the live API on 2026-07-04. Grounding -// the fixture in the real response is what makes this a contract test rather than a -// self-referential one: the newest overall version is a -SNAPSHOT (3.5.0-SNAPSHOT) -// while the newest stable release is 3.4.0, so the stable filter is exercised against -// real-world data, not an invented shape. (The v2 API this replaces now returns 410.) +// velocityV3Fixture is the PaperMC Fill v3 GET /v3/projects/velocity response body, +// captured from the live API on 2026-07-04 (keys and version strings exactly as +// returned; JSON whitespace normalized). Grounding the fixture in the real response is +// what makes this a contract test rather than a self-referential one: +// - the newest overall version is a -SNAPSHOT (3.5.0-SNAPSHOT) while the newest +// stable release is 3.4.0, so the stable filter runs against real data; and +// - the "versions" object groups the ENTIRE 3.x line under a single key "3.0.0" +// (not per-minor keys), so a parser that trusted the group key to bound the +// versions inside it would be wrong — proof the key-agnostic flatten is required. +// (The v2 API this replaces now returns HTTP 410.) const velocityV3Fixture = `{ "project": {"id": "velocity", "name": "Velocity"}, "versions": { - "3.5": ["3.5.0-SNAPSHOT"], - "3.4": ["3.4.0", "3.4.0-SNAPSHOT"], - "3.3": ["3.3.0-SNAPSHOT"], - "3.2": ["3.2.0-SNAPSHOT"], - "3.1": ["3.1.2-SNAPSHOT", "3.1.1", "3.1.1-SNAPSHOT", "3.1.0"], - "1.1": ["1.1.9"], - "1.0": ["1.0.10"] + "3.0.0": [ + "3.5.0-SNAPSHOT", + "3.4.0", + "3.4.0-SNAPSHOT", + "3.3.0-SNAPSHOT", + "3.2.0-SNAPSHOT", + "3.1.2-SNAPSHOT", + "3.1.1", + "3.1.1-SNAPSHOT", + "3.1.0" + ], + "1.1.0": ["1.1.9"], + "1.0.0": ["1.0.10"] } }` @@ -61,8 +71,12 @@ func TestPaperMCLatestStableFiltersSnapshots(t *testing.T) { } } -// TestPaperMCSendsNonGenericUserAgent proves Felis transmits the contact-carrying, -// non-generic User-Agent the Fill v3 API requires (a generic UA is refused upstream). +// TestPaperMCSendsNonGenericUserAgent proves Felis transmits a descriptive, +// contact-carrying User-Agent rather than a generic library default. PaperMC's API +// usage policy asks for this and reserves the right to block anonymous/generic agents; +// upstream enforcement was not active on the project endpoint as of 2026-07-04 (a bare +// UA got HTTP 200), so this verifies OUR compliance with the policy, not an upstream +// gate we depend on. func TestPaperMCSendsNonGenericUserAgent(t *testing.T) { var gotUA string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {