<?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: Faisal Mehmood</title>
    <description>The latest articles on DEV Community by Faisal Mehmood (@faisal77666).</description>
    <link>https://dev.to/faisal77666</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%2F4068444%2F60629d68-1ac8-4d59-98c0-e8fc71f06572.jpg</url>
      <title>DEV Community: Faisal Mehmood</title>
      <link>https://dev.to/faisal77666</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/faisal77666"/>
    <language>en</language>
    <item>
      <title>I Tried Aontu to Validate Microservice Architecture. Is It Worth the Complexity?</title>
      <dc:creator>Faisal Mehmood</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:04:34 +0000</pubDate>
      <link>https://dev.to/faisal77666/i-tried-aontu-to-validate-microservice-architecture-is-it-worth-the-complexity-2i9l</link>
      <guid>https://dev.to/faisal77666/i-tried-aontu-to-validate-microservice-architecture-is-it-worth-the-complexity-2i9l</guid>
      <description>&lt;p&gt;AI coding tools have made building software faster, but writing code is only one part of engineering. &lt;strong&gt;Keeping a system consistent as it grows is often the harder problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider a microservice application. One service depends on another, configuration values are shared across environments, and different developers make changes to the same architecture. If an AI coding agent introduces an invalid configuration or circular dependency, who catches it?&lt;/p&gt;

&lt;p&gt;This question led me to explore &lt;a href="https://aontu.dev/" rel="noopener noreferrer"&gt;Aontu&lt;/a&gt;, an open-source language and tool for defining constraints, validating structured data, and modeling relationships.&lt;/p&gt;

&lt;p&gt;I installed Aontu on Ubuntu and tested it myself. My goal wasn't to explore every feature. I wanted to understand whether its approach could solve problems I might face in a real software project.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Impression: Easy Installation, Unfamiliar Concepts
&lt;/h2&gt;

&lt;p&gt;I installed Aontu version &lt;code&gt;0.73.0&lt;/code&gt; using npm. The installation succeeded without dependency audit warnings, and the CLI worked immediately through &lt;code&gt;npx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;understanding the tool was harder than installing it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aontu has commands for schema validation, relationship analysis, provenance, compatibility checking, code generation, and more.&lt;/p&gt;

&lt;p&gt;That flexibility is attractive, but it creates an onboarding problem. Someone looking for a configuration validator may feel overwhelmed before understanding the basic workflow.&lt;/p&gt;

&lt;p&gt;Its syntax also takes adjustment. Developers familiar with JSON, YAML, or JavaScript validation libraries need to understand concepts such as unification, defaults, and map templates.&lt;/p&gt;

&lt;p&gt;I found myself asking whether Aontu was simplifying configuration management or introducing another abstraction I would have to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interesting Part: Architecture as a Constraint
&lt;/h2&gt;

&lt;p&gt;My first experiment involved defining two services: an API service and an authentication service.&lt;/p&gt;

&lt;p&gt;I gave both services a common structure and specified a default tier. Aontu correctly applied the default to the API service while preserving the authentication service's explicit value.&lt;/p&gt;

&lt;p&gt;That sounds simple, but the underlying approach matters.&lt;/p&gt;

&lt;p&gt;Traditional configuration files primarily describe values. &lt;strong&gt;Aontu can describe both the values and the rules those values must satisfy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The more compelling experiment involved circular dependencies.&lt;/p&gt;

&lt;p&gt;I modeled an API service calling an authentication service while the authentication service called the API service.&lt;/p&gt;

&lt;p&gt;For the relationship, I used Aontu's constraint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calls?: rel() &amp;amp; acyclic()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aontu rejected the model with a &lt;code&gt;relation_cycle&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;I liked this result because circular dependencies are architectural problems, not merely incorrect data types.&lt;/p&gt;

&lt;p&gt;In a larger project, expressing such restrictions as constraints could provide a useful safeguard during code reviews or automated changes.&lt;/p&gt;

&lt;p&gt;However, Aontu cannot automatically understand every rule of an application. Developers must still identify and define the right constraints. An incomplete model can provide a misleading sense of confidence.&lt;/p&gt;

&lt;p&gt;That distinction matters, particularly when AI agents are generating code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Detection Was Good. Error Communication Was Mixed.
&lt;/h2&gt;

