[{"data":1,"prerenderedAt":3273},["ShallowReactive",2],{"navigation_en":3,"posts_en:/en/blog/building-one-front":79,"blog-alt-locales:building-one-front:en":994,"posts_en:/en/blog/building-one-front:surround":995},[4],{"title":5,"path":6,"stem":7,"children":8,"page":78},"En","/en","en",[9],{"title":10,"path":11,"stem":12,"children":13,"page":78},"Blog","/en/blog","en/blog",[14,18,22,26,30,34,38,42,46,50,54,58,62,66,70,74],{"title":15,"path":16,"stem":17},"Security Practices for MCP Using JSON-RPC","/en/blog/mcp-security","en/blog/1. mcp-security",{"title":19,"path":20,"stem":21},"MCP Servers: Connecting AI to Real-Time Data","/en/blog/mcp-create","en/blog/2. mcp-create",{"title":23,"path":24,"stem":25},"MCP Servers: Risks of Using in Claude Desktop","/en/blog/mcp-risks","en/blog/3. mcp-risks",{"title":27,"path":28,"stem":29},"MCP Servers: Integrating LLM in E-Commerce Systems","/en/blog/mcp","en/blog/4. mcp",{"title":31,"path":32,"stem":33},"MCP Tiny Agents On-Premises: Breaking Free from Cloud Dependencies","/en/blog/mcp-tiny-agents-on-premises","en/blog/5. mcp-tiny-agents-on-premises",{"title":35,"path":36,"stem":37},"From Idea to API in 2 Days: Building Bankly with Agentic Workflows","/en/blog/overvibing","en/blog/6. overvibing",{"title":39,"path":40,"stem":41},"AI Chat, MCP Server build with Agentic Workflow Protocol for demo at Checkatrade .com","/en/blog/mcp-in-chat-demo","en/blog/8. mcp-in-chat-demo",{"title":43,"path":44,"stem":45},"AI Opportunities for Checkatrade considering PERN Stack","/en/blog/ai-opportunities-pern-stack","en/blog/ai-opportunities-pern-stack",{"title":47,"path":48,"stem":49},"Domain-Driven Design in Full-Stack Frameworks","/en/blog/architecture","en/blog/architecture",{"title":51,"path":52,"stem":53},"one-front, as told by git","/en/blog/building-one-front","en/blog/building-one-front",{"title":55,"path":56,"stem":57},"Commercial architecture is a chain, not a pricing page","/en/blog/commercial-architecture-chain","en/blog/commercial-architecture-chain",{"title":59,"path":60,"stem":61},"Quality: How to protect AI-powered Frontends?","/en/blog/qa-front","en/blog/qa-front",{"title":63,"path":64,"stem":65},"The Hidden Dangers of GraphQL: Security, Performance, and Complexity Risks","/en/blog/risking-with-graphql","en/blog/risking-with-graphql",{"title":67,"path":68,"stem":69},"From Zero to Hero: Scaling up to million users","/en/blog/scalability","en/blog/scalability",{"title":71,"path":72,"stem":73},"IT Consulting and Strategy","/en/blog/strategy","en/blog/strategy",{"title":75,"path":76,"stem":77},"Distributed Systems","/en/blog/systems","en/blog/systems",false,{"id":80,"title":51,"authors":81,"badge":88,"body":92,"date":985,"description":986,"extension":987,"image":988,"meta":990,"navigation":991,"path":52,"seo":992,"stem":53,"__hash__":993},"posts_en/en/blog/building-one-front.md",[82],{"name":83,"description":84,"to":85,"avatar":86},"Michael Wybraniec","System Design, GenAI Automation & Architecture","https://www.linkedin.com/in/one-front/",{"src":87},"https://media.licdn.com/dms/image/v2/D4D03AQEGvIVcrTTS2g/profile-displayphoto-shrink_800_800/B4DZbtSf7LIEAc-/0/1747737772225?e=1753315200&v=beta&t=EOviZQkM396PoGctVjDCdlG8U3vN5UKWiIeNQ8IFUgQ",{"label":89,"color":90,"variant":91},"Case study","primary","soft",{"type":93,"value":94,"toc":977},"minimark",[95,98,123,145,155,250,263,330,336,351,362,481,496,536,551,580,647,661,676,704,707,772,781,847,853,919,932,947,973],[96,97],"br",{},[99,100,103,112],"div",{"className":101},[102],"text-justify",[104,105,106,107,111],"p",{},"I do not remember one-front as a roadmap. I remember it as ",[108,109,110],"strong",{},"time"," — loud weeks, long quiet stretches, then another spike when something needed a place on the internet.",[104,113,114,115,118,119,122],{},"This post is that trail made readable: ",[108,116,117],{},"~227 commits"," on one domain, then what ",[108,120,121],{},"~2.8K"," visitors actually did. Not a feature tour — a record of when the site was alive, and when it was not.",[99,124,129,136],{"className":125},[126,127,128],"flex","justify-between","items-center",[130,131,133],"h2",{"id":132},"when-the-work-happened",[108,134,135],{},"When the work happened",[104,137,138],{},[139,140,142],"a",{"href":141},"#top",[108,143,144],{},"⤴",[99,146,148],{"className":147},[102],[104,149,150,151,154],{},"By quarter, the pattern is obvious. Two quiet seasons, then a rebuild, a writing stretch, a CV sprint, and quieter shipping again. Empty quarters are not missing data — they are months with ",[108,152,153],{},"zero"," commits.",[156,157,162],"pre",{"className":158,"code":159,"language":160,"meta":161,"style":161},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","---\nconfig:\n  xyChart:\n    width: 900\n    height: 300\n  themeVariables:\n    xyChart:\n      plotColorPalette: \"#4A7CD8\"\n---\nxychart-beta\n  title \"Commits by quarter — silence is a real bar (height 0)\"\n  x-axis [\"Q3'24\", \"Q4'24\", \"Q1'25\", \"Q2'25\", \"Q3'25\", \"Q4'25\", \"Q1'26\", \"Q2'26\", \"Q3'26\"]\n  y-axis \"Commits\" 0 --> 130\n  bar [8, 0, 0, 123, 9, 7, 72, 3, 5]\n","mermaid","",[163,164,165,173,179,185,191,197,203,209,215,220,226,232,238,244],"code",{"__ignoreMap":161},[166,167,170],"span",{"class":168,"line":169},"line",1,[166,171,172],{},"---\n",[166,174,176],{"class":168,"line":175},2,[166,177,178],{},"config:\n",[166,180,182],{"class":168,"line":181},3,[166,183,184],{},"  xyChart:\n",[166,186,188],{"class":168,"line":187},4,[166,189,190],{},"    width: 900\n",[166,192,194],{"class":168,"line":193},5,[166,195,196],{},"    height: 300\n",[166,198,200],{"class":168,"line":199},6,[166,201,202],{},"  themeVariables:\n",[166,204,206],{"class":168,"line":205},7,[166,207,208],{},"    xyChart:\n",[166,210,212],{"class":168,"line":211},8,[166,213,214],{},"      plotColorPalette: \"#4A7CD8\"\n",[166,216,218],{"class":168,"line":217},9,[166,219,172],{},[166,221,223],{"class":168,"line":222},10,[166,224,225],{},"xychart-beta\n",[166,227,229],{"class":168,"line":228},11,[166,230,231],{},"  title \"Commits by quarter — silence is a real bar (height 0)\"\n",[166,233,235],{"class":168,"line":234},12,[166,236,237],{},"  x-axis [\"Q3'24\", \"Q4'24\", \"Q1'25\", \"Q2'25\", \"Q3'25\", \"Q4'25\", \"Q1'26\", \"Q2'26\", \"Q3'26\"]\n",[166,239,241],{"class":168,"line":240},13,[166,242,243],{},"  y-axis \"Commits\" 0 --> 130\n",[166,245,247],{"class":168,"line":246},14,[166,248,249],{},"  bar [8, 0, 0, 123, 9, 7, 72, 3, 5]\n",[99,251,253],{"className":252},[102],[104,254,255,258,259,262],{},[108,256,257],{},"Q2 2025"," (rebuild + content) and ",[108,260,261],{},"Q1 2026"," (CV as product) dominate. Everything else is maintenance or pause. Concentration makes that sharper:",[156,264,266],{"className":158,"code":265,"language":160,"meta":161,"style":161},"---\nconfig:\n  xyChart:\n    width: 900\n    height: 260\n  themeVariables:\n    xyChart:\n      plotColorPalette: \"#1D4ED8\"\n---\nxychart-beta\n  title \"Commit concentration — 3 peak months = 70% of all commits\"\n  x-axis [\"Apr'25 + Jun'25 + Jan'26\", \"Other 11 active months\"]\n  y-axis \"Commits\" 0 --> 180\n  bar [160, 67]\n",[163,267,268,272,276,280,284,289,293,297,302,306,310,315,320,325],{"__ignoreMap":161},[166,269,270],{"class":168,"line":169},[166,271,172],{},[166,273,274],{"class":168,"line":175},[166,275,178],{},[166,277,278],{"class":168,"line":181},[166,279,184],{},[166,281,282],{"class":168,"line":187},[166,283,190],{},[166,285,286],{"class":168,"line":193},[166,287,288],{},"    height: 260\n",[166,290,291],{"class":168,"line":199},[166,292,202],{},[166,294,295],{"class":168,"line":205},[166,296,208],{},[166,298,299],{"class":168,"line":211},[166,300,301],{},"      plotColorPalette: \"#1D4ED8\"\n",[166,303,304],{"class":168,"line":217},[166,305,172],{},[166,307,308],{"class":168,"line":222},[166,309,225],{},[166,311,312],{"class":168,"line":228},[166,313,314],{},"  title \"Commit concentration — 3 peak months = 70% of all commits\"\n",[166,316,317],{"class":168,"line":234},[166,318,319],{},"  x-axis [\"Apr'25 + Jun'25 + Jan'26\", \"Other 11 active months\"]\n",[166,321,322],{"class":168,"line":240},[166,323,324],{},"  y-axis \"Commits\" 0 --> 180\n",[166,326,327],{"class":168,"line":246},[166,328,329],{},"  bar [160, 67]\n",[99,331,333],{"className":332},[102],[104,334,335],{},"April 2025 (58), June 2025 (42), and January 2026 (60) are most of the repo. A personal site without a habit surface goes dark; the chart is just the receipt.",[99,337,339,345],{"className":338},[126,127,128],[130,340,342],{"id":341},"what-changed-the-product",[108,343,344],{},"What changed the product",[104,346,347],{},[139,348,349],{"href":141},[108,350,144],{},[99,352,354],{"className":353},[102],[104,355,356,357,361],{},"If you keep only the commits that changed what the site ",[358,359,360],"em",{},"is",", the path is short:",[156,363,365],{"className":158,"code":364,"language":160,"meta":161,"style":161},"---\nheader: Product milestones — Aug 2024 → Sep 2026\nconfig:\n  theme: 'default'\n  themeVariables:\n    'git0': '#4A7CD8'\n    'git1': '#633486'\n    'git2': '#566F97'\n---\ngitGraph LR:\n    commit id: \"init site\" tag: \"2024-08\" type: HIGHLIGHT\n    commit id: \"contact + light mode\" tag: \"2024-09\"\n    commit id: \"pause\" tag: \"quiet\"\n    commit id: \"Nuxt rebuild\" tag: \"2025-04\" type: HIGHLIGHT\n    commit id: \"deploy pipeline\" type: REVERSE\n    commit id: \"blog + i18n\"\n    commit id: \"RnD index\" tag: \"2025-05\"\n    commit id: \"MCP writing\" tag: \"2025-06\" type: HIGHLIGHT\n    commit id: \"CV product\" tag: \"2026-01\" type: HIGHLIGHT\n    commit id: \"CI + Sentry\" tag: \"2026-02\"\n    commit id: \"Audit4 / Vibe / Overvibing\" tag: \"2026-04\" type: HIGHLIGHT\n    commit id: \"positioning refresh\" tag: \"2026-09\" type: HIGHLIGHT\n",[163,366,367,371,376,380,385,389,394,399,404,408,413,418,423,428,433,439,445,451,457,463,469,475],{"__ignoreMap":161},[166,368,369],{"class":168,"line":169},[166,370,172],{},[166,372,373],{"class":168,"line":175},[166,374,375],{},"header: Product milestones — Aug 2024 → Sep 2026\n",[166,377,378],{"class":168,"line":181},[166,379,178],{},[166,381,382],{"class":168,"line":187},[166,383,384],{},"  theme: 'default'\n",[166,386,387],{"class":168,"line":193},[166,388,202],{},[166,390,391],{"class":168,"line":199},[166,392,393],{},"    'git0': '#4A7CD8'\n",[166,395,396],{"class":168,"line":205},[166,397,398],{},"    'git1': '#633486'\n",[166,400,401],{"class":168,"line":211},[166,402,403],{},"    'git2': '#566F97'\n",[166,405,406],{"class":168,"line":217},[166,407,172],{},[166,409,410],{"class":168,"line":222},[166,411,412],{},"gitGraph LR:\n",[166,414,415],{"class":168,"line":228},[166,416,417],{},"    commit id: \"init site\" tag: \"2024-08\" type: HIGHLIGHT\n",[166,419,420],{"class":168,"line":234},[166,421,422],{},"    commit id: \"contact + light mode\" tag: \"2024-09\"\n",[166,424,425],{"class":168,"line":240},[166,426,427],{},"    commit id: \"pause\" tag: \"quiet\"\n",[166,429,430],{"class":168,"line":246},[166,431,432],{},"    commit id: \"Nuxt rebuild\" tag: \"2025-04\" type: HIGHLIGHT\n",[166,434,436],{"class":168,"line":435},15,[166,437,438],{},"    commit id: \"deploy pipeline\" type: REVERSE\n",[166,440,442],{"class":168,"line":441},16,[166,443,444],{},"    commit id: \"blog + i18n\"\n",[166,446,448],{"class":168,"line":447},17,[166,449,450],{},"    commit id: \"RnD index\" tag: \"2025-05\"\n",[166,452,454],{"class":168,"line":453},18,[166,455,456],{},"    commit id: \"MCP writing\" tag: \"2025-06\" type: HIGHLIGHT\n",[166,458,460],{"class":168,"line":459},19,[166,461,462],{},"    commit id: \"CV product\" tag: \"2026-01\" type: HIGHLIGHT\n",[166,464,466],{"class":168,"line":465},20,[166,467,468],{},"    commit id: \"CI + Sentry\" tag: \"2026-02\"\n",[166,470,472],{"class":168,"line":471},21,[166,473,474],{},"    commit id: \"Audit4 / Vibe / Overvibing\" tag: \"2026-04\" type: HIGHLIGHT\n",[166,476,478],{"class":168,"line":477},22,[166,479,480],{},"    commit id: \"positioning refresh\" tag: \"2026-09\" type: HIGHLIGHT\n",[99,482,484,490],{"className":483},[126,127,128],[130,485,487],{"id":486},"timeline-in-plain-language",[108,488,489],{},"Timeline in plain language",[104,491,492],{},[139,493,494],{"href":141},[108,495,144],{},[99,497,499,505,511,517,523,529],{"className":498},[102],[104,500,501,504],{},[108,502,503],{},"2024"," — Domain, contact, light mode. Then silence. LinkedIn still carried more attention than the site.",[104,506,507,510],{},[108,508,509],{},"Spring 2025"," — Rebuild on Nuxt. Deploy and config work that looks noisy in git and was necessary in production. Blog in Markdown, translations when a post earned them, RnD when polished “Projects” felt dishonest.",[104,512,513,516],{},[108,514,515],{},"Summer 2025"," — Writing season: MCP, testing, architecture. Some posts also ship as audio: blog → ElevenLabs → Spotify → embed.",[104,518,519,522],{},[108,520,521],{},"January 2026"," — CV treated as a product: hydration, print, locales, process discipline so the week stayed shippable.",[104,524,525,528],{},[108,526,527],{},"Later"," — Experiments live under RnD (Audit4, Vibe Drawing, Overvibing). Positioning catches up: system design and GenAI, not a freelance brochure.",[104,530,531,532,535],{},"Time moved in ",[108,533,534],{},"spikes and silence",", not in a smooth feature roadmap.",[99,537,539,545],{"className":538},[126,127,128],[130,540,542],{"id":541},"what-git-can-and-cannot-say-about-ai",[108,543,544],{},"What git can (and cannot) say about AI",[104,546,547],{},[139,548,549],{"href":141},[108,550,144],{},[99,552,554,557],{"className":553},[102],[104,555,556],{},"A “% built with AI” chart would be fiction. Git does not know that.",[104,558,559,560,563,564,567,568,571,572,575,576,579],{},"What it ",[358,561,562],{},"can"," show: commits that carry a Cursor trailer (",[163,565,566],{},"Co-authored-by: Cursor","). On this repo that is ",[108,569,570],{},"19 / 227"," (~",[108,573,574],{},"8%","), starting only in ",[108,577,578],{},"February 2026",".",[156,581,583],{"className":158,"code":582,"language":160,"meta":161,"style":161},"---\nconfig:\n  xyChart:\n    width: 900\n    height: 280\n  themeVariables:\n    xyChart:\n      plotColorPalette: \"#633486\"\n---\nxychart-beta\n  title \"Cursor-tagged commits only — 19 of 227 total (~8%)\"\n  x-axis [\"Feb'26 (12)\", \"Apr'26 (1)\", \"May'26 (2)\", \"Sep'26 (5)\"]\n  y-axis \"Tagged commits\" 0 --> 14\n  bar [12, 1, 2, 5]\n",[163,584,585,589,593,597,601,606,610,614,619,623,627,632,637,642],{"__ignoreMap":161},[166,586,587],{"class":168,"line":169},[166,588,172],{},[166,590,591],{"class":168,"line":175},[166,592,178],{},[166,594,595],{"class":168,"line":181},[166,596,184],{},[166,598,599],{"class":168,"line":187},[166,600,190],{},[166,602,603],{"class":168,"line":193},[166,604,605],{},"    height: 280\n",[166,607,608],{"class":168,"line":199},[166,609,202],{},[166,611,612],{"class":168,"line":205},[166,613,208],{},[166,615,616],{"class":168,"line":211},[166,617,618],{},"      plotColorPalette: \"#633486\"\n",[166,620,621],{"class":168,"line":217},[166,622,172],{},[166,624,625],{"class":168,"line":222},[166,626,225],{},[166,628,629],{"class":168,"line":228},[166,630,631],{},"  title \"Cursor-tagged commits only — 19 of 227 total (~8%)\"\n",[166,633,634],{"class":168,"line":234},[166,635,636],{},"  x-axis [\"Feb'26 (12)\", \"Apr'26 (1)\", \"May'26 (2)\", \"Sep'26 (5)\"]\n",[166,638,639],{"class":168,"line":240},[166,640,641],{},"  y-axis \"Tagged commits\" 0 --> 14\n",[166,643,644],{"class":168,"line":246},[166,645,646],{},"  bar [12, 1, 2, 5]\n",[99,648,650],{"className":649},[102],[104,651,652,653,656,657,660],{},"This proves when Cursor ",[358,654,655],{},"signed"," a commit. It does ",[108,658,659],{},"not"," prove AI help without a trailer, how much of a commit was generated, or work outside git (including the voice pipeline). Treat it as a limited, honest signal — not a productivity score.",[99,662,664,670],{"className":663},[126,127,128],[130,665,667],{"id":666},"what-visitors-did",[108,668,669],{},"What visitors did",[104,671,672],{},[139,673,674],{"href":141},[108,675,144],{},[99,677,679],{"className":678},[102],[104,680,681,684,685,688,689,691,692,695,696,699,700,703],{},[108,682,683],{},"Method (GA4 snapshot):"," property-wide active users ≈ ",[108,686,687],{},"2.8K",", new users ≈ ",[108,690,687],{},", events ≈ ",[108,693,694],{},"40K",", average engagement ≈ ",[108,697,698],{},"1m 29s",". Almost no returning users. Geography below uses named city rows from the export, rolled to country/region; everyone else is ",[108,701,702],{},"Other"," — not invented city-level detail.",[705,706],"ga-visitors-map",{},[156,708,710],{"className":158,"code":709,"language":160,"meta":161,"style":161},"---\nconfig:\n  xyChart:\n    width: 900\n    height: 280\n  themeVariables:\n    xyChart:\n      plotColorPalette: \"#1D4ED8\"\n---\nxychart-beta\n  title \"Users by country — named GA rows + Other (sums to ~2.8K)\"\n  x-axis [\"Poland 20%\", \"US 17%\", \"Singapore 5%\", \"France 3%\", \"China 3%\", \"Other 52%\"]\n  y-axis \"Users\" 0 --> 1600\n  bar [554, 484, 143, 82, 76, 1461]\n",[163,711,712,716,720,724,728,732,736,740,744,748,752,757,762,767],{"__ignoreMap":161},[166,713,714],{"class":168,"line":169},[166,715,172],{},[166,717,718],{"class":168,"line":175},[166,719,178],{},[166,721,722],{"class":168,"line":181},[166,723,184],{},[166,725,726],{"class":168,"line":187},[166,727,190],{},[166,729,730],{"class":168,"line":193},[166,731,605],{},[166,733,734],{"class":168,"line":199},[166,735,202],{},[166,737,738],{"class":168,"line":205},[166,739,208],{},[166,741,742],{"class":168,"line":211},[166,743,301],{},[166,745,746],{"class":168,"line":217},[166,747,172],{},[166,749,750],{"class":168,"line":222},[166,751,225],{},[166,753,754],{"class":168,"line":228},[166,755,756],{},"  title \"Users by country — named GA rows + Other (sums to ~2.8K)\"\n",[166,758,759],{"class":168,"line":234},[166,760,761],{},"  x-axis [\"Poland 20%\", \"US 17%\", \"Singapore 5%\", \"France 3%\", \"China 3%\", \"Other 52%\"]\n",[166,763,764],{"class":168,"line":240},[166,765,766],{},"  y-axis \"Users\" 0 --> 1600\n",[166,768,769],{"class":168,"line":246},[166,770,771],{},"  bar [554, 484, 143, 82, 76, 1461]\n",[99,773,775],{"className":774},[102],[104,776,777,778,780],{},"Poland (Warsaw) is the largest named city. The US city rows include names that often correlate with hosting or bot noise — counted in the total, interpreted cautiously. ",[108,779,702],{}," is still real traffic; the export simply did not list those cities.",[156,782,784],{"className":158,"code":783,"language":160,"meta":161,"style":161},"---\nconfig:\n  xyChart:\n    width: 900\n    height: 300\n  themeVariables:\n    xyChart:\n      plotColorPalette: \"#0F766E\"\n---\nxychart-beta\n  title \"Top pages — homepage ≈ 58% of views in this set\"\n  x-axis [\"Home 58%\", \"Blog 15%\", \"ONE-FRONT 15%\", \"Product CV 6%\", \"Resume 5%\", \"Bankly 2%\"]\n  y-axis \"Views\" 0 --> 11000\n  bar [9900, 2500, 2500, 950, 780, 423]\n",[163,785,786,790,794,798,802,806,810,814,819,823,827,832,837,842],{"__ignoreMap":161},[166,787,788],{"class":168,"line":169},[166,789,172],{},[166,791,792],{"class":168,"line":175},[166,793,178],{},[166,795,796],{"class":168,"line":181},[166,797,184],{},[166,799,800],{"class":168,"line":187},[166,801,190],{},[166,803,804],{"class":168,"line":193},[166,805,196],{},[166,807,808],{"class":168,"line":199},[166,809,202],{},[166,811,812],{"class":168,"line":205},[166,813,208],{},[166,815,816],{"class":168,"line":211},[166,817,818],{},"      plotColorPalette: \"#0F766E\"\n",[166,820,821],{"class":168,"line":217},[166,822,172],{},[166,824,825],{"class":168,"line":222},[166,826,225],{},[166,828,829],{"class":168,"line":228},[166,830,831],{},"  title \"Top pages — homepage ≈ 58% of views in this set\"\n",[166,833,834],{"class":168,"line":234},[166,835,836],{},"  x-axis [\"Home 58%\", \"Blog 15%\", \"ONE-FRONT 15%\", \"Product CV 6%\", \"Resume 5%\", \"Bankly 2%\"]\n",[166,838,839],{"class":168,"line":240},[166,840,841],{},"  y-axis \"Views\" 0 --> 11000\n",[166,843,844],{"class":168,"line":246},[166,845,846],{},"  bar [9900, 2500, 2500, 950, 780, 423]\n",[99,848,850],{"className":849},[102],[104,851,852],{},"Among measured top pages, the homepage is still the front door. Blog and CV matter; they are not where most sessions start.",[156,854,856],{"className":158,"code":855,"language":160,"meta":161,"style":161},"---\nconfig:\n  xyChart:\n    width: 900\n    height: 280\n  themeVariables:\n    xyChart:\n      plotColorPalette: \"#B45309\"\n---\nxychart-beta\n  title \"First-user source — Direct ≈ 64% of ~2.8K users\"\n  x-axis [\"Direct 64%\", \"Google 9%\", \"LinkedIn 3%\", \"Other 24%\"]\n  y-axis \"Users\" 0 --> 2000\n  bar [1800, 249, 74, 677]\n",[163,857,858,862,866,870,874,878,882,886,891,895,899,904,909,914],{"__ignoreMap":161},[166,859,860],{"class":168,"line":169},[166,861,172],{},[166,863,864],{"class":168,"line":175},[166,865,178],{},[166,867,868],{"class":168,"line":181},[166,869,184],{},[166,871,872],{"class":168,"line":187},[166,873,190],{},[166,875,876],{"class":168,"line":193},[166,877,605],{},[166,879,880],{"class":168,"line":199},[166,881,202],{},[166,883,884],{"class":168,"line":205},[166,885,208],{},[166,887,888],{"class":168,"line":211},[166,889,890],{},"      plotColorPalette: \"#B45309\"\n",[166,892,893],{"class":168,"line":217},[166,894,172],{},[166,896,897],{"class":168,"line":222},[166,898,225],{},[166,900,901],{"class":168,"line":228},[166,902,903],{},"  title \"First-user source — Direct ≈ 64% of ~2.8K users\"\n",[166,905,906],{"class":168,"line":234},[166,907,908],{},"  x-axis [\"Direct 64%\", \"Google 9%\", \"LinkedIn 3%\", \"Other 24%\"]\n",[166,910,911],{"class":168,"line":240},[166,912,913],{},"  y-axis \"Users\" 0 --> 2000\n",[166,915,916],{"class":168,"line":246},[166,917,918],{},"  bar [1800, 249, 74, 677]\n",[99,920,922],{"className":921},[102],[104,923,924,925,928,929,931],{},"Most people arrive ",[108,926,927],{},"direct"," (bookmark, pasted link, typed domain). Organic search and LinkedIn are smaller. ",[108,930,702],{}," includes a long tail of referrals — some legitimate, some junk. Same shape as the commit chart: spikes and paths, not a smooth growth curve.",[99,933,935,941],{"className":934},[126,127,128],[130,936,938],{"id":937},"work-with-me",[108,939,940],{},"Work with me",[104,942,943],{},[139,944,945],{"href":141},[108,946,144],{},[99,948,950,953],{"className":949},[102],[104,951,952],{},"The public trail is the point: I ship in focused bursts, leave a measurable record, and keep front-end systems maintainable after the spike — architecture, product UI, GenAI/automation glue, and the ops that keep a site alive.",[104,954,955,958,959,963,964,963,969],{},[108,956,957],{},"Next step:"," ",[139,960,962],{"href":961},"/cv/product","Product CV"," · ",[139,965,968],{"href":85,"rel":966},[967],"nofollow","LinkedIn",[139,970,972],{"href":971},"/","one-front.com",[974,975,976],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":161,"searchDepth":175,"depth":175,"links":978},[979,980,981,982,983,984],{"id":132,"depth":175,"text":135},{"id":341,"depth":175,"text":344},{"id":486,"depth":175,"text":489},{"id":541,"depth":175,"text":544},{"id":666,"depth":175,"text":669},{"id":937,"depth":175,"text":940},"2026-09-27","Two years of commits, silence, and traffic — what the public repo and analytics actually show about building a personal site.","md",{"src":989},"/blog/mcp/mcp-cover.png",{},true,{"title":51,"description":986},"Az6ASQuxzTLgVOePm47cj3aE1gEHBofiKlOPRBFgxi0",[],[996],{"id":997,"title":55,"authors":998,"badge":1001,"body":1003,"date":3266,"description":3267,"extension":987,"image":3268,"meta":3270,"navigation":991,"path":56,"seo":3271,"stem":57,"__hash__":3272},"posts_en/en/blog/commercial-architecture-chain.md",[999],{"name":83,"description":84,"to":85,"avatar":1000},{"src":87},{"label":1002,"color":90,"variant":91},"Architecture, RevOps",{"type":93,"value":1004,"toc":3243},[1005,1007,1035,1050,1104,1107,1122,1132,1140,1146,1159,1169,1334,1352,1354,1369,1379,1495,1512,1518,1627,1639,1641,1656,1666,1673,1682,1707,1797,1803,1809,1821,1982,1988,1994,2019,2025,2031,2052,2058,2067,2092,2098,2104,2120,2126,2132,2148,2154,2164,2172,2188,2190,2205,2215,2275,2317,2380,2397,2417,2495,2497,2512,2518,2610,2616,2643,2666,2668,2683,2704,2706,2721,2731,2780,2910,2950,2959,2961,2976,2982,3069,3071,3086,3092,3098,3106,3108,3123,3129,3170,3176,3178,3193,3208,3228,3241],[96,1006],{},[99,1008,1010,1017,1028],{"className":1009},[102],[104,1011,1012,1013,1016],{},"Most teams ship a ",[108,1014,1015],{},"pricing page"," and call it monetization. Then they add Stripe. Then someone hard-codes “Pro gets feature X” in three API routes. Then ops needs a complimentary account and someone opens a hotfix branch. Then the vendor bill arrives and nobody knows which tenant burned the margin.",[104,1018,1019,1020,1023,1024,1027],{},"That pattern is common because soft-launch shorthand — “pricing + FinOps + growth” — sounds complete. It is ",[108,1021,1022],{},"necessary but incomplete",". A serious commercial model is not one document, one page, or one billing integration. It is a ",[108,1025,1026],{},"system of capabilities"," with named owners, a clear sequence, and a single product catalog as source of truth.",[104,1029,1030,1031,1034],{},"This article is ",[108,1032,1033],{},"project-agnostic",": a portable commercial architecture any SaaS or multi-tenant team can reuse. Same idea as domain architecture elsewhere on this blog — boundaries first, enforce at the edge, don’t invent a second truth in the payment provider.",[99,1036,1038,1044],{"className":1037},[126,127,128],[130,1039,1041],{"id":1040},"tldr",[108,1042,1043],{},"TL;DR",[104,1045,1046],{},[139,1047,1048],{"href":141},[108,1049,144],{},[99,1051,1053],{"className":1052},[102],[1054,1055,1056,1064,1070,1076,1087,1093],"ul",{},[1057,1058,1059,1060,1063],"li",{},"Monetization is a ",[108,1061,1062],{},"chain",": catalog → agreement → provision → entitlements → meter → bill → cash (+ FinOps cost loop).",[1057,1065,1066,1069],{},[108,1067,1068],{},"One catalog"," feeds pricing, admin, entitlements, and billing — never a second plan list in the MoR.",[1057,1071,1072,1075],{},[108,1073,1074],{},"Entitlements ≠ feature flags","; deny with a clear upgrade path.",[1057,1077,1078,1079,1082,1083,1086],{},"Ship ",[108,1080,1081],{},"Encode"," (catalog + enforce + gates + admin) before ",[108,1084,1085],{},"Charge","; legalize before the first real payment.",[1057,1088,1089,1092],{},[108,1090,1091],{},"Cost ≠ revenue"," — FinOps and pricing are separate ledgers.",[1057,1094,1095,1096,1099,1100,1103],{},"Steal the ",[108,1097,1098],{},"cheat sheet"," and ",[108,1101,1102],{},"Start Monday"," lists at the end.",[1105,1106],"hr",{},[99,1108,1110,1116],{"className":1109},[126,127,128],[130,1111,1113],{"id":1112},"_1-the-chain-what-best-means",[108,1114,1115],{},"1. The chain (what “best” means)",[104,1117,1118],{},[139,1119,1120],{"href":141},[108,1121,144],{},[99,1123,1125],{"className":1124},[102],[104,1126,1127,1128,1131],{},"Leaders in billing and entitlement stacks (think Stripe Billing / Chargebee-class systems, entitlement + meter layers, FinOps practice on the cost side) treat monetization as ",[108,1129,1130],{},"one governed chain",". Marketing may start with a pricing page. Architecture starts here:",[156,1133,1138],{"className":1134,"code":1136,"language":1137,"meta":161},[1135],"language-text","Catalog (what we sell)\n  → Quote / order / subscription (commercial agreement)\n    → Provisioning (tenant exists)\n      → Entitlements (what runtime may do)\n        → Metering (what was used)\n          → Rating & billing (what we invoice)\n            → Cash & revenue ops (collected, recognized)\n","text",[163,1139,1136],{"__ignoreMap":161},[99,1141,1143],{"className":1142},[102],[104,1144,1145],{},"Each arrow is a handoff. If you skip a link — for example billing without entitlements, or entitlements without a catalog — the next team invents their own truth. That is how you get three plan lists: one on the website, one in admin folklore, one in Stripe.",[99,1147,1149],{"className":1148},[102],[104,1150,1151,1154,1155,1158],{},[108,1152,1153],{},"Nuance:"," entitlements and metering are not only left-to-right. Caps often ",[108,1156,1157],{},"read meters"," (“allow if usage is under the limit”), so meters feed entitlements as well as billing and FinOps. Keep the seven-step chain as the primary story; treat the cap check as a feedback edge, not a reason to smash “entitle + meter + bill” into one blob.",[99,1160,1162],{"className":1161},[102],[104,1163,1164,1165,1168],{},"There is also a ",[108,1166,1167],{},"parallel cost loop",". Revenue asks “what did they pay?” Cost asks “what did we pay to serve them?” Conflating those questions is how teams underprice forever or panic-cut features without data.",[156,1170,1172],{"className":158,"code":1171,"language":160,"meta":161,"style":161},"---\nheader: Commercial architecture - revenue chain and FinOps cost loop\nlegend:\n  - color: \"#3B82F6\"\n    text: \"Revenue chain\"\n  - color: \"#F59E0B\"\n    text: \"Cost loop (FinOps)\"\n---\nflowchart TB\n  subgraph Revenue[\"Revenue chain\"]\n    direction LR\n    A[Catalog] --> B[Agreement]\n    B --> C[Provision]\n    C --> D[Entitlements]\n    D --> E[Metering]\n    E --> F[Billing]\n    F --> G[Cash / RevOps]\n    E -.->|caps / fair use| D\n  end\n  subgraph Cost[\"Cost loop (FinOps)\"]\n    direction LR\n    H[Allocate spend] --> I[Unit economics]\n    I --> J[Budgets / kill-switches]\n    J --> K[Price or package change]\n  end\n  E -.-> H\n  K -.-> A\n  classDef revenue fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n  classDef cost fill:#F59E0B,stroke:#D97706,stroke-width:2px,color:#fff\n  class A,B,C,D,E,F,G revenue\n  class H,I,J,K cost\n",[163,1173,1174,1178,1183,1188,1193,1198,1203,1208,1212,1217,1222,1227,1232,1237,1242,1247,1252,1257,1262,1267,1272,1276,1281,1287,1293,1298,1304,1310,1316,1322,1328],{"__ignoreMap":161},[166,1175,1176],{"class":168,"line":169},[166,1177,172],{},[166,1179,1180],{"class":168,"line":175},[166,1181,1182],{},"header: Commercial architecture - revenue chain and FinOps cost loop\n",[166,1184,1185],{"class":168,"line":181},[166,1186,1187],{},"legend:\n",[166,1189,1190],{"class":168,"line":187},[166,1191,1192],{},"  - color: \"#3B82F6\"\n",[166,1194,1195],{"class":168,"line":193},[166,1196,1197],{},"    text: \"Revenue chain\"\n",[166,1199,1200],{"class":168,"line":199},[166,1201,1202],{},"  - color: \"#F59E0B\"\n",[166,1204,1205],{"class":168,"line":205},[166,1206,1207],{},"    text: \"Cost loop (FinOps)\"\n",[166,1209,1210],{"class":168,"line":211},[166,1211,172],{},[166,1213,1214],{"class":168,"line":217},[166,1215,1216],{},"flowchart TB\n",[166,1218,1219],{"class":168,"line":222},[166,1220,1221],{},"  subgraph Revenue[\"Revenue chain\"]\n",[166,1223,1224],{"class":168,"line":228},[166,1225,1226],{},"    direction LR\n",[166,1228,1229],{"class":168,"line":234},[166,1230,1231],{},"    A[Catalog] --> B[Agreement]\n",[166,1233,1234],{"class":168,"line":240},[166,1235,1236],{},"    B --> C[Provision]\n",[166,1238,1239],{"class":168,"line":246},[166,1240,1241],{},"    C --> D[Entitlements]\n",[166,1243,1244],{"class":168,"line":435},[166,1245,1246],{},"    D --> E[Metering]\n",[166,1248,1249],{"class":168,"line":441},[166,1250,1251],{},"    E --> F[Billing]\n",[166,1253,1254],{"class":168,"line":447},[166,1255,1256],{},"    F --> G[Cash / RevOps]\n",[166,1258,1259],{"class":168,"line":453},[166,1260,1261],{},"    E -.->|caps / fair use| D\n",[166,1263,1264],{"class":168,"line":459},[166,1265,1266],{},"  end\n",[166,1268,1269],{"class":168,"line":465},[166,1270,1271],{},"  subgraph Cost[\"Cost loop (FinOps)\"]\n",[166,1273,1274],{"class":168,"line":471},[166,1275,1226],{},[166,1277,1278],{"class":168,"line":477},[166,1279,1280],{},"    H[Allocate spend] --> I[Unit economics]\n",[166,1282,1284],{"class":168,"line":1283},23,[166,1285,1286],{},"    I --> J[Budgets / kill-switches]\n",[166,1288,1290],{"class":168,"line":1289},24,[166,1291,1292],{},"    J --> K[Price or package change]\n",[166,1294,1296],{"class":168,"line":1295},25,[166,1297,1266],{},[166,1299,1301],{"class":168,"line":1300},26,[166,1302,1303],{},"  E -.-> H\n",[166,1305,1307],{"class":168,"line":1306},27,[166,1308,1309],{},"  K -.-> A\n",[166,1311,1313],{"class":168,"line":1312},28,[166,1314,1315],{},"  classDef revenue fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n",[166,1317,1319],{"class":168,"line":1318},29,[166,1320,1321],{},"  classDef cost fill:#F59E0B,stroke:#D97706,stroke-width:2px,color:#fff\n",[166,1323,1325],{"class":168,"line":1324},30,[166,1326,1327],{},"  class A,B,C,D,E,F,G revenue\n",[166,1329,1331],{"class":168,"line":1330},31,[166,1332,1333],{},"  class H,I,J,K cost\n",[99,1335,1337],{"className":1336},[102],[104,1338,1339,1340,1343,1344,1347,1348,1351],{},"Meters feed ",[108,1341,1342],{},"entitlements"," (caps), ",[108,1345,1346],{},"invoices"," (later), and ",[108,1349,1350],{},"FinOps"," (always). You can meter for caps and fair use long before you charge a card. The dashed arrows are feedback: usage informs allow/deny and cost; cost informs packaging.",[1105,1353],{},[99,1355,1357,1363],{"className":1356},[126,127,128],[130,1358,1360],{"id":1359},"_2-who-owns-what-actors",[108,1361,1362],{},"2. Who owns what (actors)",[104,1364,1365],{},[139,1366,1367],{"href":141},[108,1368,144],{},[99,1370,1372],{"className":1371},[102],[104,1373,1374,1375,1378],{},"FinOps is only ",[108,1376,1377],{},"one"," actor — and only on the cost side. The chain fails when everything is dumped on “pricing,” “billing,” or “engineering will add a flag.” Name the owners, even if one person wears three hats early on.",[1380,1381,1382,1395],"table",{},[1383,1384,1385],"thead",{},[1386,1387,1388,1392],"tr",{},[1389,1390,1391],"th",{},"Actor",[1389,1393,1394],{},"Owns in the chain",[1396,1397,1398,1409,1419,1429,1439,1449,1459,1472,1482],"tbody",{},[1386,1399,1400,1406],{},[1401,1402,1403],"td",{},[108,1404,1405],{},"Product / packaging",[1401,1407,1408],{},"Catalog: plans, SKUs, limits, trials",[1386,1410,1411,1416],{},[1401,1412,1413],{},[108,1414,1415],{},"GTM / growth",[1401,1417,1418],{},"Who may join; invite vs open; soft-launch vs paywall",[1386,1420,1421,1426],{},[1401,1422,1423],{},[108,1424,1425],{},"Platform / engineering",[1401,1427,1428],{},"Provisioning, entitlements at request time, metering events",[1386,1430,1431,1436],{},[1401,1432,1433],{},[108,1434,1435],{},"Ops / admin",[1401,1437,1438],{},"Live overrides, caps, complimentary access, kill-switches + audit",[1386,1440,1441,1446],{},[1401,1442,1443],{},[108,1444,1445],{},"Billing / finance",[1401,1447,1448],{},"Subscriptions, invoices, renewals, revenue recognition",[1386,1450,1451,1456],{},[1401,1452,1453],{},[108,1454,1455],{},"Customer (buyer)",[1401,1457,1458],{},"Plan, usage, invoices (portal)",[1386,1460,1461,1465],{},[1401,1462,1463],{},[108,1464,1350],{},[1401,1466,1467,1468,1471],{},"What ",[358,1469,1470],{},"we"," pay; margin; budgets; cost kill-switches",[1386,1473,1474,1479],{},[1401,1475,1476],{},[108,1477,1478],{},"Legal / compliance",[1401,1480,1481],{},"Tax, merchant-of-record vs Connect, entity — before “charge” go-live",[1386,1483,1484,1492],{},[1401,1485,1486,958,1489],{},[108,1487,1488],{},"RevOps / CS",[358,1490,1491],{},"(later)",[1401,1493,1494],{},"Activate, expand, churn, support load",[99,1496,1498],{"className":1497},[102],[104,1499,1500,1503,1504,1507,1508,1511],{},[108,1501,1502],{},"RevOps"," aligns sales, marketing, and customer success around the revenue lifecycle — who owns activation, expansion, renewal, and churn signals. It usually matters ",[108,1505,1506],{},"after Charge",". Early on it is often founder + CS ",[108,1509,1510],{},"runbooks",", not a dedicated team or product surface.",[99,1513,1515],{"className":1514},[102],[104,1516,1517],{},"Two paths, same system — different ledgers:",[156,1519,1521],{"className":158,"code":1520,"language":160,"meta":161,"style":161},"---\nheader: Actors on revenue vs cost ledgers\nlegend:\n  - color: \"#3B82F6\"\n    text: \"Revenue ledger\"\n  - color: \"#F59E0B\"\n    text: \"Cost ledger\"\n---\nflowchart LR\n  subgraph Rev[\"Revenue ledger\"]\n    direction LR\n    P[Product] --> GTM[GTM] --> PL[Platform] --> OPS[Ops] --> BIL[Billing] --> CU[Customer]\n  end\n  subgraph Cost[\"Cost ledger\"]\n    direction LR\n    U[Usage / infra] --> FO[FinOps] --> FB[Package / price / gates]\n  end\n  PL -.-> U\n  FB -.-> P\n  classDef revenue fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n  classDef cost fill:#F59E0B,stroke:#D97706,stroke-width:2px,color:#fff\n  class P,GTM,PL,OPS,BIL,CU revenue\n  class U,FO,FB cost\n",[163,1522,1523,1527,1532,1536,1540,1545,1549,1554,1558,1563,1568,1572,1577,1581,1586,1590,1595,1599,1604,1609,1613,1617,1622],{"__ignoreMap":161},[166,1524,1525],{"class":168,"line":169},[166,1526,172],{},[166,1528,1529],{"class":168,"line":175},[166,1530,1531],{},"header: Actors on revenue vs cost ledgers\n",[166,1533,1534],{"class":168,"line":181},[166,1535,1187],{},[166,1537,1538],{"class":168,"line":187},[166,1539,1192],{},[166,1541,1542],{"class":168,"line":193},[166,1543,1544],{},"    text: \"Revenue ledger\"\n",[166,1546,1547],{"class":168,"line":199},[166,1548,1202],{},[166,1550,1551],{"class":168,"line":205},[166,1552,1553],{},"    text: \"Cost ledger\"\n",[166,1555,1556],{"class":168,"line":211},[166,1557,172],{},[166,1559,1560],{"class":168,"line":217},[166,1561,1562],{},"flowchart LR\n",[166,1564,1565],{"class":168,"line":222},[166,1566,1567],{},"  subgraph Rev[\"Revenue ledger\"]\n",[166,1569,1570],{"class":168,"line":228},[166,1571,1226],{},[166,1573,1574],{"class":168,"line":234},[166,1575,1576],{},"    P[Product] --> GTM[GTM] --> PL[Platform] --> OPS[Ops] --> BIL[Billing] --> CU[Customer]\n",[166,1578,1579],{"class":168,"line":240},[166,1580,1266],{},[166,1582,1583],{"class":168,"line":246},[166,1584,1585],{},"  subgraph Cost[\"Cost ledger\"]\n",[166,1587,1588],{"class":168,"line":435},[166,1589,1226],{},[166,1591,1592],{"class":168,"line":441},[166,1593,1594],{},"    U[Usage / infra] --> FO[FinOps] --> FB[Package / price / gates]\n",[166,1596,1597],{"class":168,"line":447},[166,1598,1266],{},[166,1600,1601],{"class":168,"line":453},[166,1602,1603],{},"  PL -.-> U\n",[166,1605,1606],{"class":168,"line":459},[166,1607,1608],{},"  FB -.-> P\n",[166,1610,1611],{"class":168,"line":465},[166,1612,1315],{},[166,1614,1615],{"class":168,"line":471},[166,1616,1321],{},[166,1618,1619],{"class":168,"line":477},[166,1620,1621],{},"  class P,GTM,PL,OPS,BIL,CU revenue\n",[166,1623,1624],{"class":168,"line":1283},[166,1625,1626],{},"  class U,FO,FB cost\n",[99,1628,1630],{"className":1629},[102],[104,1631,1632,1635,1636,1638],{},[108,1633,1634],{},"Pricing / catalog / billing"," sit on the revenue ledger. ",[108,1637,1350],{}," sits on the cost ledger. Neither replaces the other. When margin is wrong, FinOps informs Product; when a tenant is over limit, Platform enforces and Ops may override — with an audit trail, not a Slack DM and a forgotten deploy.",[1105,1640],{},[99,1642,1644,1650],{"className":1643},[126,127,128],[130,1645,1647],{"id":1646},"_3-hard-rules-and-how-to-implement-them",[108,1648,1649],{},"3. Hard rules — and how to implement them",[104,1651,1652],{},[139,1653,1654],{"href":141},[108,1655,144],{},[99,1657,1659],{"className":1658},[102],[104,1660,1661,1662,1665],{},"These rules are the difference between “we have pricing” and “we have commercial architecture.” Skip them and you will rebuild the same mess every six months. Under each rule: a ",[108,1663,1664],{},"strategy"," you can ship without waiting for a perfect billing suite.",[1667,1668,1670],"h3",{"id":1669},"_31-one-product-catalog",[108,1671,1672],{},"3.1 One product catalog",[99,1674,1676],{"className":1675},[102],[104,1677,1678,1679,1681],{},"Plans, SKUs, limits, and trial rules live in ",[108,1680,1377],{}," source of truth. Pricing page, admin, and billing all read it.",[99,1683,1685],{"className":1684},[102],[104,1686,1687,1690,1691,1694,1695,1698,1699,1702,1703,1706],{},[108,1688,1689],{},"Strategy:"," Put the catalog in versioned code or a small DB table — ",[163,1692,1693],{},"plan_id",", display name, limits, trial days, module defaults. Public ",[163,1696,1697],{},"/pricing"," and i18n copy ",[108,1700,1701],{},"import"," that model; they do not redefine tiers. When billing arrives, create MoR products ",[108,1704,1705],{},"from"," the catalog IDs (1:1 map), never the other way around.",[156,1708,1710],{"className":158,"code":1709,"language":160,"meta":161,"style":161},"---\nheader: One catalog as source of truth\nlegend:\n  - color: \"#10B981\"\n    text: \"Catalog SoT\"\n  - color: \"#3B82F6\"\n    text: \"Surfaces that read the catalog\"\n---\nflowchart TB\n  CAT[(Catalog SoT)]\n  CAT --> WEB[Pricing page]\n  CAT --> ADM[Admin console]\n  CAT --> RES[Entitlement resolver]\n  CAT --> MOR[Billing MoR 1:1 SKUs]\n  classDef sot fill:#10B981,stroke:#059669,stroke-width:2px,color:#fff\n  classDef consumer fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n  class CAT sot\n  class WEB,ADM,RES,MOR consumer\n",[163,1711,1712,1716,1721,1725,1730,1735,1739,1744,1748,1752,1757,1762,1767,1772,1777,1782,1787,1792],{"__ignoreMap":161},[166,1713,1714],{"class":168,"line":169},[166,1715,172],{},[166,1717,1718],{"class":168,"line":175},[166,1719,1720],{},"header: One catalog as source of truth\n",[166,1722,1723],{"class":168,"line":181},[166,1724,1187],{},[166,1726,1727],{"class":168,"line":187},[166,1728,1729],{},"  - color: \"#10B981\"\n",[166,1731,1732],{"class":168,"line":193},[166,1733,1734],{},"    text: \"Catalog SoT\"\n",[166,1736,1737],{"class":168,"line":199},[166,1738,1192],{},[166,1740,1741],{"class":168,"line":205},[166,1742,1743],{},"    text: \"Surfaces that read the catalog\"\n",[166,1745,1746],{"class":168,"line":211},[166,1747,172],{},[166,1749,1750],{"class":168,"line":217},[166,1751,1216],{},[166,1753,1754],{"class":168,"line":222},[166,1755,1756],{},"  CAT[(Catalog SoT)]\n",[166,1758,1759],{"class":168,"line":228},[166,1760,1761],{},"  CAT --> WEB[Pricing page]\n",[166,1763,1764],{"class":168,"line":234},[166,1765,1766],{},"  CAT --> ADM[Admin console]\n",[166,1768,1769],{"class":168,"line":240},[166,1770,1771],{},"  CAT --> RES[Entitlement resolver]\n",[166,1773,1774],{"class":168,"line":246},[166,1775,1776],{},"  CAT --> MOR[Billing MoR 1:1 SKUs]\n",[166,1778,1779],{"class":168,"line":435},[166,1780,1781],{},"  classDef sot fill:#10B981,stroke:#059669,stroke-width:2px,color:#fff\n",[166,1783,1784],{"class":168,"line":441},[166,1785,1786],{},"  classDef consumer fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n",[166,1788,1789],{"class":168,"line":447},[166,1790,1791],{},"  class CAT sot\n",[166,1793,1794],{"class":168,"line":453},[166,1795,1796],{},"  class WEB,ADM,RES,MOR consumer\n",[1667,1798,1800],{"id":1799},"_32-entitlements-feature-flags",[108,1801,1802],{},"3.2 Entitlements ≠ feature flags",[99,1804,1806],{"className":1805},[102],[104,1807,1808],{},"Flags = rollout. Entitlements = commercial + security truth at request time.",[99,1810,1812],{"className":1811},[102],[104,1813,1814,1816,1817,1820],{},[108,1815,1689],{}," One resolver: ",[163,1818,1819],{},"tenant → plan + overrides → { modules, caps }",". Call it from API middleware or a shared policy layer on every paid/costly path. Keep LaunchDarkly-style flags for “is this code path live,” not for “is this tenant on Pro.”",[156,1822,1824],{"className":158,"code":1823,"language":160,"meta":161,"style":161},"---\nheader: Feature flags vs entitlement resolver\nlegend:\n  - color: \"#3B82F6\"\n    text: \"Request / resolve inputs\"\n  - color: \"#8B5CF6\"\n    text: \"Decision\"\n  - color: \"#10B981\"\n    text: \"Allow\"\n  - color: \"#EF4444\"\n    text: \"Deny / not shipped\"\n---\nflowchart TD\n  R[API request] --> F{Feature flag: code live?}\n  F -->|No| X[Not shipped]\n  F -->|Yes| V[Entitlement resolver]\n  V --> C[Catalog defaults]\n  V --> O[Tenant overrides]\n  V --> M[Monetization / kill-switch mode]\n  C --> D{Allowed?}\n  O --> D\n  M --> D\n  D -->|Yes| OK[Proceed + meter if needed]\n  D -->|No| DENY[Structured deny + upgrade CTA]\n  classDef ok fill:#10B981,stroke:#059669,stroke-width:2px,color:#fff\n  classDef deny fill:#EF4444,stroke:#DC2626,stroke-width:2px,color:#fff\n  classDef decide fill:#8B5CF6,stroke:#7C3AED,stroke-width:2px,color:#fff\n  classDef input fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n  class OK ok\n  class DENY,X deny\n  class F,D decide\n  class R,V,C,O,M input\n",[163,1825,1826,1830,1835,1839,1843,1848,1853,1858,1862,1867,1872,1877,1881,1886,1891,1896,1901,1906,1911,1916,1921,1926,1931,1936,1941,1946,1951,1956,1961,1966,1971,1976],{"__ignoreMap":161},[166,1827,1828],{"class":168,"line":169},[166,1829,172],{},[166,1831,1832],{"class":168,"line":175},[166,1833,1834],{},"header: Feature flags vs entitlement resolver\n",[166,1836,1837],{"class":168,"line":181},[166,1838,1187],{},[166,1840,1841],{"class":168,"line":187},[166,1842,1192],{},[166,1844,1845],{"class":168,"line":193},[166,1846,1847],{},"    text: \"Request / resolve inputs\"\n",[166,1849,1850],{"class":168,"line":199},[166,1851,1852],{},"  - color: \"#8B5CF6\"\n",[166,1854,1855],{"class":168,"line":205},[166,1856,1857],{},"    text: \"Decision\"\n",[166,1859,1860],{"class":168,"line":211},[166,1861,1729],{},[166,1863,1864],{"class":168,"line":217},[166,1865,1866],{},"    text: \"Allow\"\n",[166,1868,1869],{"class":168,"line":222},[166,1870,1871],{},"  - color: \"#EF4444\"\n",[166,1873,1874],{"class":168,"line":228},[166,1875,1876],{},"    text: \"Deny / not shipped\"\n",[166,1878,1879],{"class":168,"line":234},[166,1880,172],{},[166,1882,1883],{"class":168,"line":240},[166,1884,1885],{},"flowchart TD\n",[166,1887,1888],{"class":168,"line":246},[166,1889,1890],{},"  R[API request] --> F{Feature flag: code live?}\n",[166,1892,1893],{"class":168,"line":435},[166,1894,1895],{},"  F -->|No| X[Not shipped]\n",[166,1897,1898],{"class":168,"line":441},[166,1899,1900],{},"  F -->|Yes| V[Entitlement resolver]\n",[166,1902,1903],{"class":168,"line":447},[166,1904,1905],{},"  V --> C[Catalog defaults]\n",[166,1907,1908],{"class":168,"line":453},[166,1909,1910],{},"  V --> O[Tenant overrides]\n",[166,1912,1913],{"class":168,"line":459},[166,1914,1915],{},"  V --> M[Monetization / kill-switch mode]\n",[166,1917,1918],{"class":168,"line":465},[166,1919,1920],{},"  C --> D{Allowed?}\n",[166,1922,1923],{"class":168,"line":471},[166,1924,1925],{},"  O --> D\n",[166,1927,1928],{"class":168,"line":477},[166,1929,1930],{},"  M --> D\n",[166,1932,1933],{"class":168,"line":1283},[166,1934,1935],{},"  D -->|Yes| OK[Proceed + meter if needed]\n",[166,1937,1938],{"class":168,"line":1289},[166,1939,1940],{},"  D -->|No| DENY[Structured deny + upgrade CTA]\n",[166,1942,1943],{"class":168,"line":1295},[166,1944,1945],{},"  classDef ok fill:#10B981,stroke:#059669,stroke-width:2px,color:#fff\n",[166,1947,1948],{"class":168,"line":1300},[166,1949,1950],{},"  classDef deny fill:#EF4444,stroke:#DC2626,stroke-width:2px,color:#fff\n",[166,1952,1953],{"class":168,"line":1306},[166,1954,1955],{},"  classDef decide fill:#8B5CF6,stroke:#7C3AED,stroke-width:2px,color:#fff\n",[166,1957,1958],{"class":168,"line":1312},[166,1959,1960],{},"  classDef input fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n",[166,1962,1963],{"class":168,"line":1318},[166,1964,1965],{},"  class OK ok\n",[166,1967,1968],{"class":168,"line":1324},[166,1969,1970],{},"  class DENY,X deny\n",[166,1972,1973],{"class":168,"line":1330},[166,1974,1975],{},"  class F,D decide\n",[166,1977,1979],{"class":168,"line":1978},32,[166,1980,1981],{},"  class R,V,C,O,M input\n",[1667,1983,1985],{"id":1984},"_33-denied-ux-is-part-of-the-engine",[108,1986,1987],{},"3.3 Denied UX is part of the engine",[99,1989,1991],{"className":1990},[102],[104,1992,1993],{},"Entitlement failure returns a clear upgrade / denied message, never a silent fail.",[99,1995,1997],{"className":1996},[102],[104,1998,1999,2001,2002,2004,2005,2004,2008,2004,2011,2014,2015,2018],{},[108,2000,1689],{}," Standard deny payload: ",[163,2003,163],{},", ",[163,2006,2007],{},"limit",[163,2009,2010],{},"current",[163,2012,2013],{},"upgrade_to",", optional ",[163,2016,2017],{},"cta_url",". UI maps codes to copy (“Storage full — upgrade to Plus”). Log denials with tenant + capability for product/FinOps. Never raw 403 with an empty body.",[1667,2020,2022],{"id":2021},"_34-meter-before-you-bill",[108,2023,2024],{},"3.4 Meter before you bill",[99,2026,2028],{"className":2027},[102],[104,2029,2030],{},"Even seat/subscription products meter for caps, fair use, and FinOps.",[99,2032,2034],{"className":2033},[102],[104,2035,2036,2038,2039,2004,2042,2004,2045,2004,2048,2051],{},[108,2037,1689],{}," Emit usage events at the edges that cost money (email, storage, AI tokens, maps, outbound sync). Store ",[163,2040,2041],{},"tenant_id",[163,2043,2044],{},"metric",[163,2046,2047],{},"quantity",[163,2049,2050],{},"at",". Start with append-only events + admin totals — no invoice math required. Caps read the same meters later; billing rates them later.",[1667,2053,2055],{"id":2054},"_35-overrides-are-first-class",[108,2056,2057],{},"3.5 Overrides are first-class",[99,2059,2061],{"className":2060},[102],[104,2062,2063,2064,579],{},"Complimentary access, custom caps, extend trial — with ",[108,2065,2066],{},"audit",[99,2068,2070],{"className":2069},[102],[104,2071,2072,2074,2075,2004,2078,2004,2081,2004,2084,2087,2088,2091],{},[108,2073,1689],{}," Model overrides as data on the tenant: ",[163,2076,2077],{},"plan_override",[163,2079,2080],{},"cap_overrides",[163,2082,2083],{},"complimentary_until",[163,2085,2086],{},"notes",". Admin UI writes them; every write appends ",[163,2089,2090],{},"who / when / what / before / after",". Resolver merges catalog defaults ← overrides. No hotfix branches for “give them Plus.”",[1667,2093,2095],{"id":2094},"_36-growth-is-gated",[108,2096,2097],{},"3.6 Growth is gated",[99,2099,2101],{"className":2100},[102],[104,2102,2103],{},"Invite vs open, monetization mode, cost kill-switches — without redeploy.",[99,2105,2107],{"className":2106},[102],[104,2108,2109,2111,2112,2115,2116,2119],{},[108,2110,1689],{}," Global settings table (or config service): ",[163,2113,2114],{},"registration_mode"," (invite / open), ",[163,2117,2118],{},"monetization_mode"," (soft_launch / trial_enforced / paid_enforced), kill-switches per expensive capability. Enforce on tenant create and on the costly features. Defaults match today’s soft-launch; flipping to paid is a settings change, not a release train.",[1667,2121,2123],{"id":2122},"_37-cost-and-revenue-are-separate-ledgers",[108,2124,2125],{},"3.7 Cost and revenue are separate ledgers",[99,2127,2129],{"className":2128},[102],[104,2130,2131],{},"FinOps (what we pay) never replaces Pricing (what they pay).",[99,2133,2135],{"className":2134},[102],[104,2136,2137,2139,2140,2143,2144,2147],{},[108,2138,1689],{}," Keep two views: ",[108,2141,2142],{},"revenue"," (plan, MRR, invoices) and ",[108,2145,2146],{},"cost"," (vendor invoices + allocated usage). Unit economics = cost per tenant vs price per tenant. When margin breaks, change package/price or kill-switch — don’t “fix” it by pretending cost is a plan feature.",[1667,2149,2151],{"id":2150},"_38-legalize-before-charge",[108,2152,2153],{},"3.8 Legalize before charge",[99,2155,2157],{"className":2156},[102],[104,2158,2159,2160,2163],{},"Entity, tax/VAT, MoR vs Connect (or equivalent) as written policy ",[108,2161,2162],{},"before"," billing go-live.",[99,2165,2167],{"className":2166},[102],[104,2168,2169,2171],{},[108,2170,1689],{}," One short go-live memo: who sells, who invoices, VAT treatment, MoR vs marketplace Connect, when books start recognizing revenue. Block production checkout until that memo exists. Product can finish Encode in parallel; Charge waits on the memo, not on perfect legal theatre forever.",[99,2173,2175],{"className":2174},[102],[104,2176,2177,2180,2181,2184,2185,2187],{},[108,2178,2179],{},"Commercial object (general):"," often the ",[108,2182,2183],{},"tenant / account / workspace"," is the primary SKU. Seats can appear later. Forcing “per user” as the only model early locks packaging into a shape that may not match how value (or cost) actually lands. ",[108,2186,1689],{}," price and entitle the tenant first; add seat SKUs when seat cost or value is proven.",[1105,2189],{},[99,2191,2193,2199],{"className":2192},[126,127,128],[130,2194,2196],{"id":2195},"_4-capability-map-three-layers",[108,2197,2198],{},"4. Capability map (three layers)",[104,2200,2201],{},[139,2202,2203],{"href":141},[108,2204,144],{},[99,2206,2208],{"className":2207},[102],[104,2209,2210,2211,2214],{},"You do not need ten product epics on day one. You do need three layers that stay distinct in ",[108,2212,2213],{},"design",", even when delivery is compressed.",[1380,2216,2217,2230],{},[1383,2218,2219],{},[1386,2220,2221,2224,2227],{},[1389,2222,2223],{},"Layer",[1389,2225,2226],{},"Capabilities",[1389,2228,2229],{},"Question",[1396,2231,2232,2245,2262],{},[1386,2233,2234,2239,2242],{},[1401,2235,2236],{},[108,2237,2238],{},"Define",[1401,2240,2241],{},"Product catalog & packaging",[1401,2243,2244],{},"What SKUs/plans exist? Limits? Trial?",[1386,2246,2247,2252,2255],{},[1401,2248,2249],{},[108,2250,2251],{},"Enforce",[1401,2253,2254],{},"Entitlements, growth/policy gates, admin overrides",[1401,2256,2257,2258,2261],{},"What may ",[358,2259,2260],{},"this tenant"," do right now? Who may join? Can ops change it live?",[1386,2263,2264,2269,2272],{},[1401,2265,2266],{},[108,2267,2268],{},"Monetize & operate",[1401,2270,2271],{},"Metering, billing, customer portal, FinOps, money-flow/compliance, RevOps",[1401,2273,2274],{},"How do we charge, show usage, protect margin, stay legal?",[156,2276,2278],{"className":158,"code":2277,"language":160,"meta":161,"style":161},"---\nheader: Capability layers - Define, Enforce, Monetize\n---\nflowchart TB\n  D[\"1. Define - catalog and packaging\"]\n  E[\"2. Enforce - entitlements, gates, admin\"]\n  M[\"3. Monetize - meter, bill, portal, FinOps\"]\n  D --> E --> M\n",[163,2279,2280,2284,2289,2293,2297,2302,2307,2312],{"__ignoreMap":161},[166,2281,2282],{"class":168,"line":169},[166,2283,172],{},[166,2285,2286],{"class":168,"line":175},[166,2287,2288],{},"header: Capability layers - Define, Enforce, Monetize\n",[166,2290,2291],{"class":168,"line":181},[166,2292,172],{},[166,2294,2295],{"class":168,"line":187},[166,2296,1216],{},[166,2298,2299],{"class":168,"line":193},[166,2300,2301],{},"  D[\"1. Define - catalog and packaging\"]\n",[166,2303,2304],{"class":168,"line":199},[166,2305,2306],{},"  E[\"2. Enforce - entitlements, gates, admin\"]\n",[166,2308,2309],{"class":168,"line":205},[166,2310,2311],{},"  M[\"3. Monetize - meter, bill, portal, FinOps\"]\n",[166,2313,2314],{"class":168,"line":211},[166,2315,2316],{},"  D --> E --> M\n",[99,2318,2320,2328,2342,2347,2361,2370],{"className":2319},[102],[104,2321,2322,2324,2325,2327],{},[108,2323,2238],{}," is boring on purpose: versionable plans and limits. If ",[163,2326,1697],{}," is a hand-written table that disagrees with runtime, you do not have a catalog — you have marketing.",[104,2329,2330,2333,2334,2337,2338,2341],{},[108,2331,2332],{},"Define — solution:"," single ",[163,2335,2336],{},"Catalog"," module exported to web, API, and (later) billing sync jobs. Snapshot or version ID on every entitlement resolution so audits know ",[358,2339,2340],{},"which"," catalog applied.",[104,2343,2344,2346],{},[108,2345,2251],{}," is where commercial truth meets the request path. Resolve plan → allowed modules / caps → allow or deny. Soft-launch can still mean “pilots get broad access,” but that must be an explicit override or mode — not “enforcement doesn’t exist yet.”",[104,2348,2349,2352,2353,2356,2357,2360],{},[108,2350,2351],{},"Enforce — solution:"," middleware / policy helper on every costly route; global growth settings + per-tenant overrides; pilot = ",[163,2354,2355],{},"complimentary"," or ",[163,2358,2359],{},"monetization_mode = soft_launch",", not missing checks.",[104,2362,2363,2365,2366,2369],{},[108,2364,2268],{}," is where money and margin live. It comes ",[108,2367,2368],{},"after"," Define + Enforce unless law forces billing first — and even then, catalog + entitlements remain the product source of truth.",[104,2371,2372,2375,2376,2379],{},[108,2373,2374],{},"Monetize — solution:"," MoR products cloned from catalog IDs; webhooks update ",[163,2377,2378],{},"tenant.plan"," / paid flags; portal reads the same catalog + meters. FinOps starts as spreadsheet + vendor dashboards; in-app budgets only after meters exist.",[99,2381,2383],{"className":2382},[102],[104,2384,2385,2388,2389,2392,2393,2396],{},[108,2386,2387],{},"Sequence rule:"," do not implement billing / portal before ",[108,2390,2391],{},"Define + Enforce",". ",[108,2394,2395],{},"Legal / money-flow policy comes before billing go-live",". Billing must not invent a second plan list.",[99,2398,2400],{"className":2399},[102],[104,2401,2402,2405,2406,2409,2410,2413,2414,579],{},[108,2403,2404],{},"After you charge:"," subscription webhooks (or equivalent) are the source of truth for ",[358,2407,2408],{},"paid state"," — they update plan / entitlements. The billing provider maps ",[108,2411,2412],{},"1:1 to the catalog",". It is SoT for whether they paid; it is not SoT for what a plan ",[358,2415,2416],{},"means",[156,2418,2420],{"className":158,"code":2419,"language":160,"meta":161,"style":161},"---\nheader: Checkout - catalog meaning vs MoR paid state\n---\nsequenceDiagram\n  participant Buyer\n  participant App\n  participant MoR as Billing MoR\n  participant Cat as Catalog\n  participant Ent as Entitlements\n  Buyer->>App: Checkout\n  App->>Cat: Resolve plan SKU\n  App->>MoR: Create or confirm subscription\n  MoR-->>App: Webhook paid / renewed / canceled\n  App->>Ent: Update tenant plan from webhook\n  Note over Cat,Ent: MoR equals paid state - Catalog equals plan meaning\n",[163,2421,2422,2426,2431,2435,2440,2445,2450,2455,2460,2465,2470,2475,2480,2485,2490],{"__ignoreMap":161},[166,2423,2424],{"class":168,"line":169},[166,2425,172],{},[166,2427,2428],{"class":168,"line":175},[166,2429,2430],{},"header: Checkout - catalog meaning vs MoR paid state\n",[166,2432,2433],{"class":168,"line":181},[166,2434,172],{},[166,2436,2437],{"class":168,"line":187},[166,2438,2439],{},"sequenceDiagram\n",[166,2441,2442],{"class":168,"line":193},[166,2443,2444],{},"  participant Buyer\n",[166,2446,2447],{"class":168,"line":199},[166,2448,2449],{},"  participant App\n",[166,2451,2452],{"class":168,"line":205},[166,2453,2454],{},"  participant MoR as Billing MoR\n",[166,2456,2457],{"class":168,"line":211},[166,2458,2459],{},"  participant Cat as Catalog\n",[166,2461,2462],{"class":168,"line":217},[166,2463,2464],{},"  participant Ent as Entitlements\n",[166,2466,2467],{"class":168,"line":222},[166,2468,2469],{},"  Buyer->>App: Checkout\n",[166,2471,2472],{"class":168,"line":228},[166,2473,2474],{},"  App->>Cat: Resolve plan SKU\n",[166,2476,2477],{"class":168,"line":234},[166,2478,2479],{},"  App->>MoR: Create or confirm subscription\n",[166,2481,2482],{"class":168,"line":240},[166,2483,2484],{},"  MoR-->>App: Webhook paid / renewed / canceled\n",[166,2486,2487],{"class":168,"line":246},[166,2488,2489],{},"  App->>Ent: Update tenant plan from webhook\n",[166,2491,2492],{"class":168,"line":435},[166,2493,2494],{},"  Note over Cat,Ent: MoR equals paid state - Catalog equals plan meaning\n",[1105,2496],{},[99,2498,2500,2506],{"className":2499},[126,127,128],[130,2501,2503],{"id":2502},"_5-anti-patterns-and-the-fix",[108,2504,2505],{},"5. Anti-patterns — and the fix",[104,2507,2508],{},[139,2509,2510],{"href":141},[108,2511,144],{},[99,2513,2515],{"className":2514},[102],[104,2516,2517],{},"If the chain is the target, these shortcuts break it. Pair each with a concrete recovery move:",[1380,2519,2520,2530],{},[1383,2521,2522],{},[1386,2523,2524,2527],{},[1389,2525,2526],{},"Anti-pattern",[1389,2528,2529],{},"Fix",[1396,2531,2532,2542,2552,2562,2580,2590,2600],{},[1386,2533,2534,2539],{},[1401,2535,2536],{},[108,2537,2538],{},"Pricing page as the product",[1401,2540,2541],{},"Drive the page from the catalog; delete duplicate hard-coded tiers",[1386,2543,2544,2549],{},[1401,2545,2546],{},[108,2547,2548],{},"Payment provider as second catalog",[1401,2550,2551],{},"MoR SKUs = catalog IDs only; regenerate from catalog, never edit live in the dashboard as SoT",[1386,2553,2554,2559],{},[1401,2555,2556],{},[108,2557,2558],{},"Feature flags as entitlements",[1401,2560,2561],{},"Split: flags for rollout, entitlement resolver for commercial allow/deny",[1386,2563,2564,2569],{},[1401,2565,2566],{},[108,2567,2568],{},"Hard-coded limits in random routes",[1401,2570,2571,2572,2575,2576,2579],{},"Central caps table + one ",[163,2573,2574],{},"assertCap()"," / ",[163,2577,2578],{},"assertModule()"," helper",[1386,2581,2582,2587],{},[1401,2583,2584],{},[108,2585,2586],{},"Billing before catalog + entitlements",[1401,2588,2589],{},"Freeze checkout; finish Encode; then map MoR 1:1",[1386,2591,2592,2597],{},[1401,2593,2594],{},[108,2595,2596],{},"Silent denial",[1401,2598,2599],{},"Structured deny codes + upgrade CTA on API and UI",[1386,2601,2602,2607],{},[1401,2603,2604],{},[108,2605,2606],{},"Productizing everything early",[1401,2608,2609],{},"Keep FinOps/RevOps as runbooks until meters and charge path exist",[99,2611,2613],{"className":2612},[102],[104,2614,2615],{},"Recovery order when you already have a mess:",[156,2617,2619],{"className":158,"code":2618,"language":160,"meta":161,"style":161},"---\nheader: Recovery order when commercial architecture is a mess\n---\nflowchart LR\n  A[Extract catalog] --> B[One resolver] --> C[Centralize checks] --> D[Admin overrides] --> E[Webhooks = paid SoT]\n",[163,2620,2621,2625,2630,2634,2638],{"__ignoreMap":161},[166,2622,2623],{"class":168,"line":169},[166,2624,172],{},[166,2626,2627],{"class":168,"line":175},[166,2628,2629],{},"header: Recovery order when commercial architecture is a mess\n",[166,2631,2632],{"class":168,"line":181},[166,2633,172],{},[166,2635,2636],{"class":168,"line":187},[166,2637,1562],{},[166,2639,2640],{"class":168,"line":193},[166,2641,2642],{},"  A[Extract catalog] --> B[One resolver] --> C[Centralize checks] --> D[Admin overrides] --> E[Webhooks = paid SoT]\n",[99,2644,2646],{"className":2645},[102],[104,2647,2648,2651,2652,2655,2656,2655,2659,2655,2662,2665],{},[108,2649,2650],{},"Extract"," from the pricing page and MoR dashboards → ",[108,2653,2654],{},"wire one resolver"," → ",[108,2657,2658],{},"move hard-coded checks behind it",[108,2660,2661],{},"add admin overrides",[108,2663,2664],{},"only then"," trust webhooks as paid-state SoT.",[1105,2667],{},[99,2669,2671,2677],{"className":2670},[126,127,128],[130,2672,2674],{"id":2673},"_6-what-done-feels-like-in-ops",[108,2675,2676],{},"6. What “done” feels like in ops",[104,2678,2679],{},[139,2680,2681],{"href":141},[108,2682,144],{},[99,2684,2686,2689,2697],{"className":2685},[102],[104,2687,2688],{},"Architecture is abstract until ops can move without engineering.",[104,2690,2691,2692,1099,2694,2696],{},"World-class feels like this: a pilot needs complimentary Plus for thirty days — ops flips a flag, sets an expiry, audit logs who and why. A vendor cost spike hits — someone toggles a kill-switch on an expensive integration without a Friday night deploy. Soft-launch closes — ",[163,2693,2114],{},[163,2695,2118],{}," change, and create-tenant / upgrade paths follow immediately.",[104,2698,2699,2700,2703],{},"If any of that still requires a hotfix branch, the chain is incomplete. Admin is not a nice-to-have UI; it is how ",[108,2701,2702],{},"overrides become first-class"," instead of tribal exceptions.",[1105,2705],{},[99,2707,2709,2715],{"className":2708},[126,127,128],[130,2710,2712],{"id":2711},"_7-minimum-path-that-still-looks-standard",[108,2713,2714],{},"7. Minimum path that still looks standard",[104,2716,2717],{},[139,2718,2719],{"href":141},[108,2720,144],{},[99,2722,2724],{"className":2723},[102],[104,2725,2726,2727,2730],{},"Capacity is finite. You can still keep ",[108,2728,2729],{},"boundaries"," even when you combine delivery. The mistake is not shipping Encode in one milestone — it is shipping Encode + Stripe as one undifferentiated blob.",[1380,2732,2733,2746],{},[1383,2734,2735],{},[1386,2736,2737,2740],{},[1389,2738,2739],{},"Delivery slice",[1389,2741,2742,2743,2745],{},"Keep separate in ",[358,2744,2213],{}," (even if shipped together)",[1396,2747,2748,2757,2770],{},[1386,2749,2750,2754],{},[1401,2751,2752],{},[108,2753,1081],{},[1401,2755,2756],{},"Catalog + entitlements + growth gates + admin overrides",[1386,2758,2759,2763],{},[1401,2760,2761],{},[108,2762,1085],{},[1401,2764,2765,2766,2769],{},"Money-flow / compliance policy ",[108,2767,2768],{},"then"," billing (catalog ↔ subscriptions ↔ entitlements)",[1386,2771,2772,2777],{},[1401,2773,2774],{},[108,2775,2776],{},"Scale",[1401,2778,2779],{},"Metering depth + customer portal (+ optional in-app FinOps)",[156,2781,2783],{"className":158,"code":2782,"language":160,"meta":161,"style":161},"---\nheader: Encode, Charge, Scale delivery path\nlegend:\n  - color: \"#3B82F6\"\n    text: \"Encode\"\n  - color: \"#10B981\"\n    text: \"Charge\"\n  - color: \"#8B5CF6\"\n    text: \"Scale\"\n---\nflowchart LR\n  subgraph Encode\n    A1[Catalog] --- A2[Entitlements] --- A3[Gates] --- A4[Admin]\n  end\n  subgraph Charge\n    B1[Legalize] --> B2[Subscribe] --> B3[Webhooks update plan]\n  end\n  subgraph Scale\n    C1[Meters] --> C2[Portal] --> C3[Optional FinOps UI]\n  end\n  Encode --> Charge --> Scale\n  classDef encode fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n  classDef charge fill:#10B981,stroke:#059669,stroke-width:2px,color:#fff\n  classDef scale fill:#8B5CF6,stroke:#7C3AED,stroke-width:2px,color:#fff\n  class A1,A2,A3,A4 encode\n  class B1,B2,B3 charge\n  class C1,C2,C3 scale\n",[163,2784,2785,2789,2794,2798,2802,2807,2811,2816,2820,2825,2829,2833,2838,2843,2847,2852,2857,2861,2866,2871,2875,2880,2885,2890,2895,2900,2905],{"__ignoreMap":161},[166,2786,2787],{"class":168,"line":169},[166,2788,172],{},[166,2790,2791],{"class":168,"line":175},[166,2792,2793],{},"header: Encode, Charge, Scale delivery path\n",[166,2795,2796],{"class":168,"line":181},[166,2797,1187],{},[166,2799,2800],{"class":168,"line":187},[166,2801,1192],{},[166,2803,2804],{"class":168,"line":193},[166,2805,2806],{},"    text: \"Encode\"\n",[166,2808,2809],{"class":168,"line":199},[166,2810,1729],{},[166,2812,2813],{"class":168,"line":205},[166,2814,2815],{},"    text: \"Charge\"\n",[166,2817,2818],{"class":168,"line":211},[166,2819,1852],{},[166,2821,2822],{"class":168,"line":217},[166,2823,2824],{},"    text: \"Scale\"\n",[166,2826,2827],{"class":168,"line":222},[166,2828,172],{},[166,2830,2831],{"class":168,"line":228},[166,2832,1562],{},[166,2834,2835],{"class":168,"line":234},[166,2836,2837],{},"  subgraph Encode\n",[166,2839,2840],{"class":168,"line":240},[166,2841,2842],{},"    A1[Catalog] --- A2[Entitlements] --- A3[Gates] --- A4[Admin]\n",[166,2844,2845],{"class":168,"line":246},[166,2846,1266],{},[166,2848,2849],{"class":168,"line":435},[166,2850,2851],{},"  subgraph Charge\n",[166,2853,2854],{"class":168,"line":441},[166,2855,2856],{},"    B1[Legalize] --> B2[Subscribe] --> B3[Webhooks update plan]\n",[166,2858,2859],{"class":168,"line":447},[166,2860,1266],{},[166,2862,2863],{"class":168,"line":453},[166,2864,2865],{},"  subgraph Scale\n",[166,2867,2868],{"class":168,"line":459},[166,2869,2870],{},"    C1[Meters] --> C2[Portal] --> C3[Optional FinOps UI]\n",[166,2872,2873],{"class":168,"line":465},[166,2874,1266],{},[166,2876,2877],{"class":168,"line":471},[166,2878,2879],{},"  Encode --> Charge --> Scale\n",[166,2881,2882],{"class":168,"line":477},[166,2883,2884],{},"  classDef encode fill:#3B82F6,stroke:#1E40AF,stroke-width:2px,color:#fff\n",[166,2886,2887],{"class":168,"line":1283},[166,2888,2889],{},"  classDef charge fill:#10B981,stroke:#059669,stroke-width:2px,color:#fff\n",[166,2891,2892],{"class":168,"line":1289},[166,2893,2894],{},"  classDef scale fill:#8B5CF6,stroke:#7C3AED,stroke-width:2px,color:#fff\n",[166,2896,2897],{"class":168,"line":1295},[166,2898,2899],{},"  class A1,A2,A3,A4 encode\n",[166,2901,2902],{"class":168,"line":1300},[166,2903,2904],{},"  class B1,B2,B3 charge\n",[166,2906,2907],{"class":168,"line":1306},[166,2908,2909],{},"  class C1,C2,C3 scale\n",[99,2911,2913,2918,2927,2933,2939,2944],{"className":2912},[102],[104,2914,2915,2917],{},[108,2916,1081],{}," is the soft-launch bar that still looks industry-shaped: one catalog, enforce on the request path, gate growth, let ops override with audit. You may not charge yet — and that is fine.",[104,2919,2920,2923,2924,2926],{},[108,2921,2922],{},"Encode — playbook:"," (1) catalog model + ",[163,2925,1697],{}," wired, (2) entitlement resolver + tests for Free vs Paid vs pilot, (3) global registration/monetization settings, (4) admin plan/override/audit UI. Ship as four workstreams in one milestone if needed — never as one undifferentiated blob FR.",[104,2928,2929,2932],{},[108,2930,2931],{},"Charge order:"," legalize → subscribe / renew / cancel → webhook updates plan. Never “create products in the MoR first and reverse-engineer a catalog later.”",[104,2934,2935,2938],{},[108,2936,2937],{},"Charge — playbook:"," go-live memo → clone catalog into MoR → checkout path → webhooks → thin “pay / manage” UX. Portal polish waits for Scale.",[104,2940,2941,2943],{},[108,2942,2776],{}," is when customers see usage and invoices in-product, meters get richer, and FinOps may move from spreadsheets into alerts. Optional — not a prerequisite for taking money cleanly.",[104,2945,2946,2949],{},[108,2947,2948],{},"Scale — playbook:"," deepen meters (admin first) → customer usage + invoices → optional budget alerts tied to kill-switches.",[99,2951,2953],{"className":2952},[102],[104,2954,2955,2958],{},[108,2956,2957],{},"Parallel ops track (not product):"," monthly vendor-bill review, first-customer margin discipline, and RevOps playbooks can run as runbooks while Encode ships. Don’t wait on an in-app FinOps UI to practice cost hygiene — and don’t confuse those rituals with the commercial chain itself.",[1105,2960],{},[99,2962,2964,2970],{"className":2963},[126,127,128],[130,2965,2967],{"id":2966},"_8-definition-of-done",[108,2968,2969],{},"8. Definition of done",[104,2971,2972],{},[139,2973,2974],{"href":141},[108,2975,144],{},[99,2977,2979],{"className":2978},[102],[104,2980,2981],{},"Use this as a bar, not a vanity checklist. The commercial architecture is “real” when:",[1054,2983,2986,2998,3004,3014,3024,3034,3043,3049,3059],{"className":2984},[2985],"contains-task-list",[1057,2987,2990,2994,2995,2997],{"className":2988},[2989],"task-list-item",[2991,2992],"input",{"disabled":991,"type":2993},"checkbox"," Changing a plan limit does ",[108,2996,659],{}," require hunting hard-coded checks in random routes",[1057,2999,3001,3003],{"className":3000},[2989],[2991,3002],{"disabled":991,"type":2993}," Denied entitlements surface a clear upgrade / denied path — never a silent fail",[1057,3005,3007,3009,3010,3013],{"className":3006},[2989],[2991,3008],{"disabled":991,"type":2993}," Ops can grant complimentary access or freeze a costly capability ",[108,3011,3012],{},"without"," a deploy",[1057,3015,3017,3019,3020,3023],{"className":3016},[2989],[2991,3018],{"disabled":991,"type":2993}," Pricing page, admin, and (later) billing show the ",[108,3021,3022],{},"same"," catalog",[1057,3025,3027,3029,3030,3033],{"className":3026},[2989],[2991,3028],{"disabled":991,"type":2993}," Registration / monetization mode / kill-switches are ",[108,3031,3032],{},"platform settings",", not tribal memory",[1057,3035,3037,3039,3040,3042],{"className":3036},[2989],[2991,3038],{"disabled":991,"type":2993}," Money-flow policy (entity, tax, MoR) exists ",[108,3041,2162],{}," the first real charge",[1057,3044,3046,3048],{"className":3045},[2989],[2991,3047],{"disabled":991,"type":2993}," Paid state flows billing events → plan / entitlements (no second plan list in the MoR)",[1057,3050,3052,3054,3055,3058],{"className":3051},[2989],[2991,3053],{"disabled":991,"type":2993}," Usage that costs money is ",[108,3056,3057],{},"visible"," before the invoice surprises you",[1057,3060,3062,3064,3065,3068],{"className":3061},[2989],[2991,3063],{"disabled":991,"type":2993}," A FinOps soft ceiling can trigger a ",[108,3066,3067],{},"kill-switch",", not only a Slack panic",[1105,3070],{},[99,3072,3074,3080],{"className":3073},[126,127,128],[130,3075,3077],{"id":3076},"_9-cheat-sheet",[108,3078,3079],{},"9. Cheat sheet",[104,3081,3082],{},[139,3083,3084],{"href":141},[108,3085,144],{},[99,3087,3089],{"className":3088},[102],[104,3090,3091],{},"Screenshot this. It is the whole article in one card.",[156,3093,3096],{"className":3094,"code":3095,"language":1137,"meta":161},[1135],"CHAIN\n  Catalog → Agreement → Provision → Entitlements → Meter → Bill → Cash\n  (+ meters → caps / FinOps → package or price)\n\nLAYERS\n  1 Define   catalog & packaging\n  2 Enforce  entitlements · growth gates · admin overrides\n  3 Monetize meter · bill · portal · FinOps · compliance · RevOps\n\nDELIVERY\n  Encode → Charge → Scale\n  (legalize before Charge; meters before fancy FinOps UI)\n\nNEVER\n  · Second catalog in the payment provider\n  · Silent 403 with no upgrade path\n  · Billing before catalog + entitlements\n  · Soft-launch vs paid as tribal memory\n",[163,3097,3095],{"__ignoreMap":161},[99,3099,3101],{"className":3100},[102],[104,3102,3103],{},[358,3104,3105],{},"A pricing page sells the story. Commercial architecture makes it enforceable.",[1105,3107],{},[99,3109,3111,3117],{"className":3110},[126,127,128],[130,3112,3114],{"id":3113},"_10-start-monday",[108,3115,3116],{},"10. Start Monday",[104,3118,3119],{},[139,3120,3121],{"href":141},[108,3122,144],{},[99,3124,3126],{"className":3125},[102],[104,3127,3128],{},"Five moves that start Encode without waiting on Stripe:",[3130,3131,3132,3138,3144,3153,3164],"ol",{},[1057,3133,3134,3137],{},[108,3135,3136],{},"List every hard-coded limit"," — search routes for plan names, caps, and “if Pro…” checks; put them on one page.",[1057,3139,3140,3143],{},[108,3141,3142],{},"Name the commercial object"," — tenant / account / workspace (seats later unless proven).",[1057,3145,3146,3149,3150,3152],{},[108,3147,3148],{},"Draft catalog IDs"," — Free / Trial / Paid… with limits and trial rules; wire ",[163,3151,1697],{}," to that model only.",[1057,3154,3155,3158,3159,2004,3161,3163],{},[108,3156,3157],{},"Pick the knobs"," — ",[163,3160,2114],{},[163,3162,2118],{},", and which expensive features get kill-switches.",[1057,3165,3166,3169],{},[108,3167,3168],{},"Name who can override"," — one ops owner for complimentary / cap overrides + an audit log habit (even a spreadsheet at first).",[99,3171,3173],{"className":3172},[102],[104,3174,3175],{},"When those five are true, you have the skeleton of Encode. Charge is a later decision — not a substitute for this work.",[1105,3177],{},[99,3179,3181,3187],{"className":3180},[126,127,128],[130,3182,3184],{"id":3183},"_11-closing",[108,3185,3186],{},"11. Closing",[104,3188,3189],{},[139,3190,3191],{"href":141},[108,3192,144],{},[99,3194,3196,3199,3205],{"className":3195},[102],[104,3197,3198],{},"A pricing page sells the story. Stripe (or any MoR) moves the money. Neither is the architecture.",[104,3200,3201,3204],{},[108,3202,3203],{},"Commercial architecture"," is the chain — catalog → agreement → provision → entitlements → meter → bill → cash — plus the cost loop, plus the actors who own each link. Build Define and Enforce before you celebrate Charge. Keep one catalog. Treat overrides and growth gates as product, not folklore.",[104,3206,3207],{},"That shape works on any project. The names of plans and providers will change. The chain should not.",[1054,3209,3210,3216,3222],{},[1057,3211,3212],{},[139,3213,3215],{"href":3214},"/blog/architecture","Explore Architecture",[1057,3217,3218],{},[139,3219,3221],{"href":3220},"/blog/strategy","Explore Strategy",[1057,3223,3224],{},[139,3225,3227],{"href":3226},"/blog/scalability","Explore Scalability",[99,3229,3235],{"className":3230},[3231,3232,3233,3234],"text-md","text-center","mt-10","font-bold",[104,3236,3237],{},[139,3238,3239],{"href":141},[108,3240,144],{},[974,3242,976],{},{"title":161,"searchDepth":175,"depth":175,"links":3244},[3245,3246,3247,3248,3258,3259,3260,3261,3262,3263,3264,3265],{"id":1040,"depth":175,"text":1043},{"id":1112,"depth":175,"text":1115},{"id":1359,"depth":175,"text":1362},{"id":1646,"depth":175,"text":1649,"children":3249},[3250,3251,3252,3253,3254,3255,3256,3257],{"id":1669,"depth":181,"text":1672},{"id":1799,"depth":181,"text":1802},{"id":1984,"depth":181,"text":1987},{"id":2021,"depth":181,"text":2024},{"id":2054,"depth":181,"text":2057},{"id":2094,"depth":181,"text":2097},{"id":2122,"depth":181,"text":2125},{"id":2150,"depth":181,"text":2153},{"id":2195,"depth":175,"text":2198},{"id":2502,"depth":175,"text":2505},{"id":2673,"depth":175,"text":2676},{"id":2711,"depth":175,"text":2714},{"id":2966,"depth":175,"text":2969},{"id":3076,"depth":175,"text":3079},{"id":3113,"depth":175,"text":3116},{"id":3183,"depth":175,"text":3186},"2026-09-19","Monetization is a governed chain with clear actors — product, GTM, platform, ops, billing, customer, FinOps, legal — not a pricing page and not “add Stripe later.”",{"src":3269},"/blog/archi/bff.png",{},{"title":55,"description":3267},"1Sv0PeeeVzFfJdx6sOw-BTpAG5-LDcHn0YilKuF0xRs",1790785957038]