|
| 1 | +# Phase 3+ Plan: Monetisation, Auth, and Premium Skills |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +Phase 1–2 (ADF agent validation) is complete: all 37 agents probed, 32/37 runnable, all terraphim-ai ADF agents fully validated across all modes. The orchestrator runs live at PID 579991. |
| 6 | + |
| 7 | +Four greenfield workstreams remain: |
| 8 | +1. **Stripe integration** — subscription billing ($20 Individual / $300 + $100/seat Enterprise) |
| 9 | +2. **better-auth-rust backend** — Gitea OAuth, API keys, agent identity |
| 10 | +3. **Custom domain routing** — `terraphim-skills.md` marketplace |
| 11 | +4. **Premium skill definitions** — tiered skill marketplace |
| 12 | + |
| 13 | +**Constraint:** MSRV 1.80, subscription-only models (C1 invariant), disciplined phases (research → design → implementation → verification → validation). |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Workstream 1 — Stripe Integration |
| 18 | + |
| 19 | +### 1.1 Research |
| 20 | + |
| 21 | +**Owner:** research agent |
| 22 | +**Artifacts:** `.docs/research-stripe-integration.md` |
| 23 | + |
| 24 | +**Questions to answer:** |
| 25 | +- How does Terraphim currently identify users? (Existing user model? Gitea OAuth already in use?) |
| 26 | +- What events trigger billing? (Agent execution? API calls? Seat provisioning?) |
| 27 | +- Enterprise tier: $300 base + $100/seat — how is "seat" defined and counted? |
| 28 | +- Stripe webhook idempotency — how to handle duplicate events? |
| 29 | +- Free tier: what limits apply, how enforced? |
| 30 | +- Existing `terraphim_usage` crate tracks internal AI costs — does this map to user billing? |
| 31 | + |
| 32 | +**Existing code to study:** |
| 33 | +- `crates/terraphim_usage/src/pricing.rs` — `PricingTable` (internal AI cost model) |
| 34 | +- `crates/terraphim_usage/src/store.rs` — `AgentMetricsRecord`, `BudgetSnapshotRecord` |
| 35 | +- `crates/terraphim_onepassword_cli/src/lib.rs` — `SecretLoader` trait (Stripe keys via `op://`) |
| 36 | +- `crates/terraphim_service/` — main server; study existing user/session model |
| 37 | +- `crates/terraphim_settings/` — twelf config layer; secrets injection |
| 38 | + |
| 39 | +### 1.2 Design |
| 40 | + |
| 41 | +**Owner:** architecture agent |
| 42 | +**Artifacts:** `.docs/design-stripe-integration.md` |
| 43 | + |
| 44 | +**Decisions to make:** |
| 45 | +- Stripe product/pricing IDs for Individual ($20/mo) and Enterprise ($300/mo + $100/seat) |
| 46 | +- Webhook endpoint path and event types to handle (`customer.subscription.*`, `invoice.*`, `checkout.session.*`) |
| 47 | +- Database schema for: `users`, `subscriptions`, `seats`, `api_keys`, `usage_records` |
| 48 | +- How "seat" counting works (real-time vs. monthly snapshot) |
| 49 | +- Free tier limits and enforcement layer (rate limiting? feature flags?) |
| 50 | +- Stripe customer → local user mapping |
| 51 | +- Refund/cancellation flow |
| 52 | +- Test environment (Stripe test mode vs. live) |
| 53 | + |
| 54 | +### 1.3 Implementation |
| 55 | + |
| 56 | +**New crate:** `crates/terraphim_billing/` |
| 57 | +**Module structure:** |
| 58 | +``` |
| 59 | +terraphim_billing/ |
| 60 | +├── Cargo.toml |
| 61 | +└── src/ |
| 62 | + ├── lib.rs # public exports |
| 63 | + ├── stripe_client.rs # Stripe API calls (subscribe, cancel, update seats) |
| 64 | + ├── webhook.rs # Stripe webhook handler (axum endpoint) |
| 65 | + ├── models.rs # Subscription, Seat, Invoice, UsageRecord |
| 66 | + ├── enforcement.rs # Feature gates / rate limits per tier |
| 67 | + └── db.rs # PostgreSQL schema + migrations (sqlx) |
| 68 | +``` |
| 69 | + |
| 70 | +**Integrations:** |
| 71 | +- `terraphim_service/src/` — mount webhook endpoint, inject subscription context into request |
| 72 | +- `terraphim_settings/` — `op://` references for `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` |
| 73 | +- `terraphim_usage/` — may extend existing usage tracking for per-user billing |
| 74 | + |
| 75 | +**Key impl steps (TDD):** |
| 76 | +1. `StripeClient::create_checkout_session()` test |
| 77 | +2. `StripeClient::cancel_subscription()` test |
| 78 | +3. `WebhookHandler::handle_event()` — route `customer.subscription.*` events |
| 79 | +4. `SeatManager::count_seats()` / `update_seats()` |
| 80 | +5. `TierEnforcer::check_tier()` feature gate tests |
| 81 | +6. Integration test: full checkout → webhook → DB update flow |
| 82 | + |
| 83 | +### 1.4 Verification |
| 84 | + |
| 85 | +- `cargo test -p terraphim_billing` |
| 86 | +- `cargo clippy -p terraphim_billing` |
| 87 | +- Stripe test mode: trigger full checkout flow end-to-end |
| 88 | +- Webhook idempotency: send duplicate event, verify no double-charge |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## Workstream 2 — better-auth-rust Backend |
| 93 | + |
| 94 | +### 2.1 Research |
| 95 | + |
| 96 | +**Owner:** research agent |
| 97 | +**Artifacts:** `.docs/research-better-auth-rust.md` |
| 98 | + |
| 99 | +**Questions:** |
| 100 | +- Is `better-auth` an existing Rust crate? What does it provide vs. `axum-auth` or `jsonwebtoken`? |
| 101 | +- Gitea OAuth: existing implementation? OAuth1 vs. OAuth2? Token refresh flow? |
| 102 | +- API key model: per-user static keys? Per-agent ephemeral keys? Key rotation? |
| 103 | +- Agent identity: how is an agent authenticated to the orchestrator? Certificate? Token? |
| 104 | +- Relationship to Stripe: does auth happen before or after payment? |
| 105 | + |
| 106 | +**Existing code to study:** |
| 107 | +- `crates/terraphim_onepassword_cli/src/lib.rs` — `SecretLoader` trait |
| 108 | +- `crates/terraphim_service/src/` — existing auth middleware? |
| 109 | +- `crates/terraphim_settings/src/` — twelf layer |
| 110 | +- `.opencode/skills/` — any auth-related skills |
| 111 | + |
| 112 | +### 2.2 Design |
| 113 | + |
| 114 | +**Owner:** architecture agent |
| 115 | +**Artifacts:** `.docs/design-better-auth-rust.md` |
| 116 | + |
| 117 | +**Decisions:** |
| 118 | +- Auth backend architecture: pure Rust library vs. external auth service |
| 119 | +- Gitea OAuth2 flow: authorisation code grant, PKCE, token storage |
| 120 | +- API key format and storage: hashed in DB? AES-encrypted at rest? |
| 121 | +- Agent-to-orchestrator auth: service account tokens, mTLS, or shared secret |
| 122 | +- Integration with existing `SecretLoader` pattern from `terraphim_onepassword_cli` |
| 123 | +- Session management: JWT access tokens + refresh tokens, expiry policy |
| 124 | +- Password recovery / Gitea OAuth only |
| 125 | + |
| 126 | +### 2.3 Implementation |
| 127 | + |
| 128 | +**New crate:** `crates/terraphim_auth/` |
| 129 | +**Module structure:** |
| 130 | +``` |
| 131 | +terraphim_auth/ |
| 132 | +├── Cargo.toml |
| 133 | +└── src/ |
| 134 | + ├── lib.rs |
| 135 | + ├── gitea_oauth.rs # OAuth2 authorisation code flow with Gitea |
| 136 | + ├── api_keys.rs # API key generation, hashing, validation |
| 137 | + ├── agent_identity.rs # Agent credentials (service accounts) |
| 138 | + ├── middleware.rs # axum middleware (authenticate_request) |
| 139 | + ├── jwt.rs # JWT minting + validation |
| 140 | + └── db.rs # Schema: users, api_keys, sessions |
| 141 | +``` |
| 142 | + |
| 143 | +**Integrations:** |
| 144 | +- `crates/terraphim_service/src/` — mount auth routes (`/auth/login`, `/auth/apikey`, `/auth/logout`) |
| 145 | +- `crates/terraphim_onepassword_cli/` — reuse `SecretLoader` for OAuth client secret |
| 146 | +- `crates/terraphim_billing/` — auth must precede billing (user identity from auth) |
| 147 | + |
| 148 | +**Key impl steps (TDD):** |
| 149 | +1. `GiteaOAuthProvider::authorisation_url()` + PKCE test |
| 150 | +2. `GiteaOAuthProvider::exchange_code()` test |
| 151 | +3. `ApiKeyManager::generate()` / `validate()` tests |
| 152 | +4. `AgentCredentials::mint_service_token()` test |
| 153 | +5. `AuthMiddleware::layer()` integration with axum Router |
| 154 | +6. `JwtManager::issue_access_token()` / `refresh()` tests |
| 155 | + |
| 156 | +### 2.4 Verification |
| 157 | + |
| 158 | +- `cargo test -p terraphim_auth` |
| 159 | +- `cargo clippy -p terraphim_auth` |
| 160 | +- E2E: OAuth login flow with Gitea test instance |
| 161 | +- E2E: API key creation → usage → revocation |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## Workstream 3 — Custom Domain terraphim-skills.md |
| 166 | + |
| 167 | +### 3.1 Research |
| 168 | + |
| 169 | +**Owner:** research agent |
| 170 | +**Artifacts:** `.docs/research-custom-domain-skills.md` |
| 171 | + |
| 172 | +**Questions:** |
| 173 | +- What is the "custom domain" exactly? `skills.terraphim.ai`? Subdomain per user/org? |
| 174 | +- How does `gitea_skill_loader.rs` currently work? Can it be extended for domain routing? |
| 175 | +- Is the skill store multi-tenant? Can organisations have private skill forks? |
| 176 | +- CDN / Cloudflare integration already exists (`scripts/add-custom-domains.sh`) — does this help? |
| 177 | + |
| 178 | +**Existing code to study:** |
| 179 | +- `crates/terraphim_orchestrator/src/gitea_skill_loader.rs` — existing remote skill loader |
| 180 | +- `crates/terraphim_orchestrator/src/control_plane/routing.rs` — routing engine |
| 181 | +- `scripts/add-custom-domains.sh` — Cloudflare Pages DNS |
| 182 | +- `data/kg/meridian/product-strategy.md` — skill marketplace notes |
| 183 | + |
| 184 | +### 3.2 Design |
| 185 | + |
| 186 | +**Owner:** architecture agent |
| 187 | +**Artifacts:** `.docs/design-custom-domain-skills.md` |
| 188 | + |
| 189 | +**Decisions:** |
| 190 | +- Domain model: `https://{org}.terraphim.ai/skills/{skill}` vs. `https://skills.terraphim.ai/{org}/{skill}` |
| 191 | +- Skill manifest: `SKILL.md` already exists — what additional metadata needed? (`domain`, `pricing_tier`, `org`, `version`) |
| 192 | +- Custom domain TLS: cert management (Let's Encrypt? Cloudflare origin cert?) |
| 193 | +- Skill versioning: immutable tags? Semantic versioning? |
| 194 | +- Private skills: authentication required to fetch? Org-gated? |
| 195 | + |
| 196 | +### 3.3 Implementation |
| 197 | + |
| 198 | +**Extensions to existing crates:** |
| 199 | +- `crates/terraphim_orchestrator/src/gitea_skill_loader.rs` — add domain-aware loader |
| 200 | +- `crates/terraphim_orchestrator/src/control_plane/routing.rs` — domain routing layer |
| 201 | + |
| 202 | +**New module:** `crates/terraphim_skill_store/` |
| 203 | +``` |
| 204 | +terraphim_skill_store/ |
| 205 | +├── Cargo.toml |
| 206 | +└── src/ |
| 207 | + ├── lib.rs |
| 208 | + ├── manifest.rs # SkillManifest: domain, pricing_tier, org, version |
| 209 | + ├── domain.rs # Custom domain resolution (nginx/Caddy config) |
| 210 | + ├── registry.rs # SkillRegistry: publish, unpublish, list, search |
| 211 | + └── cloudflare.rs # DNS/SSL automation via Cloudflare API |
| 212 | +``` |
| 213 | + |
| 214 | +**Key impl steps (TDD):** |
| 215 | +1. `SkillManifest::parse()` from `SKILL.md` frontmatter test |
| 216 | +2. `DomainResolver::resolve(skill_domain)` test |
| 217 | +3. `SkillRegistry::publish()` / `unpublish()` tests |
| 218 | +4. `CloudflareProvider::add_domain()` integration test |
| 219 | +5. `gitea_skill_loader.rs` — extend with domain-header injection |
| 220 | + |
| 221 | +### 3.4 Verification |
| 222 | + |
| 223 | +- `cargo test -p terraphim_skill_store` |
| 224 | +- `cargo clippy -p terraphim_skill_store` |
| 225 | +- Custom subdomain resolves correctly (e.g., `acme.skills.terraphim.ai`) |
| 226 | +- Private skill requires auth header |
| 227 | + |
| 228 | +--- |
| 229 | + |
| 230 | +## Workstream 4 — Premium Skill Definitions |
| 231 | + |
| 232 | +### 4.1 Research |
| 233 | + |
| 234 | +**Owner:** research agent |
| 235 | +**Artifacts:** `.docs/research-premium-skills.md` |
| 236 | + |
| 237 | +**Questions:** |
| 238 | +- What makes a skill "premium"? Higher LLM cost? Special capabilities? SLA? |
| 239 | +- How do premium skills integrate with Stripe billing? Per-execution? Monthly subscription? |
| 240 | +- Existing skill chain mechanism (`AgentDefinition.skill_chain`) — can it gate skills by tier? |
| 241 | +- Skill marketplace: discovery, rating, versioning — build or integrate? |
| 242 | + |
| 243 | +**Existing code to study:** |
| 244 | +- `skills/*/skill.md` — 5 existing skills (smart-commit, quickwit-search, learning-capture, pre-llm-validate, post-llm-check) |
| 245 | +- `crates/terraphim_orchestrator/src/config.rs` — `AgentDefinition.skill_chain` field |
| 246 | +- `crates/terraphim_usage/src/pricing.rs` — `PricingTable` (could extend with skill pricing) |
| 247 | + |
| 248 | +### 4.2 Design |
| 249 | + |
| 250 | +**Owner:** architecture agent |
| 251 | +**Artifacts:** `.docs/design-premium-skills.md` |
| 252 | + |
| 253 | +**Decisions:** |
| 254 | +- Skill tiers: Free, Pro ($20), Enterprise ($300+). What capabilities per tier? |
| 255 | +- Skill manifest metadata: `tier`, `credits_per_execution`, `monthly_fee` |
| 256 | +- Skill gating: `TierEnforcer::check_tier()` from billing workstream applied to skill chain |
| 257 | +- Premium skill examples: which of the 5 existing skills become premium? |
| 258 | +- Skill bundle: can multiple premium skills be purchased together? |
| 259 | + |
| 260 | +### 4.3 Implementation |
| 261 | + |
| 262 | +**Extensions:** |
| 263 | +- `crates/terraphim_billing/src/enforcement.rs` — extend with `check_skill_tier()` |
| 264 | +- `crates/terraphim_skill_store/src/manifest.rs` — add `tier`, `credits_per_execution` fields |
| 265 | + |
| 266 | +**New module:** `crates/terraphim_skill_premium/` |
| 267 | +``` |
| 268 | +terraphim_skill_premium/ |
| 269 | +├── Cargo.toml |
| 270 | +└── src/ |
| 271 | + ├── lib.rs |
| 272 | + ├── tiers.rs # TierDefinition: Free, Pro, Enterprise; limits per tier |
| 273 | + ├── gating.rs # SkillGate: evaluate skill chain against user tier |
| 274 | + └── registry.rs # PremiumSkillRegistry: metadata for all premium skills |
| 275 | +``` |
| 276 | + |
| 277 | +**Skill definitions to create/update:** |
| 278 | +1. `skills/premium-llm-gateway/skill.md` — Pro-tier LLM gateway with volume discounts |
| 279 | +2. `skills/priority-queue/skill.md` — Enterprise-tier priority agent scheduling |
| 280 | +3. `skills/advanced-analytics/skill.md` — Enterprise-tier usage analytics dashboard |
| 281 | +4. Update existing `skills/post-llm-check/skill.md` — mark as Pro-tier |
| 282 | + |
| 283 | +**Key impl steps (TDD):** |
| 284 | +1. `TierDefinition::from_str("pro")` / `from_str("enterprise")` tests |
| 285 | +2. `SkillGate::evaluate(skill_chain, user_tier)` tests |
| 286 | +3. `PremiumSkillRegistry::register()` / `get_tier()` tests |
| 287 | +4. Integration: `AgentDefinition::validate_skill_chain()` gated by user subscription |
| 288 | + |
| 289 | +### 4.4 Verification |
| 290 | + |
| 291 | +- `cargo test -p terraphim_skill_premium` |
| 292 | +- `cargo clippy -p terraphim_skill_premium` |
| 293 | +- E2E: Pro user can execute Pro skill; Free user blocked with 403 |
| 294 | +- E2E: Enterprise user can execute all skills |
| 295 | + |
| 296 | +--- |
| 297 | + |
| 298 | +## Cross-Cutting Concerns |
| 299 | + |
| 300 | +### Dependency Order |
| 301 | +``` |
| 302 | +Workstream 2 (auth) ──→ Workstream 1 (billing) ──→ Workstream 4 (premium skills) |
| 303 | + │ │ |
| 304 | + └──────────────────────┴──→ Workstream 3 (skill store) ──→ Skill definitions |
| 305 | +``` |
| 306 | + |
| 307 | +**Rationale:** Auth (user identity) needed before billing (who to charge). Billing needed before premium skill gating. Custom domain skill store is largely independent but builds on skill metadata from WS4. |
| 308 | + |
| 309 | +### Shared Crate |
| 310 | + |
| 311 | +**`crates/terraphim_user/`** — shared user model used by all workstreams: |
| 312 | +```rust |
| 313 | +pub enum SubscriptionTier { Free, Pro, Enterprise } |
| 314 | +pub struct User { id, email, tier, seats, created_at, ... } |
| 315 | +pub struct ApiKey { id, user_id, name, hash, created_at, ... } |
| 316 | +``` |
| 317 | + |
| 318 | +### Secrets Management |
| 319 | +All secrets via `op://` references through `terraphim_onepassword_cli::SecretLoader`: |
| 320 | +- `op://TerraphimPlatform/stripe/secret_key` |
| 321 | +- `op://TerraphimPlatform/stripe/webhook_secret` |
| 322 | +- `op://TerraphimPlatform/gitea-oauth/client_secret` |
| 323 | +- `op://TerraphimPlatform/jwt/signing_key` |
| 324 | + |
| 325 | +### Testing Strategy |
| 326 | +- Unit tests for all new crates |
| 327 | +- Integration tests against live services (Stripe test mode, Gitea test instance) |
| 328 | +- Feature flags: `#[cfg(feature = "stripe")]`, `#[cfg(feature = "gitea-oauth")]` |
| 329 | + |
| 330 | +--- |
| 331 | + |
| 332 | +## Phase Gate: Research Complete |
| 333 | + |
| 334 | +Before implementation begins, all four research documents must be approved. Each research document should answer: |
| 335 | +1. What exists today (inventory) |
| 336 | +2. What must be built (gap analysis) |
| 337 | +3. Recommended approach with trade-offs |
| 338 | +4. Risk register |
| 339 | + |
| 340 | +**Gate criterion:** All four `.docs/research-*.md` files exist and reviewed. |
| 341 | + |
| 342 | +--- |
| 343 | + |
| 344 | +## Next Steps (Immediate) |
| 345 | + |
| 346 | +1. Create four research issues on Gitea |
| 347 | +2. Run four parallel research agents |
| 348 | +3. Upon research completion, gate for architecture design |
| 349 | +4. Upon design approval, implement in dependency order |
0 commit comments