&lt;p&gt;I deliberately changed a numeric configuration value to a string.&lt;/p&gt;

&lt;p&gt;Aontu correctly rejected it and identified both the invalid value and the relevant schema declaration.&lt;/p&gt;

&lt;p&gt;Technically, that is excellent behavior. Knowing where a conflicting value originated is much better than receiving a generic validation failure.&lt;/p&gt;

&lt;p&gt;But the diagnostic also included:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Empty disjunction. The disjunction has no valid alternatives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I understood that the values were incompatible, but this terminology would probably confuse a beginner.&lt;/p&gt;

&lt;p&gt;The circular-dependency diagnostic had a similar issue. It detected the problem correctly, yet it did not display the complete &lt;code&gt;api → auth → api&lt;/code&gt; cycle in my test.&lt;/p&gt;

&lt;p&gt;Instead, it identified an offending relationship path and pointed to the schema constraint.&lt;/p&gt;

&lt;p&gt;I would prefer an error message that begins with an explanation developers can act on immediately, followed by the more detailed technical reasoning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A validation tool should not only identify what's wrong; it should make fixing the problem easier.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Feature That Impressed Me Most
&lt;/h2&gt;

&lt;p&gt;One of the most useful things I discovered was Aontu's ability to explain where configuration values came from.&lt;/p&gt;

&lt;p&gt;When I queried the API service's tier, Aontu traced the value back to the default declaration in my source file.&lt;/p&gt;

&lt;p&gt;This might seem like a small feature, but configuration debugging becomes difficult when teams introduce shared defaults, overrides, and multiple sources.&lt;/p&gt;

&lt;p&gt;Usually, developers must search through files to figure out why an application received a particular value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aontu's provenance functionality provides that explanation directly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can imagine this being particularly valuable in infrastructure repositories or systems with many interconnected configuration definitions.&lt;/p&gt;

&lt;p&gt;For me, this was one of the clearest advantages over simply storing configuration in ordinary JSON files.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Small Problem That Says a Lot About Developer Experience
&lt;/h2&gt;

&lt;p&gt;I also tried Aontu's project initialization command.&lt;/p&gt;

&lt;p&gt;It generated a sample model, example data, and a shell script containing validation checks.&lt;/p&gt;

&lt;p&gt;That was encouraging because it provided a working starting point.&lt;/p&gt;

&lt;p&gt;But when I executed the generated script, it failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check.sh: 16: aontu: not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue was straightforward. I had installed Aontu locally, while the generated script expected the executable to be available on my shell's &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After adding the local npm executable directory to &lt;code&gt;PATH&lt;/code&gt;, the script worked correctly.&lt;/p&gt;

&lt;p&gt;This wasn't a serious technical defect, but it was unnecessary friction.&lt;/p&gt;

&lt;p&gt;Developers often evaluate tools through their first successful experience. &lt;strong&gt;A starter project should account for common installation methods or clearly explain its assumptions.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation Coverage Is More Important Than It Sounds
&lt;/h2&gt;

&lt;p&gt;My final experiment revealed another interesting feature.&lt;/p&gt;

&lt;p&gt;Aontu can report how much input data was actually checked against a schema.&lt;/p&gt;

&lt;p&gt;The generated example showed that all four data leaves had been constrained.&lt;/p&gt;

&lt;p&gt;I then intentionally wrote an ineffective schema using &lt;code&gt;"*"&lt;/code&gt; where Aontu expected its &lt;code&gt;&amp;amp;:&lt;/code&gt; map-template syntax.&lt;/p&gt;

&lt;p&gt;The result was revealing: none of the four data leaves were checked.&lt;/p&gt;

&lt;p&gt;Normal validation reported an &lt;code&gt;incomplete&lt;/code&gt; result. Strict coverage returned a failure and explicitly explained that validation had examined nothing.&lt;/p&gt;

&lt;p&gt;This is a useful safeguard because successful-looking validation workflows can give developers confidence even when their rules do not cover the intended data.&lt;/p&gt;

&lt;p&gt;I particularly appreciated that Aontu exposed this problem instead of leaving developers to discover it through unexpected behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I Use Aontu in a Real Project?
&lt;/h2&gt;

&lt;p&gt;After these experiments, I have a more balanced view.&lt;/p&gt;

