thumbgate 1.27.12 → 1.27.13

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.
Files changed (132) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.well-known/llms.txt +2 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +2 -4
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/mcp/server-stdio.js +1 -1
  7. package/adapters/opencode/opencode.json +1 -1
  8. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  9. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  10. package/bin/cli.js +78 -259
  11. package/config/gate-templates.json +0 -228
  12. package/config/gates/claim-verification.json +0 -18
  13. package/package.json +35 -25
  14. package/public/assets/brand/thumbgate-logo-transparent.svg +22 -0
  15. package/public/assets/brand/thumbgate-mark-inline-v3.svg +19 -0
  16. package/public/assets/brand/thumbgate-mark.svg +11 -5
  17. package/public/blog.html +0 -30
  18. package/public/brand/thumbgate-mark.svg +9 -5
  19. package/public/chatgpt-app.html +2 -2
  20. package/public/compare.html +2 -1
  21. package/public/dashboard.html +1 -1
  22. package/public/federal.html +1 -1
  23. package/public/index.html +95 -216
  24. package/public/learn.html +59 -35
  25. package/public/lessons.html +1 -1
  26. package/public/numbers.html +2 -2
  27. package/public/pro.html +7 -7
  28. package/scripts/aws-blocks-guardrails.js +228 -0
  29. package/scripts/cli-schema.js +22 -10
  30. package/scripts/dashboard-chat.js +2 -1
  31. package/scripts/document-intake.js +1 -49
  32. package/scripts/durability/step.js +3 -3
  33. package/scripts/gate-stats.js +5 -11
  34. package/scripts/gates-engine.js +0 -49
  35. package/scripts/gemini-embedding-policy.js +2 -1
  36. package/scripts/hook-stop-anti-claim.js +116 -184
  37. package/scripts/hosted-config.js +0 -12
  38. package/scripts/lesson-search.js +1 -15
  39. package/scripts/llm-client.js +187 -5
  40. package/scripts/plausible-domain-config.js +3 -1
  41. package/scripts/seo-gsd.js +240 -1
  42. package/scripts/tool-registry.js +2 -2
  43. package/scripts/vector-store.js +44 -0
  44. package/scripts/workspace-evolver.js +62 -2
  45. package/src/api/server.js +340 -131
  46. package/public/assets/brand/thumbgate-mark-inline.svg +0 -15
  47. package/public/compare/adopt-ai.html +0 -219
  48. package/public/compare/agentix-labs.html +0 -197
  49. package/public/compare/ai-experience-orchestration.html +0 -216
  50. package/public/compare/anthropic-claude-for-legal.html +0 -260
  51. package/public/compare/anthropic-containment.html +0 -280
  52. package/public/compare/arcade.html +0 -175
  53. package/public/compare/arcjet.html +0 -239
  54. package/public/compare/bumblebee.html +0 -307
  55. package/public/compare/claude-code-hooks.html +0 -294
  56. package/public/compare/databricks-unity-ai-gateway.html +0 -215
  57. package/public/compare/fallow.html +0 -351
  58. package/public/compare/heidi.html +0 -233
  59. package/public/compare/mem0.html +0 -342
  60. package/public/compare/oak-and-sparrow-gatekeeper.html +0 -289
  61. package/public/compare/rein.html +0 -236
  62. package/public/compare/sigmashake.html +0 -256
  63. package/public/compare/speclock.html +0 -342
  64. package/public/guides/agent-harness-optimization.html +0 -342
  65. package/public/guides/agentic-web-governance.html +0 -406
  66. package/public/guides/ai-agent-governance-sprint.html +0 -415
  67. package/public/guides/ai-agent-pre-action-approval-gates.html +0 -401
  68. package/public/guides/ai-agent-workflow-migration-checklist.html +0 -392
  69. package/public/guides/ai-deployment-readiness.html +0 -415
  70. package/public/guides/ai-mode-ads-agent-governance.html +0 -401
  71. package/public/guides/ai-search-topical-presence.html +0 -342
  72. package/public/guides/autoresearch-agent-safety.html +0 -342
  73. package/public/guides/background-agent-governance.html +0 -358
  74. package/public/guides/best-tools-stop-ai-agents-breaking-production.html +0 -363
  75. package/public/guides/browser-automation-safety.html +0 -342
  76. package/public/guides/chatgpt-ads-trust.html +0 -353
  77. package/public/guides/claude-code-feedback.html +0 -339
  78. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  79. package/public/guides/claude-code-skills-guardrails.html +0 -343
  80. package/public/guides/claude-desktop.html +0 -356
  81. package/public/guides/code-knowledge-graph-guardrails.html +0 -365
  82. package/public/guides/codex-cli-guardrails.html +0 -339
  83. package/public/guides/cursor-agent-guardrails.html +0 -339
  84. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  85. package/public/guides/database-agent-safety.html +0 -406
  86. package/public/guides/deepseek-v4-runtime-guardrails.html +0 -346
  87. package/public/guides/developer-machine-supply-chain-guardrails.html +0 -358
  88. package/public/guides/gcp-mcp-guardrails.html +0 -147
  89. package/public/guides/gemini-cli-feedback-memory.html +0 -339
  90. package/public/guides/gpt-5-5-model-evaluation.html +0 -358
  91. package/public/guides/internal-ai-engineering-stack-guardrails.html +0 -348
  92. package/public/guides/long-running-agent-context-management.html +0 -346
  93. package/public/guides/mcp-tool-governance.html +0 -401
  94. package/public/guides/multica-thumbgate-setup.html +0 -134
  95. package/public/guides/native-messaging-host-security.html +0 -342
  96. package/public/guides/policy-engine-pre-action-gates.html +0 -346
  97. package/public/guides/pre-action-checks.html +0 -342
  98. package/public/guides/pretooluse-hooks-vs-advisory-prompt-rules.html +0 -342
  99. package/public/guides/prompt-tricks-to-workflow-rules.html +0 -365
  100. package/public/guides/proxy-pointer-rag-guardrails.html +0 -352
  101. package/public/guides/rag-precision-tuning-guardrails.html +0 -352
  102. package/public/guides/reasoning-compression-guardrails.html +0 -346
  103. package/public/guides/relational-knowledge-ai-recommendations.html +0 -342
  104. package/public/guides/roo-code-alternative-cline.html +0 -339
  105. package/public/guides/semantic-programmatic-seo-guardrails.html +0 -352
  106. package/public/guides/seo-agent-skills-guardrails.html +0 -344
  107. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -342
  108. package/public/learn/ac-dc-runtime-enforcement.html +0 -277
  109. package/public/learn/agent-harness-pattern.html +0 -181
  110. package/public/learn/agent-identity-connector-governance.html +0 -146
  111. package/public/learn/agent-swarms-shared-gates.html +0 -173
  112. package/public/learn/agentic-enterprise-context-brain.html +0 -117
  113. package/public/learn/agentic-os-team-governance.html +0 -146
  114. package/public/learn/ai-agent-governance.html +0 -158
  115. package/public/learn/ai-agent-persistent-memory.html +0 -211
  116. package/public/learn/anthropomorphic-claim-gates.html +0 -180
  117. package/public/learn/background-agent-control-layer.html +0 -184
  118. package/public/learn/claude-code-goal-with-rubrics.html +0 -205
  119. package/public/learn/codex-role-plugins-need-governance.html +0 -125
  120. package/public/learn/cost-aware-agent-gate-routing.html +0 -173
  121. package/public/learn/databricks-unity-ai-gateway-runtime-governance.html +0 -157
  122. package/public/learn/deterministic-agent-workflows.html +0 -185
  123. package/public/learn/feedback-loop-vs-decision-layer.html +0 -283
  124. package/public/learn/from-prototype-to-production.html +0 -223
  125. package/public/learn/learn.css +0 -51
  126. package/public/learn/mcp-pre-action-checks-explained.html +0 -172
  127. package/public/learn/pretix-stripe-connect-marketplaces.html +0 -161
  128. package/public/learn/regulated-agent-execution-boundary.html +0 -196
  129. package/public/learn/spec-driven-development.html +0 -168
  130. package/public/learn/stop-ai-agent-force-push.html +0 -134
  131. package/public/learn/vibe-coding-safety-net.html +0 -142
  132. package/scripts/reddit-browser-notification-watch.js +0 -230
