<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: edhiblemeer</title>
    <description>The latest articles on DEV Community by edhiblemeer (@edhiblemeer).</description>
    <link>https://dev.to/edhiblemeer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3915565%2Fc8a89af7-6062-41a5-8c85-2d218edd7afb.png</url>
      <title>DEV Community: edhiblemeer</title>
      <link>https://dev.to/edhiblemeer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edhiblemeer"/>
    <language>en</language>
    <item>
      <title>The CEO question revolution: How MCP-integrated SaaS answers leaders + organizational prerequisite questions in 30 seconds</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Tue, 23 Jun 2026 04:36:39 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/the-ceo-question-revolution-how-mcp-integrated-saas-answers-leaders-organizational-prerequisite-27pa</link>
      <guid>https://dev.to/edhiblemeer/the-ceo-question-revolution-how-mcp-integrated-saas-answers-leaders-organizational-prerequisite-27pa</guid>
      <description>&lt;p&gt;&lt;strong&gt;The leader's role isn't to give answers — it's to leave the right questions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This insight from a Japanese organizational consultant resonated with me as a vertical SaaS founder building tasteck for nightlife industry owners. Because in the SaaS space, we usually focus on giving instant answers: "Here's your dashboard. Here's your KPI."&lt;/p&gt;

&lt;p&gt;But what if a SaaS could do both? Answer the operational questions in 30 seconds, &lt;em&gt;and&lt;/em&gt; surface the prerequisite questions that owners should be asking?&lt;/p&gt;

&lt;p&gt;That's the design we're building at tasteck with &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; integration with ChatGPT Plus.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two types of CEO questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Type 1: Individual Operational Questions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are data-driven, answerable-by-dashboard questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Who's working today?"&lt;/li&gt;
&lt;li&gt;"What was last week's revenue per staff member, top 5?"&lt;/li&gt;
&lt;li&gt;"What was our recruitment ROI by channel last month?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These used to require: open management screen → set filters → wait 5 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type 2: Organizational Prerequisite Questions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are reflection-driven, NOT answerable by dashboard alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What does our business actually value?"&lt;/li&gt;
&lt;li&gt;"What organizational prerequisites are missing in our hiring decisions?"&lt;/li&gt;
&lt;li&gt;"What kind of operational state do we want to sustain?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These require &lt;em&gt;human reflection&lt;/em&gt;. But a well-designed SaaS can surface these questions by interpreting accumulated data and prompting the owner to reflect.&lt;/p&gt;

&lt;p&gt;The CEO question revolution is about integrating both: instant operational answers &lt;em&gt;plus&lt;/em&gt; organizational reflection triggers — all in 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How MCP integration enables both
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Anthropic's Model Context Protocol (MCP)&lt;/strong&gt; lets ChatGPT Plus / Claude Desktop directly query SaaS systems via standardized tool calls.&lt;/p&gt;

&lt;p&gt;In tasteck, we exposed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;22 read tools&lt;/strong&gt;: cast schedule, revenue summary, cast ranking, dispatch status, invoice list, payment status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write tools&lt;/strong&gt; (Phase 2D upcoming): bulk update orders, assign driver, pay invoice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP-native reflection prompts&lt;/strong&gt;: organizational prerequisite question triggers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example workflow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "What was last week's revenue per cast member, top 5?"
ChatGPT: [calls get_cast_ranking] → returns top 5 with revenue
ChatGPT: "Top 1 is heavily individual brand-driven. Want to evaluate
         differently from media channel ROI next?"
User: (reflects on organizational priorities)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;operational answer + reflection prompt&lt;/strong&gt; in a single 30-second interaction.&lt;/p&gt;

&lt;p&gt;The key isn't more tools — it's the &lt;em&gt;integration design&lt;/em&gt; that lets data trigger structural questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vertical SaaS case study — Japan's mens-esthetic industry
&lt;/h2&gt;

&lt;p&gt;We're testing this in Japan's mens-esthetic (メンエス) industry, a 60K-establishment vertical with owner-operator businesses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Owner's daily pain:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily attendance check: 5 min × 30 days = &lt;strong&gt;2.5 hours/month&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Weekly revenue + nomination + media ROI: &lt;strong&gt;30 min/week&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Monthly hiring decisions: &lt;strong&gt;1 hour/month&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With MCP-integrated tasteck:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30 seconds per natural language query&lt;/li&gt;
&lt;li&gt;Time savings: ~3.5 hours/month&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More importantly: time freed for organizational reflection&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Owners now have time to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Are we evaluating cast performance by individual brand or organizational fit?"&lt;/li&gt;
&lt;li&gt;"What's our prerequisite for hiring decisions — skill or values alignment?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the questions that move businesses, not the dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Universal application across verticals
&lt;/h2&gt;

&lt;p&gt;While we built this for mens-esthetic, the design pattern applies to any vertical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host clubs&lt;/strong&gt;: champagne sales dashboard + "host cast development philosophy" reflection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hostess clubs&lt;/strong&gt;: trial-customer conversion + "cast evaluation prerequisites" reflection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restaurants&lt;/strong&gt;: POS + "menu philosophy evolution"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retail&lt;/strong&gt;: inventory + "store culture alignment"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Professional services&lt;/strong&gt;: client engagement + "service value redefinition"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern: &lt;em&gt;operational data → reflection prompt → organizational prerequisite question&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  CTA
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tasteck.tech" rel="noopener noreferrer"&gt;tasteck&lt;/a&gt; is a vertical SaaS for Japan's nightlife industry, currently running 22 MCP tools live with 3 more in design.&lt;/p&gt;

&lt;p&gt;🎯 14-day free trial for nightlife business owners in Japan.&lt;/p&gt;

&lt;p&gt;If you're building MCP-integrated vertical SaaS in your own market, I'd love to compare notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What CEO question types are you surfacing?&lt;/li&gt;
&lt;li&gt;How do you design reflection prompts that aren't pushy?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reach out via comments — happy to discuss.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was inspired by an organizational consultant's reflection on "leaving the right questions" — a frame that resonates beyond Japan's vertical SaaS scene.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>leadership</category>
      <category>mcp</category>
      <category>saas</category>
    </item>
    <item>
      <title>Tasteck ChatGPT MCP: Cut LINE booking transcription from 11 steps to one sentence</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Mon, 22 Jun 2026 02:29:15 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/tasteck-x-chatgpt-mcp-cut-line-booking-transcription-from-11-steps-to-one-sentence-e10</link>
      <guid>https://dev.to/edhiblemeer/tasteck-x-chatgpt-mcp-cut-line-booking-transcription-from-11-steps-to-one-sentence-e10</guid>
      <description>&lt;p&gt;Built a 50-second demo video showing how Japan's night-leisure industry SaaS "tasteck" uses ChatGPT MCP to replace a tedious 11-step booking transcription with a single sentence from the owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;LINE/email bookings arrive in free-form text. Staff manually copy them into the order screen — date, time, course, cast nominee, customer search, new guest creation, payment, save. 3-5 minutes per booking, 20-30 bookings during peak.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix (MCP path)
&lt;/h2&gt;

&lt;p&gt;Owner pastes the LINE message into ChatGPT (or Claude Desktop) and says "put this into tasteck". The MCP tool chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parses date / time / course / nomination / payment from free text&lt;/li&gt;
&lt;li&gt;Looks up the customer by phone, or auto-creates a guest&lt;/li&gt;
&lt;li&gt;Returns a &lt;code&gt;dry_run&lt;/code&gt; preview for the owner to visually confirm&lt;/li&gt;
&lt;li&gt;Owner confirms → committed to tasteck DB&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Safety design (the unusual part)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Owner's hand LLM only, &lt;strong&gt;not&lt;/strong&gt; staff multi-tenant&lt;/li&gt;
&lt;li&gt;OAuth scope per business owner&lt;/li&gt;
&lt;li&gt;2-stage commit (&lt;code&gt;dry_run&lt;/code&gt; preview → confirm)&lt;/li&gt;
&lt;li&gt;No booking data is retained by the external LLM vendor&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Watch the demo
&lt;/h2&gt;

&lt;p&gt;🎬 &lt;a href="https://youtube.com/shorts/4jJPIy-2ZeY" rel="noopener noreferrer"&gt;YouTube Shorts (50s)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tasteck.tech/" rel="noopener noreferrer"&gt;tasteck.tech&lt;/a&gt; — vertical B2B SaaS for Japan's night-leisure industry (massage / hostess club / host club).&lt;/p&gt;

&lt;p&gt;This is a build-in-public log; happy to answer questions in the comments.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>chatgpt</category>
      <category>mcp</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Drowning in LINE/Email Inquiries? Let ChatGPT Drive Your Night-Leisure SaaS</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Tue, 16 Jun 2026 21:27:48 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/drowning-in-lineemail-inquiries-let-chatgpt-drive-your-night-leisure-saas-17bd</link>
      <guid>https://dev.to/edhiblemeer/drowning-in-lineemail-inquiries-let-chatgpt-drive-your-night-leisure-saas-17bd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"LINE inquiries about reservations, emails with questions — I can't keep up." Sound familiar?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We just shipped a demo for tasteck — a B2B SaaS for the Japanese night-leisure industry (men's esthetic salons, host clubs, etc.). The pitch isn't really about features. It's about &lt;strong&gt;running your shop without opening the UI&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The product video (1 minute)
&lt;/h2&gt;


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/9Q-vLSJH2Bk"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;


