Documentation
¶
Overview ¶
World plan enforcement contract.
This file is the ONE place cloud maps a Hanzo World plan to the limits that gate the /v1/world data plane. The values are NOT hardcoded here: they are resolved from the @hanzo/plans catalog (the single source of truth) via the canonical `world.*` entitlement vocabulary, so the numbers live in exactly one place — subscription.json in hanzoai/plans — and are read, never duplicated.
Contract (keys defined in hanzoai/plans entitlements.schema.json):
world.api_rate_limit requests/minute REST /v1/world/* (-1 = unlimited) world.mcp_rate_limit requests/minute MCP surface (-1 = unlimited) world.max_alerts count saved OSINT alert rules (-1 = unlimited) world.model_api boolean /v1/world/model + SSE stream gate
Tier snapshot (sourced from the catalog, shown for reference only):
api/min mcp/min alerts model_api world-free 60 30 3 denied world-pro 6000 3000 -1 granted world-team 30000 15000 -1 granted world-enter -1 -1 -1 granted (contact sales)
ENFORCEMENT SEAM. Two callers consume this contract:
- this subsystem's /v1/world/{news,pipeline,stream} handlers (rate limiting + the SSE model_api gate), and
- the /v1/world/model planet-scale engine (feat/world-model-engine), whose pro-tier gate calls ResolveWorldLimits and checks WorldLimits.ModelAPI.
Both MUST resolve through ResolveWorldLimits so the policy stays single-sourced.
FOLLOW-UP (documented, coordinated — not built here). Per-request ENFORCEMENT needs the caller's effective plan id (org -> plan), which is a subscription lookup owned by the billing plane (commerce /v1/billing/subscriptions); the gateway principal carries org/project but no plan claim today. Until that org->plan resolver lands, ResolveWorldLimits takes an explicit plan id and GET /v1/world/limits echoes any plan's limits so agents/dashboards self-config against the live catalog. Wiring the rate limiter into the handlers is the rollout step, coordinated with the feat/world-model-engine gate to avoid two enforcement paths.
Package world mounts the Hanzo Cloud "World" news data plane: 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 (all org/project-scoped; /v1 only):
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/stream SSE live refresh (ZAP-native) -> event: news
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).
Order 142 binds /v1/world/* ahead of the AI /v1/* catch-all (150).
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 []string `json:"regions"`
Keywords []string `json:"keywords"`
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 string `json:"source"`
Title string `json:"title"`
Link string `json:"link"`
PubDate string `json:"pubDate"` // RFC3339 UTC, or "" when the upstream gave no parseable date
Lang string `json:"lang,omitempty"`
Image string `json:"image,omitempty"`
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.