ScenarioForge — licensing & entitlements

Marketing / product name is ScenarioForge. The on-disk identifier (Python package, CLI, env vars) stays scenariogen for backwards compatibility. See docs/BRAND.md for the naming rule.

scenariogen ships with an enterprise licensing layer that gates trackers, features, projects, seats, and monthly run quotas. When no licence is configured the tool runs in community mode (Jira + CSV ingest, single project, no enterprise features) — exactly the behaviour shipped before this layer was added. Existing pipelines are not affected.

Licence token format

Tokens are compact, JWS-like, Ed25519-signed strings:

base64url(header) "." base64url(payload) "." base64url(signature)

Only the vendor holds the private key. Customers ship with the bundled public key (scenariogen/licensing/keys/scenariogen_pub.pem), or override it via SCENARIOGEN_LICENSE_PUBLIC_KEY_FILE for air-gapped installs.

Configuration

Env var Purpose
SCENARIOGEN_LICENSE_KEY Inline licence token. Wins over the file.
SCENARIOGEN_LICENSE_FILE Path to a file containing the token. Falls back to ~/.scenariogen/license.jwt.
SCENARIOGEN_LICENSE_PUBLIC_KEY Inline public key PEM (rare; air-gap rotation).
SCENARIOGEN_LICENSE_PUBLIC_KEY_FILE Path to override the bundled public key.

Monthly run counters persist under <SCENARIOGEN_CACHE_DIR>/license/runs.json.

CLI

scenariogen license show      # tabular view of the active licence
scenariogen license verify    # exit 0 if valid, non-zero otherwise

Tiers and entitlements

Tier Trackers Projects Seats Runs/mo Notes
community jira 1 n/a unlimited default when no licence is present
team 1 3 5 200 single squad
pro up to 3 10 20 1,000 multi-squad QA org
business up to 6 30 75 5,000 regulated / multi-tracker
enterprise unlimited unl unl unl air-gap, custom adapters

Tiers are advisory — the actual limits in any issued licence are whatever fields the vendor signs into the token (max_projects, max_seats, max_runs_per_month, trackers, features).

Programmatic gating

from scenariogen.licensing import get_entitlements

ent = get_entitlements()
ent.require_tracker("linear")        # raises EntitlementError if not licensed
ent.require_feature("sso")
ent.check_project_count(active_projects)
ent.record_run()                     # increments monthly counter, enforces quota

Call sites: - ingest.*_client resolvers should call require_tracker(kind) before hitting the API. - orchestrator.run_pipeline should call record_run() at the start of each run. - Server admin endpoints should check require_feature("sso") / require_feature("audit_export") before exposing those routes.

Vendor: issuing a licence

The vendor private key is not shipped with the package. Issuance is a manual offline step.

Bootstrap a keypair once (commit the public PEM only):

python scripts/issue_license.py generate-keypair \
    --private-out vendor-secrets/scenariogen_license.private.pem \
    --public-out  scenariogen/licensing/keys/scenariogen_pub.pem

Mint a customer licence:

python scripts/issue_license.py issue \
    --private-key vendor-secrets/scenariogen_license.private.pem \
    --customer "Acme Bank" \
    --tier business \
    --trackers jira,linear,ado \
    --features sso,audit_export \
    --max-projects 30 --max-seats 75 --max-runs-per-month 5000 \
    --days 365

The customer pastes the printed token into SCENARIOGEN_LICENSE_KEY or ~/.scenariogen/license.jwt.

Air-gap considerations

BYOK — bring your own key

scenariogen is BYOK by design. All LLM, embedding, and web-search calls go directly from the customer's environment to the customer's own provider account using credentials they configure (OPENAI_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_*, AWS_BEDROCK_*, GOOGLE_API_KEY, OPENAI_COMPATIBLE_BASE_URL, TAVILY_API_KEY).

The licence layer therefore never: - proxies LLM traffic, - holds or rotates customer LLM keys, - meters tokens, prompts, completions, or any provider-side spend, - charges margin on inference.

Quota gating in this licence layer is run-level (max_runs_per_month) — a coarse usage signal that lets us tier the product without touching the BYOK pipe. If a future tier ever needs finer-grained limits, they must still respect BYOK: count runs, durations, or work-items — not tokens.

This is a non-negotiable product constraint. Any new wedge (packaging, auth, observability, multi-tracker) must preserve it.

What this layer does NOT do