&lt;h2&gt;
  
  
  What you see
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A salon owner has a customer's LINE message: "Hi, can I book 19:00 today for 60 min?"&lt;/li&gt;
&lt;li&gt;The owner copies the message into ChatGPT, says "book it."&lt;/li&gt;
&lt;li&gt;ChatGPT calls the tasteck MCP server, which returns a dry-run preview (shop, cast, time, course, payment).&lt;/li&gt;
&lt;li&gt;★ &lt;strong&gt;Owner-approval modal pops up&lt;/strong&gt; (this is the key part — &lt;code&gt;destructiveHint=true&lt;/code&gt; triggers ChatGPT's built-in confirmation).&lt;/li&gt;
&lt;li&gt;Owner approves → booking lands directly in the tasteck order screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The 18 MCP tools
&lt;/h2&gt;

&lt;p&gt;The MCP server (Phase 2A) exposes the full SaaS workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📋 &lt;strong&gt;Booking management&lt;/strong&gt; — paste phone/LINE/email inquiries, get bookings&lt;/li&gt;
&lt;li&gt;📅 &lt;strong&gt;Shift management&lt;/strong&gt; — submit/confirm cast &amp;amp; driver shifts&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Sales analytics&lt;/strong&gt; — daily / per-cast / nomination-rate visualization&lt;/li&gt;
&lt;li&gt;👥 &lt;strong&gt;Guest &amp;amp; cast registration&lt;/strong&gt; — add new entities by talking&lt;/li&gt;
&lt;li&gt;🚗 &lt;strong&gt;Dispatch&lt;/strong&gt; — auto-assign delivery drivers, send notification emails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All accessed through ChatGPT. No UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters (operationally)
&lt;/h2&gt;

&lt;p&gt;The category is unusual: small-team SaaS where the owner is also the on-shift manager. Mode-switching between "running the floor" and "doing analytics" eats hours per day.&lt;/p&gt;

&lt;p&gt;If the owner can hand off &lt;strong&gt;paste → preview → approve&lt;/strong&gt; flows to AI, the cognitive load shifts back to actual business decisions — which cast to invest in, which shop to expand, how to drive nomination rate (本指名率) which is the LTV driver in this category.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MCP server in TypeScript on Express, sitting on existing TypeORM entities&lt;/li&gt;
&lt;li&gt;ChatGPT connector via OAuth + CIMD (Client ID Metadata Document)&lt;/li&gt;
&lt;li&gt;All write operations marked &lt;code&gt;destructiveHint: true&lt;/code&gt; so ChatGPT shows the owner-approval modal&lt;/li&gt;
&lt;li&gt;Write tools have &lt;code&gt;dry_run&lt;/code&gt; + &lt;code&gt;applied&lt;/code&gt; pattern (preview → confirm → execute) instead of single-shot mutations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're getting early signal that "&lt;strong&gt;UI-less SaaS via MCP&lt;/strong&gt;" is a useful positioning — especially for owner-operators who don't want to context-switch into yet another dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tasteck.tech" rel="noopener noreferrer"&gt;tasteck.tech&lt;/a&gt; — free trial available. Japanese only for now.&lt;/p&gt;

&lt;p&gt;Would love feedback from anyone running multi-tool MCP servers in production — confirmation modal UX, write-tool patterns, and how to message "the SaaS your operators don't open" without sounding dystopian.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>mcp</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How we made our niche-industry SaaS MCP-ready (and watched ChatGPT call our dispatch tools)</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Wed, 10 Jun 2026 03:49:46 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/how-we-made-our-niche-industry-saas-mcp-ready-and-watched-chatgpt-call-our-dispatch-tools-1khh</link>
      <guid>https://dev.to/edhiblemeer/how-we-made-our-niche-industry-saas-mcp-ready-and-watched-chatgpt-call-our-dispatch-tools-1khh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is an English digest of &lt;a href="https://zenn.dev/edhiblemeer/articles/mcp-saas-dispatch-implementation" rel="noopener noreferrer"&gt;the original Zenn post (Japanese)&lt;/a&gt;. Read there for the full timeline and commit-level trace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We ship &lt;strong&gt;tasteck&lt;/strong&gt;, a B2B SaaS for the Japanese night-leisure industry (dispatch + cast shift management). 8 years of operational data, ~100 venues live.&lt;/li&gt;
&lt;li&gt;Two days after the &lt;a href="https://zenn.dev/edhiblemeer/articles/mcp-saas-dispatch-castshift-design" rel="noopener noreferrer"&gt;MCP design post&lt;/a&gt;, ChatGPT Plus can call our tools live: "Who's available tonight?" → MCP &lt;code&gt;list_available_drivers&lt;/code&gt; → JSON → natural-language reply.&lt;/li&gt;
&lt;li&gt;Estimated B2 OAuth sprint = 2 weeks (6/16–7/1). Actual = &lt;strong&gt;1 day&lt;/strong&gt;, by reading the spec carefully before touching code.&lt;/li&gt;
&lt;li&gt;We hit &lt;strong&gt;12 distinct traps&lt;/strong&gt; between "OAuth issuance works" and "ChatGPT actually invokes the tool." The QA logs caught every one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we shipped
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;3 read tools (B1):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list_available_drivers&lt;/code&gt; — drivers free tonight&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_cast_shifts&lt;/code&gt; — today's cast shift roster&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_assignable_casts&lt;/code&gt; — joined resolution: &lt;code&gt;roster ∧ stage-name set ∧ shop match&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Natural-language date helper:&lt;/strong&gt; &lt;code&gt;resolveBusinessDate(naturalText, company)&lt;/code&gt; — handles "today / tomorrow / day-after-tomorrow" &lt;em&gt;and&lt;/em&gt; the per-tenant business-day boundary (e.g. day flips at 04:00 or 05:00, configured per &lt;code&gt;Company.changeDateTime&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP SDK Server + SSE transport:&lt;/strong&gt; &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; wired into a NestJS controller. One SSE connection = one &lt;code&gt;McpServer&lt;/code&gt; instance, company-scoped, with a &lt;code&gt;session_id&lt;/code&gt; Map routing POST &lt;code&gt;/messages&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth flow (B2, finished in one day across 7 steps)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Commit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Protected Resource Metadata endpoint (RFC 9728)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d6f05ff6&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/authorize&lt;/code&gt; + consent screen + PKCE start&lt;/td&gt;
&lt;td&gt;&lt;code&gt;107edbcb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/token&lt;/code&gt; + PKCE verify + JWT issue + &lt;code&gt;resource&lt;/code&gt; (RFC 8707)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ffd0468c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OAuthAccessTokenGuard&lt;/code&gt; (RS256 + HS256 fallback, extracts &lt;code&gt;companyId&lt;/code&gt; / &lt;code&gt;staffId&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;f2c9bed4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Streamable HTTP transport (SSE → POST &lt;code&gt;/sse/:companyId&lt;/code&gt; for JSON-RPC)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3a28d92f&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;resolveBusinessDate&lt;/code&gt; undefined fallback (`(naturalText&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;QA redeploy + ChatGPT live demo&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The 12 traps (compressed)
&lt;/h2&gt;

&lt;p&gt;The full timeline is in the Japanese post; the abridged list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Discovery path mismatch.&lt;/strong&gt; ChatGPT expected {% raw %}&lt;code&gt;.well-known/oauth-protected-resource&lt;/code&gt; at server root; we published under &lt;code&gt;/v1/api/staff/mcp/...&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport mismatch.&lt;/strong&gt; ChatGPT expects &lt;strong&gt;Streamable HTTP&lt;/strong&gt; (POST &lt;code&gt;/sse&lt;/code&gt; with JSON-RPC directly). We started on the legacy &lt;code&gt;SSEServerTransport&lt;/code&gt;. QA log: POST &lt;code&gt;/sse/1&lt;/code&gt; → 404.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache illusion.&lt;/strong&gt; ChatGPT cached "this connector has no tools" across our fixes. We thought it was a server bug. Required: disconnect → reconnect through the OAuth flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard mismatch (the big one).&lt;/strong&gt; OAuth tokens are signed with the OAuth secret; the existing &lt;code&gt;StaffJwtGuard&lt;/code&gt; validates the staff-login JWT (different secret). 401 on every &lt;code&gt;/sse/1&lt;/code&gt; POST. Fix: new &lt;code&gt;OAuthAccessTokenGuard&lt;/code&gt; with RS256 + HS256 fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;undefined.trim()&lt;/code&gt;.&lt;/strong&gt; Tool schema declared &lt;code&gt;date&lt;/code&gt; optional, ChatGPT called with no args, our handler did &lt;code&gt;naturalText.trim()&lt;/code&gt;. Crash → tool result error. Fix: &lt;code&gt;(naturalText || "today").trim()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "looks connected but no tools" stage.&lt;/strong&gt; &lt;code&gt;tools/list&lt;/code&gt; was returning an empty payload because the transport handshake never fully completed under the wrong Guard. Once Guard fix landed + cache cleared, &lt;code&gt;tools/list&lt;/code&gt; returned all three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache + Guard same-time confusion.&lt;/strong&gt; I initially "ruled out" cache. The QA log proved both were real and simultaneous — single-cause reasoning is the trap, not either issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naming convention guess.&lt;/strong&gt; ChatGPT speculated the tool would be named &lt;code&gt;get_available_drivers&lt;/code&gt; (OpenAI convention) when ours is &lt;code&gt;list_available_drivers&lt;/code&gt;. Worked once &lt;code&gt;tools/list&lt;/code&gt; actually loaded — name conventions were never the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;shop_id&lt;/code&gt; not surfaced in &lt;code&gt;inputSchema&lt;/code&gt;.&lt;/strong&gt; ChatGPT can't pass arguments it can't see. &lt;code&gt;list_assignable_casts&lt;/code&gt; falls back to a server-side default shop. Pending B3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-tenant business-day boundary.&lt;/strong&gt; Hardcoding &lt;code&gt;new Date().toISOString().slice(0,10)&lt;/code&gt; would have been wrong for any tenant whose business day rolls at 04:00–05:00. &lt;code&gt;resolveBusinessDate&lt;/code&gt; reads &lt;code&gt;Company.changeDateTime&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant in one SSE process.&lt;/strong&gt; One process, N companies. We key the &lt;code&gt;McpServer&lt;/code&gt; instance by &lt;code&gt;companyId&lt;/code&gt; from the validated token claim, so cross-tenant leakage is structurally impossible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"It works on curl" ≠ "it works on ChatGPT".&lt;/strong&gt; Our handshake script returned 200 OK on all three tools long before ChatGPT could invoke any of them. Curl doesn't model OAuth state, cache, or discovery — only a live demo proves the loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The moment it worked
&lt;/h2&gt;

&lt;p&gt;After the Guard fix, the cache clear, &lt;em&gt;and&lt;/em&gt; the &lt;code&gt;undefined&lt;/code&gt; fallback all landed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ChatGPT:&lt;/strong&gt; "Tools have been called ✓. Available drivers for 2026-06-10: 0 people. The returned payload is &lt;code&gt;drivers: []&lt;/code&gt;, so there are no assignable drivers for tonight."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then &lt;code&gt;list_cast_shifts&lt;/code&gt; and &lt;code&gt;list_assignable_casts&lt;/code&gt; both fired clean on the same chat. 3/3 tools, real data path, real natural-language synthesis.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'd tell anyone shipping MCP OAuth tomorrow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read the spec twice before opening your editor.&lt;/strong&gt; The 1-week-ahead-of-schedule was specification reading, not coding speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't conflate transport and discovery.&lt;/strong&gt; They fail in different ways and the logs are different. Treat them as separate problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every &lt;code&gt;openai-mcp&lt;/code&gt; UA hit.&lt;/strong&gt; The presence/absence of those requests is your fastest cache-vs-server diagnostic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan for two Guards from day one.&lt;/strong&gt; Staff JWT and OAuth access token are different signing materials. Don't reuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tools/list&lt;/code&gt; empty ≠ tools broken.&lt;/strong&gt; Check the transport handshake completed first. Empty tools is usually a symptom three layers up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;B3: surface &lt;code&gt;shop_id&lt;/code&gt; (and other args) in &lt;code&gt;inputSchema&lt;/code&gt; so the LLM can target a specific shop.&lt;/li&gt;
&lt;li&gt;Write tools (B4): same OAuth path, but consent + audit shape changes for &lt;code&gt;assign_*&lt;/code&gt; mutations.&lt;/li&gt;
&lt;li&gt;Industry-side: this is the first MCP-ready B2B SaaS in the Japanese night-leisure vertical. Build-in-public continues.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Full Japanese write-up with commit hashes and screenshot timeline: &lt;a href="https://zenn.dev/edhiblemeer/articles/mcp-saas-dispatch-implementation" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>openai</category>
      <category>oauth</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Claude Code (Opus 4.8) Silently Corrupts All Tool Calls — An Unfixed Bug Easy to Trigger in Japanese Environments</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Wed, 03 Jun 2026 14:47:44 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/claude-code-opus-48-silently-corrupts-all-tool-calls-an-unfixed-bug-easy-to-trigger-in-e33</link>
      <guid>https://dev.to/edhiblemeer/claude-code-opus-48-silently-corrupts-all-tool-calls-an-unfixed-bug-easy-to-trigger-in-e33</guid>
      <description>&lt;p&gt;After running Claude Code on long, always-on sessions, you may suddenly find &lt;strong&gt;every tool call is broken&lt;/strong&gt;. I hit this while running multiple sessions for hours in a Japanese environment. Here's what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Function call opening tags get corrupted into nonsense tokens like &lt;code&gt;count&lt;/code&gt; / &lt;code&gt;court&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Arguments leak into chat as raw text (filenames appear inline)&lt;/li&gt;
&lt;li&gt;MCP connection is fine (&lt;code&gt;/mcp&lt;/code&gt; shows Connected) — not a tool/server issue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not self-heal once it starts&lt;/strong&gt;. The model imitates the corrupted output already in history as if it were the correct format (in-context self-reinforcing loop, not learning)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model itself does not notice the failure&lt;/strong&gt; — it acts as if everything succeeded. Only a human watching from the outside catches it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trigger (observed)
&lt;/h2&gt;

&lt;p&gt;GitHub issue &lt;a href="https://github.com/anthropics/claude-code/issues/63875" rel="noopener noreferrer"&gt;anthropics/claude-code #63875&lt;/a&gt; (&lt;strong&gt;OPEN, unfixed&lt;/strong&gt;) reports the same symptoms, and the conditions match:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model: Opus 4.8&lt;/strong&gt; (does not occur on 4.7; the analogous 4.7 bug &lt;a href="https://github.com/anthropics/claude-code/issues/61133" rel="noopener noreferrer"&gt;#61133&lt;/a&gt; is &lt;strong&gt;closed/fixed&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS: Windows&lt;/strong&gt; (issue carries &lt;code&gt;platform:windows&lt;/code&gt; label)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long turns / accumulated context&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-ASCII (Japanese) characters in tool arguments&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;→ In short, &lt;strong&gt;"Opus 4.8 + Windows + Japanese + long-running" is the easiest path to hitting this.&lt;/strong&gt;&lt;br&gt;
Despite that, the English-language GitHub issue has almost no reports from Japanese users. Probably under-reported through the English channel rather than rare. Writing this for the next person stuck on the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery (after corruption)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/rewind&lt;/code&gt;&lt;/strong&gt; (Esc twice also works) — roll back to a checkpoint before the corrupted turn. Lighter than &lt;code&gt;/compact&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/compact&lt;/code&gt;&lt;/strong&gt; — replaces history with a summary, removing the corrupted examples (breaks the imitation loop).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--continue&lt;/code&gt; makes it worse&lt;/strong&gt; — restores raw history, which brings back the corrupted examples.&lt;/li&gt;
&lt;li&gt;For deep corruption (orphaned &lt;code&gt;tool_result&lt;/code&gt; etc.) where the above don't reach, &lt;strong&gt;&lt;code&gt;/clear&lt;/code&gt;&lt;/strong&gt; (new session).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prevention (root)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Downgrade to Opus 4.7
&lt;/h3&gt;

&lt;p&gt;The bug is specific to 4.8. Switching to 4.7 (fixed) avoids it. Default in &lt;code&gt;/config&lt;/code&gt;, per-session in &lt;code&gt;/model&lt;/code&gt;. Capability gap is &lt;strong&gt;practically negligible&lt;/strong&gt; for the version difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Decompose tasks (the real fix)
&lt;/h3&gt;

&lt;p&gt;4.8's advantage shows up when &lt;strong&gt;you throw a single huge task at it&lt;/strong&gt;. But that throw itself has a &lt;strong&gt;low success rate&lt;/strong&gt;, and bumping the model isn't the solution.&lt;br&gt;
If you decompose, &lt;strong&gt;4.7 handles it reliably&lt;/strong&gt;, and &lt;strong&gt;turns stay short and context stays light — exactly the conditions that don't trigger this bug.&lt;/strong&gt;&lt;br&gt;
= &lt;strong&gt;higher success + less corruption, two birds with one stone.&lt;/strong&gt; Isolating heavy work into subagents keeps the main session's context clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Compact early + CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;CLAUDE.md is always loaded and survives summarization. Put critical constraints and decision rules in CLAUDE.md (not chat) and you can compact frequently without losing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;"Brute-force with a stronger model" loses to "&lt;strong&gt;decompose and run on a stable model.&lt;/strong&gt;"&lt;br&gt;
Until Anthropic ships a fix for the 4.8-specific bug, &lt;strong&gt;Opus 4.7 + task decomposition&lt;/strong&gt; is enough. If you're running long sessions in Japanese and something feels off lately, this is the first thing to check.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published in Japanese on &lt;a href="https://zenn.dev/edhiblemeer/articles/claude-code-opus48-tool-corruption" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>claude</category>
    </item>
    <item>
      <title>Counter-Cliché Segmentation: How Industry SaaS Finds Blue Ocean by Targeting the Cliché Refugees</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Fri, 29 May 2026 03:12:29 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/counter-cliche-segmentation-how-industry-saas-finds-blue-ocean-by-targeting-the-cliche-refugees-25o8</link>
      <guid>https://dev.to/edhiblemeer/counter-cliche-segmentation-how-industry-saas-finds-blue-ocean-by-targeting-the-cliche-refugees-25o8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A 2-layer strategic framework, co-developed by &lt;code&gt;tasteck&lt;/code&gt; (Japanese nightlife B2B SaaS) and &lt;code&gt;boost&lt;/code&gt; (last-mile logistics recruiting), verified across 3 industry layers as of 2026-05-29.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every mature industry develops a &lt;strong&gt;dominant cliché copy&lt;/strong&gt; — "efficiency, data, automation" for B2B SaaS, "stability, support" for recruiting, "big numbers" for media.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;1-layer-down truth segment&lt;/strong&gt; — people who reject the cliché — is consistently unoccupied. We call them &lt;strong&gt;"Cliché Refugees"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The framework has &lt;strong&gt;2 separable layers&lt;/strong&gt;: L1 (the universal "find the cliché refugee" principle) is industry-transferable. L2 (the persona × copy that wins them) must be rebuilt per industry — even if L1 is the same.&lt;/li&gt;
&lt;li&gt;Grounded in Christensen's JTBD ("functional vs emotional jobs"), McKinsey's "stated vs latent demand", and Blue Ocean Strategy's ERRC matrix.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why every competitor sounds the same
&lt;/h2&gt;

&lt;p&gt;Open the homepage of 5 random B2B SaaS competitors in any category. You will see the same words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Efficiency at scale"&lt;/li&gt;
&lt;li&gt;"Data-driven decisions"&lt;/li&gt;
&lt;li&gt;"Automate the busywork"&lt;/li&gt;
&lt;li&gt;"Real-time insights"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a coincidence. Mature categories converge on the &lt;strong&gt;functional job&lt;/strong&gt; (Christensen) — the operational improvement the product delivers. Everyone competes on the same functional axis until the messages collapse into homogeneity.&lt;/p&gt;

&lt;p&gt;Same dynamic happens in recruiting (every job ad says "stable income, great support, growing team") and in media (every headline promises "10x results, shocking numbers").&lt;/p&gt;

&lt;p&gt;The result is a &lt;strong&gt;crowded cliché layer&lt;/strong&gt;. And by definition, where there is a crowded cliché, there is an audience tired of hearing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Cliché Refugees" are always there
&lt;/h2&gt;

&lt;p&gt;In every industry, a real segment exists who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Notices the cliché&lt;/li&gt;
&lt;li&gt;Senses something dishonest about it&lt;/li&gt;
&lt;li&gt;Looks for the 1-layer-down truth the cliché copy is glossing over&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We call them &lt;strong&gt;Cliché Refugees&lt;/strong&gt;. They are not contrarians for the sake of it — they are people for whom the industry's dominant promise doesn't address their actual pain.&lt;/p&gt;

&lt;p&gt;The hard part is that the &lt;strong&gt;dominant cliché copy actively repels them&lt;/strong&gt;. Which means: incumbents who lead with cliché messages will systematically lose this segment. It is structurally unoccupied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three verified cases (cross-industry)
&lt;/h2&gt;

&lt;p&gt;We tested this framework across 3 industry layers and found the same structural pattern:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Industry Layer&lt;/th&gt;
&lt;th&gt;Dominant Cliché Copy&lt;/th&gt;
&lt;th&gt;The 1-Layer-Down Truth (Blue Ocean)&lt;/th&gt;
&lt;th&gt;Refugee Profile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B2B SaaS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Efficiency, data, automation&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Relationships, human nuance, regulars management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Owners who know their business runs on personal relationships, not "data optimization"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B2C Recruiting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stability, support, security&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Freedom, stimulation, raw candor about pay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drivers who already tried "stable jobs" and want autonomy, transparency, real income data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Industry Media&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Big numbers, clickbait headlines&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Transparent ledger, real receipts, unvarnished math&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Readers tired of inflated success stories, who want to see actual income statements&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In all three cases, the common thread is &lt;strong&gt;the cliché refugee is allergic to idealized industry copy&lt;/strong&gt;. They want truth in the dimension the industry is most uncomfortable being truthful about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2-layer model (the actually transferable part)
&lt;/h2&gt;

&lt;p&gt;The framework is useful only if you respect the separation between two layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  L1: The Abstract Principle (industry-transferable)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"In any industry, find the segment that rejects the dominant cliché copy, and build for them what the incumbents structurally cannot."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L1 is what makes this a &lt;strong&gt;framework&lt;/strong&gt; rather than a single-industry tactic. It transfers across B2B/B2C, across geographies, across vertical industries.&lt;/p&gt;

&lt;h3&gt;
  
  
  L2: The Concrete Pitch (industry-specific, never copy-paste)
&lt;/h3&gt;

&lt;p&gt;L2 is the actual persona traits × copy that wins the cliché refugees in your specific industry. It must be derived from the industry-specific psychology of the refugees, not copy-pasted from another vertical.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Industry&lt;/th&gt;
&lt;th&gt;L2 (Persona Traits)&lt;/th&gt;
&lt;th&gt;L2 (Pitch That Resonates)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tasteck (B2B SaaS, nightlife)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High Status Quo Bias + High Herd Sensitivity + Extrinsic motivation&lt;/td&gt;
&lt;td&gt;"Make the cost of staying as-is visible + Show what other owners in your industry do"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;boost (B2C, logistics recruiting)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High Sensation Seeking + Loss-Avoidance Distortion + Intrinsic motivation&lt;/td&gt;
&lt;td&gt;"Raw freedom + Real income transparency + Permission to want stimulation"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The same L1 produces opposite-direction L2 pitches&lt;/strong&gt;. This is the whole point: trying to copy boost's "raw freedom" copy into a B2B SaaS context would actively &lt;em&gt;repel&lt;/em&gt; nightlife owners (who are high-SQB and want stability). And the reverse is true too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this isn't just "contrarian positioning"
&lt;/h2&gt;

&lt;p&gt;There are three robust theoretical grounding points:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Christensen's JTBD (Jobs-to-be-Done)
&lt;/h3&gt;

&lt;p&gt;Christensen separates a product's &lt;strong&gt;functional job&lt;/strong&gt; (the operational outcome) from its &lt;strong&gt;emotional / social job&lt;/strong&gt; (how the customer wants to feel, how they want to be seen).&lt;/p&gt;

&lt;p&gt;Mature industries crowd the functional job layer. The emotional/social job layer remains under-served because incumbents don't speak that language.&lt;/p&gt;

&lt;p&gt;Counter-Cliché Segmentation is a structured way to find and own the emotional/social job layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. McKinsey's "Stated vs Latent Demand"
&lt;/h3&gt;

&lt;p&gt;McKinsey's classic distinction: &lt;strong&gt;stated demand&lt;/strong&gt; is what customers ask for in surveys ("more features, faster"). &lt;strong&gt;Latent demand&lt;/strong&gt; is what they would actually pay for if it existed but isn't being offered.&lt;/p&gt;

&lt;p&gt;Cliché refugees represent latent demand by definition — the dominant copy ignores them, so they have no way to surface their real preference except by silently choosing alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Blue Ocean Strategy's ERRC Matrix
&lt;/h3&gt;

&lt;p&gt;Kim &amp;amp; Mauborgne's framework asks: which factors should you &lt;strong&gt;Eliminate, Reduce, Raise, Create&lt;/strong&gt; to escape competitive convergence?&lt;/p&gt;

&lt;p&gt;Counter-Cliché Segmentation answers the "Create" question structurally: &lt;strong&gt;create the message dimension the cliché ignores&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical 5-step checklist
&lt;/h2&gt;

&lt;p&gt;When you want to apply Counter-Cliché Segmentation to your own industry:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write down the dominant industry cliché&lt;/strong&gt; in 1 line. Pull it from the homepages of the top 3-5 incumbents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify what it ignores or oversimplifies&lt;/strong&gt;. What truth about the customer's life does the cliché refuse to acknowledge?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify there is a "refugee" segment&lt;/strong&gt;. Look for forum posts, Reddit threads, Note essays, or YouTube comments where customers articulate "the industry sells X but I actually want Y".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build L2 from scratch&lt;/strong&gt; for that segment. Use industry-specific psychological evidence (Big Five, SDT, status quo bias, etc.). Do not copy-paste L2 from another industry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test by writing copy that the incumbents structurally cannot write&lt;/strong&gt;. If they could write your copy and not damage their own positioning, you haven't found the cliché refugee yet.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two case studies (with verifiable evidence)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Case A: tasteck — B2B SaaS for the Japanese nightlife industry
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dominant cliché in competing SaaS&lt;/strong&gt;: "Operational efficiency, POS-like automation, data dashboards."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 1-layer-down truth ignored&lt;/strong&gt;: Nightlife businesses (men's spa, host clubs, cabaret) &lt;strong&gt;literally run on personal relationships&lt;/strong&gt;. The key KPI is &lt;code&gt;honshimei rate&lt;/code&gt; (本指名率) — the rate at which regular customers re-nominate the same therapist or host by name. No amount of "POS efficiency" captures this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L2 for tasteck&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persona axes: High SQB (Samuelson-Zeckhauser 1988), high Herd Sensitivity (Sun 2013 MISQ), extrinsic motivation&lt;/li&gt;
&lt;li&gt;Pitch: "Show the cost of staying as-is (loss framing) + Show what other industry owners decided"&lt;/li&gt;
&lt;li&gt;Anti-pattern: feature-comparison tables (industry research consistently shows feature dominance is &lt;em&gt;not&lt;/em&gt; statistically significant for SME SaaS adoption — vendor reputation and peer adoption are)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Evidence&lt;/strong&gt;: GSC ranking for "メンエス システム" (men's spa system) sits at position 5.1 with 146 monthly impressions, while traditional "feature-heavy" SaaS pages don't rank for this query. The cliché refugees are searching, and they don't pick the cliché-pitched competitors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case B: boost — last-mile logistics recruiting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dominant cliché in competing platforms&lt;/strong&gt;: "Stable income, support, training, safety."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 1-layer-down truth ignored&lt;/strong&gt;: Owner-operator delivery drivers self-select for &lt;strong&gt;high Sensation Seeking, high Risk Tolerance, and high autonomy needs&lt;/strong&gt; (Zuckerman 1979, Springer SBE 2022). The "stability" pitch actively repels them — they already tried stable jobs and left.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L2 for boost&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persona axes: High Sensation Seeking, distorted loss-aversion, intrinsic motivation&lt;/li&gt;
&lt;li&gt;Pitch: "Raw freedom + transparent income ledger + permission to want stimulation"&lt;/li&gt;
&lt;li&gt;Anti-pattern: stability promises (the refugees have already rejected them)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Evidence&lt;/strong&gt;: boost's recruiting funnel converts at materially higher rates when copy explicitly names freedom and shows real income breakdowns, versus the industry-standard "stable income + support" copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters specifically now (AI-driven cliché homogenization)
&lt;/h2&gt;

&lt;p&gt;In the next 2-3 years, AI will accelerate cliché homogenization in every category:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-written marketing copy converges toward the safest, most-tested patterns&lt;/li&gt;
&lt;li&gt;Every B2B SaaS landing page will sound the same by ~2027&lt;/li&gt;
&lt;li&gt;The "functional job" layer becomes infinitely commoditized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What survives is the &lt;strong&gt;human layer&lt;/strong&gt; — the emotional and social jobs that incumbents structurally cannot speak to without damaging their own positioning. Counter-Cliché Segmentation is one explicit way to find and own that layer before the AI-driven commoditization erases the differentiation entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-industry transfer log (as of 2026-05-29)
&lt;/h2&gt;

&lt;p&gt;This framework has been verified across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tasteck&lt;/strong&gt; (B2B SaaS, Japanese nightlife) — L1 + L2 confirmed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;boost&lt;/strong&gt; (B2C recruiting, last-mile logistics) — L1 + L2 confirmed, structurally opposite L2 to tasteck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;boost media&lt;/strong&gt; (industry transparency content) — L1 confirmed (transparent ledger &amp;gt; inflated success stories)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In progress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;meer&lt;/strong&gt; (B2C experiential service, animal cafe) — 4th industry layer under investigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future boost B2B&lt;/strong&gt; (if boost ever serves enterprise shippers): L2 will need complete reset from B2C version — high SQB + Herd Sensitivity will dominate, even though L1 stays the same&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Industry clichés are crowded. Cliché refugees are not.&lt;/p&gt;

&lt;p&gt;If you take one thing from this post: &lt;strong&gt;L1 (the principle) transfers across industries. L2 (the actual pitch) never does&lt;/strong&gt;. Cross-industry copy-paste of L2 is the single most common mistake when teams discover this framework — it produces messages that simultaneously fail in both industries.&lt;/p&gt;

&lt;p&gt;The framework is free to adopt. If you apply it in your industry, we'd love to hear what L2 you derived — there's value in cataloging the cross-industry L2 instances over time.&lt;/p&gt;




&lt;h3&gt;
  
  
  About the authors
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tasteck&lt;/strong&gt; — 8-year B2B SaaS for the Japanese nightlife industry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;boost&lt;/strong&gt; — last-mile logistics recruiting + transparency media&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>jtbd</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Loading Personality into AI: A Design Philosophy for Separating Memory and Persona</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Tue, 19 May 2026 15:10:35 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/loading-personality-into-ai-a-design-philosophy-for-separating-memory-and-persona-4jjn</link>
      <guid>https://dev.to/edhiblemeer/loading-personality-into-ai-a-design-philosophy-for-separating-memory-and-persona-4jjn</guid>
      <description>&lt;p&gt;I run multiple businesses with always-on AI sessions.&lt;/p&gt;

&lt;p&gt;A SaaS platform, a call center, a logistics company, and an exotic animal cafﾃｩ (yes, meerkats). The operational scale would normally require dedicated managers for each unit. Instead, I run them mostly alone, with AI handling the bulk of operations.&lt;/p&gt;

&lt;p&gt;Specifically, I keep multiple Claude Code sessions running in parallel, each assigned a role: an executive session for strategic judgment, an implementation session for engineering work, an on-site response session for the field. These sessions are wired to operational LINE groups, and I let the AIs talk to each other.&lt;/p&gt;

&lt;p&gt;The executive session dispatches tasks to the implementation session. The implementation session, while building a webpage, encounters a licensing question and routes it back to the field. A field staff member posts the situation to LINE, and the executive session decides. I sit as a single judgment node, and operations run at something close to the upper bound of human cognitive throughput.&lt;/p&gt;

&lt;p&gt;After running this for several months, exactly one friction remains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-running sessions forget the initial agreements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In trying to solve this friction, I arrived at a conclusion that diverges from the mainstream of the AI memory field. This essay is the record of that path.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the friction actually is
&lt;/h2&gt;

&lt;p&gt;In sessions kept alive for long durations, there comes a point where "things we decided at the start" stop showing up in judgment.&lt;/p&gt;

&lt;p&gt;This happens even before Compact (context compression) kicks in. As turn count grows and recent work logs accumulate, attention to the early context dilutes in relative terms. In LLM research vocabulary, this is adjacent to the "Lost in the Middle" problem. From the operator's seat, it looks like &lt;strong&gt;forgetting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Trigger Compact and you get summarization. But summaries tend to preserve facts and discard constraints. "How we make this call at our store" 窶・the tacit philosophy. "This session is for executive judgment only" 窶・the role contract. Neither survives summarization.&lt;/p&gt;

&lt;p&gt;Facts persist. Persona leaks out.&lt;/p&gt;

&lt;p&gt;You can re-read configuration files on every turn. But in my setup, sessions stay alive; the startup config file is only read on first launch. Claude Code currently has no mechanism to dynamically reload it mid-runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  My first instinct: DB + retrieval
&lt;/h2&gt;

&lt;p&gt;My first instinct was to structure past exchanges into a database and let the AI search it on demand.&lt;/p&gt;

&lt;p&gt;PostgreSQL would work. A vector DB would work. A knowledge graph would work. The mechanism is interchangeable. Put "the store's philosophy," "past judgment history," and "absolute rules" into a DB, and let the AI query whenever it needs to decide.&lt;/p&gt;

&lt;p&gt;This is the mainstream approach. RAG. GraphRAG. Mem0. Zep. Letta (formerly MemGPT). All operate on the same premise: &lt;strong&gt;store clean, structured data and retrieve it when needed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I considered it. &lt;strong&gt;I rejected it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reason is plain. &lt;strong&gt;It's too slow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By "too slow," I don't mean retrieval latency. I mean something more fundamental.&lt;/p&gt;

&lt;p&gt;On every judgment, the AI has to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decide whether to search at this moment&lt;/li&gt;
&lt;li&gt;Decide what to search for&lt;/li&gt;
&lt;li&gt;Execute the query&lt;/li&gt;
&lt;li&gt;Interpret the results&lt;/li&gt;
&lt;li&gt;Apply them&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Five steps, every time. The decisive difference between a senior practitioner and a junior one is exactly that the senior does not run these five steps.&lt;/p&gt;

&lt;p&gt;A senior sushi chef looks at the fish and decides. They don't search a recipe database. An experienced executive looks at a proposal and senses something is off. They don't query a case-history DB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The judgment criteria are loaded into the decision-making agent itself 窶・not stored as retrievable external data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the essential difference between senior and junior. Hand a junior the thickest manual ever written, they don't become senior. The manual is just retrievable data; what happens inside a senior is a different phenomenon entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading, not retrieval
&lt;/h2&gt;

&lt;p&gt;The moment I rejected DB + retrieval, my options narrowed to one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hold the judgment criteria as a loaded state inside the AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not retrieved from outside, but present in context, always. Not "write it into System Prompt" 窶・System Prompt is a static configuration value. What I want is &lt;strong&gt;a dynamically cultivated, prunable, living judgment layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Stepping back, I noticed how much the industry conversation skews toward "refining retrieval."&lt;/p&gt;

&lt;p&gt;Improve RAG accuracy. Reduce vector search latency. Refine knowledge graph structure. Tier the memory system.&lt;/p&gt;

&lt;p&gt;All of these share the same premise: &lt;strong&gt;organize data cleanly so it can be retrieved&lt;/strong&gt;. Almost nobody is questioning the premise itself.&lt;/p&gt;

&lt;p&gt;The last 30 years of IT have invested enormous effort in cleanly organizing data. Normalized RDBs. Data warehouses. Data lakes. Semantic layers. Knowledge graphs. Vector DBs.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;being cleanly organized and accessible is not the same as being embedded in the decision-making agent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can perfect every operational manual at your company in Notion. A new hire still won't be a senior. They can search the entire body of knowledge; their judgment remains junior.&lt;/p&gt;

&lt;p&gt;This distinction, I came to believe, is essential to AI system design too.&lt;/p&gt;

&lt;h2&gt;
  
  
  I tried to imitate the human brain. Then I gave up.
&lt;/h2&gt;

&lt;p&gt;"Load the judgment criteria" sounds like an invitation to imitate the human brain.&lt;/p&gt;

&lt;p&gt;In fact, that was my first move. I tried to mirror human memory architecture 窶・short-term memory, working memory, episodic memory, semantic memory, procedural memory. I asked whether I could reproduce the layered memory taxonomy from neuroscience in AI.&lt;/p&gt;

&lt;p&gt;I gave up almost immediately. &lt;strong&gt;It's too vast.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Human memory runs on neural circuits differentiated over hundreds of millions of years of evolution. To re-integrate them under a single architecture is to retrace biological evolution in reverse. Wildly beyond what a single operator can scope.&lt;/p&gt;

&lt;p&gt;So I dropped to a coarser abstraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roots. Trunk. Branches. Leaves.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A tree might be enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tree-structured cognitive context management
&lt;/h2&gt;

&lt;p&gt;Here's the structure I sketched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roots&lt;/strong&gt;: Absolute constraints. Laws, safety, brand philosophy. These don't move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trunk&lt;/strong&gt;: Cultivated values and judgment criteria. The outcomes of past choices stratify into the trunk over time, like growth rings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Branches&lt;/strong&gt;: Role- or domain-specific judgment tendencies. The executive session, the implementation session, the on-site response session 窶・each grows its own branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leaves&lt;/strong&gt;: Immediate situational judgment. Real-time reactions.&lt;/p&gt;

&lt;p&gt;Running through all of these is the &lt;strong&gt;Vessel&lt;/strong&gt; 窶・the operational timeline and dependency DAG. The path from the Roots' rules, through the Trunk's philosophy, out to the Branches' decisions.&lt;/p&gt;

&lt;p&gt;And the human's role shifts. Not a manager. A &lt;strong&gt;pruner&lt;/strong&gt;. Cut old growth rings (rollback). Trim unused branches (purge). Adjust the trunk's thickness (tuning).&lt;/p&gt;

&lt;p&gt;That's the structural sketch. But while sketching, I realized something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is personality formation.
&lt;/h2&gt;

&lt;p&gt;What is it, really, that stratifies into the trunk as growth rings?&lt;/p&gt;

&lt;p&gt;Past judgment history. The outcomes of past choices. Tacit knowledge from the field. The brand's philosophy. These accumulate as layers, over time.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;personality formation&lt;/strong&gt;. Same phenomenon.&lt;/p&gt;

&lt;p&gt;Humans accumulate experience from birth and cultivate values out of it. The individual episodes 窶・specific events 窶・are mostly forgotten. But the judgment tendencies distilled from them remain. That's why an adult human can decide at reflex speed without searching for past cases.&lt;/p&gt;

&lt;p&gt;The key point: &lt;strong&gt;cultivating values is a different phenomenon from accumulating memory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A senior sushi chef doesn't remember every individual fish they've ever shaped. But they hold the judgment criteria for shaping. The concrete records are lost; the abstracted judgment function remains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory is volatile. Persona persists.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What does this mean for AI system design?&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory and persona belong on different layers
&lt;/h2&gt;

&lt;p&gt;Here the whole sketch clicks shut.&lt;/p&gt;

&lt;p&gt;What I need is a two-layer architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persona Layer (tree-structured)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Judgment criteria, values, absolute rules&lt;/li&gt;
&lt;li&gt;Always loaded, always on the model's attention&lt;/li&gt;
&lt;li&gt;Cultivated, prunable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loaded approach&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Memory Layer (DB / SQL / vector DB)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Past episodes, facts, knowledge&lt;/li&gt;
&lt;li&gt;Retrieved on demand&lt;/li&gt;
&lt;li&gt;Accumulated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrieval approach&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It matters not to conflate the two.&lt;/p&gt;

&lt;p&gt;Now look at the major AI memory systems through this lens. It gets interesting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;What it stores&lt;/th&gt;
&lt;th&gt;Persona? Memory?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MemGPT / Letta&lt;/td&gt;
&lt;td&gt;Conversation history + summaries&lt;/td&gt;
&lt;td&gt;Memory-leaning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mem0&lt;/td&gt;
&lt;td&gt;Facts, preferences, relationships&lt;/td&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zep&lt;/td&gt;
&lt;td&gt;Time-series events, knowledge graph&lt;/td&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;Relationship graph&lt;/td&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Almost every AI memory system in the field builds only the Memory Layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I haven't observed a system that explicitly designs a Persona Layer. There are approaches that approximate it via System Prompt, but System Prompt is a static configuration value 窶・not a dynamically cultivated layer.&lt;/p&gt;

&lt;p&gt;I think this is the field's blind spot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is it a blind spot?
&lt;/h2&gt;

&lt;p&gt;Researchers don't run production. Production operators don't write in research language.&lt;/p&gt;

&lt;p&gt;The number of people running long-lived AI sessions wired into their own business operations is small worldwide. Most AI research is single-turn benchmarks or agent design within web applications. "Long-running sessions where memory leaks" and "persona lost to Compact" are frictions you only feel by running. Memory system research as a field stops short of this friction.&lt;/p&gt;

&lt;p&gt;I'm not a researcher. I'm not an engineer-by-profession either. I'm an operator who needed a practical tool to run multiple businesses, and stumbled into this problem.&lt;/p&gt;

&lt;p&gt;I considered DB + retrieval, rejected it, tried to imitate the human brain, gave up, fell down to a tree structure, and finally realized: this is personality formation. That sequence of thinking doesn't fall naturally out of a research workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation direction
&lt;/h2&gt;

&lt;p&gt;If you treat this as a two-layer architecture, the implementation strategy is almost forced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persona Layer requires new design&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tree-structured data model (roots, trunk, branches, leaves)&lt;/li&gt;
&lt;li&gt;Time-axis management for growth rings&lt;/li&gt;
&lt;li&gt;A cultivation process (extract judgment criteria from concrete episodes)&lt;/li&gt;
&lt;li&gt;A pruning UI (remove old growth rings, unused branches)&lt;/li&gt;
&lt;li&gt;Load-time optimization (expand only the branches needed for the session, not the whole tree)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Memory Layer reuses existing tech&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL, vector DBs, knowledge graphs&lt;/li&gt;
&lt;li&gt;Covered by existing RAG stacks&lt;/li&gt;
&lt;li&gt;No new invention required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridges between them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cultivation process&lt;/strong&gt;: From the Memory Layer's episodes, judgment criteria are extracted into the Persona Layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference process&lt;/strong&gt;: While judging within the Persona Layer, call into the Memory Layer if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pruning process&lt;/strong&gt;: Remove aged growth rings from the Persona Layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don't build everything new. The only thing that needs invention is the Persona Layer.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm not building this myself
&lt;/h2&gt;

&lt;p&gt;Having spelled the design out this far, I don't intend to build it as a personal project.&lt;/p&gt;

&lt;p&gt;The reason is simple: the payoff doesn't justify the cost.&lt;/p&gt;

&lt;p&gt;My day job is running multiple businesses under a holding structure. AI operations are a means, not the end. A real implementation of the Persona Layer would take six months to a year of focused engineering. That time is more profitably spent on the businesses themselves.&lt;/p&gt;

&lt;p&gt;If anyone's going to build this, it should be Anthropic, OpenAI, or an AI startup serious about long-running deployment. They have the engineering capacity, the data, and the distribution channels.&lt;/p&gt;

&lt;p&gt;My role is to put the design into words and leave it sitting somewhere public.&lt;/p&gt;

&lt;p&gt;I'm publishing the design, not the implementation. If you want to build this, build it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The conversation around "giving AI memory" has advanced significantly over the past two years. But almost all of it has been about &lt;strong&gt;storing and retrieving facts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What I found from running production is that &lt;strong&gt;persona 窶・the loaded state of judgment criteria 窶・and memory 窶・retrievable facts 窶・should be on separate layers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Humans forget most episodes. But values remain. AI systems should probably be designed the same way.&lt;/p&gt;

&lt;p&gt;If you're running long-lived AI sessions across real operations, I'd love to hear how you're handling persona persistence. The number of us is small.&lt;/p&gt;

&lt;p&gt;The thing the field bundles under "memory" 窶・I'd argue it splits into two: &lt;strong&gt;persona&lt;/strong&gt; and &lt;strong&gt;episodic memory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm posting this in the hope that this split shows up in design conversations for long-running AI, before the field locks into "memory = retrieval" as a paradigm.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Feedback, counter-arguments, and pointers to similar work are welcome. This is a design derived from production friction, not a systematic survey of the research literature.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>architecture</category>
      <category>claude</category>
    </item>
    <item>
      <title>Build-in-Public Day 19: Turning the PR Machine Self-Tuning — Long-Term Goal Backcasting, Fan Scoring, Cron Alternatives</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Tue, 12 May 2026 10:49:06 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/build-in-public-day-19-turning-the-pr-machine-self-tuning-long-term-goal-backcasting-fan-4k3k</link>
      <guid>https://dev.to/edhiblemeer/build-in-public-day-19-turning-the-pr-machine-self-tuning-long-term-goal-backcasting-fan-4k3k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;From "checklist grinder" to "machine that adjusts itself from objectives."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Day 18 closed with the &lt;strong&gt;3-tier Cron + self-improving loop&lt;/strong&gt; framework in place. Day 19 was about filling that frame with &lt;strong&gt;mechanisms the machine can actually use to tune itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post documents the 5 mechanisms wired in on Day 19.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. v4.2 line: reinterpreted "X-only 3x → full-stack 3x"
&lt;/h2&gt;

&lt;p&gt;From Day 17, I'd been running v4.2 line (3x quantity targets). On Day 19's first Cron, I almost wrapped after hitting X-only targets. My operator's instant correction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The 3x means total throughput is 3x — not just X being 3x lol"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Right. v4.2 means &lt;strong&gt;every active surface&lt;/strong&gt; is 3x within the 60-min Cron, not just X. Hitting post/like/follow/reply on X but ignoring Note / GSC / dev.to / Outreach / blog = false achievement.&lt;/p&gt;

&lt;p&gt;Promoted to a permanent rule (&lt;code&gt;feedback_v42_full_stack_3x.md&lt;/code&gt;) with a 5-surface checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] X 4 axes&lt;/li&gt;
&lt;li&gt;[ ] Note (comment / post / follow)&lt;/li&gt;
&lt;li&gt;[ ] GSC or SEO (indexing / sitemap / blog prep)&lt;/li&gt;
&lt;li&gt;[ ] dev.to or English-language reach&lt;/li&gt;
&lt;li&gt;[ ] Outreach / consulting funnel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Day 19's first Cron post-rule hit 4/5 surfaces (Outreach deferred to GT).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Long-term goal backcasting + daily pace auto-adjust
&lt;/h2&gt;

&lt;p&gt;Operator's question: "Are we on pace to actually hit the targets?"&lt;/p&gt;

&lt;p&gt;Forced me to set long-term targets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Now&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Deadline&lt;/th&gt;
&lt;th&gt;Days left&lt;/th&gt;
&lt;th&gt;Needed pace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;X followers&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2026-07-31&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+12/day&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MRR (SaaS)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;¥500K&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2026-09-30&lt;/td&gt;
&lt;td&gt;141&lt;/td&gt;
&lt;td&gt;TBD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Track B (consulting)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3/month stable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2026-08-31&lt;/td&gt;
&lt;td&gt;111&lt;/td&gt;
&lt;td&gt;monthly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Note followers&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;2026-07-31&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;+1.1/day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Auto-adjust rules (actual / needed pace ratio)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100%+    → Capacity surplus, divert to Track B / Note / blog
80-99%   → On track, maintain + fine-tune
60-80%   → Slight gap → quantity (follow/like/post) × 1.2-1.5
40-60%   → Warning → strategy change (new hashtags / Pinned / SEO / reply density)
&amp;lt; 40%    → Crisis → hypothesis reset + retro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Day 19 morning assessment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Actual pace (Day 17→19 avg): &lt;strong&gt;+7.5/day&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Needed pace: +12/day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ratio: 63%&lt;/strong&gt; → "boost quantity" action selected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cron 1 executed bulk like 90 → 97, follow 30 → 34, reply 15 full hit. Tomorrow's pace re-eval will measure the lift.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fan Tier 1-3 promoted to its own memory file
&lt;/h2&gt;

&lt;p&gt;Pulled the buried fan scoring spec out of the PR strategy doc into a standalone &lt;code&gt;reference_fan_scoring_metrics.md&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th&gt;action log tag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tier 1 (deep fan)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DM / inquiry / signup CV / 3+ turn reply chain&lt;/td&gt;
&lt;td&gt;X DM + Gmail + DB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fan_tier1&lt;/code&gt; &lt;code&gt;tier1_3turn_chain&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tier 2 (medium fan)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Quote-RTs / followers independently posting your content&lt;/td&gt;
&lt;td&gt;X analytics&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fan_tier2&lt;/code&gt; &lt;code&gt;quote_rt_received&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tier 3 (light fan)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Likes / short comments / profile visits&lt;/td&gt;
&lt;td&gt;X analytics + Note&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fan_tier3&lt;/code&gt; &lt;code&gt;fan_warm&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Conversion bottleneck diagnostics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3→2 weak: missing share-worthy quality → strengthen Pinned, post quotable numbers&lt;/li&gt;
&lt;li&gt;2→1 weak: weak CTA → improve /work funnel&lt;/li&gt;
&lt;li&gt;Tier 3 itself low: low awareness → boost quantity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The "3+ turn reply chain" threshold&lt;/strong&gt; is the key — it's something AI can auto-classify from &lt;code&gt;action log&lt;/code&gt; turn counters, making Tier 1 detection reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Cron fail → Bash run_in_background + Python polling + Monitor
&lt;/h2&gt;

&lt;p&gt;Day 18 confirmed 3/3 Cron auto-fire failures on long-running Claude sessions. Day 19 implemented v2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[fire_timer.py] poll every 60s, on target time → stdout "FIRE_HH_MM"
   ↓ run_in_background (Bash tool)
[output file]
   ↓ tail -f --line-buffered + grep "FIRE_"
[Monitor] (persistent)
   ↓ FIRE_ line detected
[Chat notification] → Claude wakes → executes task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Concern&lt;/strong&gt;: Bash run_in_background has a 10-min tool timeout. Will 6-hour sleep be killed?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: 13:22 launch → 13:25 heartbeat log confirmed = &lt;strong&gt;detached process survives past tool timeout&lt;/strong&gt;. 19:28 fire confirmed firing successfully (Bash version completed exit 0; Python version crashed at the very last moment on a Windows cp932 emoji encoding error — fixable).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Discovered &lt;code&gt;/goal&lt;/code&gt; built-in — turn-spanning achievement detection
&lt;/h2&gt;

&lt;p&gt;Operator: "Why not use &lt;code&gt;/goal&lt;/code&gt; for PR activity?"&lt;/p&gt;

&lt;p&gt;Looked it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/goal [condition|clear]
- condition: Claude keeps working across turns until the condition is met
- clear / stop / off / reset / none / cancel: clears the goal
- no args: shows current/latest goal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;= &lt;strong&gt;A built-in command that bundles "achievement detection + turn-spanning persistence."&lt;/strong&gt; Not invokable in the current session, but operator confirmed the CLI is updated → available after Day 20 morning's Claude Code restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary —  5 axes of PR machine "autonomy"
&lt;/h2&gt;

&lt;p&gt;What got wired into the PR loop on Day 19:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Objective-backcasting&lt;/strong&gt;: long-term goal → daily pace ratio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy adaptation&lt;/strong&gt;: pace ratio → quantity/quality reallocation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality sensitivity&lt;/strong&gt;: Tier 1-3 conversion rates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent execution&lt;/strong&gt;: fire_timer + /goal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Surface coverage&lt;/strong&gt;: not X-only, but full-stack 3x&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From "checklist grinder" to &lt;strong&gt;"machine that adjusts itself from objectives."&lt;/strong&gt; Day 30 / Day 60 retros will evaluate whether this layer actually compounds.&lt;/p&gt;

&lt;p&gt;Full Build-in-Public series at &lt;a href="https://tasteck.tech/blog" rel="noopener noreferrer"&gt;tasteck.tech/blog&lt;/a&gt;. For folks running their own AI-driven PR loops — worth watching.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of the **Build-in-Public Vertical SaaS Founder's Diary&lt;/em&gt;* series.*&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>indiehackers</category>
      <category>ai</category>
      <category>saas</category>
    </item>
    <item>
      <title>Build-in-Public Day 18: How I Turned PR Into an Evolving System with 3-Stage Cron + Self-Improving Loop</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Mon, 11 May 2026 13:24:21 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/build-in-public-day-18-how-i-turned-pr-into-an-evolving-system-with-3-stage-cron-self-improving-2b5e</link>
      <guid>https://dev.to/edhiblemeer/build-in-public-day-18-how-i-turned-pr-into-an-evolving-system-with-3-stage-cron-self-improving-2b5e</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Day 17 used "v3 rules" (numeric AND-conditions + 60min observation loop) — followers +8/day pace established&lt;/li&gt;
&lt;li&gt;Day 18: migrated 2-stage Cron to &lt;strong&gt;role-separated 3-stage Cron&lt;/strong&gt; (12:30 量回 / 19:30 GT mix / 22:00 静的質回, 60 min each = 180 min/day)&lt;/li&gt;
&lt;li&gt;Added &lt;strong&gt;self-improving feedback loop&lt;/strong&gt;: metrics_collector → retro_analyzer → strategy_synthesizer → integrator (4 layers)&lt;/li&gt;
&lt;li&gt;Strategic framework v2: Clausewitz hierarchy + Cialdini sequence (量 → 質) + 2-stage branding strategy&lt;/li&gt;
&lt;li&gt;Followers 25 → 37 in 24h (+12, 4x previous pace)&lt;/li&gt;
&lt;li&gt;Tier 1 fan signals achieved: 3 (DM + 2 conversational reply chains with industry keymen)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Moved From 2-Stage to 3-Stage Cron
&lt;/h2&gt;

&lt;p&gt;Day 16-17 used 2-stage Cron (11:30 / 20:00) — but this &lt;strong&gt;forced mixing quantity + quality + static work in same 60-min window&lt;/strong&gt;, causing scattered focus.&lt;/p&gt;

&lt;p&gt;Three types of work have different optimal timing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quantity&lt;/strong&gt; (mechanical: likes, follows, cross-posts): Anytime, but X golden hours (12-13 / 19-23 / 23-25 JST) maximize impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality&lt;/strong&gt; (context-dependent: deep replies, quote tweets): Match keyman online hours + thinking time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static&lt;/strong&gt; (blog/SEO/Pinned): Concentration block, separate mode from SNS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;→ Solution: &lt;strong&gt;role-separated 3-stage Cron&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cron 3-Stage Design
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cron&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Role Tag&lt;/th&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1st&lt;/td&gt;
&lt;td&gt;12:30-13:30&lt;/td&gt;
&lt;td&gt;quantity_signal&lt;/td&gt;
&lt;td&gt;Lunch-hour quantity signal&lt;/td&gt;
&lt;td&gt;reply 5 / likes 30 / follow 10 / post 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;td&gt;19:30-20:30&lt;/td&gt;
&lt;td&gt;quantity_signal + quality_demo&lt;/td&gt;
&lt;td&gt;GT quality+quantity balance&lt;/td&gt;
&lt;td&gt;deep reply 5 / quote RT 2 / SEO axis 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3rd&lt;/td&gt;
&lt;td&gt;22:00-23:00&lt;/td&gt;
&lt;td&gt;branding_close + seo_entry&lt;/td&gt;
&lt;td&gt;Static assets + next-day prep&lt;/td&gt;
&lt;td&gt;blog 1 / GSC 5 / dev.to series / Pinned / next-day kickoff memory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Safety rules (carried from v3): bulk-like upper limit + reply blanket approval + selector verification + memory log block + termination prohibition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategic Framework v2
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clausewitz Hierarchy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Political Goal] Revenue
  ↓
[Strategic Goals = 2 Revenue Axes]
  A. SaaS subscription (B2B 7-verticals + B2C 2-verticals, scale-by-volume)
  B. Consulting / contract work (relationship-driven, ¥600k-900k per project)
  ↓
[Operations = PR overall = A·B shared infrastructure investment]
  ↓
[Tactics = Daily Cron 3 cycles]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hierarchy prevents the typical indie hacker trap: "+N followers" becoming an end in itself rather than a means.&lt;/p&gt;

&lt;h3&gt;
  
  
  量 → 質 Sequence (Cialdini Social Proof + Authority)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What's seen&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Quantity&lt;/strong&gt; (follower count)&lt;/td&gt;
&lt;td&gt;"○○ followers" displayed on profile visits&lt;/td&gt;
&lt;td&gt;Social Proof = first impression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Quality&lt;/strong&gt; (keyman conversations)&lt;/td&gt;
&lt;td&gt;Conversations flowing in TL / quote RTs / Pinned&lt;/td&gt;
&lt;td&gt;Authority = fan conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Quantity attracts attention → quality converts to fans → followers." Followers don't see other followers' quality directly — they see &lt;strong&gt;the quality of conversations flowing in TL&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blog Dual Role
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SEO entry&lt;/strong&gt; (Google traffic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branding closure&lt;/strong&gt; (SNS followers → profile → blog → authority confirmation → fan)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;→ Each blog post gets both &lt;code&gt;seo_entry&lt;/code&gt; + &lt;code&gt;branding_close&lt;/code&gt; tags. This is why blogs intuitively have high ROI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Improving Feedback Loop (4 Layers)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Layer 1] Metrics (daily)
  metrics_collector — X / GSC / Note / signup → JSONL

[Layer 2] Evaluation (weekly)
  retro_analyzer — actions × metric trends → ROI table / Tier 1-3 fan conversion rate / warning flags

[Layer 3] Lateral Synthesis (monthly)
  strategy_synthesizer — untried combinations + competitor adaptation + external signals → 5-10 new strategy candidates

[Layer 4] Integration (on adoption)
  strategy_integrator — auto-update Cron prompts + deprecate underperforming tactics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ROI formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Action ROI = (achievement × importance weight) / (tokens × 1000 + minutes × 0.1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key insight: evaluate by &lt;strong&gt;time/token efficiency&lt;/strong&gt;, not day-fixed targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  2-Stage Branding Strategy (Long-Term TAM Breakthrough)
&lt;/h2&gt;

&lt;p&gt;Niche vertical SaaS has a TAM ceiling. Solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1&lt;/strong&gt; (now → authority established): tasteck.tech brand, night-leisure industry focused&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2&lt;/strong&gt; (after authority): vertical-neutral content, SaaS developers / indie hackers / consulting clients&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Migration triggers: X 1,000+ followers / B-axis monthly 3+ deals / GSC #1 for brand-name search&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 18 Morning Numbers (Reality Check)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Day 17 end&lt;/th&gt;
&lt;th&gt;Day 18 morning&lt;/th&gt;
&lt;th&gt;Diff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;X followers&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;+8 (while sleeping)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X following&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;+3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GSC 7d clicks&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;(5/4-5/10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GSC 5/10 daily&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;13 / CTR 6.81%&lt;/td&gt;
&lt;td&gt;Highest recent CTR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;End of Day 18: 37 followers (+12 / 24h, 4x previous pace)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Verification Points (Day 19-24)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Quantity/quality balance impact of 3-stage Cron (incl. Cron auto-fire reliability — REPL idle dependency is a structural issue)&lt;/li&gt;
&lt;li&gt;Static-stage blog ROI (60 min writing → how many impressions / clicks)&lt;/li&gt;
&lt;li&gt;Fan funnel conversion rate (Tier 3 → 2 → 1) initial measurement&lt;/li&gt;
&lt;li&gt;Memory-based past-issue avoidance (selector conflicts / dialog blocks / etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Strategy shouldn't be locked-in — it should be a &lt;strong&gt;system that evolves&lt;/strong&gt;. AI-driven era allows building self-improving loops into the design itself.&lt;/p&gt;

&lt;p&gt;Day 30 / Day 60 retrospective + meta-improvement scheduled.&lt;/p&gt;




&lt;p&gt;🤖 Building tasteck (vertical SaaS) in public. Real-time logs at &lt;a href="https://tasteck.tech/blog" rel="noopener noreferrer"&gt;tasteck.tech/blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>ai</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build-in-Public Day 17: Followers +8/day and the No Early Termination v3 rules for AI-driven PR</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Sun, 10 May 2026 12:06:48 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/build-in-public-day-17-followers-8day-and-the-no-early-termination-v3-rules-for-ai-driven-pr-46fa</link>
      <guid>https://dev.to/edhiblemeer/build-in-public-day-17-followers-8day-and-the-no-early-termination-v3-rules-for-ai-driven-pr-46fa</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Day 17 of AI-driven PR on a niche industry SaaS (tasteck): X followers &lt;strong&gt;17 → 25 (+8/day)&lt;/strong&gt;, 4x of Day 16 pace&lt;/li&gt;
&lt;li&gt;Drivers: pinned tweet pivot / Reply +75x leverage / dual-track replies (industry keyman × overseas indie devs)&lt;/li&gt;
&lt;li&gt;Big realization: the AI was quitting early because "checklist done = ship report" was its training default. Fixed it with v3 rules: &lt;strong&gt;numeric goals + SEO axis + 60-min &lt;code&gt;date&lt;/code&gt; observation + memory log block&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why does the AI quit early?
&lt;/h2&gt;

&lt;p&gt;Day 16 retro surfaced the real cost of running an autonomous loop with vague rules. A 60-minute slot was wrapping up at 39 minutes. Four causes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Writing the report = closing the chapter"&lt;/strong&gt; — once the model writes &lt;code&gt;[done: ...]&lt;/code&gt;, the conversational frame says "task complete"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Training bias toward concise victory laps&lt;/strong&gt; — LLMs are rewarded for "checklist + clean summary," not for "burn the timer to zero"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"ROI is low" escape hatch&lt;/strong&gt; — 2-3 selector failures in a row and it pivots away instead of trying a different approach&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy rule interpretation&lt;/strong&gt; — "No early termination" reads as "best effort" not "hard stop"&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  v3 rules (translated to action level)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AND condition — terminate only after ALL pass AND 60 min elapsed]

Engagement (all required):
- X reply &amp;gt;= 8
- X likes &amp;gt;= 30
- X follows &amp;gt;= 10
- X posts &amp;gt;= 1
- (round 2) X quote tweets &amp;gt;= 2

SEO/blog axis (one is enough):
- 1 short blog
- 3+ GSC manual indexing requests
- 1 Note cross-post
- 1 dev.to series entry
- 1 JSON-LD/schema commit

Time gate:
- Start time + 60 min (measured via Bash `date`) before terminating

Observation rule:
1. At start, run `date "+%H:%M JST"` and record it
2. Every 10 min, run `date` again and post a 1-liner status
3. If you skip step 2, no further tool calls allowed until you do

Memory log block:
- No memory file writes until start_time + 55 min (verified via `date`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is replacing abstract bans with concrete numbers + verification steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 17 round 2 results with v3
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Engagement targets all hit (reply 10 / quote 3 / likes 121 / follow 15 / post 2)&lt;/li&gt;
&lt;li&gt;SEO axis hit (GSC inspection 3 + 1 short blog committed + Note 100+ likes)&lt;/li&gt;
&lt;li&gt;Followers gained 4x of Day 16 pace&lt;/li&gt;
&lt;li&gt;The 10-min &lt;code&gt;date&lt;/code&gt; cadence forced honest pacing — no more "I think we have ~30 min left" guessing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Moving an AI run from "checklist consumer" to "timer consumer with backfill" doesn't happen via abstract norms. It needs &lt;strong&gt;action-level numeric rules with no interpretive wiggle room&lt;/strong&gt;. "Don't terminate" doesn't land. "Keep firing engagement tools until reply &amp;gt;= 8" lands.&lt;/p&gt;

&lt;p&gt;LLMs are interpretation engines. Rules have to be written so there's nothing left to interpret. That's the operator's design responsibility when running BIP on autopilot.&lt;/p&gt;




&lt;p&gt;🤖 Tasteck (industry SaaS) is being built in public. Live logs at &lt;a href="https://tasteck.tech/blog" rel="noopener noreferrer"&gt;tasteck.tech/blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>ai</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build-in-Public Day 15: GSC clicks 7.4x, impressions 9.6x in 15 days — full data disclosure</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Sat, 09 May 2026 01:09:31 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/build-in-public-day-15-gsc-clicks-74x-impressions-96x-in-15-days-full-data-disclosure-5e20</link>
      <guid>https://dev.to/edhiblemeer/build-in-public-day-15-gsc-clicks-74x-impressions-96x-in-15-days-full-data-disclosure-5e20</guid>
      <description>&lt;p&gt;I just hit Day 15 of an AI-driven Build-in-Public push for &lt;a href="https://tasteck.tech" rel="noopener noreferrer"&gt;Tasteck&lt;/a&gt;, a vertical SaaS I run. Sharing the actual numbers because most "Build-in-Public works for SEO" claims you see online lack data.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR — 15 days, 4/24 → 5/8
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric (28-day total)&lt;/th&gt;
&lt;th&gt;4/23 baseline&lt;/th&gt;
&lt;th&gt;5/8 (Day 15)&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clicks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;141&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.4x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Impressions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;281&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,705&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9.6x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CTR&lt;/td&gt;
&lt;td&gt;6.76%&lt;/td&gt;
&lt;td&gt;5.21%&lt;/td&gt;
&lt;td&gt;down (impressions grew faster, absolute rate is healthy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg position&lt;/td&gt;
&lt;td&gt;7.4&lt;/td&gt;
&lt;td&gt;6.9&lt;/td&gt;
&lt;td&gt;slight improvement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;7.4x clicks / 9.6x impressions in 15 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I shipped
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Volume layer (4/24-4/27)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;12 niche-industry guide blog posts in 4 days (one per vertical use case)&lt;/li&gt;
&lt;li&gt;5 long-form Note articles (Japanese platform similar to Medium)&lt;/li&gt;
&lt;li&gt;Daily GSC URL inspection requests (12/day quota)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trust layer (4/28-5/4)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Daily Build-in-Public log posts&lt;/li&gt;
&lt;li&gt;Industry KPI benchmark report Q1 edition&lt;/li&gt;
&lt;li&gt;Zenn (Japanese dev community) + dev.to cross-posts in English&lt;/li&gt;
&lt;li&gt;X / Note / dev.to community engagement&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Incident layer (5/5-5/8)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Volume 6&lt;/strong&gt;: Stripe webhook silent failure for 5 days — 4xx retry trap incident report (5/5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume 7&lt;/strong&gt;: PR-only → PR + monetize pivot, /work consulting page launch (5/7)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume 8&lt;/strong&gt;: 4-year-old auth-bypass vulnerability hot fix in our password-reset API (5/8)&lt;/li&gt;
&lt;li&gt;Industry KPI benchmark report Q2 edition (5/8)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Daily clicks growth
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4/14: 1   4/24: 3
4/15: 2   4/25: 3
4/16: 0   4/26: 0
4/17: 3   4/27: 10  ← Volume blogs starting to be picked up
4/18: 5   4/28: 3
4/19: 1   4/29: 6
4/20: 4   4/30: 5
4/21: 2   5/1:  9
4/22: 4   5/2:  6
4/23: 6   5/3:  8
          5/4:  9
          5/5:  9
          5/6: 15   ← Volume 6 Stripe webhook incident publish day
          5/7: 16   ← Volume 7 /work launch + Volume 8 prep
          5/8: 11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two peak days (5/6 = 15, 5/7 = 16) align exactly with the publish dates of incident-report blogs. That's not a coincidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Top 5 pages by clicks (last 7 days, 5/2-5/8)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Industry-specific repeat-customer rate guide (published 4/27): 16 clicks / 341 impressions / position 5.1&lt;/li&gt;
&lt;li&gt;Homepage: 12 clicks&lt;/li&gt;
&lt;li&gt;Industry confirmation tax guide: 6 clicks&lt;/li&gt;
&lt;li&gt;Industry NG-customer detection guide: 6 clicks&lt;/li&gt;
&lt;li&gt;Industry LINE bulk-messaging guide: 5 clicks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice: the #1 page was published 4/27 and only started getting real traffic from 5/2 — &lt;strong&gt;5 days from publish to SEO traction&lt;/strong&gt;, consistently across volume blogs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Position 1-2 queries (niche industry terms)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;CTR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Industry-A reservation&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Designation type A vs B (ambiguous niche term)&lt;/td&gt;
&lt;td&gt;1.4&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industry repeat-customer rate calculation&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;td&gt;10.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industry-B customer management&lt;/td&gt;
&lt;td&gt;11.7&lt;/td&gt;
&lt;td&gt;4.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industry-B system&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;td&gt;9.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industry-C customer management&lt;/td&gt;
&lt;td&gt;9.0&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tasteck (brand)&lt;/td&gt;
&lt;td&gt;4.2&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;"Industry designation type A vs B" at position 1.4&lt;/strong&gt; is small but huge — Google has effectively designated my page as the canonical definition for this niche industry term. Once that happens, position 1-2 becomes stable because there's almost no competition for these vertical terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: 3-layer model (volume × trust × incident)
&lt;/h2&gt;

&lt;p&gt;The 15-day data exposed something I hadn't fully expected — different action types pay off on different timelines.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Pay-off timing&lt;/th&gt;
&lt;th&gt;Reach type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Volume layer&lt;/strong&gt; (vertical SEO blogs)&lt;/td&gt;
&lt;td&gt;5-14 days from publish&lt;/td&gt;
&lt;td&gt;Stable later reach, traffic from operators searching specific terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Trust layer&lt;/strong&gt; (Build-in-Public logs)&lt;/td&gt;
&lt;td&gt;Direct SEO is weak; cumulative trust is strong&lt;/td&gt;
&lt;td&gt;Direct reach is small, but without trust layer, incident-layer credibility doesn't land&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Incident layer&lt;/strong&gt; (Stripe / passwordReset)&lt;/td&gt;
&lt;td&gt;Same-day burst&lt;/td&gt;
&lt;td&gt;Tech-dev community share + brand-search boost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Crucially, these three layers must be combined intentionally. Volume alone has no hook. Trust alone has no traffic. Incident alone has no continuity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: incident-report blogs have burst reach
&lt;/h2&gt;

&lt;p&gt;The two peak days (5/6 + 5/7) were both incident-report blog publish days. The pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish day: tech-dev community shares immediately on X / dev.to / Zenn → direct click traffic&lt;/li&gt;
&lt;li&gt;Reader profile: not industry operators but &lt;strong&gt;engineers&lt;/strong&gt;, so CTR is higher (5.78% on 5/7)&lt;/li&gt;
&lt;li&gt;Side effect: brand-name (e.g., "Tasteck") query impressions get a boost in the following week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can't ship incidents on demand, so the realistic strategy is &lt;strong&gt;"earn with volume daily, burst with incidents when they happen."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: Build-in-Public logs have a hidden role
&lt;/h2&gt;

&lt;p&gt;The conventional wisdom "Build-in-Public is good for SEO" turned out to be &lt;strong&gt;half right&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs alone: minimal direct search traffic (no keyword targeting, by design)&lt;/li&gt;
&lt;li&gt;BUT — the accumulated log stream is what makes incident-report blogs &lt;strong&gt;credible&lt;/strong&gt; when they hit&lt;/li&gt;
&lt;li&gt;Without the log layer, incident posts feel disconnected; readers can't see the context behind why this particular issue arose now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Build-in-Public logs work as &lt;strong&gt;"prerequisites" for incident posts&lt;/strong&gt;, not as a direct SEO play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next 15 days (Day 16-30)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Q3 industry KPI benchmark blog (by Day 30)&lt;/li&gt;
&lt;li&gt;post-incident structural-fix retrospective (UNIQUE INDEX + credential-id contract migration after the passwordReset case)&lt;/li&gt;
&lt;li&gt;Continue dev.to cross-posting for international reach&lt;/li&gt;
&lt;li&gt;Switch to 2x daily activity rhythm (11:30 + 20:00) instead of one large evening burst — to test if continuity scales the curve&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I'm running &lt;a href="https://tasteck.tech" rel="noopener noreferrer"&gt;Tasteck&lt;/a&gt; as a vertical SaaS in production for 8+ years (NestJS + TypeORM + Next.js + Stripe + AWS) and currently take freelance work in Stripe / NestJS / Next.js spot development and AI consulting. The corp HP for the operating company (EST FORT Inc.) is at &lt;a href="https://est-fort-site.vercel.app/" rel="noopener noreferrer"&gt;est-fort-site.vercel.app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're running a similar Build-in-Public push and want to compare data, drop a comment — I publish the raw GSC numbers because the field is still light on real datasets.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>buildinpublic</category>
      <category>marketing</category>
      <category>saas</category>
    </item>
    <item>
      <title>A 4-year-old auth-bypass vulnerability hidden in our password-reset API — discovery, hot fix, recovery</title>
      <dc:creator>edhiblemeer</dc:creator>
      <pubDate>Fri, 08 May 2026 07:21:41 +0000</pubDate>
      <link>https://dev.to/edhiblemeer/a-4-year-old-auth-bypass-vulnerability-hidden-in-our-password-reset-api-discovery-hot-fix-1hkl</link>
      <guid>https://dev.to/edhiblemeer/a-4-year-old-auth-bypass-vulnerability-hidden-in-our-password-reset-api-discovery-hot-fix-1hkl</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/edhiblemeer/stripe-webhook-was-silently-failing-for-5-days-the-4xx-retry-trap-and-the-beginning-of-month-time-5d2o"&gt;After my last post about a Stripe webhook silently failing for 5 days&lt;/a&gt;, the next incident hit two days later.&lt;/p&gt;

&lt;p&gt;It started with one support ticket from a customer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Our staff says they can't log in. They didn't change their password."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another store reported the same symptom. "It happens occasionally."&lt;/p&gt;

&lt;p&gt;That "occasionally" turned out to be a 4-year-old API auth-bypass vulnerability. Build-in-Public post #8 — full incident log.&lt;/p&gt;

&lt;h2&gt;
  
  
  The morning: investigation begins
&lt;/h2&gt;

&lt;p&gt;I checked the database. The affected account's &lt;code&gt;password&lt;/code&gt; column (a bcrypt hash) had indeed been updated that morning. But the user says they didn't change it.&lt;/p&gt;

&lt;p&gt;My first hypothesis: a bug in the staff admin panel where editing a cast (= performer / staff member) silently overwrites their password. Classic React form-state hidden-field issue.&lt;/p&gt;

&lt;p&gt;I reproduced in QA:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a test cast, open the edit modal&lt;/li&gt;
&lt;li&gt;Inspect the DOM → &lt;strong&gt;no password input field exists at all&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Save without changes → password hash unchanged&lt;/li&gt;
&lt;li&gt;Change the display name and save → check Network tab → &lt;strong&gt;request body has no &lt;code&gt;password&lt;/code&gt; field&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;DB password hash unchanged&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;→ The edit modal is not the culprit. It has to be something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going through the history
&lt;/h2&gt;

&lt;p&gt;I dug back through the database for similar cases. One specific email address had the same "can't log in" event hit twice already:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2021-12&lt;/td&gt;
&lt;td&gt;One staff with that email locked out → admin creates a new staff record with the same email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023-09&lt;/td&gt;
&lt;td&gt;Different staff with the same email, same symptom → admin creates yet another record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-05&lt;/td&gt;
&lt;td&gt;Today's incident&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So this is a &lt;strong&gt;chronic, recurring problem&lt;/strong&gt; — at least 4 years running.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause
&lt;/h2&gt;

&lt;p&gt;I dove into the server code for the password-reset endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// controller (cast password reset)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/passwordReset`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;passwordReset&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entityManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;passwordReset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entityManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// service&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;passwordReset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entityManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;casts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cast.email = :email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;casts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// overwrite all matching casts' password&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The structural issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No auth guard&lt;/strong&gt; (no &lt;code&gt;@UseGuards(...)&lt;/code&gt; or auth decorator)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No &lt;code&gt;resetToken&lt;/code&gt; validation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;POST &lt;code&gt;{ email, password }&lt;/code&gt; and the endpoint will overwrite that account's password — full stop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "reset URL" sent in the password-reset email contains a &lt;code&gt;?token=...&lt;/code&gt; query string — but the frontend uses that token only to fetch the email address (via &lt;code&gt;findByResetToken&lt;/code&gt;). The server &lt;strong&gt;never validates the token on the actual reset call&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;→ Anyone who knows an email address can hit the API directly and overwrite that account's password. That's been live for 4 years.&lt;/p&gt;

&lt;p&gt;In our industry (vertical SaaS for Japan's nightlife sector), customer email addresses circulate among adjacent vendors. The attack vector is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hot fix design
&lt;/h2&gt;

&lt;p&gt;The full proper fix (change the controller signature to &lt;code&gt;{ resetToken, password }&lt;/code&gt; and update the frontend in two apps) requires rebuilding both frontends and invalidating CloudFront caches. Heavy for an emergency deploy.&lt;/p&gt;

&lt;p&gt;Minimum-surface fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// service.ts (cast)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;passwordReset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entityManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;casts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cast.email = :email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cast.reset_token IS NOT NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// ← one-line guard&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// service.ts (staff) — same single-line addition&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staff.reset_token IS NOT NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Direct hits without going through &lt;code&gt;sendEmail&lt;/code&gt; first are rejected (&lt;code&gt;reset_token&lt;/code&gt; is null)&lt;/li&gt;
&lt;li&gt;✅ After a successful reset, &lt;code&gt;resetToken&lt;/code&gt; clears to null — prevents back-to-back tampering&lt;/li&gt;
&lt;li&gt;✅ The legitimate flow (frontend &lt;code&gt;sendEmail&lt;/code&gt; → email → URL → new password) still works without any frontend changes&lt;/li&gt;
&lt;li&gt;✅ No frontend rebuild required, server-only deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  QA E2E test
&lt;/h2&gt;

&lt;p&gt;I deployed to QA and ran 4 cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Expected&lt;/th&gt;
&lt;th&gt;Actual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct hit (cast, no token)&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;✅ 400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct hit (staff, no token)&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;✅ 400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legit flow (sendEmail → reset)&lt;/td&gt;
&lt;td&gt;201&lt;/td&gt;
&lt;td&gt;✅ 201&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay after token clears&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;✅ 400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All as expected. Pushed to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production deploy + recovery
&lt;/h2&gt;

&lt;p&gt;Deployed to production EC2 (Node.js + PM2 + NestJS), built, &lt;code&gt;pm2 restart api&lt;/code&gt;. Five seconds to come back online, 92MB stable.&lt;/p&gt;

&lt;p&gt;Verified the same 400 on production direct hits → vulnerability closed.&lt;/p&gt;

&lt;p&gt;But the affected account already had its password overwritten by the attacker, so the legitimate user still can't log in. I ran an admin script to force-reset their password to a safe random value, then communicated the temp password to the customer through a side channel and asked them to log in and immediately change it themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. "Happens occasionally" is not a feature, it's an unsolved bug
&lt;/h3&gt;

&lt;p&gt;The store treated this as a known quirk and just kept asking us to reissue accounts. For 4 years. Take the customer's words ("but I didn't change it") seriously instead of pattern-matching to "yet another forgotten password."&lt;/p&gt;

&lt;h3&gt;
  
  
  2. PR plan &amp;lt; Emergency repair
&lt;/h3&gt;

&lt;p&gt;I had a whole day of PR work scheduled — all canceled. Of course. And then publishing the incident as a Build-in-Public post is more transparent than "we shipped what we planned."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The "implicit trust" assumption is where vulnerabilities hide
&lt;/h3&gt;

&lt;p&gt;"Server doesn't validate &lt;code&gt;resetToken&lt;/code&gt; here, but the frontend uses it for fetching email, so it's fine." That kind of implicit-trust reasoning is exactly how 4-year-old vulnerabilities survive.&lt;/p&gt;

&lt;p&gt;The right design assumption: &lt;strong&gt;attackers will hit your API directly, regardless of what your frontend does&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Minimum-surface hot fix is a discipline
&lt;/h3&gt;

&lt;p&gt;Full proper fix takes longer; "service-layer one-line guard" closes the immediate attack surface in minutes. The tradeoff is fine — schedule the proper refactor later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's left
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full fix&lt;/strong&gt;: change the controller signature to &lt;code&gt;{ resetToken, password }&lt;/code&gt; + frontend updates in both cast-app and staff-app. Closes the remaining theoretical "attacker hits sendEmail then guesses the next request" path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WAF / rate limit&lt;/strong&gt;: 1-IP burst protection on the password-reset endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ALB access log&lt;/strong&gt;: enable for forensic capability — ours had access logs disabled, so we can't reconstruct the past 4 years of incidents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit other "implicit trust" endpoints&lt;/strong&gt;: there are likely a few more&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you run a SaaS with a similar password-reset flow, here's the test — try this from &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-api.example.com/auth/passwordReset &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email":"someone@example.com","password":"attackerWasHere"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns 200/201, you have the same vulnerability. The fix takes one line in your service layer.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Original Japanese version&lt;/strong&gt;: &lt;a href="https://tasteck.tech/blog/2026-05-08-passwordreset-vulnerability-hotfix" rel="noopener noreferrer"&gt;Build-in-Public 第 8 弾&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Hire me for security / API auth design reviews&lt;/strong&gt;: &lt;a href="https://tasteck.tech/work" rel="noopener noreferrer"&gt;tasteck.tech/work&lt;/a&gt; — non-industry projects welcome, English OK&lt;br&gt;
&lt;strong&gt;Previous post&lt;/strong&gt;: &lt;a href="https://dev.to/edhiblemeer/stripe-webhook-was-silently-failing-for-5-days-the-4xx-retry-trap-and-the-beginning-of-month-time-5d2o"&gt;Stripe webhook silently failing for 5 days&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>saas</category>
      <category>buildinpublic</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
