world

package
v1.801.360 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package world is a live news feed filtered to what your project cares about.

A per-org, per-project intelligence feed that normalizes GDELT + host-allowlisted RSS/Atom into one NewsItem stream, applies the project's keyword/region/source filter, and serves it over REST + SSE. It is the Go backend for the World monitor frontend (hanzoai/world), replacing that app's Vercel edge functions (api/gdelt-doc.js, api/rss-proxy.js) with an org-scoped, in-binary subsystem.

Surface (/v1 only; org/project-scoped except where marked public):

GET /v1/world            front door: the product and its wires    -> {…} PUBLIC
GET /v1/world/news       merged, filtered, freshest-first feed -> {items:[…]}
GET /v1/world/pipeline   per-project pipeline config (read)    -> {…}
PUT /v1/world/pipeline   per-project pipeline config (write)   -> {…}
GET /v1/world/limits     a World plan's rate/alert/model gates -> {…} PUBLIC
GET /v1/world/stream     SSE live refresh (ZAP-native)         -> event: news

Two more wires answer under this prefix and are NOT served by this binary: /v1/world/mcp (Model Context Protocol) and /v1/world/zap (ZAP over WebSocket) are carved off the cloud catch-all by the ingress and answered by world-gw. The generated document cannot declare them — openapi.Describe renders prose only for a route this router actually serves, which is what stops the document claiming an operation nothing answers — so GET /v1/world names them instead. That op is the only place in the product's own surface those addresses appear.

TENANT ISOLATION is enforced SERVER-SIDE on every request. The (org, project) tuple is principal.Org + principal.Project (the values SanitizeIdentity minted from the VALIDATED bearer, HIP-0026) — never a query param, body, or client header. Every store statement carries `WHERE org=? AND project=?`; the SSE bus filters on org and the stream loop drops other projects. A request with no validated principal is a 403.

SECURITY. The RSS fetcher is an SSRF boundary: a feed URL's host must be in the ported rss-proxy.js allowlist (allowlist.go), enforced at BOTH the PUT write boundary and at fetch time, including on redirect targets (client CheckRedirect).

The host routes by first matching prefix in manifest.Apps order, and world's row (/v1/world) precedes the ai row that answers the bare /v1 remainder.

Index

Constants

This section is empty.

Variables

View Source
var FreeWorldLimits = WorldLimits{APIRateLimit: 60, MCPRateLimit: 30, MaxAlerts: 3, ModelAPI: false}

FreeWorldLimits is the fail-closed floor: the limits applied when no plan can be resolved (unknown plan, or the catalog is unreachable). It equals the world-free tier so a catalog outage degrades to exactly Free — never above, and never granting the model/stream API.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount registers the world surface on app per HIP-0106.

func Shutdown

func Shutdown() error

Shutdown closes the SSE bus (unblocking every open stream) and the pipeline store. Idempotent — safe to call when nothing is mounted.

Types

type Filters

type Filters struct {
	// Regions keeps only items whose TITLE contains one of these, matched
	// case-insensitively as a substring. Empty keeps every region.
	Regions []string `json:"regions"`
	// Keywords keeps only items whose TITLE contains one of these,
	// case-insensitively. They are also the GDELT queries the feed fans out to,
	// one per keyword of three characters or more — so a keyword both widens what
	// is fetched and narrows what is kept.
	Keywords []string `json:"keywords"`
	// Sources keeps only items whose outlet name contains one of these,
	// case-insensitively. Empty keeps every outlet.
	Sources []string `json:"sources"`
}

Filters narrows the merged news stream. Each axis is an OR-set of terms; a non-empty axis is an AND predicate against the item (empty axis = pass-through). Keywords ALSO seed the GDELT queries, so it is both a fetch input (fresh keyword-matched articles) and a post-merge filter.

type NewsItem

type NewsItem struct {
	// Source is the outlet the item came from, as the upstream named it.
	Source string `json:"source"`
	// Title is the headline.
	Title string `json:"title"`
	// Link is the article's URL at the outlet.
	Link string `json:"link"`
	// PubDate is when the outlet published it, RFC3339 UTC. Empty when the
	// upstream gave no date this could parse — items with no date sort last.
	PubDate string `json:"pubDate"`
	// Lang is the article's language code when the upstream reported one.
	Lang string `json:"lang,omitempty"`
	// Image is a lead-image URL when the upstream carried one.
	Image string `json:"image,omitempty"`
	// Tone is GDELT's own sentiment score for the article, as text. Only GDELT
	// items carry it.
	Tone string `json:"tone,omitempty"`
}

NewsItem is the normalized, source-agnostic shape every upstream (GDELT, RSS, Atom) is projected into and the wire contract for GET /v1/world/news.

type Pipeline

type Pipeline struct {
	Org       string
	Project   string
	Feeds     []string
	Filters   Filters
	CreatedAt int64
	UpdatedAt int64
}

Pipeline is the persisted per-(org,project) news pipeline config. Feeds are host-allowlisted RSS/Atom URLs; Filters narrows the merged result.

type PipelineStore

type PipelineStore struct {
	// contains filtered or unexported fields
}

PipelineStore is the world pipeline metastore over one SQLite file ({DataDir}/world.db). Tenancy is the (org, project) key.

func (*PipelineStore) Close

func (s *PipelineStore) Close() error

Close closes the underlying database.

func (*PipelineStore) Get

func (s *PipelineStore) Get(ctx context.Context, org, project string) (Pipeline, error)

Get returns the persisted pipeline for (org, project), or errNotFound when the org has never written a pipeline for that project (the caller then serves the default pipeline).

func (*PipelineStore) Put

func (s *PipelineStore) Put(ctx context.Context, p Pipeline) (Pipeline, error)

Put upserts the feeds + filters for (org, project). It is idempotent on the composite key and preserves created_at across updates.

type WorldLimits

type WorldLimits struct {
	APIRateLimit int  // world.api_rate_limit
	MCPRateLimit int  // world.mcp_rate_limit
	MaxAlerts    int  // world.max_alerts
	ModelAPI     bool // world.model_api — /v1/world/model + SSE stream access
}

WorldLimits is the resolved World plan contract for one caller: the enforcement inputs both the /v1/world handlers and the /v1/world/model gate read. Rate limits are requests/minute; -1 means unlimited.

func ResolveWorldLimits

func ResolveWorldLimits(ctx context.Context, planID string) (WorldLimits, error)

ResolveWorldLimits resolves the World limits for a plan id straight from the @hanzo/plans catalog. An empty id resolves the free tier. On any resolution error it returns FreeWorldLimits together with the error so callers can log the degradation while still failing closed.

func WorldLimitsFromEntitlements

func WorldLimitsFromEntitlements(ent map[string]any) WorldLimits

WorldLimitsFromEntitlements maps a resolved `world.*` entitlement block onto WorldLimits. Pure and total: any key absent from the block keeps the Free-floor value, so a partial catalog can only ever narrow access. Exported so the /v1/world/model gate can derive limits from an already-resolved entitlement map without a second catalog round-trip.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL