plan

package
v1.801.464 Latest Latest
Warning

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

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

Documentation

Overview

Package plan is the plan catalog: every tier you can buy, what it costs, and what it grants.

It serves /v1/plans/* — cloud, subscription, blockchain, DNS, GPU and storage tiers, the entitlement vocabulary each grants and its JSON Schema, and a resolver from a plan id to both. It is the catalog of RECORD; apps/pricing reads the same @hanzo/plans source and answers eight of these sections again under /v1/pricing/*.

STRATEGY: wrap, don't rewrite. @hanzo/plans is a Node data package (JSON catalog + entitlements.mjs transforms). We do NOT reimplement the entitlement vocabulary in Go and we do NOT copy the catalog into cloud. Instead:

  • github.com/hanzoai/plans (the service repo's Go embed module) ships goja/bundle.js — the ESM-free port of entitlements.mjs + the /v1/plans route table — plus the embedded *.json catalog (plans.Data()).
  • This wrapper loads that bundle into a goja runtime (apps/goja), injects the catalog as globalThis.__PLANS_DATA__, and declares one TYPED op per address (ops.go) that calls globalThis.handle({route, params, tenant}). The entitlement transforms (fromLegacy/toLicenseFeatures/resolvePlan) run in goja — real JS, not a Go reimplementation.

The plans data is read-only public-catalog content; there are no secrets here. The licensing SIGNER/fingerprint that consumes toLicenseFeatures stays in hanzoai/licensing. This wrapper is pure glue.

IAM gating + X-Org-Id tenant scope: every /v1/plans route threads the VALIDATED org into the bundle as the tenant, so a reseller org (tenant_id != "hanzo") sees its own catalog overrides. A typed op receives only a context, so that org arrives on the context (cloud.Bridge parks it, ops.go/catalogTenant reads it) and is never an In field — an In field is caller-supplied, and a tenant read from one would hand any caller any reseller's catalog. The plan catalog is readable by any authenticated caller; no admin scope is required for reads.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Entitlements

func Entitlements(ctx context.Context, id string) (map[string]any, error)

Entitlements resolves the canonical entitlement block for a plan id from the @hanzo/plans catalog (the single source of truth). It runs the bundle's "entitlements" route on the shared goja host and returns the parsed, namespaced `entitlements` map (e.g. "world.api_rate_limit", "ai.tokens_per_min"). This is the one Go seam other subsystems use to read plan entitlements without importing the catalog data or reimplementing the fromLegacy derivation. Tenant is the public "hanzo" catalog (plan entitlements are tenant-independent metadata).

It errors if the plans subsystem is not mounted or the plan id is unknown, so callers can fail closed rather than silently granting a default tier.

func LicenseEntitlement

func LicenseEntitlement(ctx context.Context, id string) (entitlements map[string]any, features []string, found bool, err error)

LicenseEntitlement resolves BOTH the canonical entitlement block AND the flat license-feature list for a plan id from the @hanzo/plans catalog (the single source of truth). It runs the bundle's "entitlements" route on the shared goja host — the SAME route /v1/plans/entitlements/:id serves — and returns the parsed `entitlements` map plus the `license_features` list the bundle's toLicenseFeatures transform produces. It is the seam the commerce entitlement resolver (commerce.CheckEntitlement) uses to map a subscription's plan tier to the flat features a signed license carries, WITHOUT reimplementing the vocabulary in Go (the entitlement transforms stay in one place — the JS bundle).

found reports whether the plan id exists in the catalog. A 404 from the bundle is (nil, nil, false, nil): a real "unknown plan", NOT a machinery error — so a caller scanning several subscriptions can skip an unknown tier and keep going. ANY other failure (plans subsystem not mounted, dispatch error, non-200/404 status, decode error) returns a non-nil error so the money-path caller FAILS CLOSED rather than treating an unresolved plan as "grants nothing".

func Mount

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

Mount registers the /v1/plans/* surface on app per HIP-0106.

func Paid(id string) (bool, error)

Paid reports whether plan tier id is a PAID Hanzo cloud subscription tier — the paywall's "this org holds a real plan" predicate. Pro/Plus/Max/Team/Team-Max/ Enterprise/Custom → (true, nil); the free Developer tier, a non-cloud product plan (world-*/social-*), an empty or unknown id → (false, nil).

A non-nil error means the embedded catalog could not be read (a build/embed defect — effectively impossible in a booted binary). It is returned rather than swallowed so the caller can FAIL OPEN (admit the request) rather than mistake a catalog outage for "no paid tier" and lock a subscriber out. This function NEVER fabricates a grant: it returns true only for a tier the catalog says is a paid cloud account tier.

func Shutdown

func Shutdown(context.Context) error

Shutdown drops the goja host. Idempotent.

Types

This section is empty.

Jump to

Keyboard shortcuts

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