Core Editions

Core Editions

Huitzo Core runs self-hosted in two editions: Core Preview and Core Licensed. Huitzo's own hosted service runs a third licence tier. The edition comes from the tier field of the signed license.json. Every Preview limit is a claim signed into that file, so an operator cannot raise it by editing settings or tenant quotas.

Each license also carries a signed purpose: Demo/POC licenses are Core Preview; Pilot, not-for-resale and Paid licenses are Core Licensed (Purposes and term caps). Operators install and renew the file with License status and renewal.

Editions at a glance

Core Preview Core Licensed Hosted
Licence tier preview licensed enterprise
Who runs it A prospective customer evaluating Huitzo, on their own infrastructure. At launch it is issued only through partners and Huitzo (ADR-008 D8). A customer, on their own infrastructure Huitzo (huitzo.ai production and staging)
Purpose Experimenting, not running a business Production operations Huitzo's SaaS service
Commercial basis Free, renewable 90-day licence Term contract, under the licence agreement/EULA —
Limits The signed Preview limits None: unlimited tenants, seats, packs, executions, schedules and runners None
white_label No Yes Yes
publish_public (non-private publishing) No: private only Yes Yes (implied by the tier)
saas (credits, wallets, Stripe, paid marketplace) No No Yes
Banner "Preview — not for production use" None None
Expiry grace 7 days 14 days 14 days

Grace is signed in the file; HUITZO_OFFLINE_GRACE_DAYS may only lower it (ADR-008 D2).

Repo docs quote no licence prices. Commercial terms are in the licence agreement.

Preview limits

Canon §3 limits. Each one is a signed claim in license.json. A Preview may be issued with a lower value, never a higher one: a Preview file signed above any of these values is INVALID (402).

Area Preview limit Licence claim Checked at Refusal
Tenants Home tenants, plus 1 team tenant max_team_tenants=1 Team-tenant create, a staff kind correction to team, and a tenant.create coupon redeem 409 license_limit_exceeded; a coupon redeem returns the uniform 400 coupon_invalid
Seats 3 per tenant max_seats_per_tenant=3 Tenant tenant.join coupon mint (advisory), coupon tenant.join at registration and redeem (authoritative) 409 license_limit_exceeded; a coupon redeem returns the uniform 400 coupon_invalid
Packs 3 enabled, deployment-wide max_packs=3 Pack create and pack enable 409 license_limit_exceeded
Executions 500 per deployment, trailing 30 days monthly_executions=500 REST, MCP and pipeline execute; worker scheduled dispatch 409 license_limit_exceeded; the worker skips and logs
Schedules 2 max_schedules=2 Schedule create and re-enable (command and Studio), pack publish that adds crons, and admin enable of a pack that declares crons 409 license_limit_exceeded
Runners 1 max_runners=1 Runner registration and admin re-enable of a disabled runner; disabled runners are not counted 409 license_limit_exceeded
Publishing Private only publish_public feature absent Pack and dashboard create, and every visibility change 403 publish_public_required for public, unlisted and organization; private is always allowed
Branding and SaaS No white_label, no saas Feature tokens Licence verification The licence is INVALID (402) if a Preview carries them
UI Preview banner tier=preview Hub, on every screen —

What is counted for each limit is defined in ADR-008 E8. Where each check sits in the code is listed in ADR-008 E9.

What every edition includes

Canon §0.1 and §3: "Tenant types, per-tenant metering, the platform-admin usage view and coupons stay available in Preview." Core owns these, so every edition has them.

Capability Notes
Tenants (home and team), membership, roles, tenant quotas Tenant quotas may only tighten a licence limit (How limits are enforced)
Packs, pipelines, schedules, runners, dashboards Preview caps the counts
Per-tenant usage metering and the platform-admin usage view GET /api/v1/admin/usage/tenants (ADR-043). Core measures usage but never prices it.
Coupons (account.create, tenant.join, tenant.create) Always on, with no flag (ADR-042); coupons are the only admission path. Redeeming one still passes the seat and team-tenant limits.
Private pack and dashboard catalog Every edition. Non-private visibility needs publish_public.

Core has no credits, wallets or prices. They belong to the SaaS add-on, which needs the saas entitlement.

What Core Licensed excludes

Canon §3, confirmed by the founder (D-024 r4): Core Licensed "excludes the saas entitlement (Huitzo's billing/credits/Stripe marketplace) and resale rights."

