<?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: Enos Omenda</title>
    <description>The latest articles on DEV Community by Enos Omenda (@enos_omenda_35affdc043dba).</description>
    <link>https://dev.to/enos_omenda_35affdc043dba</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%2F4230677%2F56b378be-dcad-49c9-9298-5b4850178039.jpg</url>
      <title>DEV Community: Enos Omenda</title>
      <link>https://dev.to/enos_omenda_35affdc043dba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/enos_omenda_35affdc043dba"/>
    <language>en</language>
    <item>
      <title>Aontu says no clearly, but only when you ask it to</title>
      <dc:creator>Enos Omenda</dc:creator>
      <pubDate>Fri, 06 Nov 2026 15:08:41 +0000</pubDate>
      <link>https://dev.to/enos_omenda_35affdc043dba/aontu-says-no-clearly-but-only-when-you-ask-it-to-59jn</link>
      <guid>https://dev.to/enos_omenda_35affdc043dba/aontu-says-no-clearly-but-only-when-you-ask-it-to-59jn</guid>
      <description>&lt;p&gt;I'm a full-stack developer in Nairobi, and I recently took on two small tasks for &lt;a href="https://voxgig.com" rel="noopener noreferrer"&gt;Voxgig&lt;/a&gt;: build an open-source SDK with their SDK generator, then write a critical review of Aontu, the language the generator is built on.&lt;/p&gt;

&lt;p&gt;This post covers both. First, what I built. Then what I found when I tried to break Aontu.&lt;/p&gt;

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

&lt;p&gt;I used the Voxgig SDK generator to produce a TypeScript client for &lt;a href="https://pokeapi.co" rel="noopener noreferrer"&gt;PokéAPI&lt;/a&gt;. The model behind it is 102 &lt;code&gt;.aontu&lt;/code&gt; files across &lt;code&gt;entity/&lt;/code&gt; and &lt;code&gt;flow/&lt;/code&gt;, 11,622 lines in total.&lt;/p&gt;

&lt;p&gt;The full project is on GitHub: &lt;strong&gt;&lt;a href="https://github.com/Enos2/my-sdk-work" rel="noopener noreferrer"&gt;github.com/Enos2/my-sdk-work&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The review
&lt;/h2&gt;

&lt;p&gt;I did not choose Aontu. I met it because the Voxgig SDK generator describes an API in &lt;code&gt;.aontu&lt;/code&gt; files, and I used the generator to build a TypeScript client for PokéAPI. Afterwards I took the Aontu 0.76.1 that the generator installs and spent some time trying to break it.&lt;/p&gt;

&lt;p&gt;The site describes it as a language for the entities in a system, the types they must have and the relations that must hold. Schema and data share one notation, and statements combine by unification: compatible statements narrow each other, contradictory ones are an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  What works
&lt;/h2&gt;