&lt;p&gt;I wouldn't introduce Aontu into every application. For a small project with straightforward configuration requirements, conventional schema validation would probably be easier to understand and maintain.&lt;/p&gt;

&lt;p&gt;But for a system with interconnected services, complex configuration rules, and frequent automated changes, I can see its value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The strongest features I tested were relationship validation, clear source provenance, and validation coverage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Its main weaknesses were the learning curve, specialized diagnostic language, and small onboarding inconsistencies.&lt;/p&gt;

&lt;p&gt;I also wouldn't treat Aontu as a complete solution for AI-generated code quality. It can reject violations of defined constraints, but it cannot replace meaningful tests, security reviews, or engineering judgement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;What interests me most about Aontu is not its connection to AI tools. It's the idea that architectural expectations can become executable constraints rather than remaining only in documentation.&lt;/p&gt;

&lt;p&gt;My local experiments showed that this approach can catch real inconsistencies.&lt;/p&gt;

&lt;p&gt;At the same time, powerful abstractions come with costs. Teams need to understand the language, maintain accurate models, and interpret validation results correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My verdict:&lt;/strong&gt; Aontu is worth exploring when architectural consistency is a recurring problem, but I would validate its benefits through a small proof of concept before adopting it across a project.&lt;/p&gt;

&lt;p&gt;That seems like a more meaningful reason to use it than simply adding another tool to an AI development workflow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Project reviewed: &lt;a href="https://aontu.dev/" rel="noopener noreferrer"&gt;Aontu&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>devops</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>One Prompt Template, Three Engines That Disagree</title>
      <dc:creator>Faisal Mehmood</dc:creator>
      <pubDate>Sat, 08 Aug 2026 07:34:16 +0000</pubDate>
      <link>https://dev.to/faisal77666/one-prompt-templatethree-engines-that-disagree-ifi</link>
      <guid>https://dev.to/faisal77666/one-prompt-templatethree-engines-that-disagree-ifi</guid>
      <description>&lt;p&gt;&lt;em&gt;Written against &lt;code&gt;main&lt;/code&gt; at the time of publishing. Every regex and line number is cited so you can re-check with &lt;code&gt;grep -n&lt;/code&gt;  and there's a runnable reproduction at the end that needs no build.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;A &lt;code&gt;PROMPT_TEMPLATE&lt;/code&gt; artifact in Apicurio Registry is rendered by &lt;strong&gt;three&lt;/strong&gt;&lt;br&gt;
