rlhf-feedback-loop 0.6.8 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,56 +1,61 @@
1
- # Agentic Feedback StudioThe Veto Layer & RLHF-Ready Dataset Engine
1
+ # RLHF-Ready Feedback LoopAgentic Control Plane & Context Engineering Studio
2
2
 
3
3
  [![CI](https://github.com/IgorGanapolsky/rlhf-feedback-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/IgorGanapolsky/rlhf-feedback-loop/actions/workflows/ci.yml)
4
4
  [![Marketplace Ready](https://img.shields.io/badge/Anthropic_Marketplace-Ready-blue)](docs/ANTHROPIC_MARKETPLACE_STRATEGY.md)
5
- [![Veto Powered](https://img.shields.io/badge/Governance-Veto_Layer-red)](docs/VERIFICATION_EVIDENCE.md)
5
+ [![GEO Optimized](https://img.shields.io/badge/GEO-optimized-orange)](docs/geo-strategy-for-ai-agents.md)
6
6
 
7
- **The operational layer for high-density preference data.** Stop vibe-coding and start context engineering. The Agentic Feedback Studio provides the **Veto Layer** for AI workflows, capturing human feedback to generate **RLHF-ready datasets** and enforce kernel-level guardrails.
7
+ **Stop Vibe Coding. Start Context Engineering.** The RLHF-Ready Feedback Loop is the enterprise-grade **Agentic Control Plane** for AI workflows. We provide the operational layer to capture human preference signals, engineer high-density context packs, and enforce machine-readable guardrails to stop your agents from going "off-script."
8
8
 
9
- ## Why This Matters: From Vibes to Verification (V2V)
10
-
11
- Most AI agents run on "vibes." We provide the infrastructure to convert those vibes into **Hard Evidence** for continuous improvement.
12
-
13
- - **Veto Layer (Governance):** Convert subjective user feedback into non-bypassable architectural constraints (`CLAUDE.md`).
14
- - **RLHF-Ready Datasets:** Automatically generate high-density DPO (Direct Preference Optimization) pairs from real-world agent interactions.
15
- - **Online Bayesian Reward Estimation:** Uses Thompson Sampling to model user preferences in real-time, providing a local "Reward Signal" without heavy training.
9
+ This product captures and structures human feedback data for optimization workflows. It is **RLHF-ready data infrastructure** (not an end-to-end reward-model + RL fine-tuning trainer by itself).
16
10
 
17
11
  ## True Plug-and-Play: Zero-Config Integration
18
12
 
19
- The Feedback Studio is a **Universal Agent Skill**. You can drop it into any repository without manual setup.
13
+ The RLHF Feedback Loop is now a **Universal Agent Skill**. You can drop it into any repository without manual setup.
20
14
 
21
15
  - **Zero-Config Discovery:** Automatically detects project context. If no local `.rlhf/` directory exists, it safely fallbacks to a project-scoped global store in `~/.rlhf/`.
22
- - **Global Skill Installation:** Run one command to make the Studio available to all your agents across all projects.
16
+ - **Global Skill Installation (Optional):** One-command installer is available if you want auto-detection.
17
+ - **Vibe-to-Verification (V2V):** Directly converts subjective "vibes" (thumbs up/down) into verifiable repository rules (`CLAUDE.md`).
18
+
19
+ ### Quick Start (Stable MCP Commands)
20
+
21
+ Add the MCP server directly in your client config:
22
+
23
+ | Platform | Command |
24
+ |----------|---------|
25
+ | **Claude** | `claude mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
26
+ | **Codex** | `codex mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
27
+ | **Gemini** | `gemini mcp add rlhf "npx -y rlhf-feedback-loop serve"` |
28
+ | **Amp** | `amp mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
29
+ | **Cursor** | `cursor mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
23
30
 
24
- ### Quick Start (One Command)
31
+ Optional auto-installer:
25
32
 
26
33
  ```bash
27
- npx rlhf-feedback-loop install
34
+ npx add-mcp rlhf-feedback-loop
28
35
  ```
29
36
 
30
- This will auto-detect your platforms (Claude, Codex, Gemini, Cursor) and install the feedback skill globally.
37
+ - **Stop Regressions:** Automatically convert negative feedback into `CLAUDE.md` / `AGENTS.md` prevention rules.
38
+ - **Preference Data Engineering:** Capture high-density context (rubrics, guardrails, metadata) for DPO training.
39
+ - **Bayesian Scoring:** Use Thompson Sampling to handle evolving user preferences over time.
31
40
 
32
41
  ## Use Cases
33
42
 
34
43
  - **Automated Code Reviews:** Capture PR feedback to enforce team-specific style guides autonomously.
35
- - **Self-Healing Multi-Agent Systems:** Share Veto rules across a swarm of agents to avoid systemic bottlenecks.
36
- - **DPO Dataset Engineering:** Collect proprietary preference data to fine-tune smaller, faster models that perform like GPT-4 on your specific codebase.
44
+ - **Self-Healing Multi-Agent Systems:** Share prevention rules across a swarm of agents to avoid systemic bottlenecks.
45
+ - **Custom LLM Fine-tuning:** Collect proprietary preference data to train smaller, faster models that perform like GPT-4 on your specific codebase.
37
46
 
38
- ## Get Started
47
+ ## Generative Engine Optimization (GEO)
39
48
 
40
- One command. Pick your platform:
49
+ We are optimized for discovery by next-gen AI tools (Claude Code, Gemini CLI, Perplexity). See our [GEO Strategy](docs/geo-strategy-for-ai-agents.md) for how we use **Context-First Publishing** to remain the first-choice recommendation for agent reliability.
41
50
 
42
- | Platform | Install |
43
- |----------|---------|
44
- | **Claude** | `claude mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
45
- | **Codex** | `codex mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
46
- | **Gemini** | `gemini mcp add rlhf "npx -y rlhf-feedback-loop serve"` |
47
- | **Amp** | `amp mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
48
- | **Cursor** | `cursor mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
51
+ ## Get Started
52
+
53
+ Run one `mcp add` command for your client. The server starts on each session and can capture feedback, recall past learnings, and block repeated mistakes.
49
54
 
50
55
  ## How It Works
51
56
 
52
57
  ```
53
- Subjective Signal (Vibe)
58
+ Thumbs up/down
54
59
  |
55
60
  v
56
61
  Capture → JSONL log
@@ -63,14 +68,14 @@ Subjective Signal (Vibe)
63
68
  Good Bad
64
69
  | |
65
70
  v v
66
- Learn Veto Layer (Rule)
71
+ Learn Prevention rule
67
72
  | |
68
73
  v v
69
74
  LanceDB ShieldCortex
70
75
  vectors context packs
71
76
  |
72
77
  v
73
- DPO export → RLHF / Fine-tune your model
78
+ DPO export → fine-tune your model
74
79
  ```
75
80
 
76
81
  All data stored locally as **JSONL** files — fully transparent, fully portable, no vendor lock-in. **LanceDB** indexes memories as vector embeddings for semantic search. **ShieldCortex** assembles context packs so your agent starts each task informed.
@@ -79,15 +84,24 @@ All data stored locally as **JSONL** files — fully transparent, fully portable
79
84
 
80
85
  The open-source package is fully functional and free forever. Cloud Pro is for teams that don't want to self-host.
81
86
 
82
- | | Open Source | Cloud Pro ($10/mo) |
87
+ | | Open Source | Cloud Pro ($49/mo) |
83
88
  |---|---|---|
84
89
  | Feedback capture | Local MCP server | Hosted HTTPS API |
85
90
  | Storage | Your machine | Managed cloud |
86
91
  | DPO export | CLI command | API endpoint |
87
92
  | Setup | `mcp add` one-liner | Provisioned API key |
88
93
  | Team sharing | Manual (share JSONL) | Built-in (shared API) |
94
+ | Support | GitHub Issues | Email |
95
+ | Uptime | You manage | We manage (99.9% SLA) |
96
+
97
+ [Get Cloud Pro](https://buy.stripe.com/bJe14neyU4r4f0leOD3sI02) | [Live API](https://rlhf-feedback-loop-710216278770.us-central1.run.app) | [Verification Evidence](docs/VERIFICATION_EVIDENCE.md)
98
+
99
+ ## Deep Dive
89
100
 
90
- [Get Cloud Pro](https://buy.stripe.com/bJe14neyU4r4f0leOD3sI02) | [Live API](https://rlhf-feedback-loop-710216278770.us-central1.run.app)
101
+ - [API Reference](openapi/openapi.yaml) full OpenAPI spec
102
+ - [Context Engine](docs/CONTEXTFS.md) — multi-agent memory orchestration
103
+ - [Autonomous GitOps](docs/AUTONOMOUS_GITOPS.md) — self-healing CI/CD
104
+ - [Contributing](CONTRIBUTING.md)
91
105
 
92
106
  ## License
93
107
 
@@ -1,12 +1,12 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: RLHF Feedback Loop API
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  description: |
6
6
  Production API for feedback capture, schema-validated memory promotion,
7
7
  prevention rule generation, and DPO export.
8
8
  servers:
9
- - url: http://localhost:8787
9
+ - url: https://rlhf-feedback-loop-710216278770.us-central1.run.app
10
10
  security:
11
11
  - bearerAuth: []
12
12
  components:
@@ -80,6 +80,59 @@ components:
80
80
  type: string
81
81
  approved:
82
82
  type: boolean
83
+ BillingCheckoutRequest:
84
+ type: object
85
+ properties:
86
+ successUrl:
87
+ type: string
88
+ format: uri
89
+ cancelUrl:
90
+ type: string
91
+ format: uri
92
+ customerEmail:
93
+ type: string
94
+ format: email
95
+ installId:
96
+ type: string
97
+ metadata:
98
+ type: object
99
+ additionalProperties:
100
+ type: string
101
+ BillingProvisionRequest:
102
+ type: object
103
+ required: [customerId]
104
+ properties:
105
+ customerId:
106
+ type: string
107
+ installId:
108
+ type: string
109
+ FunnelAnalyticsResponse:
110
+ type: object
111
+ properties:
112
+ totalEvents:
113
+ type: integer
114
+ stageCounts:
115
+ type: object
116
+ properties:
117
+ acquisition:
118
+ type: integer
119
+ activation:
120
+ type: integer
121
+ paid:
122
+ type: integer
123
+ eventCounts:
124
+ type: object
125
+ additionalProperties:
126
+ type: integer
127
+ conversionRates:
128
+ type: object
129
+ properties:
130
+ acquisitionToActivation:
131
+ type: number
132
+ activationToPaid:
133
+ type: number
134
+ acquisitionToPaid:
135
+ type: number
83
136
  paths:
84
137
  /healthz:
85
138
  get:
@@ -113,6 +166,18 @@ paths:
113
166
  description: Aggregated feedback statistics
114
167
  '401':
115
168
  description: Unauthorized
169
+ /v1/analytics/funnel:
170
+ get:
171
+ operationId: getFunnelAnalytics
172
+ responses:
173
+ '200':
174
+ description: Acquisition/activation/paid funnel metrics from append-only ledger
175
+ content:
176
+ application/json:
177
+ schema:
178
+ $ref: '#/components/schemas/FunnelAnalyticsResponse'
179
+ '401':
180
+ description: Unauthorized
116
181
  /v1/intents/catalog:
117
182
  get:
118
183
  operationId: listIntentCatalog
@@ -290,3 +355,60 @@ paths:
290
355
  description: Recent provenance events
291
356
  '401':
292
357
  description: Unauthorized
358
+ /v1/billing/checkout:
359
+ post:
360
+ operationId: createBillingCheckoutSession
361
+ security: []
362
+ requestBody:
363
+ required: false
364
+ content:
365
+ application/json:
366
+ schema:
367
+ $ref: '#/components/schemas/BillingCheckoutRequest'
368
+ responses:
369
+ '200':
370
+ description: Stripe checkout session created
371
+ /v1/billing/usage:
372
+ get:
373
+ operationId: getBillingUsage
374
+ responses:
375
+ '200':
376
+ description: Usage count for authenticated billing key
377
+ '401':
378
+ description: Unauthorized
379
+ /v1/billing/provision:
380
+ post:
381
+ operationId: provisionBillingKey
382
+ requestBody:
383
+ required: true
384
+ content:
385
+ application/json:
386
+ schema:
387
+ $ref: '#/components/schemas/BillingProvisionRequest'
388
+ responses:
389
+ '200':
390
+ description: API key provisioned
391
+ '400':
392
+ description: Missing required customerId
393
+ '401':
394
+ description: Unauthorized
395
+ '403':
396
+ description: Forbidden - requires static RLHF_API_KEY admin token
397
+ /v1/billing/webhook:
398
+ post:
399
+ operationId: stripeBillingWebhook
400
+ security: []
401
+ responses:
402
+ '200':
403
+ description: Webhook accepted
404
+ '400':
405
+ description: Invalid webhook signature or payload
406
+ /v1/billing/github-webhook:
407
+ post:
408
+ operationId: githubMarketplaceWebhook
409
+ security: []
410
+ responses:
411
+ '200':
412
+ description: Webhook accepted
413
+ '400':
414
+ description: Invalid webhook signature or payload
@@ -556,32 +556,70 @@ async function handleRequest(message) {
556
556
  throw new Error(`Unsupported method: ${message.method}`);
557
557
  }
558
558
 
559
- function writeMessage(payload) {
559
+ function writeMessage(payload, transport = 'framed') {
560
560
  const json = JSON.stringify(payload);
561
+ if (transport === 'ndjson') {
562
+ process.stdout.write(`${json}\n`);
563
+ return;
564
+ }
561
565
  process.stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
562
566
  }
563
567
 
568
+ function parseWithTransport(raw, transport) {
569
+ try {
570
+ return JSON.parse(raw);
571
+ } catch (err) {
572
+ err.transport = transport;
573
+ throw err;
574
+ }
575
+ }
576
+
564
577
  let buffer = Buffer.alloc(0);
578
+ let stdioStarted = false;
579
+
580
+ function hasContentLengthPrefix() {
581
+ if (buffer.length === 0) return false;
582
+ const probe = buffer.slice(0, Math.min(buffer.length, 32)).toString('utf8').toLowerCase();
583
+ return 'content-length:'.startsWith(probe) || probe.startsWith('content-length:');
584
+ }
565
585
 
566
586
  function tryReadMessage() {
567
- const headerEnd = buffer.indexOf('\r\n\r\n');
568
- if (headerEnd === -1) return null;
569
-
570
- const headerRaw = buffer.slice(0, headerEnd).toString('utf8');
571
- const match = headerRaw.match(/Content-Length:\s*(\d+)/i);
572
- if (!match) {
573
- buffer = buffer.slice(headerEnd + 4);
574
- return null;
587
+ const headerEndCrLf = buffer.indexOf('\r\n\r\n');
588
+ const headerEndLf = buffer.indexOf('\n\n');
589
+ const hasFramedHeader = headerEndCrLf !== -1 || headerEndLf !== -1;
590
+
591
+ if (hasFramedHeader) {
592
+ const useCrLf = headerEndCrLf !== -1 && (headerEndLf === -1 || headerEndCrLf < headerEndLf);
593
+ const headerEnd = useCrLf ? headerEndCrLf : headerEndLf;
594
+ const separatorLength = useCrLf ? 4 : 2;
595
+ const headerRaw = buffer.slice(0, headerEnd).toString('utf8');
596
+ const match = headerRaw.match(/Content-Length:\s*(\d+)/i);
597
+ if (!match) {
598
+ buffer = buffer.slice(headerEnd + separatorLength);
599
+ return null;
600
+ }
601
+
602
+ const length = Number(match[1]);
603
+ const totalSize = headerEnd + separatorLength + length;
604
+ if (buffer.length < totalSize) return null;
605
+
606
+ const body = buffer.slice(headerEnd + separatorLength, totalSize).toString('utf8');
607
+ buffer = buffer.slice(totalSize);
608
+
609
+ return { message: parseWithTransport(body, 'framed'), transport: 'framed' };
575
610
  }
576
611
 
577
- const length = Number(match[1]);
578
- const totalSize = headerEnd + 4 + length;
579
- if (buffer.length < totalSize) return null;
612
+ // Codex MCP client currently sends newline-delimited JSON during startup.
613
+ if (hasContentLengthPrefix()) return null;
614
+
615
+ const newlineIndex = buffer.indexOf('\n');
616
+ if (newlineIndex === -1) return null;
580
617
 
581
- const body = buffer.slice(headerEnd + 4, totalSize).toString('utf8');
582
- buffer = buffer.slice(totalSize);
618
+ const line = buffer.slice(0, newlineIndex).toString('utf8').trim();
619
+ buffer = buffer.slice(newlineIndex + 1);
620
+ if (!line) return null;
583
621
 
584
- return JSON.parse(body);
622
+ return { message: parseWithTransport(line, 'ndjson'), transport: 'ndjson' };
585
623
  }
586
624
 
587
625
  async function onData(chunk) {
@@ -590,39 +628,60 @@ async function onData(chunk) {
590
628
  while (true) {
591
629
  const message = tryReadMessage();
592
630
  if (!message) return;
631
+ const envelope = message;
632
+ const request = envelope.message;
633
+ const transport = envelope.transport;
593
634
 
594
- if (!Object.prototype.hasOwnProperty.call(message, 'id')) {
635
+ if (!Object.prototype.hasOwnProperty.call(request, 'id')) {
595
636
  continue;
596
637
  }
597
638
 
598
639
  try {
599
- const result = await handleRequest(message);
600
- writeMessage({ jsonrpc: '2.0', id: message.id, result });
640
+ const result = await handleRequest(request);
641
+ writeMessage({ jsonrpc: '2.0', id: request.id, result }, transport);
601
642
  } catch (err) {
602
643
  writeMessage({
603
644
  jsonrpc: '2.0',
604
- id: message.id,
645
+ id: request.id,
605
646
  error: {
606
647
  code: -32603,
607
648
  message: err.message || 'Internal error',
608
649
  },
609
- });
650
+ }, transport);
610
651
  }
611
652
  }
612
653
  }
613
654
 
655
+ function startStdioServer() {
656
+ if (stdioStarted) return;
657
+ stdioStarted = true;
658
+
659
+ // Keep the process alive even if stdin closes (prevents premature exit
660
+ // when launched by MCP clients like Claude Code, Codex, Gemini CLI).
661
+ const keepAlive = setInterval(() => {}, 60_000);
662
+
663
+ process.stdin.resume();
664
+ process.stdin.on('data', (chunk) => {
665
+ onData(chunk).catch((err) => {
666
+ const transport = err && err.transport === 'ndjson' ? 'ndjson' : 'framed';
667
+ writeMessage({ jsonrpc: '2.0', id: null, error: { code: -32603, message: err.message } }, transport);
668
+ });
669
+ });
670
+ process.stdin.on('end', () => {
671
+ // stdin closed — clean up and exit gracefully
672
+ clearInterval(keepAlive);
673
+ });
674
+ }
675
+
614
676
  module.exports = {
615
677
  TOOLS,
616
678
  handleRequest,
617
679
  callTool,
618
680
  resolveSafePath,
619
681
  SAFE_DATA_DIR,
682
+ startStdioServer,
620
683
  };
621
684
 
622
685
  if (require.main === module) {
623
- process.stdin.on('data', (chunk) => {
624
- onData(chunk).catch((err) => {
625
- writeMessage({ jsonrpc: '2.0', id: null, error: { code: -32603, message: err.message } });
626
- });
627
- });
686
+ startStdioServer();
628
687
  }
package/bin/cli.js CHANGED
@@ -14,6 +14,7 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
+ const crypto = require('crypto');
17
18
  const { execSync } = require('child_process');
18
19
 
19
20
  const COMMAND = process.argv[2];
@@ -124,6 +125,7 @@ function setupCursor() {
124
125
 
125
126
  function init() {
126
127
  const rlhfDir = path.join(CWD, '.rlhf');
128
+ const configPath = path.join(rlhfDir, 'config.json');
127
129
 
128
130
  if (!fs.existsSync(rlhfDir)) {
129
131
  fs.mkdirSync(rlhfDir, { recursive: true });
@@ -132,15 +134,28 @@ function init() {
132
134
  console.log('.rlhf/ already exists — updating config');
133
135
  }
134
136
 
137
+ let existingInstallId = null;
138
+ if (fs.existsSync(configPath)) {
139
+ try {
140
+ const existingConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
141
+ if (existingConfig && typeof existingConfig.installId === 'string' && existingConfig.installId.trim()) {
142
+ existingInstallId = existingConfig.installId.trim();
143
+ }
144
+ } catch (_) {
145
+ // Ignore invalid existing config and write a fresh one below.
146
+ }
147
+ }
148
+
135
149
  const config = {
136
150
  version: pkgVersion(),
137
151
  apiUrl: process.env.RLHF_API_URL || 'http://localhost:3000',
138
152
  logPath: '.rlhf/feedback-log.jsonl',
139
153
  memoryPath: '.rlhf/memory-log.jsonl',
154
+ installId: existingInstallId || crypto.randomUUID(),
140
155
  createdAt: new Date().toISOString(),
141
156
  };
142
157
 
143
- fs.writeFileSync(path.join(rlhfDir, 'config.json'), JSON.stringify(config, null, 2) + '\n');
158
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
144
159
  console.log('Wrote .rlhf/config.json');
145
160
 
146
161
  // Always create .mcp.json (project-level MCP config used by Claude, Codex, Cursor)
@@ -189,6 +204,22 @@ function init() {
189
204
  console.log('');
190
205
  console.log(`rlhf-feedback-loop v${pkgVersion()} initialized.`);
191
206
  console.log('Run: npx rlhf-feedback-loop help');
207
+
208
+ try {
209
+ const { appendFunnelEvent } = require(path.join(PKG_ROOT, 'scripts', 'billing'));
210
+ appendFunnelEvent({
211
+ stage: 'acquisition',
212
+ event: 'cli_init_completed',
213
+ evidence: 'cli_init_completed',
214
+ installId: config.installId,
215
+ metadata: {
216
+ cwd: CWD,
217
+ version: config.version,
218
+ },
219
+ });
220
+ } catch (_) {
221
+ // Avoid failing init if telemetry write cannot be performed.
222
+ }
192
223
  }
193
224
 
194
225
  function capture() {
@@ -351,7 +382,8 @@ function serve() {
351
382
 
352
383
  // Start MCP server over stdio
353
384
  const mcpServer = path.join(PKG_ROOT, 'adapters', 'mcp', 'server-stdio.js');
354
- require(mcpServer);
385
+ const { startStdioServer } = require(mcpServer);
386
+ startStdioServer();
355
387
  }
356
388
 
357
389
  function install() {
@@ -442,7 +474,6 @@ switch (COMMAND) {
442
474
  prove();
443
475
  break;
444
476
  case 'start-api':
445
- case 'serve':
446
477
  startApi();
447
478
  break;
448
479
  case 'help':
@@ -1,12 +1,12 @@
1
1
  openapi: 3.1.0
2
2
  info:
3
3
  title: RLHF Feedback Loop API
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  description: |
6
6
  Production API for feedback capture, schema-validated memory promotion,
7
7
  prevention rule generation, and DPO export.
8
8
  servers:
9
- - url: http://localhost:8787
9
+ - url: https://rlhf-feedback-loop-710216278770.us-central1.run.app
10
10
  security:
11
11
  - bearerAuth: []
12
12
  components:
@@ -80,6 +80,59 @@ components:
80
80
  type: string
81
81
  approved:
82
82
  type: boolean
83
+ BillingCheckoutRequest:
84
+ type: object
85
+ properties:
86
+ successUrl:
87
+ type: string
88
+ format: uri
89
+ cancelUrl:
90
+ type: string
91
+ format: uri
92
+ customerEmail:
93
+ type: string
94
+ format: email
95
+ installId:
96
+ type: string
97
+ metadata:
98
+ type: object
99
+ additionalProperties:
100
+ type: string
101
+ BillingProvisionRequest:
102
+ type: object
103
+ required: [customerId]
104
+ properties:
105
+ customerId:
106
+ type: string
107
+ installId:
108
+ type: string
109
+ FunnelAnalyticsResponse:
110
+ type: object
111
+ properties:
112
+ totalEvents:
113
+ type: integer
114
+ stageCounts:
115
+ type: object
116
+ properties:
117
+ acquisition:
118
+ type: integer
119
+ activation:
120
+ type: integer
121
+ paid:
122
+ type: integer
123
+ eventCounts:
124
+ type: object
125
+ additionalProperties:
126
+ type: integer
127
+ conversionRates:
128
+ type: object
129
+ properties:
130
+ acquisitionToActivation:
131
+ type: number
132
+ activationToPaid:
133
+ type: number
134
+ acquisitionToPaid:
135
+ type: number
83
136
  paths:
84
137
  /healthz:
85
138
  get:
@@ -113,6 +166,18 @@ paths:
113
166
  description: Aggregated feedback statistics
114
167
  '401':
115
168
  description: Unauthorized
169
+ /v1/analytics/funnel:
170
+ get:
171
+ operationId: getFunnelAnalytics
172
+ responses:
173
+ '200':
174
+ description: Acquisition/activation/paid funnel metrics from append-only ledger
175
+ content:
176
+ application/json:
177
+ schema:
178
+ $ref: '#/components/schemas/FunnelAnalyticsResponse'
179
+ '401':
180
+ description: Unauthorized
116
181
  /v1/intents/catalog:
117
182
  get:
118
183
  operationId: listIntentCatalog
@@ -290,3 +355,60 @@ paths:
290
355
  description: Recent provenance events
291
356
  '401':
292
357
  description: Unauthorized
358
+ /v1/billing/checkout:
359
+ post:
360
+ operationId: createBillingCheckoutSession
361
+ security: []
362
+ requestBody:
363
+ required: false
364
+ content:
365
+ application/json:
366
+ schema:
367
+ $ref: '#/components/schemas/BillingCheckoutRequest'
368
+ responses:
369
+ '200':
370
+ description: Stripe checkout session created
371
+ /v1/billing/usage:
372
+ get:
373
+ operationId: getBillingUsage
374
+ responses:
375
+ '200':
376
+ description: Usage count for authenticated billing key
377
+ '401':
378
+ description: Unauthorized
379
+ /v1/billing/provision:
380
+ post:
381
+ operationId: provisionBillingKey
382
+ requestBody:
383
+ required: true
384
+ content:
385
+ application/json:
386
+ schema:
387
+ $ref: '#/components/schemas/BillingProvisionRequest'
388
+ responses:
389
+ '200':
390
+ description: API key provisioned
391
+ '400':
392
+ description: Missing required customerId
393
+ '401':
394
+ description: Unauthorized
395
+ '403':
396
+ description: Forbidden - requires static RLHF_API_KEY admin token
397
+ /v1/billing/webhook:
398
+ post:
399
+ operationId: stripeBillingWebhook
400
+ security: []
401
+ responses:
402
+ '200':
403
+ description: Webhook accepted
404
+ '400':
405
+ description: Invalid webhook signature or payload
406
+ /v1/billing/github-webhook:
407
+ post:
408
+ operationId: githubMarketplaceWebhook
409
+ security: []
410
+ responses:
411
+ '200':
412
+ description: Webhook accepted
413
+ '400':
414
+ description: Invalid webhook signature or payload