Files
waggle-os/docs/backend-map/DIAGRAMS/05-tier-gating.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

8.4 KiB

Tier + Trust Gating

Waggle gates every action on two orthogonal axes. The subscription tier (TRIAL / FREE / PRO / TEAMS / ENTERPRISE) decides whether a feature exists for this user at all — enforced on HTTP routes via requireTier() returning 403 TIER_INSUFFICIENT, and on tool registration (KVARK tools only exist when KVARK is configured). The autonomy / trust level (Normal / Trusted / YOLO) decides, per tool call, whether the UI must show an approval prompt — a pure runtime UX lever with a hardcoded critical blacklist that always wins, even at YOLO. The rule of thumb: tier decides whether the door exists; trust decides whether it needs a key turn each time you walk through. Always run a stored tier through getEffectiveTier(tier, trialStartedAt) before gating, because an expired TRIAL collapses to FREE.


1. The two gating axes (overview)

flowchart TD
  USER["User stored tier plus trialStartedAt"] --> EFF["getEffectiveTier\nTRIAL expired collapses to FREE"]
  EFF --> CAPS["getCapabilities\nTierCapabilities flag set"]

  CAPS --> AXIS1["AXIS 1 — Tier gate\nfeature exists for this plan?"]
  SESSION["Session AutonomyLevel\nnormal / trusted / yolo"] --> AXIS2["AXIS 2 — Trust gate\nclick needed for this call?"]

  AXIS1 --> ROUTES["requireTier preHandler\n403 TIER_INSUFFICIENT on fail"]
  AXIS1 --> KREG["KVARK tools registered\nonly when KVARK configured"]
  AXIS2 --> CONF["needsConfirmationWithAutonomy\nper tool call"]

  ROUTES --> GATED["Marketplace, Personas, Admin,\nTeam, Cloud-sync, Enterprise packs"]
  KREG --> KVARK["kvark_search, kvark_feedback,\nkvark_action, kvark_ask_document"]
  CONF --> TOOLS["Tools, connectors, bash,\ninstall_capability"]

2. The 5 tiers and what each unlocks

flowchart LR
  subgraph TRIAL["TRIAL — 0 USD / 15 days"]
    T1["All features unlocked\nmirrors TEAMS plus ENTERPRISE\nselfHosted OFF\ndecays to FREE after 15 days"]
  end
  subgraph FREE["FREE — 0 USD forever"]
    F1["5 workspaces, 5 connectors\nspawnAgents ON, memory free\nbuilt-in skills only\nexport txt and md\nauditLog none, kvarkCta subtle"]
  end
  subgraph PRO["PRO — 19 USD / mo (solo)"]
    P1["Unlimited workspaces plus connectors\ncustomSkills ON, marketplace\nexport txt md pdf json\nteamMembersLimit 1, auditLog basic\nno sharedWorkspaces, no cloudSync"]
  end
  subgraph TEAMS["TEAMS — 49 USD / seat"]
    M1["sharedWorkspaces, teamSkillLibrary\ncloudSync, adminPanel\nauditLog full, selfHosted\nmanagedModelPool, priorityModels\nWaggleDance, kvarkCta active"]
  end
  subgraph ENT["ENTERPRISE — consultative"]
    E1["KVARK sovereign on-prem\nall TEAMS capabilities\nselfHosted ON, kvarkCta none\ngovernance permissions route"]
  end

  TRIAL -->|"trial expires"| FREE
  FREE -->|"upgrade 19/mo"| PRO
  PRO -->|"upgrade 49/seat"| TEAMS
  TEAMS -->|"sales contact"| ENT

3. Tier x Capability matrix (verbatim from TIER_CAPABILITIES)

-1 means unlimited. Values quoted from packages/shared/src/tiers.ts.

Capability TRIAL FREE PRO TEAMS ENTERPRISE
connectorLimit -1 5 -1 -1 -1
workspaceLimit -1 5 -1 -1 -1
embeddingProviders all 6 inprocess, mock, ollama + voyage, openai all 6 all 6
embeddingQuotaPerMonth -1 -1 -1 -1 -1
messageHistoryLimit -1 -1 -1 -1 -1
spawnAgents yes yes yes yes yes
customSkills yes no yes yes yes
teamSkillLibrary yes no no yes yes
cloudSync yes no no yes yes
exportFormats txt md pdf json txt md txt md pdf json txt md pdf json txt md pdf json
teamMembersLimit -1 1 1 -1 -1
sharedWorkspaces yes no no yes yes
adminPanel yes no no yes yes
auditLog full none basic full full
selfHosted no no no yes yes
managedModelPool yes no no yes yes
priorityModels yes no no yes yes
kvarkCta subtle subtle subtle active none
stripePriceId null null STRIPE_PRICE_PRO STRIPE_PRICE_TEAMS null

