license

package
v0.13.21 Latest Latest
Warning

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

Go to latest
Published: Jun 18, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package license implements offline, self-hosted license validation for agnt Pro features. Licenses are hyperboloide/lk blobs: an ECDSA (P-384) signature over a JSON Payload. The signing private key never ships; the binary embeds only the public key (see key.go) and validates fully offline.

Index

Constants

View Source
const GracePeriod = 14 * 24 * time.Hour

GracePeriod is how long Pro features keep working past Expiry before they hard-block. Warnings surface throughout the window.

Variables

View Source
var ErrInvalidLicense = errors.New("license: signature verification failed")

ErrInvalidLicense is returned by Validate when a blob fails verification.

View Source
var ErrNoLicense = errors.New("license: no license installed")

ErrNoLicense is returned by Load when no license blob is stored.

Functions

func GenerateKeypair added in v0.13.20

func GenerateKeypair() (privB32, pubB32 string, err error)

GenerateKeypair returns a fresh (private, public) base32 keypair for operator / server setup (`agnt license keygen`). The public half is embedded in the client; the private half is the signing key.

func Load added in v0.13.20

func Load() (string, error)

Load reads the stored license blob. ErrNoLicense if none is installed.

func Mint added in v0.13.20

func Mint(privKeyB32 string, p *Payload) (string, error)

Mint signs a payload into a license blob using the base32-encoded private signing key, returning the base32 blob handed to a customer.

This is the integration seam for the Stripe webhook → license issuance flow: a future self-hosted service builds a Payload from the customer record and calls Mint with the signing key kept server-side. Kept client-side too for manual issuance (`agnt license issue`) and tests. The private key must NEVER be embedded in or distributed with the client binary.

func Remove added in v0.13.20

func Remove() error

Remove deletes the stored license blob. Absent file is not an error.

func Save added in v0.13.20

func Save(blob string) error

Save writes the license blob atomically (temp file + rename) at mode 600.

func SetVerifyKeyForTest added in v0.13.20

func SetVerifyKeyForTest(k *lk.PublicKey) (restore func())

SetVerifyKeyForTest overrides the verification key and returns a restore func. Test-only: production code never calls it.

Types

type Capability added in v0.13.20

type Capability string

Capability is a Pro capability key. A license payload lists the capabilities it grants; Check authorizes a feature only if its capability is granted and the license is in a serving state.

const (
	// CapWholeSite gates whole-site / multi-page crawling and operations
	// (vs. the free single-page tools).
	CapWholeSite Capability = "whole_site"
	// CapAnalysisReport gates generated analysis / reporting features.
	CapAnalysisReport Capability = "analysis_report"
	// CapComponentExtract gates component-library / CSS extraction from a
	// codebase.
	CapComponentExtract Capability = "component_extract"
	// CapAdvancedTesting gates the advanced testing suite.
	CapAdvancedTesting Capability = "advanced_testing"
)

type GateError added in v0.13.20

type GateError struct {
	Capability Capability
	State      State
}

GateError is returned by Check when a Pro capability is denied. It carries the deciding state so callers can tailor messaging.

func (*GateError) Error added in v0.13.20

func (e *GateError) Error() string

type Manager added in v0.13.20

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

Manager loads and caches the installed license, and is the single chokepoint Pro features consult via Check. Validation (ECDSA verify) happens once at load, never on the hot path.

func NewManager added in v0.13.20

func NewManager() *Manager

NewManager returns an unloaded Manager. Call Load (or let Check lazy-load) before relying on its state.

func NewManagerForTest added in v0.13.20

func NewManagerForTest(s Status) *Manager

NewManagerForTest returns a Manager preloaded with the given Status, bypassing disk load and signature validation. It exists so external-package tests can build a Manager that grants (or denies) a capability deterministically. For tests only — production code must use NewManager + Load.

func (*Manager) Check added in v0.13.20

func (m *Manager) Check(cap Capability) (warning string, err error)

Check authorizes a Pro capability. It returns nil when the license is in a serving state (Valid or Grace) and grants cap; otherwise a *GateError. When the state is Grace, it also returns a non-empty warning the caller should surface (the error is still nil — Pro is allowed during grace).

func (*Manager) Load added in v0.13.20

func (m *Manager) Load() error

Load reads, verifies, and evaluates the stored license, caching the result. A missing license is not an error — it yields StateMissing. A present but unverifiable blob yields StateInvalid (also not an error: the agent should see the state, not a failure). Returns an error only for unexpected I/O.

func (*Manager) Reload added in v0.13.20

func (m *Manager) Reload() error

Reload forces a re-read from disk (e.g. after `agnt activate`).

func (*Manager) Status added in v0.13.20

func (m *Manager) Status() Status

Status returns the cached license status, lazy-loading on first use.

type Payload added in v0.13.20

type Payload struct {
	// Email identifies the licensee. Informational only — not an auth boundary.
	Email string `json:"email"`

	// CustomerID is the upstream (Stripe) customer reference, for support and
	// re-issue. Informational only.
	CustomerID string `json:"customer_id,omitempty"`

	// Plan is a human label for the tier (e.g. "team", "enterprise").
	Plan string `json:"plan,omitempty"`

	// IssuedAt records when the license was minted (UTC).
	IssuedAt time.Time `json:"issued_at"`

	// Expiry is when the paid term ends (UTC). After this, the license enters
	// the grace window (see Evaluate) before Pro features hard-block.
	Expiry time.Time `json:"expiry"`

	// Capabilities lists the Pro capability keys this license grants. A Check
	// for a capability not present here fails even on a fully valid license.
	Capabilities []string `json:"capabilities,omitempty"`
}

Payload is the JSON document carried inside a signed license blob. It is the single source of truth for who a license is for, when it expires, and which Pro capabilities it grants. The signing server (Stripe webhook → issue.Mint) builds this from the customer record; the client only ever reads it.

func Validate added in v0.13.20

func Validate(blob string) (*Payload, error)

Validate verifies a license blob against the embedded public key and decodes its payload. A tampered, wrong-key, or malformed blob yields ErrInvalidLicense (wrapped). The blob is the base32 string handed to the customer.

func (*Payload) Grants added in v0.13.20

func (p *Payload) Grants(cap Capability) bool

Grants reports whether the payload authorizes the given capability.

type State added in v0.13.20

type State int

State is the serving state of a stored license.

const (
	// StateMissing means no license blob is stored.
	StateMissing State = iota
	// StateInvalid means a blob is stored but failed signature/parse.
	StateInvalid
	// StateValid means the license is signed and within its paid term.
	StateValid
	// StateGrace means past Expiry but within GracePeriod — Pro still serves,
	// with a warning.
	StateGrace
	// StateExpired means past Expiry + GracePeriod — Pro blocks.
	StateExpired
)

func (State) String added in v0.13.20

func (s State) String() string

type Status added in v0.13.20

type Status struct {
	State    State
	Payload  *Payload // nil when State is Missing or Invalid
	DaysLeft int      // days until block: to Expiry when Valid, to grace-end when Grace; 0 otherwise
}

Status is the evaluated view of a license at a point in time.

func Evaluate added in v0.13.20

func Evaluate(p *Payload, now time.Time) Status

Evaluate computes serving state from a payload and the current time. A nil payload is StateMissing. Clock is trusted (good-faith compliance only).

Jump to

Keyboard shortcuts

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