Excluded Why How it is enforced
saas: credits, wallets, Stripe, the paid marketplace Running a paid multi-customer marketplace requires the partner agreement The issuer refuses saas on licensed, and the verifier treats a licensed file carrying saas as INVALID (402). Without it, the SaaS extension never mounts.
Resale rights Nobody resells Core Licensed. Under the referral model, a partner refers and implements; Huitzo licenses and invoices each end customer (ADR-008 D10). Contractual. There is no licence claim for it.

Hosted edition

The hosted edition runs Huitzo's own deployments under tier=enterprise, with saas and white_label in features.

  • No limit claims are set, so every limit is unlimited.
  • publish_public is implied by the tier, so the hosted licence needs no reissue.
  • Founder confirmation is pending (FQ-8).

enterprise is not sold as a self-hosted edition.

Preview banner

On a Preview deployment, the Hub shows "Preview — not for production use":

  • on every screen, including login and signup, which read the edition from the unauthenticated GET /api/v1/capabilities (license.edition);
  • as a non-dismissible status region (role="status", aria-live="polite");
  • never on Licensed or Hosted.

How limits are enforced

  • One effective limit. effective = min(licence ceiling, tenants.quotas, code default).
  • The code default applies only while a tenant quota is unset; a configured quota replaces it. The licence ceiling bounds both.
  • An operator may lower a licence limit through tenant quotas, but never raise it.
  • A quota update above the licence ceiling is refused with 422 quota_above_license_ceiling.
  • Refusals name the binding limit.
  • The licence ceiling → 409 license_limit_exceeded, with details {key, limit, current, attempted_delta, scope}.
  • A tighter tenant quota → the existing 409 tenant_quota_exceeded.
  • REST and MCP return the same body.
  • Pack ceiling and allowlist. A pack over the deployment max_packs ceiling is refused with 409 license_limit_exceeded at pack create and at operator enable. A pack that is not on the licence's licensed_packs allowlist is refused with 403: that is an authorisation refusal, not a count. Concurrent creates from different tenants are serialized, so the ceiling is never overshot.
  • Publishing is an entitlement, not a count. Without publish_public, a non-private visibility is refused with 403 publish_public_required, details {visibility, allowed: ["private"]}.
  • 402 is different. 402 LICENSE_INVALID means the licence file itself is missing, tampered, of an unknown tier or expired past grace (ADR-008 §4). A 409 means the licence is fine and the edition's limit has been reached.
  • A failed count refuses. If a limit applies and the usage count cannot be read, the API refuses with 503 license_count_unavailable rather than letting the request through. Licensed and Hosted run no counts. The worker logs the error and proceeds.
  • Expiry and refresh.
  • Expiry is evaluated on every request.
  • The file is re-read from disk at most hourly. HUITZO_LICENSE_CHECK_INTERVAL is capped at 3600 seconds; a higher value fails at boot with a message naming the cap.
  • POST /api/v1/license/reload applies a new file immediately and requires admin or owner.
  • Executions are a soft cap. REST command execute, MCP tools/call and pipeline execute check monthly_executions before they start the run, without a lock, so runs already in flight together can pass the limit by a few. A refused request records no execution. Studio build stages and nested ctx.commands calls are not separate executions and are not checked.
  • The worker checks too. It mounts license.json read-only. It skips scheduled runs when the licence is INVALID or the execution ceiling has been reached.
  • No off switch. No environment variable or setting disables or raises a claim. A higher limit means a newly signed licence.
  • Status. GET /api/v1/license/status reports the edition and, for each claim, {limit, current, source}. The deployment-wide current counts are returned only to tenant admins and owners; other members see the edition and the limits.

Moving between editions

Changing edition is a licence replacement:

  1. Obtain the new signed license.json.
  2. Drop it in place.
  3. Call POST /api/v1/license/reload.

No data migration is involved. Moving from Preview to Licensed lifts every limit and removes the banner. License management covers install and renewal.

Retired tiers

The licence tiers that came before D-024 are retired with no alias (ADR-008 E14).

  • The verifier accepts only preview, licensed and enterprise. A file carrying any other tier is INVALID (402) from the release that ships CR55.
  • An outstanding field licence on a retired tier must be reissued as preview or licensed before that upgrade (FQ-9).
  • The earlier self-hosted price lists are retired too.

Anchor index

Implementation rows cite docs/reference/core-editions.md#<anchor>.

Anchor Implemented by
#editions-at-a-glance CR55, CR56, CR64, CR66
#preview-limits CR55, CR58–CR63, CR66
#what-every-edition-includes CR66
#what-core-licensed-excludes CR55, CR56
#hosted-edition CR55, CR60
#preview-banner CR64, CR65
#how-limits-are-enforced CR58, CR62, CR64, CR65, CR66
#moving-between-editions CR64, CR66
#retired-tiers CR55, CR56, CR66