all 6 embedding providers = inprocess, mock, ollama, voyage, openai, litellm. Tier ordering (TIER_ORDER): FREE 0, PRO 1, TEAMS 2, ENTERPRISE 3, TRIAL 3 — TRIAL ties ENTERPRISE because it has max capabilities, but is time-limited.


4. Tier-gated HTTP routes (Axis 1)

The frontend must catch 403 TIER_INSUFFICIENT and show an upgrade prompt using required and upgradeUrl.

Method Path Min tier
POST /api/marketplace/install PRO
POST /api/marketplace/publish PRO
GET /api/marketplace/enterprise-packs ENTERPRISE (plus KVARK configured)
POST /api/personas PRO
POST /api/personas/generate PRO
GET /api/cost/by-workspace TEAMS
POST /api/cloud-sync/toggle TEAMS
GET /api/admin/overview TEAMS
POST /api/admin/audit-export TEAMS
POST /api/team/connect TEAMS
GET /api/team/governance/permissions ENTERPRISE

KVARK tools (kvark_search, kvark_feedback, kvark_action, kvark_ask_document) are registered only when getKvarkConfig(vault) returns a connection — 4 tools when configured, 0 otherwise.


5. The Normal / Trusted / YOLO autonomy gate (Axis 2)

flowchart TD
  CALL["Tool call name plus args"] --> NC{"needsConfirmation?\nALWAYS_CONFIRM, connector write,\ndestructive bash"}
  NC -- no --> RUN["Run silently"]
  NC -- yes --> LVL{"AutonomyLevel"}

  LVL -- normal --> PROMPT["Show approval prompt"]
  LVL -- "trusted or yolo" --> CRIT{"isCriticalNeverAutopass?"}

  CRIT -- yes --> PROMPT
  CRIT -- no --> WHICH{"which level?"}

  WHICH -- yolo --> AUTO["Auto-approve plus audit step"]
  WHICH -- trusted --> TAP{"in TRUSTED_AUTOPASS\nor safe bash?"}
  TAP -- yes --> AUTO
  TAP -- no --> PROMPT

Level behavior:

Level Behavior
normal Gate everything needsConfirmation flags (writes, connector writes, git, install, destructive bash).
trusted Auto-pass TRUSTED_AUTOPASS (write_file, edit_file, generate_docx, read_other_workspace, read_other_workspace_file) and non-critical bash. Still gate git push/commit/pr/merge, install_capability, connector writes, cross-workspace writes.
yolo Auto-pass everything except isCriticalNeverAutopass.

isCriticalNeverAutopass (never auto-passes, even at YOLO): rm -rf / or ~ or $HOME or /*, any sudo, format C:, mkfs, reg delete, dd if=… of=/dev, forced git push --force to main/master/production, fork bomb; install_capability with _riskLevel === 'high'.

Autonomy override travels in the chat request body { "autonomy": { "level": "trusted"|"yolo", "expiresAt": <ms epoch> } }; if expiresAt is absent or past, the server falls back to normal.


6. How tier + trust combine (worked examples)

flowchart TD
  A["FREE user — connector_github_create_issue"] --> A1["Tier: no route gate"] --> A2["Trust: write so Normal prompts,\nTrusted gates, YOLO auto-passes"] --> A3["Runs after approval if connected"]
  B["FREE user — Install marketplace pack"] --> B1["Tier: 403 needs PRO"] --> B3["Blocked, upgrade prompt"]
  C["ENTERPRISE user — kvark_action"] --> C1["Tier: KVARK configured so registered"] --> C2["Trust: requires approval"] --> C3["Governed action runs with audit ref"]
  D["Any tier, YOLO — bash sudo rm -rf /"] --> D1["Tier: none"] --> D2["Trust: isCriticalNeverAutopass"] --> D3["Still prompts even at YOLO"]
  E["PRO user — install_capability starter-pack"] --> E1["Tier: none"] --> E2["Trust: ALWAYS_CONFIRM,\nclass from _riskLevel"] --> E3["Prompts, critical if high-risk"]
Scenario Tier check Trust check Net result
FREE — connector_github_create_issue none (not a requireTier route) write → Normal prompts; Trusted gates; YOLO auto-passes Runs after approval if connected
FREE — install marketplace pack 403 TIER_INSUFFICIENT (needs PRO) n/a (blocked first) Blocked → upgrade prompt
ENTERPRISE — kvark_action (KVARK configured) tools registered requires approval Governed action runs with audit ref
Any tier, YOLO — bash sudo rm -rf / none isCriticalNeverAutopass Still prompts even at YOLO
PRO — install_capability (starter-pack) none in ALWAYS_CONFIRM Prompts (critical if high-risk)