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 ¶
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 ¶
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.
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.