carlos / platform Public

console: feed publish rules (required type, monotonic per origin) (geoip spec §10.3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 40c8f3a75dfb1bd73cdf05014e680a1d4c902b25 parent 62b80bb
2 files changed, +39 −0
  • internal/console/geoippublish.go +12 −0
  • internal/console/geoippublish_test.go +27 −0
diff --git a/internal/console/geoippublish.go b/internal/console/geoippublish.go
index 070091bcd..a5bdc44aa 100644
--- a/internal/console/geoippublish.go
+++ b/internal/console/geoippublish.go
@@ -53,6 +53,12 @@ type geoipPublisher struct {
type geoipPublishOpts struct {
Origin, Edition string
Force bool
+ // RequireType refuses a database of any other type (the feed's check,
+ // spec §10.3, including on the first publish). Monotonic refuses a
+ // build that is not newer than the current pointer's when that pointer
+ // came from the same Origin. The feed sets both and never Force.
+ RequireType string
+ Monotonic bool
}
// newGeoipPublisher clears tmpRoot (the dedicated geoip-staging directory)
@@ -208,7 +214,13 @@ func (p *geoipPublisher) Commit(ctx context.Context, st *geoipStaged, o geoipPub
if p.expired(db.Source(), db.BuildEpoch(), p.now()) {
return geoip.Pointer{}, refused("the database is already expired (GeoLite2, 30 days after build)")
}
+ if o.RequireType != "" && db.Source() != o.RequireType {
+ return geoip.Pointer{}, refused("the feed returned a " + db.Source() + " database, not " + o.RequireType)
+ }
cur, etag, present, curErr := st.cur, st.curEtag, st.curPresent, st.curErr
+ if o.Monotonic && present && curErr == nil && cur.Origin == o.Origin && !db.BuildEpoch().After(cur.Build()) {
+ return geoip.Pointer{}, refused("not newer than the published edition")
+ }
switch {
case curErr != nil:
// A pointer exists but does not parse: replace it (etag still guards).
diff --git a/internal/console/geoippublish_test.go b/internal/console/geoippublish_test.go
index 1563bc46f..f258f6671 100644
--- a/internal/console/geoippublish_test.go
+++ b/internal/console/geoippublish_test.go
@@ -393,3 +393,30 @@ func TestGeoCountriesKeepsOtherLast(t *testing.T) {
t.Fatalf("last row = %+v, want _other rendered as other", rows[len(rows)-1])
}
}
+
+func TestFeedPublishRules(t *testing.T) {
+ withFixtureProbes(t)
+ ctx := context.Background()
+ p, _ := newTestPublisher(t)
+ city := fixture(t, "GeoIP2-City-Test.mmdb")
+ if _, err := p.Publish(ctx, bytes.NewReader(city), geoipPublishOpts{Origin: "feed:x", RequireType: "GeoIP2-Country", Monotonic: true}); !errors.Is(err, errGeoipRefused) || !strings.Contains(err.Error(), "not GeoIP2-Country") {
+ t.Fatalf("wrong type on first publish = %v", err)
+ }
+ first, err := p.Publish(ctx, bytes.NewReader(city), geoipPublishOpts{Origin: "feed:x", RequireType: "GeoIP2-City", Monotonic: true})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := p.Publish(ctx, bytes.NewReader(city), geoipPublishOpts{Origin: "feed:x", RequireType: "GeoIP2-City", Monotonic: true}); !errors.Is(err, errGeoipRefused) || !strings.Contains(err.Error(), "not newer") {
+ t.Fatalf("same edition again = %v", err)
+ }
+ if cur, _, _, _ := p.Current(ctx); cur.SHA256 != first.SHA256 {
+ t.Fatal("a refused feed publish moved the pointer")
+ }
+ // A manual pointer does not hold the feed back.
+ if _, err := p.Publish(ctx, bytes.NewReader(fixture(t, "GeoIP2-Country-Test.mmdb")), geoipPublishOpts{Origin: "manual", Force: true}); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := p.Publish(ctx, bytes.NewReader(city), geoipPublishOpts{Origin: "feed:x", RequireType: "GeoIP2-City", Monotonic: true}); err != nil {
+ t.Fatalf("feed over a manual pointer = %v", err)
+ }
+}