different pieces of code that don't share an engine and don't agree on what a template means beyond a plain &lt;code&gt;{{name}}&lt;/code&gt;. Add a space, a dot, or an &lt;code&gt;{{#if}}&lt;/code&gt; block and the same stored template renders three different ways. If you're going to build anything on top of prompt rendering  a UI playground,a new client you have to know which of the three you're actually rendering against.&lt;/p&gt;


&lt;h2&gt;
  
  
  Storing a prompt template looks simple
&lt;/h2&gt;

&lt;p&gt;You save some text with &lt;code&gt;{{variable}}&lt;/code&gt; holes in it, hand it a set of arguments, and get a rendered string back. One artifact, one render.&lt;/p&gt;

&lt;p&gt;Except the registry doesn't render it in one place. It renders it in &lt;strong&gt;three&lt;/strong&gt;,and those three don't share an engine. A template that renders correctly over the&lt;br&gt;
REST API can come back untouched over the MCP server, and be flagged as having novariables at all by the validator that gated it on the way in.&lt;/p&gt;

&lt;p&gt;A renderer that returns &lt;em&gt;a&lt;/em&gt; result is not the same as a renderer that returns a &lt;em&gt;consistent&lt;/em&gt; one. This post walks through exactly where the three surfaces diverge, using the real classes and the real regexes.&lt;/p&gt;
&lt;h2&gt;
  
  
  The three-surface model
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;PROMPT_TEMPLATE&lt;/code&gt; artifact is consumed by three independent pieces of code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PromptRenderingService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Renders a template to a string for HTTP clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PromptTemplateConverter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Renders a template for Model Context Protocol clients, including &lt;code&gt;{{#if}}&lt;/code&gt; blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Validator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PromptTemplateContentValidator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;On write, decides which variables the template "declares"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They live in three different modules (&lt;code&gt;app&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;schema-util&lt;/code&gt;). None of them calls the others. Each one carries its own idea of what a template &lt;em&gt;is&lt;/em&gt; encoded in its own regular expression.&lt;/p&gt;
&lt;h2&gt;
  
  
  The objects involved
&lt;/h2&gt;

&lt;p&gt;There is exactly one source of truth: the stored artifact content. Everything downstream is a consumer reading the same bytes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌──────────────────────────────┐
                    │  Stored PROMPT_TEMPLATE       │
                    │  content:  "Hi {{ name }}"    │   ← one source of truth
                    └───────────────┬──────────────┘
                                    │  same bytes, three consumers
           ┌────────────────────────┼────────────────────────┐
           ▼                        ▼                        ▼
   ┌───────────────┐       ┌────────────────┐       ┌──────────────────┐
   │ REST          │       │ MCP            │       │ Validator        │
   │ PromptRender- │       │ PromptTemplate-│       │ PromptTemplate-  │
   │ ingService    │       │ Converter      │       │ ContentValidator │
   ├───────────────┤       ├────────────────┤       ├──────────────────┤
   │ \{\{([^}]+)\}\}│      │ key spliced    │       │ \{\{(\w+)\}\}    │
   │ + .trim()     │       │ unescaped      │       │  (strict)        │
   └───────┬───────┘       └───────┬────────┘       └────────┬─────────┘
           ▼                       ▼                         ▼
       "Hi Ada"            "Hi {{ name }}"            declares: (nothing)
        renders            whitespace breaks it       name not recognized
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single picture is the whole story. The rest of this post is just proving&lt;br&gt;
each of those three boxes really behaves that way.&lt;/p&gt;
&lt;h2&gt;
  
  
  A small template example
&lt;/h2&gt;

&lt;p&gt;Take the simplest possible template and one argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;template:  Hi {{name}}
args:      { "name": "Ada" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three surfaces agree here. REST renders &lt;code&gt;Hi Ada&lt;/code&gt;, MCP renders &lt;code&gt;Hi Ada&lt;/code&gt;, and&lt;br&gt;
the validator sees one declared variable, &lt;code&gt;name&lt;/code&gt;. Good. This is the case everyone&lt;br&gt;
tests, and it's why the divergence hides so well.&lt;/p&gt;

&lt;p&gt;Now change one thing at a time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two grammars, not one
&lt;/h2&gt;

&lt;p&gt;The REST renderer matches variables with this pattern, and then trims the capture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PromptRenderingService.java:27&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Pattern&lt;/span&gt; &lt;span class="no"&gt;VARIABLE_PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\\{\\{([^}]+)\\}\\}"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// PromptRenderingService.java:361&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;varName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;[^}]+&lt;/code&gt; means "one or more characters that aren't a closing brace." That is a&lt;br&gt;
&lt;em&gt;generous&lt;/em&gt; grammar. It matches whitespace. It matches dots. It matches &lt;code&gt;#&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;/&lt;/code&gt;. And because of the &lt;code&gt;.trim()&lt;/code&gt;, &lt;code&gt;{{ name }}&lt;/code&gt; and &lt;code&gt;{{name}}&lt;/code&gt; are treated as the&lt;br&gt;
same variable.&lt;/p&gt;

&lt;p&gt;The validator — the code that decides, on write, which variables a template&lt;br&gt;
declares — uses a &lt;em&gt;different, stricter&lt;/em&gt; pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PromptTemplateContentValidator.java:30&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Pattern&lt;/span&gt; &lt;span class="no"&gt;TEMPLATE_VARIABLE_PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\\{\\{(\\w+)\\}\\}"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;\w+&lt;/code&gt; is "word characters only." No spaces, no dots, no symbols. So the set of&lt;br&gt;
variables the server &lt;em&gt;accepts as declared&lt;/em&gt; and the set of variables the server&lt;br&gt;
&lt;em&gt;will actually substitute&lt;/em&gt; are defined by two different regexes in two different&lt;br&gt;
modules. The write path and the render path do not speak the same language.&lt;/p&gt;
&lt;h2&gt;
  
  
  What happens with whitespace
&lt;/h2&gt;

&lt;p&gt;Add a space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;template:  Hi {{ name }}
args:      { "name": "Ada" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;REST&lt;/strong&gt; → &lt;code&gt;Hi Ada&lt;/code&gt;. &lt;code&gt;[^}]+&lt;/code&gt; captures &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;.trim()&lt;/code&gt; makes it &lt;code&gt;name&lt;/code&gt;,
substituted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; → &lt;code&gt;Hi {{ name }}&lt;/code&gt;. Unchanged. (Why, in the next section.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validator&lt;/strong&gt; → declares &lt;em&gt;nothing&lt;/em&gt;. &lt;code&gt;\w+&lt;/code&gt; never matched &lt;code&gt;name&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same template. One surface renders it, one leaves it raw, and the gatekeeper&lt;br&gt;
thinks it has no variables at all.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why MCP leaves it raw — the substitution nobody escapes
&lt;/h2&gt;

&lt;p&gt;MCP declares a &lt;code&gt;VARIABLE_PATTERN&lt;/code&gt; at the top of the file, exactly like the others:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PromptTemplateConverter.java:25  — declared but never referenced&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Pattern&lt;/span&gt; &lt;span class="no"&gt;VARIABLE_PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\\{\\{(\\w+)\\}\\}"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's never used. The real substitution doesn't compile a pattern for the whole&lt;br&gt;
template. It loops over the &lt;em&gt;arguments&lt;/em&gt; and builds a regex per key, on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PromptTemplateConverter.java:441&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;placeholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"\\{\\{"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"\\}\\}"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// :443&lt;/span&gt;
&lt;span class="n"&gt;rendered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rendered&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replaceAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Matcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;quoteReplacement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things fall out of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It builds a regex that matches &lt;code&gt;{{name}}&lt;/code&gt; &lt;strong&gt;exactly&lt;/strong&gt; — no &lt;code&gt;[^}]+&lt;/code&gt;, no &lt;code&gt;.trim()&lt;/code&gt;.
So &lt;code&gt;{{ name }}&lt;/code&gt; with spaces never matches, and the placeholder survives into
the output. That's the whitespace result above.&lt;/li&gt;
&lt;li&gt;The replacement &lt;em&gt;value&lt;/em&gt; is escaped with &lt;code&gt;Matcher.quoteReplacement&lt;/code&gt;, but the
&lt;em&gt;key&lt;/em&gt; is spliced straight into the pattern with no &lt;code&gt;Pattern.quote&lt;/code&gt;. The key
becomes regex source. For an ordinary name that's harmless; for a key with
regex metacharacters it is not the same string match REST would do.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The triple-brace and the dotted path
&lt;/h2&gt;

&lt;p&gt;Two more probes, to show it isn't only whitespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;template:  Raw: {{{name}}}     args: { "name": "Ada" }
  REST → Raw: {{{name}}}       (the greedy match lands on {name, no such arg → left literal)
  MCP  → Raw: {Ada}            ({{name}} is found *inside* the braces and replaced)

template:  Hi {{user.email}}   args: { "user.email": "a@x" }
  REST → Hi a@x                ([^}]+ matches the dot)
  MCP  → Hi a@x                (the unescaped '.' happens to match the literal dot)
  Validator → declares nothing (\w+ rejects the dot)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dotted-path row is the sneaky one: REST and MCP &lt;em&gt;agree&lt;/em&gt; on the output, and&lt;br&gt;
it's the &lt;strong&gt;validator&lt;/strong&gt; that's the odd one out — it never considered &lt;code&gt;user.email&lt;/code&gt;&lt;br&gt;
a declared variable in the first place. So "the surfaces disagree" isn't even a&lt;br&gt;
clean two-against-one; which surface is the outlier depends on the construct.&lt;/p&gt;
&lt;h2&gt;
  
  
  The full divergence table
&lt;/h2&gt;

&lt;p&gt;This is the output of the reproduction script (&lt;code&gt;verify_divergence.py&lt;/code&gt;), which&lt;br&gt;
re-implements each surface using its exact on-&lt;code&gt;main&lt;/code&gt; regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;probe          REST                          MCP              validator sees
--------------------------------------------------------------------------------
plain          Hi Ada                        Hi Ada           name
whitespace     Hi Ada                        Hi {{ name }}    (none)
triple-stache  Raw: {{{name}}}               Raw: {Ada}       name
if-block       {{#if premium}}VIP Ada{{/if}} VIP Ada          name
dotted-path    Hi a@x                        Hi a@x           (none)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the first row — plain &lt;code&gt;{{name}}&lt;/code&gt; — is unanimous. Every other construct&lt;br&gt;
produces at least one disagreement. And &lt;code&gt;{{#if}}&lt;/code&gt; blocks only exist on MCP at&lt;br&gt;
all: REST has no concept of them, so it emits the raw &lt;code&gt;{{#if}}&lt;/code&gt; and &lt;code&gt;{{/if}}&lt;/code&gt;&lt;br&gt;
tags into the output.&lt;/p&gt;
&lt;h2&gt;
  
  
  So  is this a bug?
&lt;/h2&gt;

&lt;p&gt;Some of these divergences may be intentional; &lt;code&gt;{{#if}}&lt;/code&gt; on MCP but not REST could&lt;br&gt;
be a deliberate feature-gap, not an oversight. I'm not calling the code broken.&lt;br&gt;
The point that survives either way is narrower and more useful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The same stored template does not have a single, well-defined rendering. What&lt;br&gt;
it means depends on which surface serves it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a fact about the architecture, and it's the fact anyone building on top of prompt rendering needs to design around.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I care: building a UI Playground on top
&lt;/h2&gt;

&lt;p&gt;I'm proposing a Prompt Template Playground in the Registry UI a panel where you edit a template, fill in variables, and see the rendered result before you save.&lt;/p&gt;

&lt;p&gt;The table above is the reason this feature is harder than it looks, and the reason it's worth doing carefully. A playground has to render the preview &lt;em&gt;somehow&lt;/em&gt;. If it quietly reimplements a fourth regex in TypeScript, it becomes a &lt;strong&gt;fourth&lt;/strong&gt;&lt;br&gt;
dialect  now there are four answers to "what does this template mean." The correct design is the opposite: the playground must render against the surface the&lt;br&gt;
user is actually going to call, and be explicit about which one. Anything else is a preview that lies.&lt;br&gt;
That's also why the first useful contributions here are backend-shaped, not just&lt;br&gt;
UI polish: before a playground can tell the truth, the surfaces have to agree on what the truth is.&lt;/p&gt;
&lt;h2&gt;
  
  
  What to remember
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;PROMPT_TEMPLATE&lt;/code&gt; is rendered by &lt;strong&gt;three independent surfaces&lt;/strong&gt; (REST, MCP,
validator) that do &lt;strong&gt;not&lt;/strong&gt; share an engine.&lt;/li&gt;
&lt;li&gt;They encode &lt;strong&gt;three different grammars&lt;/strong&gt;: &lt;code&gt;[^}]+&lt;/code&gt;+trim (REST), exact-key splice
(MCP), strict &lt;code&gt;\w+&lt;/code&gt; (validator). Only plain &lt;code&gt;{{name}}&lt;/code&gt; renders the same everywhere.&lt;/li&gt;
&lt;li&gt;Before you build anything &lt;em&gt;on top&lt;/em&gt; of prompt rendering, pin down &lt;strong&gt;which surface&lt;/strong&gt; you're rendering against  or you'll ship a fourth dialect.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Reproduce it yourself
&lt;/h2&gt;

&lt;p&gt;No build required  the repo re-implements each surface with its exact on-&lt;code&gt;main&lt;/code&gt; regex and prints the table above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Faisal77666/prompt-template-render-divergence
&lt;span class="nb"&gt;cd &lt;/span&gt;prompt-template-render-divergence
python3 verify_divergence.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source it's checking against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PromptRenderingService.java&lt;/code&gt;  REST renderer (&lt;code&gt;:27&lt;/code&gt;, &lt;code&gt;:361&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PromptTemplateConverter.java&lt;/code&gt;  MCP renderer (&lt;code&gt;:25&lt;/code&gt;, &lt;code&gt;:441&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PromptTemplateContentValidator.java&lt;/code&gt;  write-time validator (&lt;code&gt;:30&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Line numbers reflect &lt;code&gt;main&lt;/code&gt; at the time of writing. If they've drifted, a quick&lt;br&gt;
&lt;code&gt;grep -n&lt;/code&gt; on the patterns above will find them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>llm</category>
      <category>cncf</category>
      <category>apicurioregistry</category>
    </item>
  </channel>
</rss>