@@ -1,223 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>From git init to v1.17.0 in 70 days: an honest ThumbGate build log — ThumbGate</title>
7
- <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
8
- <meta name="description" content="70 days, 112+ commits, 17 minor releases, ~6,000 npm downloads, $0 customer revenue. What I learned shipping ThumbGate from a one-line repo init to a live production stack at thumbgate.ai.">
9
- <meta name="keywords" content="ThumbGate, build log, AI agent guardrails, pre-action gates, indie SaaS, npm package, Railway deploy, solo founder, AI coding agents, prototype to production">
10
- <meta property="og:title" content="From git init to v1.17.0 in 70 days: an honest ThumbGate build log">
11
- <meta property="og:description" content="The unedited story of taking ThumbGate from a one-line repo init to a live production stack — including what's working, what isn't, and the part where revenue is still zero.">
12
- <meta property="og:type" content="article">
13
- <meta property="og:url" content="https://thumbgate.ai/learn/from-prototype-to-production">
14
- <meta property="og:image" content="https://thumbgate.ai/og.png">
15
- <meta name="twitter:card" content="summary_large_image">
16
- <link rel="canonical" href="https://thumbgate.ai/learn/from-prototype-to-production">
17
-
18
- <script type="application/ld+json">
19
- {
20
- "@context": "https://schema.org",
21
- "@type": "TechArticle",
22
- "headline": "From git init to v1.17.0 in 70 days: an honest ThumbGate build log",
23
- "description": "70 days, 17 minor releases, 6k npm downloads, $0 real revenue. What I learned shipping ThumbGate from a repo init to production.",
24
- "author": {
25
- "@type": "Person",
26
- "name": "Igor Ganapolsky",
27
- "url": "https://github.com/IgorGanapolsky"
28
- },
29
- "publisher": {
30
- "@type": "Organization",
31
- "name": "ThumbGate",
32
- "url": "https://thumbgate.ai"
33
- },
34
- "datePublished": "2026-05-12",
35
- "dateModified": "2026-05-12",
36
- "mainEntityOfPage": "https://thumbgate.ai/learn/from-prototype-to-production",
37
- "about": [
38
- {"@type": "Thing", "name": "indie SaaS build log"},
39
- {"@type": "Thing", "name": "AI agent guardrails"},
40
- {"@type": "Thing", "name": "shipping in public"}
41
- ]
42
- }
43
- </script>
44
-
45
- <link rel="stylesheet" href="/learn/learn.css">
46
- <style>
47
- .timeline { border-left: 2px solid var(--border); padding-left: 1.2rem; margin: 1.5rem 0; }
48
- .timeline-item { margin-bottom: 1.2rem; position: relative; }
49
- .timeline-item::before { content: ''; position: absolute; left: -1.5rem; top: 0.5rem; width: 10px; height: 10px; border-radius: 50%; background: var(--cyan); }
50
- .timeline-date { color: var(--cyan); font-family: ui-monospace, monospace; font-size: 0.85rem; }
51
- .timeline-title { font-weight: 600; margin: 0.1rem 0; }
52
- .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.8rem; margin: 1.5rem 0; }
53
- .stat { background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 8px; padding: 0.8rem; }
54
- .stat-num { font-family: ui-monospace, monospace; color: var(--green); font-size: 1.4rem; font-weight: 600; }
55
- .stat-label { color: var(--muted); font-size: 0.8rem; margin-top: 0.2rem; }
56
- .lesson { border-left: 3px solid var(--green); padding: 0.6rem 0.9rem; margin: 1rem 0; background: rgba(0,255,128,0.04); }
57
- .honest { border-left: 3px solid #f59e0b; padding: 0.6rem 0.9rem; margin: 1rem 0; background: rgba(245,158,11,0.06); }
58
- </style>
59
- </head>
60
- <body>
61
-
62
- <nav>
63
- <a href="/" class="brand"><img src="/assets/brand/thumbgate-mark-inline.svg" alt="ThumbGate" class="logo-mark" width="28" height="28"><span class="logo-text">ThumbGate</span></a>
64
- <a href="/guide">Setup Guide</a>
65
- <a href="/learn">Learn</a>
66
- <a href="/dashboard">Dashboard</a>
67
- <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
68
- </nav>
69
-
70
- <main>
71
-
72
- <h1>From <code>git init</code> to v1.17.0 in 70 days: an honest ThumbGate build log</h1>
73
- <p class="meta">Igor Ganapolsky · May 12, 2026 · 8 min read</p>
74
-
75
- <p>On <strong>March 3</strong>, the first commit on this repo was three words: <code>chore: initialize repository</code>. Today the live build at <a href="https://thumbgate.ai/health">thumbgate.ai/health</a> reports <code>"version":"1.17.0"</code>. In between, 70 days, 112 commits on the visible branch, 60+ tagged releases, ~6,000 npm downloads, and one expensive lesson: <em>shipping is not the same as selling</em>.</p>
76
-
77
- <p>This is the unedited build log — the architecture choices that held, the ones that didn't, and the part where revenue is still zero.</p>
78
-
79
- <div class="stats">
80
- <div class="stat"><div class="stat-num">70</div><div class="stat-label">days, init → production</div></div>
81
- <div class="stat"><div class="stat-num">112+</div><div class="stat-label">commits, this branch</div></div>
82
- <div class="stat"><div class="stat-num">17</div><div class="stat-label">minor releases (v1.0 → v1.17)</div></div>
83
- <div class="stat"><div class="stat-num">~6k</div><div class="stat-label">npm downloads, last 30d</div></div>
84
- <div class="stat"><div class="stat-num">15</div><div class="stat-label">live products in Stripe</div></div>
85
- <div class="stat"><div class="stat-num">$0</div><div class="stat-label">cold-traffic revenue</div></div>
86
- </div>
87
-
88
- <h2>The hypothesis</h2>
89
-
90
- <p>AI coding agents (Claude Code, Cursor, Codex, Aider, Cline) burn money making the same mistake twice. Force-pushing to <code>main</code>. Editing <code>.env</code> without checking what's already in it. Claiming "deployed" without curling the health endpoint. Calling shell with un-escaped user input.</p>
91
-
92
- <p>If you've watched an agent retry the same broken patch four times in a row, you know exactly what I mean.</p>
93
-
94
- <p>The hypothesis: <strong>capture the moment of failure, distill it into a rule, and gate the next attempt <em>before</em> the tool call fires</strong>. Not a linter (those run too late). Not a post-action review (the damage is done). A pre-action gate with an opinion.</p>
95
-
96
- <h2>The timeline</h2>
97
-
98
- <div class="timeline">
99
- <div class="timeline-item">
100
- <div class="timeline-date">2026-03-03</div>
101
- <div class="timeline-title">Repo init. First commit: <code>bootstrap OSS RLHF feedback system</code>.</div>
102
- <p>The original framing was "RLHF for coding agents." It was wrong — what I was actually building was context engineering plus enforcement. The pivot took weeks.</p>
103
- </div>
104
-
105
- <div class="timeline-item">
106
- <div class="timeline-date">2026-03-06</div>
107
- <div class="timeline-title">First release: v0.6.8. The version starts at 0.6 because there's a private prototype that predates the public repo.</div>
108
- </div>
109
-
110
- <div class="timeline-item">
111
- <div class="timeline-date">2026-03-09 → 2026-04-02</div>
112
- <div class="timeline-title">~40 patch releases. The architecture stabilizes.</div>
113
- <p>SQLite + FTS5 for the lesson database (because it ships in the npm package and survives no-network installs). LanceDB for vectors. Thompson Sampling for which gates to fire when. ContextFS for context assembly. <a href="/learn/agent-harness-pattern">The agent harness pattern</a> as the mental model.</p>
114
- </div>
115
-
116
- <div class="timeline-item">
117
- <div class="timeline-date">2026-04-08</div>
118
- <div class="timeline-title">v1.0.0 cut. The OSS package ships on npm as <code>thumbgate</code>.</div>
119
- <p>The same day, v1.1.0 lands the HuggingFace export. The cadence is "release on green CI, don't wait."</p>
120
- </div>
121
-
122
- <div class="timeline-item">
123
- <div class="timeline-date">2026-04-20</div>
124
- <div class="timeline-title">npm downloads peak at 1,334 in a single day.</div>
125
- <p>That spike was a Reddit post, not paid traffic. Organic interest exists. Conversion to paid does not, yet.</p>
126
- </div>
127
-
128
- <div class="timeline-item">
129
- <div class="timeline-date">2026-04-21</div>
130
- <div class="timeline-title">X/Twitter retired from active distribution.</div>
131
- <p>Active outbound moved to Reddit, LinkedIn, Threads, Bluesky, Instagram, YouTube. All routed through one social-publishing layer (Zernio) so I'm not maintaining six token rotations.</p>
132
- </div>
133
-
134
- <div class="timeline-item">
135
- <div class="timeline-date">2026-05-11</div>
136
- <div class="timeline-title">v1.17.0 ships. Free tier opens up: unlimited feedback captures, 5 active prevention rules (was 3 lifetime + 1 rule).</div>
137
- <p>The hard wall on the free tier was killing habit formation. Daily-use lane is now open; Pro gates the dashboard, recall, lesson search, and DPO export.</p>
138
- </div>
139
-
140
- <div class="timeline-item">
141
- <div class="timeline-date">2026-05-12 (today)</div>
142
- <div class="timeline-title">Stripe surface fully reconciled. 15 active products, 14 customer-facing payment links, zero duplicates, every product carries the correct tier logo. Dashboard live.</div>
143
- </div>
144
- </div>
145
-
146
- <h2>What's running in production</h2>
147
-
148
- <ul>
149
- <li><strong>npm package <code>thumbgate</code></strong> — the public shell. CLI, hook installer, adapter configs, JSON schemas. Node ≥18.18, MIT.</li>
150
- <li><strong>Railway-hosted API</strong> at <code>thumbgate.ai</code> — health, dashboard, checkout intent, billing summary. Docker rebuild on every push to <code>main</code> (2–5 min). Verified post-deploy by curling <code>/health</code> and grepping the version string.</li>
151
- <li><strong>Stripe</strong> — 15 active products (Pro $19/mo + $149/yr; Team $49/seat/mo; plus 13 funnel/diagnostic SKUs from $1 to $1,500). All carry the canonical ThumbGate logo. Checkout sessions verified to mint live <code>cs_live_</code> IDs.</li>
152
- <li><strong>SQLite + FTS5 lesson DB</strong> — ships in the package, runs on-device, never phones home.</li>
153
- <li><strong>LanceDB vector index</strong> — for semantic lesson recall.</li>
154
- <li><strong>Plausible analytics</strong> — privacy-respecting, no cookie banner, no IP logging.</li>
155
- <li><strong>Two-repo split</strong>: public <code>ThumbGate</code> (this) is the thin shell; <code>ThumbGate-Core</code> (private) holds the ranking, synthesis, billing intelligence, and org-wide telemetry. Wire protocol between them, never <code>require()</code>.</li>
156
- </ul>
157
-
158
- <h2>Five lessons I'd give my March-3 self</h2>
159
-
160
- <div class="lesson">
161
- <strong>1. A deployment-verification gate beats a memory note.</strong> I told myself "always curl <code>/health</code> after merge" for weeks. It didn't stick. The fix was a hard rule in <code>CLAUDE.md</code> that blocks the words "done", "deployed", "live", or "shipped" until two grep commands return non-empty output. The day that gate landed was the day I stopped lying to myself about ship status.
162
- </div>
163
-
164
- <div class="lesson">
165
- <strong>2. Behavioral rules only work at ZERO/ALWAYS thresholds.</strong> "1 in 5 social posts should mention the product" silently degrades to "every post mentions the product" because the LLM can't count across sessions. Write absolutes: <em>never</em> auto-post replies, <em>always</em> show <code>gh pr view</code> output before claiming done. The middle ground does not exist.
166
- </div>
167
-
168
- <div class="lesson">
169
- <strong>3. Memory and instructions decay together.</strong> When you change a rule in <code>CLAUDE.md</code>, also update the lesson DB entry or prevention rule that contradicts it. Otherwise the agent follows stale memory over the new instruction. Memory always wins ties.
170
- </div>
171
-
172
- <div class="lesson">
173
- <strong>4. Fix-on-fix commits are a systemic-failure signal.</strong> If a bug takes 3+ commit attempts to land, stop pushing. Read the platform docs. The five-commit CSS chase that turned out to be one line of <code>scroll-snap-type: none</code> on mobile cost me a full afternoon of CI minutes and trust.
174
- </div>
175
-
176
- <div class="lesson">
177
- <strong>5. The shape of "production" is fractal.</strong> "Production" was running by week 2 (Railway, dashboard, npm). "Production-ready for paying customers" took until week 10 (clean Stripe, verified deploy gate, kill-stale-payment-links pass, logo on every product). The first one ships in a weekend. The second one is the actual job.
178
- </div>
179
-
180
- <h2>The part that's still broken</h2>
181
-
182
- <div class="honest">
183
- <strong>Real customer revenue is $0.</strong> Excluding test transactions, no cold-traffic visitor has ever paid for ThumbGate Pro or Team. The npm download trend is real (6k in 30 days), the landing page conversion is not. 50 recent Stripe Checkout sessions started; zero completed.
184
- </div>
185
-
186
- <p>This is honest because pretending otherwise would be the exact kind of "live revenue claim" the deploy-verification gate exists to stop. The product works. The funnel doesn't yet. That's a distribution problem, not a product-doesn't-work problem — but it's still a problem.</p>
187
-
188
- <p>The next 30 days are about three things:</p>
189
-
190
- <ol>
191
- <li><strong>Friction-free first-gate-fires.</strong> If <code>npx thumbgate init</code> doesn't surface a real, named gate inside 60 seconds on a real codebase, install-to-aha is broken. Measuring this with anonymous telemetry on the install flow.</li>
192
- <li><strong>Story-driven distribution.</strong> Build logs (like this one) and gate-of-the-week posts. Specific war stories beat feature lists. Verified by Plausible referrer reports, not vibes.</li>
193
- <li><strong>Refund-rate triage on the funnel SKUs.</strong> The $1, $5, $19, $49 quick-read tier exists to validate willingness-to-pay at low friction. If those convert, the $1,500 Workflow Hardening Sprint becomes a real consulting funnel.</li>
194
- </ol>
195
-
196
- <h2>The tools I shipped this on</h2>
197
-
198
- <p>Solo founder, no team. Stack: <strong>Claude Code</strong> as the primary engineer (CTO mode, autonomous PR/merge/deploy when CI is green); <strong>Cursor</strong> for ad-hoc edits; <strong>Railway</strong> for hosting; <strong>Stripe</strong> for billing; <strong>Resend</strong> for transactional email; <strong>Plausible</strong> for analytics; <strong>Zernio</strong> for cross-platform social publishing.</p>
199
-
200
- <p>The single highest-ROI choice was treating Claude Code as a CTO with a strict <code>CLAUDE.md</code> contract — branch from <code>main</code>, push, PR, wait for green CI, merge, verify, deploy. The model handles the work; the gate file handles the discipline.</p>
201
-
202
- <h2>Try it</h2>
203
-
204
- <p>One-line install:</p>
205
-
206
- <pre><code>npx thumbgate init</code></pre>
207
-
208
- <p>You'll have a working pre-action gate in your AI coding agent inside 60 seconds, or the install is broken and I want to hear about it.</p>
209
-
210
- <p>If you've been burned by an AI agent doing something it shouldn't have, the <a href="/checkout/pro?utm_source=blog&amp;utm_medium=prototype_to_production&amp;utm_campaign=build_log">Pro tier</a> is $19/mo and adds the dashboard, recall, and lesson search. Or just install the free version, get a feel for it, and decide later. The whole thing is MIT.</p>
211
-
212
- <p>I'll write the next build log at v2.0.0 — or when the first cold-traffic customer pays, whichever comes first.</p>
213
-
214
- <p class="meta">— Igor</p>
215
-
216
- <hr>
217
-
218
- <p class="meta">More build logs: <a href="/learn/agent-harness-pattern">The agent harness pattern</a> · <a href="/learn/mcp-pre-action-checks-explained">MCP pre-action checks explained</a> · <a href="/learn/stop-ai-agent-force-push">Stop AI agents from force-pushing</a> · <a href="/learn/vibe-coding-safety-net">A safety net for vibe-coding</a></p>
219
-
220
- </main>
221
-
222
- </body>
223
- </html>
@@ -1,51 +0,0 @@
1
- /* ThumbGate Learn Hub — shared styles for /learn pages */
2
- *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
3
- :root {
4
- --bg: #0a0a0b; --bg-card: #161618; --bg-raised: #111113;
5
- --border: #222225; --text: #e8e8ec; --muted: #8b8b94;
6
- --cyan: #22d3ee; --green: #34d399; --red: #f87171;
7
- }
8
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); line-height: 1.7; }
9
- .container { max-width: 700px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }
10
- nav { padding: 1rem 2rem; border-bottom: 1px solid var(--border); display: flex; gap: 1.5rem; align-items: center; }
11
- nav a { color: var(--muted); text-decoration: none; font-size: 0.9rem; }
12
- nav a:hover { color: var(--cyan); }
13
- nav .brand { color: var(--text); font-weight: 700; font-size: 1.1rem; display: inline-flex; align-items: center; gap: 8px; text-decoration: none; }
14
- nav .brand .logo-mark { width: 28px; height: 28px; display: block; }
15
- h1 { font-size: 2rem; line-height: 1.2; margin: 2rem 0 1rem; }
16
- h2 { font-size: 1.4rem; margin: 2.5rem 0 0.75rem; color: var(--cyan); }
17
- h3 { font-size: 1.1rem; margin: 1.5rem 0 0.5rem; }
18
- p, li { margin-bottom: 0.75rem; }
19
- ul, ol { padding-left: 1.5rem; }
20
- code { background: #1a1a1e; padding: 0.15em 0.4em; border-radius: 4px; font-size: 0.9em; color: var(--cyan); font-family: 'SF Mono', 'Cascadia Code', 'JetBrains Mono', Consolas, monospace; }
21
- pre { background: var(--bg-raised); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
22
- pre code { background: none; padding: 0; color: var(--text); }
23
- .breadcrumb { font-size: 0.85rem; color: var(--muted); margin-bottom: 0.5rem; }
24
- .breadcrumb a { color: var(--cyan); text-decoration: none; }
25
- .breadcrumb a:hover { text-decoration: underline; }
26
- .callout { background: var(--bg-card); border-left: 3px solid var(--cyan); padding: 1rem 1.25rem; border-radius: 0 8px 8px 0; margin: 1.5rem 0; }
27
- .callout-red { border-left-color: var(--red); }
28
- .callout-green { border-left-color: var(--green); }
29
- .cta-box { margin-top: 3rem; padding: 2rem; background: var(--bg-raised); border: 1px solid var(--border); border-radius: 12px; text-align: center; }
30
- .cta-box p { color: var(--muted); }
31
- .cta-install { display: inline-block; background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 10px 20px; font-family: 'SF Mono', Consolas, monospace; font-size: 0.95rem; color: var(--cyan); margin-top: 1rem; }
32
- .cta-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-top: 1.1rem; }
33
- .cta-link { display: inline-flex; align-items: center; justify-content: center; min-height: 42px; padding: 0.65rem 0.9rem; border-radius: 8px; background: var(--cyan); color: #031114; text-decoration: none; font-weight: 700; }
34
- .cta-link:hover { opacity: 0.9; text-decoration: none; }
35
- .cta-link-secondary { background: transparent; color: var(--text); border: 1px solid var(--border); }
36
- .cta-link-secondary:hover { border-color: var(--cyan); color: var(--cyan); }
37
- .related { margin-top: 2rem; }
38
- .related a { color: var(--cyan); text-decoration: none; display: block; margin-bottom: 0.5rem; }
39
- .related a:hover { text-decoration: underline; }
40
- @media (max-width: 700px) { h1 { font-size: 1.5rem; } .container { padding: 1.5rem 1rem 3rem; } }
41
-
42
- /* TL;DR box — appears right after h1 to hook skimmers */
43
- .tldr { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.25rem; margin: 1rem 0 2rem; font-size: 0.95rem; }
44
- .tldr strong { color: var(--cyan); }
45
- /* Sticky bottom CTA bar */
46
- .sticky-cta { position: fixed; bottom: 0; left: 0; right: 0; background: var(--bg-raised); border-top: 1px solid var(--border); padding: 10px 0; text-align: center; z-index: 100; display: flex; align-items: center; justify-content: center; gap: 16px; font-size: 0.9rem; }
47
- .sticky-cta code { background: var(--bg-card); padding: 4px 12px; border-radius: 6px; color: var(--cyan); font-family: 'SF Mono', Consolas, monospace; font-size: 0.85rem; border: 1px solid var(--border); }
48
- .sticky-cta a { color: var(--cyan); text-decoration: none; font-weight: 600; }
49
- .sticky-cta a:hover { text-decoration: underline; }
50
- /* Add padding-bottom to body so sticky bar doesn't cover content */
51
- body { padding-bottom: 52px; }
@@ -1,172 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>MCP Pre-Action Checks Explained — ThumbGate</title>
7
- <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
8
- <meta name="description" content="What pre-action checks are, how they work in the Model Context Protocol, and why enforcement beats prompt rules for AI coding agent safety.">
9
- <meta name="keywords" content="MCP pre-action checks, PreToolUse hooks, Model Context Protocol, AI agent enforcement, Claude Code hooks, MCP server guardrails, tool call interception, ThumbGate">
10
- <meta property="og:title" content="MCP Pre-Action Checks Explained">
11
- <meta property="og:description" content="Technical deep-dive: how pre-action checks intercept tool calls and enforce safety rules for AI coding agents.">
12
- <meta property="og:type" content="article">
13
- <meta property="og:url" content="https://thumbgate.ai/learn/mcp-pre-action-checks-explained">
14
- <link rel="canonical" href="https://thumbgate.ai/learn/mcp-pre-action-checks-explained">
15
-
16
- <script type="application/ld+json">
17
- {
18
- "@context": "https://schema.org",
19
- "@type": "TechArticle",
20
- "headline": "MCP Pre-Action Checks Explained",
21
- "description": "Technical deep-dive into pre-action checks: how they intercept tool calls and enforce safety rules at the MCP hook layer.",
22
- "author": {
23
- "@type": "Person",
24
- "name": "Igor Ganapolsky",
25
- "url": "https://github.com/IgorGanapolsky"
26
- },
27
- "publisher": {
28
- "@type": "Organization",
29
- "name": "ThumbGate",
30
- "url": "https://thumbgate.ai"
31
- },
32
- "datePublished": "2026-04-01",
33
- "dateModified": "2026-04-01",
34
- "mainEntityOfPage": "https://thumbgate.ai/learn/mcp-pre-action-checks-explained",
35
- "about": [
36
- {"@type": "Thing", "name": "Model Context Protocol"},
37
- {"@type": "Thing", "name": "PreToolUse hooks"},
38
- {"@type": "Thing", "name": "AI agent enforcement"}
39
- ]
40
- }
41
- </script>
42
-
43
- <link rel="stylesheet" href="/learn/learn.css">
44
- <style>
45
- .flow-diagram { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin: 1.5rem 0; font-family: 'SF Mono', Consolas, monospace; font-size: 0.85rem; color: var(--text); line-height: 2; text-align: center; }
46
- .flow-diagram .arrow { color: var(--cyan); }
47
- .flow-diagram .block { color: var(--green); }
48
- .flow-diagram .deny { color: var(--red); }
49
- table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
50
- th, td { text-align: left; padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
51
- th { color: var(--cyan); font-weight: 600; }
52
- </style>
53
- </head>
54
- <body>
55
-
56
- <nav>
57
- <a href="/" class="brand"><img src="/assets/brand/thumbgate-mark-inline.svg" alt="ThumbGate" class="logo-mark" width="28" height="28"><span class="logo-text">ThumbGate</span></a>
58
- <a href="/guide">Setup Guide</a>
59
- <a href="/learn">Learn</a>
60
- <a href="/dashboard">Dashboard</a>
61
- <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
62
- </nav>
63
-
64
- <div class="container">
65
- <div class="breadcrumb"><a href="/learn">Learn</a> / MCP Pre-Action Checks</div>
66
- <h1>MCP Pre-Action Checks Explained</h1>
67
- <p style="color:var(--muted);">4 min read &middot; Technical deep-dive for developers building on MCP</p>
68
-
69
- <div class="tldr"><strong>TL;DR:</strong> Pre-action checks intercept tool calls before they execute. Unlike prompt rules, they cannot be ignored. This is how enforcement actually works in MCP.</div>
70
-
71
- <h2>What is a pre-action check?</h2>
72
- <p>A pre-action check is an enforcement rule that intercepts an AI agent's tool call <em>before</em> it executes. If the tool call matches a known-bad pattern, the check blocks it and returns a rejection to the agent. The agent then adapts its approach without ever having run the dangerous action.</p>
73
- <p>Checks run at the hook layer of the Model Context Protocol (MCP). They are external to the agent's reasoning chain, which means they cannot be overridden by prompt injection, context overflow, or chain-of-thought reasoning.</p>
74
-
75
- <h2>How it works: the tool call lifecycle</h2>
76
- <div class="flow-diagram">
77
- <span class="block">Agent decides to call tool</span><br>
78
- <span class="arrow">&darr;</span><br>
79
- <span class="block">PreToolUse hook fires</span><br>
80
- <span class="arrow">&darr;</span><br>
81
- <span class="block">ThumbGate checks tool call against checks</span><br>
82
- <span class="arrow">&darr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&darr;</span><br>
83
- <span class="block">No match &rarr; ALLOW</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="deny">Match &rarr; BLOCK + reason</span><br>
84
- <span class="arrow">&darr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&darr;</span><br>
85
- <span class="block">Tool executes</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="deny">Agent receives rejection</span>
86
- </div>
87
-
88
- <h2>Prompt rules vs. pre-action checks</h2>
89
- <table>
90
- <thead>
91
- <tr><th>Property</th><th>Prompt Rules</th><th>Pre-Action Checks</th></tr>
92
- </thead>
93
- <tbody>
94
- <tr><td>Where they live</td><td>Inside agent context</td><td>External hook layer</td></tr>
95
- <tr><td>Can be overridden</td><td>Yes (context overflow, reasoning)</td><td>No (runs outside agent)</td></tr>
96
- <tr><td>Enforcement</td><td>Advisory</td><td>Physical block</td></tr>
97
- <tr><td>Persistence</td><td>Per-session (context-dependent)</td><td>Permanent (database-backed)</td></tr>
98
- <tr><td>Adapts over time</td><td>No</td><td>Yes (Thompson Sampling)</td></tr>
99
- <tr><td>Explains why</td><td>No</td><td>Yes (reason chain per block)</td></tr>
100
- </tbody>
101
- </table>
102
-
103
- <h2>The three layers of a check</h2>
104
-
105
- <h3>1. Pattern matcher</h3>
106
- <p>Checks match against the tool name and its arguments. For a <code>Bash</code> tool call, the pattern might match <code>git push --force</code> targeting <code>main</code>. For a <code>Write</code> tool call, it might match writes to <code>.env</code> or <code>production.config</code>.</p>
107
- <pre><code>{
108
- "tool": "Bash",
109
- "pattern": "git push.*(--force|-f).*main",
110
- "action": "BLOCK"
111
- }</code></pre>
112
-
113
- <h3>2. Evidence and reasoning</h3>
114
- <p>Every check decision includes a reasoning chain: why this pattern exists, how many times it has fired, what the original failure was. This transparency lets you audit the system and tune it.</p>
115
- <pre><code>{
116
- "check": "no-force-push-main",
117
- "decision": "BLOCK",
118
- "reason": "Force-push to main blocked",
119
- "evidence": "User reported loss of 14 commits (2026-03-15)",
120
- "fire_count": 7,
121
- "confidence": 0.94
122
- }</code></pre>
123
-
124
- <h3>3. Thompson Sampling adaptation</h3>
125
- <p>Not all patterns deserve the same enforcement level. Thompson Sampling uses a beta distribution to model each check's risk profile. High-risk patterns (many failures, few successes) get strict enforcement. Low-risk patterns (rarely triggered, occasionally overridden) stay relaxed.</p>
126
-
127
- <div class="callout">
128
- <strong>Why Thompson Sampling?</strong> It balances exploration vs. exploitation. New checks start with wide confidence intervals and tighten as evidence accumulates. This prevents over-blocking on sparse data while ensuring genuinely dangerous patterns get strict enforcement fast.
129
- </div>
130
-
131
- <h2>How checks are created</h2>
132
- <ol>
133
- <li><strong>Feedback capture:</strong> Developer gives thumbs-down with context about what went wrong</li>
134
- <li><strong>Lesson storage:</strong> Feedback is stored in SQLite with FTS5 indexing for fast retrieval</li>
135
- <li><strong>Corrective inference:</strong> ThumbGate matches the failure against similar past failures and infers what should be prevented</li>
136
- <li><strong>Rule promotion:</strong> After configurable repeated failures, the lesson promotes to a prevention rule</li>
137
- <li><strong>Check activation:</strong> The prevention rule becomes an active check in the PreToolUse hook</li>
138
- </ol>
139
-
140
- <h2>Supported agents</h2>
141
- <p>Pre-action checks work with any agent that supports MCP hooks:</p>
142
- <ul>
143
- <li><strong>Claude Code</strong> &mdash; PreToolUse hooks in <code>.claude/settings.json</code></li>
144
- <li><strong>Cursor</strong> &mdash; MCP server configuration in <code>.cursor/mcp.json</code></li>
145
- <li><strong>Codex</strong> &mdash; MCP tool interception via server config</li>
146
- <li><strong>Gemini CLI</strong> &mdash; MCP server hooks</li>
147
- <li><strong>Amp, OpenCode</strong> &mdash; any MCP-compatible agent</li>
148
- </ul>
149
- <p>Run <code>npx thumbgate init</code> to auto-detect your agent and configure the correct hook format.</p>
150
-
151
- <div class="cta-box">
152
- <h2 style="color:var(--text);font-size:1.3rem;margin:0 0 8px;">Build your first check</h2>
153
- <p>Install, give your first thumbs-down, and watch the check auto-generate.</p>
154
- <div class="cta-install">$ npx thumbgate init</div>
155
- </div>
156
-
157
- <div class="related">
158
- <h3>Related guides</h3>
159
- <a href="/learn/stop-ai-agent-force-push">How to Stop AI Agents From Force-Pushing to Main &rarr;</a>
160
- <a href="/learn/vibe-coding-safety-net">The Vibe Coding Safety Net You Are Missing &rarr;</a>
161
- <a href="/guide">Full Setup Guide &rarr;</a>
162
- </div>
163
- </div>
164
-
165
-
166
- <div class="sticky-cta">
167
- <span style="color:var(--muted)">Try it now:</span>
168
- <code>npx thumbgate init</code>
169
- <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub &rarr;</a>
170
- </div>
171
- </body>
172
- </html>
@@ -1,161 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Building a Pretix + Stripe Connect Plugin for Live-Music Venues — ThumbGate</title>
7
- <script defer data-domain="thumbgate.ai" src="https://plausible.io/js/script.js"></script>
8
- <meta name="description" content="How to design a multi-venue Stripe Connect ticketing plugin on Pretix, keeping venues as Merchant of Record without messy platform-fee tax reporting.">
9
- <meta name="keywords" content="Pretix, Stripe Connect, Direct Charges, Application Fees, Multi-Venue Ticketing, SetupIntent, PaymentIntent, Webhook Idempotency, Merchant of Record">
10
- <meta property="og:title" content="Building a Pretix + Stripe Connect Plugin for Live-Music Venues">
11
- <meta property="og:description" content="How to design a multi-venue Stripe Connect ticketing plugin on Pretix, keeping venues as Merchant of Record without messy platform-fee tax reporting.">
12
- <meta property="og:type" content="article">
13
- <meta property="og:url" content="https://thumbgate.ai/learn/pretix-stripe-connect-marketplaces">
14
- <meta property="og:image" content="https://thumbgate.ai/og.png">
15
- <meta name="twitter:card" content="summary_large_image">
16
- <link rel="canonical" href="https://thumbgate.ai/learn/pretix-stripe-connect-marketplaces">
17
-
18
- <script type="application/ld+json">
19
- {
20
- "@context": "https://schema.org",
21
- "@type": "TechArticle",
22
- "headline": "Building a Pretix + Stripe Connect Plugin for Live-Music Venues",
23
- "description": "How to design a multi-venue Stripe Connect ticketing plugin on Pretix, keeping venues as Merchant of Record without messy platform-fee tax reporting.",
24
- "author": {
25
- "@type": "Person",
26
- "name": "Igor Ganapolsky",
27
- "url": "https://github.com/IgorGanapolsky"
28
- },
29
- "publisher": {
30
- "@type": "Organization",
31
- "name": "ThumbGate",
32
- "url": "https://thumbgate.ai"
33
- },
34
- "datePublished": "2026-06-05",
35
- "dateModified": "2026-06-05",
36
- "mainEntityOfPage": "https://thumbgate.ai/learn/pretix-stripe-connect-marketplaces",
37
- "about": [
38
- {"@type": "Thing", "name": "Stripe Connect Direct Charges"},
39
- {"@type": "Thing", "name": "Pretix plugin architecture"},
40
- {"@type": "Thing", "name": "marketplace accounting"}
41
- ]
42
- }
43
- </script>
44
-
45
- <link rel="stylesheet" href="/learn/learn.css">
46
- <style>
47
- .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.8rem; margin: 1.5rem 0; }
48
- .stat { background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 8px; padding: 0.8rem; }
49
- .stat-num { font-family: ui-monospace, monospace; color: var(--green); font-size: 1.4rem; font-weight: 600; }
50
- .stat-label { color: var(--muted); font-size: 0.8rem; margin-top: 0.2rem; }
51
- .decision { border-left: 3px solid var(--cyan); padding: 0.6rem 0.9rem; margin: 1.5rem 0; background: rgba(0,212,170,0.04); }
52
- .takeaway { border-left: 3px solid #f59e0b; padding: 0.6rem 0.9rem; margin: 1.5rem 0; background: rgba(245,158,11,0.06); }
53
- </style>
54
- </head>
55
- <body>
56
-
57
- <nav>
58
- <a href="/" class="brand"><img src="/assets/brand/thumbgate-mark-inline.svg" alt="ThumbGate" class="logo-mark" width="28" height="28"><span class="logo-text">ThumbGate</span></a>
59
- <a href="/guide">Setup Guide</a>
60
- <a href="/learn">Learn</a>
61
- <a href="/dashboard">Dashboard</a>
62
- <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
63
- </nav>
64
-
65
- <main>
66
-
67
- <h1>Building a Pretix + Stripe Connect Plugin for Live-Music Venues</h1>
68
- <p class="meta">Igor Ganapolsky · June 5, 2026 · 5 min read</p>
69
-
70
- <p><em>Facts verified against Upwork contract history + commit log. No metric stated below was invented; if it isn't measured, it isn't here.</em></p>
71
-
72
- <h2>The Brief</h2>
73
-
74
- <p>A regional live-music operator (Hilltown Media Group) needed to ship venue-branded online ticketing on top of <a href="https://pretix.eu/">Pretix</a> — a self-hosted, open-source event ticketing platform — with a multi-venue Stripe Connect topology where each venue stays Merchant of Record (MoR) for tax purposes. They came in with a clear architectural opinion: Direct Charges (or Destination with <code>on_behalf_of</code>), a fixed $1.00 platform fee decoupled from the venue's sales tax, and a Pretix-plugin-shaped extension surface so nothing forks the upstream platform.</p>
75
-
76
- <h2>What Was Delivered or Funded (Phase 1 Evidence Gates)</h2>
77
-
78
- <p>The Upwork contract has three Phase 1 milestones with separate evidence gates. I am keeping the accounting language strict here:</p>
79
-
80
- <ul>
81
- <li><strong>M1</strong> — Released and withdrawn: Pretix plugin scaffold (<code>yourstage_core</code>) with the venue-as-MoR Stripe Connect integration, Direct Charges on the connected account, and the $1 platform fee implemented as <code>application_fee_amount</code> so it never lands in the venue's taxable-revenue ledger.</li>
82
- <li><strong>M2</strong> — Approved and pending balance conversion: multi-venue onboarding glue, Stripe account connection flow, local tax-rate settings, and buyer-flow proof.</li>
83
- <li><strong>M3</strong> — Escrow funded: checkout hardening, Stripe evidence, webhook/refund reconciliation, and production-readiness proof.</li>
84
- </ul>
85
-
86
- <p>The verified cash gate as of June 5, 2026 is M1 withdrawn at $1,350 net, M2 pending at $900 net, and M3 escrow funded at $1,500 gross. Phase 2 ($5,000) covers pre-tab drink tokens, featured-merch upsells at checkout, an automated waitlist with <code>SetupIntent</code> -> off-session <code>PaymentIntent</code> conversion, on-door upsell modals on the Pretix scan API, and Stripe Terminal JS/RN integration for the box office.</p>
87
-
88
- <div class="stats">
89
- <div class="stat"><div class="stat-num">$1,350</div><div class="stat-label">M1 Net Withdrawn</div></div>
90
- <div class="stat"><div class="stat-num">$900</div><div class="stat-label">M2 Net Pending</div></div>
91
- <div class="stat"><div class="stat-num">$1,500</div><div class="stat-label">M3 Gross Escrow</div></div>
92
- <div class="stat"><div class="stat-num">$5,000</div><div class="stat-label">Phase 2 Budget</div></div>
93
- </div>
94
-
95
- <h2>Three Architectural Decisions Worth Lifting</h2>
96
-
97
- <p>These are the choices that took the most thinking and that I'd reuse on the next marketplace platform — listed not because they're novel but because each one quietly avoids a class of bug.</p>
98
-
99
- <h3>1. <code>application_fee_amount</code> instead of cart-line platform fee</h3>
100
-
101
- <div class="decision">
102
- <strong>The Problem:</strong> The naive way to charge a per-ticket platform fee is to add a line item to the cart. That's wrong on a marketplace where each venue is its own MoR — the fee shows up in the venue's taxable revenue, and the venue's accountant has to back it out manually.
103
- </div>
104
-
105
- <p>Using Stripe Connect's <code>application_fee_amount</code> keeps the fee on the <em>platform's</em> Stripe account, off the venue's books, and out of every tax jurisdiction's audit surface. Two-line change. The reason to know about it isn't the code, it's the months of back-office pain it removes.</p>
106
-
107
- <h3>2. Native Pretix add-on items, not custom cart math</h3>
108
-
109
- <div class="decision">
110
- <strong>The Problem:</strong> Phase 2 needs to inject upsells (drink tokens, featured merch) into the buyer flow. The naive way is a parallel cart that re-totals at checkout. That breaks every downstream Pretix invariant: refund math, partial-refund signals, organizer reporting, tax calculation.
111
- </div>
112
-
113
- <p>Modeling the upsells as native Pretix <code>Item</code> add-ons means the existing Phase 1 Stripe Direct Charge math calculates the new totals correctly with zero changes. Same applies for refunds — Pretix's <code>order_canceled</code> / <code>order_refunded</code> signals already know how to attribute partial refunds back to add-on items.</p>
114
- <p>The general principle: when extending a platform with a plugin model, lean on the platform's native primitives even when they feel verbose. The downstream-invariant cost of going around them is paid forever.</p>
115
-
116
- <h3>3. <code>SetupIntent</code> for the waitlist, not stored card-on-file</h3>
117
-
118
- <div class="decision">
119
- <strong>The Problem:</strong> Phase 2's "never lose a sale" waitlist needs to charge the next buyer when a ticket is returned — possibly hours or days after they joined the waitlist. The naive way is to store a <code>PaymentMethod</code> against the user record.
120
- </div>
121
-
122
- <p>The right way is <code>SetupIntent</code> &rarr; vault <code>setup_intent_id</code> only (not the PaymentMethod, not last4) &rarr; convert to off-session <code>PaymentIntent</code> when a seat frees. PCI scope stays SAQ-A. And the off-session path forces you to handle <code>authentication_required</code> declines (5-15% expected on EU/UK SCA + US 3DS2) with a skip-and-notify fallback — which means the waitlist actually keeps moving instead of stalling on one issuer step-up.</p>
123
-
124
- <h2>What I'd tell the next freelance dev shipping a Pretix-plus-Stripe-Connect project</h2>
125
-
126
- <div class="takeaway">
127
- <strong>Pretix Primitives:</strong> Read the Pretix <code>signals.py</code> source before you write your own. Half the work is already there.
128
- </div>
129
-
130
- <div class="takeaway">
131
- <strong>Webhook Idempotency:</strong> Webhook reconciliation isn't optional. Build the <code>StripeWebhookEvent</code> table (event-id PK for idempotency) on day one. It's the dashboard you'll want at 11pm when a venue's first show is selling.
132
- </div>
133
-
134
- <div class="takeaway">
135
- <strong>Stripe Terminal Connect:</strong> Stripe Terminal on Connect is a multi-week integration, not a multi-day one. Reader Location registration alone is its own onboarding flow.
136
- </div>
137
-
138
- <h2>What's Next</h2>
139
-
140
- <p>Phase 2 ($5K, milestone-scoped — upsells, automated waitlist, box office POS, and webhook reconciliation), then Phase 3 (gamification + SMS marketing) and Phase 4 (self-serve onboarding + PWA scanner).</p>
141
-
142
- <div class="decision" style="margin-top:2rem;">
143
- <strong>Building a Pretix or Stripe Connect marketplace?</strong>
144
- <p style="margin:0.6rem 0 1rem 0;">Book a <strong>$499 architecture diagnostic</strong> — 90-minute review of your Connect topology (Direct Charges vs Destination, MoR placement, <code>application_fee_amount</code> vs cart-line, SetupIntent → off-session conversion, webhook idempotency). You leave with a one-page architecture decision record and a punch list of the 3-5 bugs you'd ship without it.</p>
145
- <p style="margin:0;">
146
- <a href="https://buy.stripe.com/00w14neyUcXA5pL5e33sI0e" style="background:var(--cyan);color:#06121a;padding:10px 18px;border-radius:6px;font-weight:700;text-decoration:none;display:inline-block;">Book $499 diagnostic →</a>
147
- &nbsp;&nbsp;
148
- <a href="https://buy.stripe.com/fZu9AT76saPsg4pbCr3sI0f" style="color:var(--cyan);font-weight:600;">or a full $1,500 integration sprint →</a>
149
- </p>
150
- </div>
151
-
152
- <p class="meta">— Igor</p>
153
-
154
- <hr>
155
-
156
- <p class="meta">More build logs: <a href="/learn/from-prototype-to-production">From git init to production</a> · <a href="/learn/agent-harness-pattern">The agent harness pattern</a> · <a href="/learn/mcp-pre-action-checks-explained">MCP pre-action checks explained</a></p>
157
-
158
- </main>
159
-
160
- </body>
161
- </html>