&lt;p&gt;The example from the front page runs as published:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type({ service&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;string tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*1&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$.spec.service&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;services: api: owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform"&lt;/span&gt;
&lt;span class="na"&gt;services: auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;identity"&lt;/span&gt; &lt;span class="nv"&gt;tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;api&lt;/code&gt; picks up &lt;code&gt;tier: 1&lt;/code&gt; from the default and &lt;code&gt;auth&lt;/code&gt; keeps its 3. The &lt;code&gt;&amp;amp;:&lt;/code&gt; line applies the schema to every entry in &lt;code&gt;services&lt;/code&gt;, including entries added later in the file.&lt;/p&gt;

&lt;p&gt;The error messages stood out. Two values for one key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;9090&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[aontu/scalar_value]: Cannot unify values at path $.server.port

 Cannot unify value: 9090 with value: 8080
  --&amp;gt; conflict.aontu:2:17
  1 | server: { port: 8080 }
  2 | server: { port: 9090 }
                      ^ value was: 9090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above that excerpt sits a short explanation with worked examples, and the process exits with status 1. Two unrelated conflicts in one file produced two errors in one run, so there is no fix-and-rerun loop. Help works offline: &lt;code&gt;aontu help language&lt;/code&gt; prints a grammar card and &lt;code&gt;aontu explain closed&lt;/code&gt; explains an error code without a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The typo that passes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec: type({ service: { owner:string tier: *1 } })
services: { &amp;amp;: $.spec.service }
services: api: { owner: "platform", tire: 3 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This evaluates without complaint and emits &lt;code&gt;"tier": 1&lt;/code&gt; and &lt;code&gt;"tire": 3&lt;/code&gt; side by side. Struct types are open by default. Wrapping the schema in &lt;code&gt;close()&lt;/code&gt; fixes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec: type({ service: close({ owner:string tier: *1 }) })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the same file fails with &lt;code&gt;[aontu/closed]&lt;/code&gt; at &lt;code&gt;$.services.api.tire&lt;/code&gt;, explained as "Cannot add to closed structure", with the caret on line 3.&lt;/p&gt;

&lt;p&gt;The site pitches Aontu as a set of guardrails for coding agents. A misspelled or invented key is an easy mistake for a model to make, so a default that accepts it leaves the guardrail off for anyone who has not read about &lt;code&gt;close()&lt;/code&gt;. JSON Schema is also open by default, which makes Aontu consistent with the format most people know. CUE, the other unification-based configuration language, closes definitions by default. I would want closed structs by default, or at least a project-wide switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error that points at the wrong line
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec: type({ service: { owner:string tier: *1 } })
services: { &amp;amp;: $.spec.service }
services: api: tier: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The author of line 3 forgot &lt;code&gt;owner&lt;/code&gt;. The output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[aontu/mapval_no_gen]: Cannot resolve value at path $.services.api.owner

This value was present after unification, and cannot be generated
because it is not a literal value.
  --&amp;gt; missing.aontu:1:31
  1 | spec: type({ service: { owner:string tier: *1 } })
                                    ^ key owner value was: string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caret lands on the schema, and the explanation describes the evaluator's state instead of the mistake. Compared with the conflict errors above, this one needs translating. A message like "services.api is missing required field owner" would do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the model files contain
&lt;/h2&gt;

&lt;p&gt;My PokéAPI model has 102 &lt;code&gt;.aontu&lt;/code&gt; files in &lt;code&gt;entity/&lt;/code&gt; and &lt;code&gt;flow/&lt;/code&gt;, 11,622 lines in total. I searched them for &lt;code&gt;type(&lt;/code&gt;, &lt;code&gt;close(&lt;/code&gt;, &lt;code&gt;&amp;amp;:&lt;/code&gt;, &lt;code&gt;$.&lt;/code&gt; references and &lt;code&gt;*&lt;/code&gt; defaults. None appear. The only language feature present is the &lt;code&gt;@"&lt;/code&gt; include, in the index files. An entity file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;fields:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;id:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;h:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Id"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;n:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;r:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;ro:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;t:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"`$INTEGER`"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is JSON with unquoted keys, comments and includes. The schema that gives it meaning is a 290-line file inside &lt;code&gt;@voxgig/sdkgen&lt;/code&gt;. The one-letter keys are the generator's choice. Aontu evaluated the whole model into about 350 KB of JSON in 2.9 seconds, so speed is fine. The result is that a person who uses Aontu only through a generator never meets the parts the site advertises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rough edges
&lt;/h2&gt;

&lt;p&gt;Running the CLI directly on the model printed a warning that an include resolved outside the entry root and that "a future release will deny this by default". The SDK model includes files from &lt;code&gt;node_modules&lt;/code&gt;, so a future upgrade will need the &lt;code&gt;--trust system&lt;/code&gt; flag or a change in the generator.&lt;/p&gt;

&lt;p&gt;The generated &lt;code&gt;AGENTS.md&lt;/code&gt; says literal disjunctions such as &lt;code&gt;'prod' | 'peer' | 'dev'&lt;/code&gt; are fragile and should be avoided. I tried one with a valid value, an invalid value and no value at all. The valid one resolved, the invalid one produced an &lt;code&gt;empty&lt;/code&gt; error showing both source locations, and the unresolved one produced &lt;code&gt;disjunct_no_gen&lt;/code&gt; with advice to add a &lt;code&gt;*&lt;/code&gt; preference. I could not reproduce any fragility. Either it needs a more complicated setup or the warning is out of date, and a reader cannot tell which.&lt;/p&gt;

&lt;p&gt;The site I read documented 0.73.0 while my install was 0.76.1, and a 0.x version carries no stability promise.&lt;/p&gt;

&lt;p&gt;Later lines never override earlier ones, so changing &lt;code&gt;port: 8080&lt;/code&gt; to &lt;code&gt;9090&lt;/code&gt; further down the file is an error. The design is consistent and the site says so. Anyone coming from overlay-style configuration such as Docker Compose or Helm values may expect the later line to win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;I would use Aontu for a hand-written model that a team wants checked in CI, with &lt;code&gt;close()&lt;/code&gt; on every schema. I would not adopt it for data that a generator produces, because JSON plus JSON Schema gives the same checks with tooling every editor already supports. I would like closed structs by default, and the missing-field error rewritten to point at the line that left the field out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SDK project: &lt;a href="https://github.com/Enos2/my-sdk-work" rel="noopener noreferrer"&gt;github.com/Enos2/my-sdk-work&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://enos-omenda.web.app" rel="noopener noreferrer"&gt;enos-omenda.web.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Enos2" rel="noopener noreferrer"&gt;github.com/Enos2&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I'm Enos Omondi Omenda, a full-stack web developer based in Nairobi, Kenya. I build with React, Node.js, Express and MongoDB, work on AI backend workflows as a Back-End AI Engineering Intern at FlyRank, and I'm the founder of Narra, a live-streaming and content management platform. I enjoy exploring open-source tools and writing about what I learn along the way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>sdk</category>
      <category>api</category>
    </item>
  </channel>
</rss>
