<?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>Open Forem</title>
    <description>The most recent home feed on Open Forem.</description>
    <link>https://open.forem.com</link>
    <atom:link rel="self" type="application/rss+xml" href="https://open.forem.com/feed"/>
    <language>en</language>
    <item>
      <title>Postgres Tells You Your Query Was Slow. Not Which Index Was Wasted.</title>
      <dc:creator>Ali Afana </dc:creator>
      <pubDate>Sat, 09 May 2026 11:45:42 +0000</pubDate>
      <link>https://open.forem.com/alimafana/postgres-tells-you-your-query-was-slow-not-which-index-was-wasted-171g</link>
      <guid>https://open.forem.com/alimafana/postgres-tells-you-your-query-was-slow-not-which-index-was-wasted-171g</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Postgres has &lt;code&gt;pg_stat_user_indexes&lt;/code&gt;. It tells you how many times each index was scanned. It does &lt;em&gt;not&lt;/em&gt; tell you whether the slow query you're chasing actually used the index you added for it, or whether you're maintaining indexes the planner never picks. I built a 3-file analyzer — a query wrapper, a logs table, a dashboard — and the first time I ran it against my own production database, &lt;strong&gt;20 of my 51 indexes had never been scanned. 78% of my total index disk was being maintained for nothing.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Gap in Postgres's Stats
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;pg_stat_user_indexes&lt;/code&gt; right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx_tup_read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx_tup_fetch&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;schemaname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see one row per index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;idx_scan&lt;/code&gt; — how many times the index was used&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idx_tup_read&lt;/code&gt; — tuples read via the index&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idx_tup_fetch&lt;/code&gt; — tuples fetched from the heap after the index hit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;You won't see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which queries used which index&lt;/li&gt;
&lt;li&gt;Whether the slow query you wrote a CREATE INDEX for is actually using it&lt;/li&gt;
&lt;li&gt;How much each unused index is costing you per INSERT&lt;/li&gt;
&lt;li&gt;Whether the planner picked your composite index over a single-column one (and made the single-column one redundant)&lt;/li&gt;
&lt;li&gt;Plan diffs when the same query starts going through a different index next week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a hobby project, fine. For a production database with hot tables, you're guessing.&lt;/p&gt;

&lt;p&gt;I'm building a multi-tenant AI sales chatbot. The schema has 51 indexes spread across stores, products, conversations, messages, leads, webhook logs, and the rest of the tables. Some I added intentionally. Some came along with migrations as scaffolding. Some I'd genuinely forgotten about. I had no idea which ones were earning their keep.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pg_stat_user_indexes&lt;/code&gt; told me &lt;code&gt;idx_conversations_store_id&lt;/code&gt; had been scanned 13 times this month. That number was useless on its own. Was it the chat handler? The merchant dashboard? The webhook? Did the planner pick it because it was the only viable plan, or because a composite index that's already on the table would have done the same job for free? No way to know.&lt;/p&gt;

&lt;p&gt;So I built my own observability. Three files. One afternoon.&lt;/p&gt;




&lt;h2&gt;
  
  
  File 1: The Wrapper (&lt;code&gt;query-logger.ts&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;The core idea: don't run queries directly. Run them through a wrapper that captures the plan, measures execution, and logs everything asynchronously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not pg_stat_statements or auto_explain?
&lt;/h3&gt;

&lt;p&gt;I started with &lt;code&gt;pg_stat_statements&lt;/code&gt; and &lt;code&gt;auto_explain&lt;/code&gt;. The first gives you per-query stats but not plans — it tells you a query is slow without telling you which index the planner picked. The second writes plans to the Postgres log, which means parsing log files instead of querying a table. I wanted plans + dimensions in one row I could JOIN against &lt;code&gt;pg_stat_user_indexes&lt;/code&gt;. Hence the wrapper.&lt;/p&gt;

&lt;h3&gt;
  
  
  EXPLAIN Without ANALYZE
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;EXPLAIN (FORMAT JSON)&lt;/code&gt; returns the planner's chosen plan without executing the query. Cost is sub-millisecond for most queries. It gives you a tree of nodes — Index Scan, Bitmap Heap Scan, Seq Scan — each tagged with the relation and the index it touches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Last checked against Postgres 16 — https://www.postgresql.org/docs/current/sql-explain.html&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`EXPLAIN (FORMAT JSON) &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUERY PLAN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractIndexes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indexes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Index Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;indexes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Index Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Plans&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Plan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;indexes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;planContainsSeqScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Node Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Seq Scan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Plans&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Plan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Wrapper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;supabaseAdmin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/supabase/admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;QueryMeta&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;storeId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loggedQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QueryMeta&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Fire-and-forget log — never blocks the response&lt;/span&gt;
  &lt;span class="nx"&gt;supabaseAdmin&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query_logs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;query_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;sql_preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;store_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;indexes_used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;extractIndexes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;seq_scan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;planContainsSeqScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;planning_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Planning Time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;execution_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rows_returned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="c1"&gt;// Silent fail — monitoring never breaks the app&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;hash(sql)&lt;/code&gt; is SHA-1 over the SQL string with &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt;, etc. stripped via regex — "same query, different parameters" collapses into one group.)&lt;/p&gt;

&lt;h3&gt;
  
  
  The One Pattern That Matters: Fire-and-Forget
&lt;/h3&gt;

&lt;p&gt;Same rule as every other observability layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="c1"&gt;// Silent fail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The log insert is &lt;strong&gt;not awaited&lt;/strong&gt;. If the database is overloaded, if the table is locked behind VACUUM, if the row blows up some constraint — the user-facing query still goes through.&lt;/p&gt;

&lt;p&gt;In testing, the log insert takes 8–25ms. The actual query takes 5–800ms. If I awaited the log, on a cheap read I'd literally double the latency for zero user benefit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring must never slow down the thing it's monitoring.&lt;/strong&gt; That's the only rule that matters here.&lt;/p&gt;

&lt;p&gt;There's a second cost worth naming: &lt;code&gt;EXPLAIN&lt;/code&gt; plans the query, then the actual &lt;code&gt;db.query&lt;/code&gt; plans it again. Two plans per measurement. For most queries it's microseconds. For planner-heavy queries with lots of joins, it adds up. Solution: sample. I run the wrapper on 1 in 10 queries, controlled by an env var. Enough signal, low overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  File 2: The Table (&lt;code&gt;query_logs&lt;/code&gt;)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;query_logs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;query_hash&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sql_preview&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;indexes_used&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="n"&gt;seq_scan&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;planning_ms&lt;/span&gt; &lt;span class="nb"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;execution_ms&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;rows_returned&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_query_logs_hash&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;query_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_query_logs_created&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;query_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_query_logs_indexes_used&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;query_logs&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexes_used&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The columns are the dimensions. Each one answers a question Postgres's stats can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;query_hash&lt;/code&gt;&lt;/strong&gt; — group identical queries. The same chat-search query with different &lt;code&gt;store_id&lt;/code&gt; is one logical query. Hash the SQL with parameters stripped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;indexes_used&lt;/code&gt;&lt;/strong&gt; — array of index names the planner picked. The GIN index lets you ask "show me every query that touched &lt;code&gt;idx_products_store_id&lt;/code&gt;" in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;seq_scan&lt;/code&gt;&lt;/strong&gt; — true if the plan contains a Seq Scan node. Fast filter for "queries that fell off the index entirely."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;planning_ms&lt;/code&gt; + &lt;code&gt;execution_ms&lt;/code&gt;&lt;/strong&gt; — separate them. A query with 50ms planning and 5ms execution is a different problem from 5ms planning and 50ms execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;rows_returned&lt;/code&gt;&lt;/strong&gt; — combined with execution time, surfaces queries where the index scan retrieved 100k rows just to filter down to 12.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One detail that's easy to miss: &lt;strong&gt;&lt;code&gt;indexes_used&lt;/code&gt; as &lt;code&gt;TEXT[]&lt;/code&gt;, not &lt;code&gt;TEXT&lt;/code&gt;&lt;/strong&gt;. A single query can scan three indexes (composite + bitmap OR + index-only scan). Store it as a comma-separated string and you'll spend the rest of your life writing &lt;code&gt;LIKE '%idx_name%'&lt;/code&gt; queries. Use the array. Use the GIN index. Move on.&lt;/p&gt;




&lt;h2&gt;
  
  
  File 3: The Dashboard
&lt;/h2&gt;

&lt;p&gt;The killer query — the one that makes this whole exercise worth the afternoon — is the join you've been waiting for. Indexes that exist in Postgres, never appear in any logged plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;plan_indexes&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexes_used&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;index_name&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;query_logs&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 days'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx_scan&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pg_scan_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'NEVER PLANNED'&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'used'&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;plan_indexes&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelname&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schemaname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's the question Postgres can't answer alone: &lt;strong&gt;which of my indexes does the planner never pick over a real workload window?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The other views I added on top of &lt;code&gt;query_logs&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slowest query groups&lt;/strong&gt; — &lt;code&gt;query_hash&lt;/code&gt; ordered by &lt;code&gt;avg(execution_ms)&lt;/code&gt;, with &lt;code&gt;indexes_used&lt;/code&gt; displayed alongside. Now "this query is slow" becomes "this query is slow &lt;em&gt;and it's using &lt;code&gt;idx_X&lt;/code&gt;&lt;/em&gt; — is &lt;code&gt;idx_X&lt;/code&gt; doing what I thought?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries that fell to Seq Scan&lt;/strong&gt; — &lt;code&gt;WHERE seq_scan = true&lt;/code&gt;, grouped by &lt;code&gt;query_hash&lt;/code&gt;. Often the index you added doesn't match the predicate exactly (wrong column order, missing WHERE clause).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index swap candidates&lt;/strong&gt; — pairs of indexes where one is a strict prefix of another. The shorter one is usually dead weight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planning time spikes&lt;/strong&gt; — queries where &lt;code&gt;planning_ms &amp;gt; execution_ms&lt;/code&gt;. Almost always a sign the planner is fighting too many indexes on the table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI is intentionally boring. Stat cards: total queries logged, distinct query shapes, % seq-scan, count of indexes never planned. A table per view. No charts that take longer to read than the underlying number.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Found
&lt;/h2&gt;

&lt;p&gt;Real numbers, the day I built the dashboard:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total indexes (public schema, excluding primary keys)&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexes with &lt;code&gt;idx_scan = 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexes with &lt;code&gt;idx_scan&lt;/code&gt; between 1 and 50&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total index disk&lt;/td&gt;
&lt;td&gt;3,720 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk used by never-scanned indexes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,896 kB (78%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that last row again. Of the disk Postgres was using for indexes on this database, 78% of it was sitting on b-trees the planner had never once chosen.&lt;/p&gt;

&lt;p&gt;Pre-launch is exactly the right time to build this lens. The 78% is real, and the makeup is honest: roughly half of the zero-scan indexes are on a paused workspace whose feature isn't running, four more are on Messenger-related tables still gated behind Meta's app review. Those will earn their keep eventually. The rest — and the boundary cases sitting at 2 or 14 scans — are the actual question. The dashboard's job today isn't to drop anything. It's to give me a queued list to revisit 30 days after the product takes real traffic, when "zero scans" means waste and not "feature hasn't shipped." That list took one afternoon to build. The point isn't the headline number — it's that without the lens, I couldn't have separated dormant from wasted at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 2,552 kB Index Nobody Has Ever Used
&lt;/h3&gt;

&lt;p&gt;The single most surprising finding: &lt;code&gt;idx_products_embedding&lt;/code&gt;, the pgvector index for semantic search, is &lt;strong&gt;2,552 kB on its own&lt;/strong&gt; — 94% of the index disk on the &lt;code&gt;products&lt;/code&gt; table, and around two-thirds of the entire database's index disk. The planner has never once chosen it.&lt;/p&gt;

&lt;p&gt;Semantic search hasn't run at production volume yet — chat is gated to admins until Meta clears the Messenger app — so this isn't waste, it's a dormant feature. But that's exactly what makes the dashboard valuable. The day customers start chatting at scale, this index either lights up or it doesn't, and I'll know within hours whether semantic search is actually using it or quietly falling back to ILIKE.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Barely-Used Tier
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;idx_conversations_store_id&lt;/code&gt; at 13 scans, &lt;code&gt;idx_leads_store_id&lt;/code&gt; at 14, &lt;code&gt;idx_products_status&lt;/code&gt; at 4, &lt;code&gt;idx_webhook_logs_store_id&lt;/code&gt; at 2. These are the boundary cases — indexes the planner has picked once or twice and otherwise ignored. They're the exact set worth watching: some will graduate to actively used as traffic grows, others will sit at 14 scans for the next month and join the drop list.&lt;/p&gt;

&lt;p&gt;That's the loop. &lt;code&gt;pg_stat_user_indexes&lt;/code&gt; tells you how many times each index was scanned. It can't tell you whether each scan was the &lt;em&gt;only&lt;/em&gt; way the query could have run, or whether the zero-scan indexes are dormant scaffolding or genuine waste. Without a dashboard like this you can't even ask the question.&lt;/p&gt;




&lt;h2&gt;
  
  
  5 Things I Learned Building This
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Index stats don't equal index value
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;idx_scan = 0&lt;/code&gt; is a candidate, not a verdict. On a mature database, it usually does mean drop. On a young one, it means "the planner has never picked this &lt;em&gt;yet&lt;/em&gt;" — could be redundant, could be dormant scaffolding for a feature you haven't shipped. Either way, treat it as a question. And &lt;code&gt;idx_scan = 50,000&lt;/code&gt; doesn't mean an index is earning its keep either; if a sibling index would have been picked instead, the high scan count is just an artifact of which one the planner sorted first. &lt;strong&gt;Plans tell the truth. Stats tell you what the planner did, not what it could have done without you.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. EXPLAIN without ANALYZE is your friend
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; runs the query. &lt;code&gt;EXPLAIN&lt;/code&gt; alone just plans it. The plan is what you usually want. Reach for ANALYZE when you specifically need actual row counts vs. estimates — but for "which index would the planner pick for this," EXPLAIN alone is enough and orders of magnitude cheaper.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Sample — don't measure every query
&lt;/h3&gt;

&lt;p&gt;Wrap every query and your monitoring becomes a meaningful fraction of your DB load. For index usage — fundamentally a frequency question — 10% sampling captures ~99% of the signal at 10% of the cost. Confidence intervals on aggregate stats stay tighter than the noise floor you're chasing anyway. Tail-latency hunting is the exception: chasing the slowest 1% of queries needs higher sampling or full coverage. For "which indexes does the planner pick," 10% is plenty.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The dimensions are the product
&lt;/h3&gt;

&lt;p&gt;Same lesson as every other observability piece I've written. &lt;code&gt;query_logs&lt;/code&gt; only answers questions you thought to ask when you designed the schema. &lt;code&gt;endpoint&lt;/code&gt;, &lt;code&gt;table_name&lt;/code&gt;, &lt;code&gt;seq_scan&lt;/code&gt;, &lt;code&gt;indexes_used&lt;/code&gt; as a typed array — each column is a question you'll get to ask cheaply later. Add them when you build, not when you have a problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Indexes are a cost, not a feature
&lt;/h3&gt;

&lt;p&gt;Every secondary index has to be updated on every INSERT and on every UPDATE that touches its columns. On a hot table with 8 indexes, that's up to 8 b-tree maintenance operations per write. Most teams treat &lt;code&gt;CREATE INDEX&lt;/code&gt; as free because the read got faster &lt;em&gt;now&lt;/em&gt;. The cost shows up six months later in INSERT latency that nobody traces back to "we added an index for that one report."&lt;/p&gt;




&lt;h2&gt;
  
  
  What to Add When You're Ready
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Plan diff over time.&lt;/strong&gt; Same &lt;code&gt;query_hash&lt;/code&gt;, different &lt;code&gt;indexes_used&lt;/code&gt; today vs. last week is a regression alarm. Cardinality changed. Statistics went stale. ANALYZE didn't run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost-of-write per index.&lt;/strong&gt; Multiply each table's INSERT/UPDATE rate by the number of indexes that touch the modified columns. Indexes on rarely-modified columns are nearly free. Indexes on hot-update columns are budget items.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bloat tracking.&lt;/strong&gt; &lt;code&gt;pg_stat_user_indexes&lt;/code&gt; doesn't tell you when an index is fragmented and needs &lt;code&gt;REINDEX&lt;/code&gt;. Add a column tracking the ratio between live tuples and index size — a sudden divergence is almost always bloat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seq Scan threshold alerts.&lt;/strong&gt; A query that flips from Index Scan to Seq Scan in production is usually a missing or stale index. Catch it the day it happens, not the day the table grows enough to make it user-visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Counterfactual planning.&lt;/strong&gt; Run the same query with &lt;code&gt;SET enable_indexscan = off&lt;/code&gt; and compare plan costs. If the cost barely moves, the index is decorative.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Three files. One afternoon. About 350 lines.&lt;/p&gt;

&lt;p&gt;A query wrapper that captures plans. A table with the dimensions you need to slice by. A dashboard that joins query plans to index stats — because that join is the question Postgres structurally cannot answer on its own.&lt;/p&gt;

&lt;p&gt;You don't need pgBadger or pganalyze (those are great if you have the budget). You need the smallest possible instrument that answers "which of my indexes does the planner never actually pick" — because that's the question your &lt;code&gt;pg_stat_user_indexes&lt;/code&gt; view can't.&lt;/p&gt;

&lt;p&gt;The first time I ran mine, it told me 20 of 51 indexes had never been scanned, and 78% of my index disk was being maintained for nothing. Some of that is pre-launch noise. Some of it isn't. I now have a queued list of indexes to revisit 30 days after the product takes real traffic — and I have it because I built the lens before I needed it.&lt;/p&gt;

&lt;p&gt;Build the lens before you ship. The schema is simplest now, and the question "which of these indexes will the planner actually use?" is one your future self will pay to answer if you don't pay to answer it cheaply today.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building **Provia&lt;/em&gt;* — an AI sales chatbot for Arabic-speaking e-commerce stores. Follow for more posts on building AI products from Gaza on a tight budget.*&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built These Tools Because the Frustration Was Real</title>
      <dc:creator>Sushil Kulkarni</dc:creator>
      <pubDate>Sat, 09 May 2026 11:43:48 +0000</pubDate>
      <link>https://open.forem.com/smkulkarni/i-built-these-tools-because-the-frustration-was-real-gkh</link>
      <guid>https://open.forem.com/smkulkarni/i-built-these-tools-because-the-frustration-was-real-gkh</guid>
      <description>&lt;p&gt;Most side projects don’t start with inspiration.&lt;/p&gt;

&lt;p&gt;They start with irritation.&lt;/p&gt;

&lt;p&gt;A repeated task.&lt;br&gt;
A broken workflow.&lt;br&gt;
A problem you keep telling yourself you’ll “fix later.”&lt;/p&gt;

&lt;p&gt;That’s how all of mine started.&lt;/p&gt;

&lt;p&gt;Not with a grand plan.&lt;br&gt;
Just friction I couldn’t ignore anymore.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. TermiCool — Because Terminal Customization Shouldn’t Feel Like Surgery
&lt;/h2&gt;

&lt;p&gt;Customizing my terminal was taking way more time than it should.&lt;/p&gt;

&lt;p&gt;Editing config files.&lt;br&gt;
Hunting hex codes.&lt;br&gt;
Tweaking themes.&lt;br&gt;
Accidentally breaking everything.&lt;/p&gt;

&lt;p&gt;Something that should take 30 seconds was eating hours.&lt;/p&gt;

&lt;p&gt;I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one click to apply a theme&lt;/li&gt;
&lt;li&gt;one click to undo it&lt;/li&gt;
&lt;li&gt;zero risk of breaking my setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;strong&gt;TermiCool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Simple, fast, reversible terminal theming without the usual pain.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. TreePress — Because PDF Export Shouldn’t Destroy Your Work
&lt;/h2&gt;

&lt;p&gt;Exporting to PDF was worse.&lt;/p&gt;

&lt;p&gt;Code lost syntax highlighting.&lt;br&gt;
Markdown lost formatting.&lt;br&gt;
HTML dashboards broke completely.&lt;/p&gt;

&lt;p&gt;And tabs?&lt;br&gt;
Especially painful.&lt;/p&gt;

&lt;p&gt;PDF has no concept of tabs, so content hidden behind tabs would simply disappear.&lt;/p&gt;

&lt;p&gt;Half the page gone.&lt;/p&gt;

&lt;p&gt;That made exports useless for real documentation.&lt;/p&gt;

&lt;p&gt;I needed exports that looked like what I actually built—not a flattened, broken version of it.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;TreePress&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now it creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pixel-faithful exports&lt;/li&gt;
&lt;li&gt;searchable PDFs&lt;/li&gt;
&lt;li&gt;tab-aware rendering&lt;/li&gt;
&lt;li&gt;proper HTML + Markdown support&lt;/li&gt;
&lt;li&gt;predictable output without manual fixing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No config.&lt;br&gt;
No surprises.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. CCL — Because Rebuilding Claude Code Setup Was Repetitive
&lt;/h2&gt;

&lt;p&gt;Every new project meant rebuilding the same Claude Code setup.&lt;/p&gt;

&lt;p&gt;Same files.&lt;br&gt;
Same folder structure.&lt;br&gt;
Same configuration.&lt;/p&gt;

&lt;p&gt;Again.&lt;br&gt;
And again.&lt;/p&gt;

&lt;p&gt;It felt like setup work pretending to be productivity.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;CCL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now I run one command and get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;subagents&lt;/li&gt;
&lt;li&gt;skills&lt;/li&gt;
&lt;li&gt;security hooks&lt;/li&gt;
&lt;li&gt;project-aware scaffolding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All generated around the actual codebase.&lt;/p&gt;

&lt;p&gt;Done in seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  None of This Was Planned
&lt;/h2&gt;

&lt;p&gt;These weren’t startup ideas.&lt;/p&gt;

&lt;p&gt;They were problems I got tired of tolerating.&lt;/p&gt;

&lt;p&gt;They were built in stolen hours:&lt;/p&gt;

&lt;p&gt;late nights after work,&lt;br&gt;
early weekend mornings,&lt;br&gt;
small windows between real life and responsibilities.&lt;/p&gt;

&lt;p&gt;That’s how most side projects actually happen.&lt;/p&gt;

&lt;p&gt;Not in perfect focus.&lt;/p&gt;

&lt;p&gt;In fragments.&lt;/p&gt;




&lt;h2&gt;
  
  
  If You’re Facing the Same Friction
&lt;/h2&gt;

&lt;p&gt;If terminal customization feels like a chore → &lt;strong&gt;&lt;a href="https://sushilkulkarni1389.github.io/termicool/" rel="noopener noreferrer"&gt;TermiCool&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your PDF exports destroy what you built → &lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=treepress.vscode-treepress" rel="noopener noreferrer"&gt;TreePress&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you keep rebuilding Claude Code setup from scratch → &lt;strong&gt;&lt;a href="https://sushilkulkarni1389.github.io/ccl/" rel="noopener noreferrer"&gt;CCL&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built these because the pain was real.&lt;/p&gt;

&lt;p&gt;Maybe one of them saves you a few hours too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Turn
&lt;/h2&gt;

&lt;p&gt;What repetitive frustration have you been tolerating for too long?&lt;/p&gt;

&lt;p&gt;And which one of these feels the most painful to you?&lt;/p&gt;

&lt;p&gt;Would love to hear it ↓&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>opensource</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building an Authentication Starter: Lessons from Integrating Next.js, PostgreSQL, Prisma, and NextAuth</title>
      <dc:creator>Berkay Sonel</dc:creator>
      <pubDate>Sat, 09 May 2026 11:36:14 +0000</pubDate>
      <link>https://open.forem.com/berkaysson/building-an-authentication-starter-lessons-from-integrating-nextjs-postgresql-prisma-and-2hfg</link>
      <guid>https://open.forem.com/berkaysson/building-an-authentication-starter-lessons-from-integrating-nextjs-postgresql-prisma-and-2hfg</guid>
      <description>&lt;h1&gt;
  
  
  Building an Authentication Starter: Lessons from Integrating Next.js, PostgreSQL, Prisma, and NextAuth
&lt;/h1&gt;

&lt;p&gt;When starting a new web project, setting up user authentication can often feel like a significant initial friction. There are many components involved: managing user data, securing routes, handling different login methods, and keeping everything type-safe. My goal was to create a solid starting point for future projects, something that handles these complexities without requiring extensive setup each time. This led me to develop a Next.js authentication starter app, and I'd like to share some insights from the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Foundation: Why These Technologies?
&lt;/h2&gt;

&lt;p&gt;The project is built on a few core technologies, chosen for their individual strengths and how well they work together. These technologies also promising and future proof.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next.js
&lt;/h3&gt;

&lt;p&gt;I chose &lt;strong&gt;Next.js 14&lt;/strong&gt; as the application framework. Its App Router structure makes route definition and data fetching much clearer, especially for server-side rendered applications. It offers a modern approach that simplifies many common web development tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL &amp;amp; Prisma
&lt;/h3&gt;

&lt;p&gt;For data management, I implemented for &lt;strong&gt;PostgreSQL&lt;/strong&gt; and &lt;strong&gt;Prisma&lt;/strong&gt;. PostgreSQL is a widely used relational database, known for its stability. Prisma, as an ORM, significantly simplifies database interactions. It provides a type-safe way to define models and perform queries, which was a huge win for developer experience and reducing potential bugs. Defining my database schema in &lt;code&gt;prisma/schema.prisma&lt;/code&gt; and using Prisma migrations felt a natural fit for managing changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  NextAuth.js
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;NextAuth.js&lt;/strong&gt; handles the core authentication logic. It abstracts away much of the complexity of session management, social logins (like Google and GitHub), and callback handling. Configuring it in &lt;code&gt;auth.config.ts&lt;/code&gt; allowed me to define providers and custom callbacks easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Resend&lt;/strong&gt;: For sending emails related to verification and password resets. This was chosen for its developer-friendly API.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Tailwind CSS &amp;amp; ShadCN&lt;/strong&gt;: To get a user interface up and running quickly. Tailwind CSS helps with rapid styling, and ShadCN provides a collection of accessible components built on top of it.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Zod&lt;/strong&gt;: For form validation. Defining schemas in &lt;code&gt;schemas/&lt;/code&gt; made sure my data inputs were always correctly formatted and type-safe, preventing many common form-related issues.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;bcryptjs&lt;/strong&gt;: For password encryption, a crucial security measure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features I Focused On
&lt;/h2&gt;

&lt;p&gt;This starter app includes several features designed to cover common authentication needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;User Forms&lt;/strong&gt;: Login, verification, update password, reset password and registration pages, supporting both email/password and social logins (Google, GitHub).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Protected Routes&lt;/strong&gt;: Implementing &lt;code&gt;middleware.ts&lt;/code&gt; to check user sessions before allowing access to specific parts of the application, like &lt;code&gt;/settings&lt;/code&gt;. The user should add his/her application pages to here.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Email Verification &amp;amp; Password Reset&lt;/strong&gt;: Generating secure tokens and using Resend to send emails for these critical flows.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data Handling&lt;/strong&gt;: A clear separation between &lt;code&gt;actions/&lt;/code&gt; for database mutations and &lt;code&gt;data/&lt;/code&gt; for read operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the Project is Organized
&lt;/h2&gt;

&lt;p&gt;Understanding the project's layout was important for building it and will be for anyone extending it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;app/&lt;/code&gt;&lt;/strong&gt;: Contains all route definitions, including public routes like &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;/auth/login&lt;/code&gt;, and protected routes like &lt;code&gt;/(protected)/settings&lt;/code&gt;. App router.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;actions/&lt;/code&gt;&lt;/strong&gt;: Where server actions live, connecting to the database for operations like creating a new user or updating a profile.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;data/&lt;/code&gt;&lt;/strong&gt;: Functions for fetching data from the database, typically read-only.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;components/&lt;/code&gt;&lt;/strong&gt;: My reusable UI elements, keeping the interface consistent.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;lib/&lt;/code&gt;&lt;/strong&gt;: General utilities and helpers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;prisma/&lt;/code&gt;&lt;/strong&gt;: Holds the Prisma schema and generated client for database models and migrations.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;schemas/&lt;/code&gt;&lt;/strong&gt;: All Zod validation schemas, ensuring data integrity.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;middleware.ts&lt;/code&gt;&lt;/strong&gt;: Handles route protection and authentication checks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;auth.config.ts&lt;/code&gt;&lt;/strong&gt;: NextAuth configuration details, including providers and callbacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Insights and Lessons Learned
&lt;/h2&gt;

&lt;p&gt;One of the main takeaways from putting this together was the power of explicit data validation with Zod. It really helped to catch errors early and provided clear feedback for form inputs. Separating concerns between &lt;code&gt;actions/&lt;/code&gt; and &lt;code&gt;data/&lt;/code&gt; also made the server-side logic much easier to reason about and maintain as the project grew. User can use tanstack query or useSwr for data fetching, starter pack enables every possible way that user can go.&lt;/p&gt;

&lt;p&gt;Thinking about alternatives, I considered different ORMs like neon, but Prisma's type-safety with TypeScript was a strong argument for its use and also very easy to use, I found that Neon is another powerful alternative but for a starter kit it was too much like raw SQL. For email sending, there are many options, but Resend's API worked well for integrating token generation. These parts very easy to change and use alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluding Thoughts
&lt;/h2&gt;

&lt;p&gt;This Next.js authentication starter project is designed to offer a solid starting point for applications needing user authentication. It brings together well-regarded tools to handle many common challenges. If you're looking to kickstart a project with Next.js 14 and need a reliable authentication setup, feel free to explore it. I'm always open to hearing about different approaches or improvements!&lt;/p&gt;

&lt;h3&gt;
  
  
  Repo:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/berkaysson/nextjs-auth-starter" rel="noopener noreferrer"&gt;Next.js Auth Starter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>postgres</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Attractor Engineering: Seeing Software Development as Field Dynamics</title>
      <dc:creator>Hiroyuki Nakahata</dc:creator>
      <pubDate>Sat, 09 May 2026 11:25:16 +0000</pubDate>
      <link>https://open.forem.com/iroha1203/attractor-engineering-seeing-software-development-as-field-dynamics-16g5</link>
      <guid>https://open.forem.com/iroha1203/attractor-engineering-seeing-software-development-as-field-dynamics-16g5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A codebase can be read as a field that attracts future changes, and a pull request can be read as a force applied to that field.&lt;/li&gt;
&lt;li&gt;A good field makes good changes easier to make. A bad field repeatedly makes bad shortcuts look natural. In an era where AI can produce PRs quickly, this attraction becomes stronger.&lt;/li&gt;
&lt;li&gt;I call the practice of designing where future changes are pulled &lt;strong&gt;Attractor Engineering&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;CI/CD, tests, reviews, and harnesses can be read as dissipative systems: they remove unwanted force and shape the trajectory.&lt;/li&gt;
&lt;li&gt;ArchSig, or Architecture Signature, is a tool for observing that trajectory along multiple axes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first half of this article is written for practitioners: it explains the intuition in terms of codebases, PRs, review, CI, and AI-assisted development. The second half is more mathematical: it connects the same intuition to AAT, Architecture Signature, Lean formalization, and finite counterexamples.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Discovery
&lt;/h2&gt;

&lt;p&gt;The starting point was a simple thought experiment.&lt;/p&gt;

&lt;p&gt;What if we look at software architecture not only as a set of directories, design rules, or conventions, but as an &lt;strong&gt;algebraic structure&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;From that point of view, everyday changes such as feature additions, refactorings, splits, migrations, repairs, protections, deletions, and integrations become operations acting on the structure we call architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current codebase
  + feature addition
  + refactoring
  + review fix
  + migration
  + repair
  -&amp;gt; next codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we take one more step, we can ask: if these operations are not applied once, but repeated dozens or hundreds of times, can the whole development process be read as a kind of dynamics?&lt;/p&gt;

&lt;p&gt;Each individual PR is small.&lt;/p&gt;

&lt;p&gt;But after enough PRs, the codebase gradually moves in some direction.&lt;/p&gt;

&lt;p&gt;When a good structure already exists, the next change tends to fit into a good place.&lt;/p&gt;

&lt;p&gt;When a bad structure exists, a locally natural change tends to take the same bad shortcut again.&lt;/p&gt;

&lt;p&gt;Can we treat "where changes tend to go" as something we design?&lt;/p&gt;

&lt;p&gt;I decided to call this way of thinking &lt;strong&gt;Attractor Engineering&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Codebase Is a Field, and a PR Is a Force
&lt;/h2&gt;

&lt;p&gt;The central interpretation is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;codebase = a field that attracts changes
PR       = a force applied to that field
ArchSig  = an observer for the movement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A codebase is not a neutral space that passively receives future changes.&lt;/p&gt;

&lt;p&gt;Existing names, types, responsibility boundaries, tests, directory structure, previous implementation examples, and review culture all shape which next change feels natural.&lt;/p&gt;

&lt;p&gt;A PR is a force applied to that field. Each one may be small, but repeated PRs create a trajectory of change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current codebase
  -&amp;gt; a PR is applied
  -&amp;gt; an architectural change is observed
  -&amp;gt; a trajectory of change emerges
  -&amp;gt; this becomes the next codebase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is that a PR changes the codebase, and the changed codebase then changes what the next PR is likely to look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  People and Systems Create the Field
&lt;/h2&gt;

&lt;p&gt;This field is not created only by engineers.&lt;/p&gt;

&lt;p&gt;Product managers, product owners, engineers, reviewers, AI agents, CI, tests, design documents, coding standards, and existing examples all participate in it.&lt;/p&gt;

&lt;p&gt;Everyone and everything involved in development affects which changes become likely next.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Participant / mechanism&lt;/th&gt;
&lt;th&gt;Effect on the field&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product manager&lt;/td&gt;
&lt;td&gt;Decides which values and demands are repeatedly injected into the system.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product owner&lt;/td&gt;
&lt;td&gt;Shapes PRs through requirement granularity, priorities, and acceptance criteria.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineer / architect&lt;/td&gt;
&lt;td&gt;Creates paths for change through boundaries, abstractions, standard patterns, and reference implementations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer&lt;/td&gt;
&lt;td&gt;Pushes back bad force and redirects it toward better directions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI / tests / types&lt;/td&gt;
&lt;td&gt;Rejects, weakens, and narrows inappropriate force.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI agent&lt;/td&gt;
&lt;td&gt;Reads the existing field and quickly proposes changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The way requirements are sliced, prioritized, scheduled, and accepted changes how later PRs are produced. Even people who do not write code apply indirect force to the field of the codebase.&lt;/p&gt;

&lt;p&gt;This becomes especially important in AI-assisted development. Vague requirements can quickly become vague PRs. If boundaries and non-goals are clear, an AI system is more likely to produce useful changes within those boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes in AI-Assisted Development
&lt;/h2&gt;

&lt;p&gt;The essence of AI-assisted development is not simply that code can be written faster.&lt;/p&gt;

&lt;p&gt;The more important change is that &lt;strong&gt;the distribution of selected change operations&lt;/strong&gt; changes.&lt;/p&gt;

&lt;p&gt;An AI system reads existing code, neighboring files, names, types, tests, previous implementation examples, READMEs, and design documents, and then generates the next proposed change.&lt;/p&gt;

&lt;p&gt;In other words, the whole codebase becomes input context for the AI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the codebase becomes input context for AI
  -&amp;gt; AI proposes a change
  -&amp;gt; the proposal becomes a PR
  -&amp;gt; review / CI / merge process handles it
  -&amp;gt; the codebase is updated
  -&amp;gt; the next input context changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a good reference implementation is nearby, the AI is likely to imitate it.&lt;/p&gt;

&lt;p&gt;If a bad shortcut already exists, the AI is also likely to treat it as a natural option.&lt;/p&gt;

&lt;p&gt;In this sense, AI rapidly reproduces the local style already present in the field. That is why, in the AI era, what matters is not only the capability of an individual AI agent. The design of the field in which the AI participates matters just as much.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an Attractor?
&lt;/h2&gt;

&lt;p&gt;When a codebase contains a huge &lt;code&gt;common&lt;/code&gt; module, an overly convenient helper, or an ambiguous service, changes tend to be pulled there.&lt;/p&gt;

&lt;p&gt;Conversely, when good responsibility boundaries and clear implementation examples exist, the next change tends to follow them.&lt;/p&gt;

&lt;p&gt;This destination toward which changes are pulled is what I call an attractor. When something moves repeatedly, it often tends to approach certain places or states.&lt;/p&gt;

&lt;p&gt;The surrounding region from which things are likely to fall into that attractor is what I call a basin.&lt;/p&gt;

&lt;p&gt;Technical debt can be read as a bad basin.&lt;/p&gt;

&lt;p&gt;Once a codebase falls into it, locally natural changes keep adding to the same place, and the cost of refactoring out of it grows higher and higher.&lt;/p&gt;

&lt;p&gt;The important point is that attractors can be good or bad.&lt;/p&gt;

&lt;p&gt;A good attractor pulls in good changes.&lt;/p&gt;

&lt;p&gt;A bad attractor makes bad shortcuts get selected again and again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Attractor Engineering?
&lt;/h2&gt;

&lt;p&gt;Attractor Engineering is the idea that we should deliberately design these attractors.&lt;/p&gt;

&lt;p&gt;Its target is not just the codebase.&lt;/p&gt;

&lt;p&gt;It includes the whole development organization: product managers, product owners, engineers, reviewers, AI agents, CI/CD, tests, design documents, and coding standards.&lt;/p&gt;

&lt;p&gt;The goal is not only to block bad changes from the outside. The goal is to create a field where good changes are naturally easier to select.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part of Attractor Engineering&lt;/th&gt;
&lt;th&gt;What it shapes&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create the field&lt;/td&gt;
&lt;td&gt;Which changes feel natural to propose.&lt;/td&gt;
&lt;td&gt;Requirements, priorities, design boundaries, types, APIs, examples, templates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dissipate bad force&lt;/td&gt;
&lt;td&gt;Which proposed changes are rejected, weakened, or redirected.&lt;/td&gt;
&lt;td&gt;Harnesses, CI, tests, reviews, PR granularity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observe the trajectory&lt;/td&gt;
&lt;td&gt;How architectural movement becomes visible over time.&lt;/td&gt;
&lt;td&gt;ArchSig, architecture features, drift reports.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Attractor Engineering is an integrated design theory for the era of AI-assisted development. It treats the entire development organization as part of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Harness Engineering as a Dissipative System
&lt;/h2&gt;

&lt;p&gt;We cannot simply take changes produced by AI and put them directly into the codebase.&lt;/p&gt;

&lt;p&gt;Harnesses, CI, tests, type checking, static analysis, and review divide proposed changes into "accept", "fix and check again", and "do not merge".&lt;/p&gt;

&lt;p&gt;In Attractor Engineering, this behavior can be read as dissipation.&lt;/p&gt;

&lt;p&gt;Dissipation is the mechanism that removes unwanted components of the force entering the field.&lt;/p&gt;

&lt;p&gt;If dissipation is too weak, the fast change force produced by AI enters the codebase directly. If it is too strong, nothing moves. A good harness weakens the force that increases debt while preserving the force that moves the product forward.&lt;/p&gt;

&lt;p&gt;In this view, CI/CD is not merely a checklist. It is more like brakes, rails, signals, and safety equipment that convert fast PR generation into safe productivity.&lt;/p&gt;

&lt;p&gt;What matters in AI-assisted development is not only making the engine stronger.&lt;/p&gt;

&lt;p&gt;It is also preparing the field and the dissipative system that can receive a stronger engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ArchSig Observes
&lt;/h2&gt;

&lt;p&gt;To design the field, we need to observe what is happening.&lt;/p&gt;

&lt;p&gt;That is the role of ArchSig.&lt;/p&gt;

&lt;p&gt;ArchSig is short for Architecture Signature.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2" rel="noopener noreferrer"&gt;repository&lt;/a&gt;, I use it to mean an observation framework for reading changes in a codebase or PR along multiple axes. Dependencies, boundaries, abstractions, runtime exposure, semantic drift, and test observability are not collapsed into a single score. They are treated as multiple features.&lt;/p&gt;

&lt;p&gt;ArchSig is an observer for seeing which direction a PR moves the architecture.&lt;/p&gt;

&lt;p&gt;For example, we may observe axes like these:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;What we want to observe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Static dependencies&lt;/td&gt;
&lt;td&gt;Dependency direction and violations of forbidden dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boundary rules&lt;/td&gt;
&lt;td&gt;Connections that cross boundaries or bypass rules.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Abstraction leakage&lt;/td&gt;
&lt;td&gt;Concrete dependencies that jump over abstractions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic drift&lt;/td&gt;
&lt;td&gt;Whether responsibilities or meanings have shifted away from what was intended.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test observability&lt;/td&gt;
&lt;td&gt;Whether the change can be observed through tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-PR change&lt;/td&gt;
&lt;td&gt;How each axis moves in a single PR.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important point is that we do not compress good and bad into one score.&lt;/p&gt;

&lt;p&gt;What we want to know is which axes got worse, which axes improved, and what kind of force each change applies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR
  -&amp;gt; observe change with ArchSig
  -&amp;gt; observe the trajectory of change
  -&amp;gt; see whether it is moving toward a good or bad region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ArchSig becomes an observer for the AI PR era.&lt;/p&gt;

&lt;p&gt;Are AI-generated changes moving toward good attractors?&lt;/p&gt;

&lt;p&gt;Are they falling into a pool of technical debt?&lt;/p&gt;

&lt;p&gt;Is the harness dissipating enough bad force?&lt;/p&gt;

&lt;p&gt;ArchSig gives us shared language for discussing those questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  PRs Become More Important, Not Less
&lt;/h2&gt;

&lt;p&gt;When AI reduces the cost of generating code, it may look as if PRs become less important.&lt;/p&gt;

&lt;p&gt;From a dynamical systems viewpoint, the opposite is true.&lt;/p&gt;

&lt;p&gt;A PR is not just a work unit.&lt;/p&gt;

&lt;p&gt;A PR has the following roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It cuts continuous change into observable units.&lt;/li&gt;
&lt;li&gt;It lets us separate the directions in which a change acts.&lt;/li&gt;
&lt;li&gt;It embeds the dissipative process of review, CI, and approval.&lt;/li&gt;
&lt;li&gt;It creates a boundary for rollback and reversibility.&lt;/li&gt;
&lt;li&gt;It creates a unit for decision-making and discussion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What AI lowers is mainly the cost of producing a PR.&lt;/p&gt;

&lt;p&gt;But the value of PRs as units of observation, decomposition, dissipation, reversibility, and decision-making increases. In the AI era, PRs do not become unnecessary. They become more important as units for observing and controlling architectural movement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Development Organizations
&lt;/h2&gt;

&lt;p&gt;In future development organizations, the central problem will not be only how fast we can write code. It will be how to design a field that can safely receive that speed.&lt;/p&gt;

&lt;p&gt;A fast train cannot run safely just because it has a powerful motor.&lt;/p&gt;

&lt;p&gt;It needs rails, brakes, signals, safety equipment, and operations control.&lt;/p&gt;

&lt;p&gt;Software development is similar. AI is a powerful motor, but by itself it can create semantic drift, responsibility drift, degradation of design properties we wanted to preserve, merge confusion, and flow toward technical debt.&lt;/p&gt;

&lt;p&gt;What we need is a field with properties like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small and observable PRs.&lt;/li&gt;
&lt;li&gt;Fast feedback.&lt;/li&gt;
&lt;li&gt;Reliable CI.&lt;/li&gt;
&lt;li&gt;A useful type system.&lt;/li&gt;
&lt;li&gt;Architecture tests.&lt;/li&gt;
&lt;li&gt;Carefully selected reference implementations.&lt;/li&gt;
&lt;li&gt;Isolation of legacy code.&lt;/li&gt;
&lt;li&gt;Clear demands, requirements, and design boundaries.&lt;/li&gt;
&lt;li&gt;Human-designed boundaries for value and acceptance criteria.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The safest AI coding environment is not the one with the strongest external harness. It is the one where good changes are natural, easy to imitate, observable, and less likely to fall into bad shortcuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary So Far
&lt;/h2&gt;

&lt;p&gt;The success or failure of AI-assisted development is not determined only by how fast AI can write code.&lt;/p&gt;

&lt;p&gt;The direction of the next PR changes depending on the field created by the codebase, requirements, design boundaries, reference implementations, review, CI/CD, and ArchSig.&lt;/p&gt;

&lt;p&gt;A good field attracts good changes. A bad field repeatedly makes bad changes look natural. That is why architecture design in the AI era becomes the design of where future changes are attracted.&lt;/p&gt;

&lt;p&gt;So far, this has been Attractor Engineering in practical engineering language. In the second half, I translate the same intuition into the language of AAT, Algebraic Architecture Theory, and dynamical systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Here, in Mathematical Language
&lt;/h2&gt;

&lt;p&gt;The rest of this article uses more mathematical formulation. If you only want the practical view first, it is fine to skip to the conclusion.&lt;/p&gt;

&lt;p&gt;Around AI development, there are many heuristics: "this prompt worked", "this workflow made us faster", and so on. These heuristics are valuable. But by themselves, they make it hard to separate why something worked, how far it generalizes, and under what conditions it breaks.&lt;/p&gt;

&lt;p&gt;So I decompose the flow: a change is selected, becomes a PR, passes review / CI, is merged, and then the updated codebase changes the distribution of future changes. By separating state, operation, observation, invariant, obstruction witness, and proof obligation, we can turn heuristics into something easier to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Short Introduction to AAT
&lt;/h2&gt;

&lt;p&gt;The background theory for this discussion is AAT, or Algebraic Architecture Theory. Here I introduce only the minimal vocabulary needed for the rest of the article.&lt;/p&gt;

&lt;p&gt;The Lean snippets below are excerpts from implemented APIs, adjusted for readability. Namespaces, imports, and some proof bodies are omitted.&lt;/p&gt;

&lt;p&gt;AAT treats software development not merely as a sequence of code changes, but as a theory of architectural extension, decomposition, repair, and composition.&lt;/p&gt;

&lt;p&gt;Its central proposition does not appear in Lean as one large equation. It appears as packages that combine operations and proof obligations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;OperationProofObligation&lt;/span&gt; (&lt;span class="n"&gt;State&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;) (&lt;span class="n"&gt;Witness&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;kind&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureOperationKind&lt;/span&gt;
  &lt;span class="n"&gt;obligation&lt;/span&gt; : &lt;span class="n"&gt;ProofObligation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Witness&lt;/span&gt;
  &lt;span class="n"&gt;precondition&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusion&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Operations are first classified as an operation catalog on the Lean side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;inductive&lt;/span&gt; &lt;span class="n"&gt;ArchitectureOperationKind&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;compose&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;refine&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;replace&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;isolate&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;protect&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;migrate&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;repair&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;synthesize&lt;/span&gt;
  &lt;span class="n"&gt;deriving&lt;/span&gt; &lt;span class="n"&gt;DecidableEq&lt;/span&gt;, &lt;span class="n"&gt;Repr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is that names such as &lt;code&gt;split&lt;/code&gt; or &lt;code&gt;repair&lt;/code&gt; prove nothing by themselves. An operation kind is a classification for theorem packages. Claims about preservation, improvement, or repair must be stated separately as proof obligations.&lt;/p&gt;

&lt;p&gt;From this viewpoint, a design review is not only the question "is this design good or bad?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Is the existing structure embedded after extension?
- Can the new feature be split out from the existing structure?
- Do interactions pass through declared interfaces?
- Which invariants are preserved, and which invariants were broken?
- If a split is not possible, which obstruction witness blocks it?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The smallest object in AAT is &lt;code&gt;ArchitectureCore&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;ArchitectureCore&lt;/span&gt; (&lt;span class="n"&gt;C&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;) (&lt;span class="n"&gt;A&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;)
    (&lt;span class="n"&gt;StaticObs&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;) (&lt;span class="n"&gt;SemanticExpr&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;)
    (&lt;span class="n"&gt;SemanticObs&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;flatness&lt;/span&gt; :
    &lt;span class="n"&gt;ArchitectureFlatnessModel&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;StaticObs&lt;/span&gt; &lt;span class="n"&gt;SemanticExpr&lt;/span&gt; &lt;span class="n"&gt;SemanticObs&lt;/span&gt;
  &lt;span class="n"&gt;staticUniverse&lt;/span&gt; : &lt;span class="n"&gt;ComponentUniverse&lt;/span&gt; &lt;span class="n"&gt;flatness&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;
  &lt;span class="n"&gt;componentDecidableEq&lt;/span&gt; : &lt;span class="n"&gt;DecidableEq&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;
  &lt;span class="n"&gt;staticEdgeDecidable&lt;/span&gt; : &lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;flatness&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;static&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;
  &lt;span class="n"&gt;runtimeEdgeDecidable&lt;/span&gt; : &lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;flatness&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;
  &lt;span class="n"&gt;boundaryPolicyDecidable&lt;/span&gt; : &lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;flatness&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;boundaryAllowed&lt;/span&gt;
  &lt;span class="n"&gt;abstractionPolicyDecidable&lt;/span&gt; : &lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;flatness&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abstractionAllowed&lt;/span&gt;
  &lt;span class="n"&gt;runtimeRole&lt;/span&gt; : &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RuntimeDependencyRole&lt;/span&gt;
  &lt;span class="n"&gt;semanticRequiredDecidable&lt;/span&gt; :
    &lt;span class="o"&gt;∀&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; : &lt;span class="n"&gt;RequiredDiagram&lt;/span&gt; &lt;span class="n"&gt;SemanticExpr&lt;/span&gt;,
      &lt;span class="n"&gt;Decidable&lt;/span&gt; (&lt;span class="n"&gt;flatness&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requiredSemantic&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here it is important that &lt;code&gt;ArchitectureCore&lt;/code&gt; is not the whole real-world codebase itself. It is a finite or bounded object extracted from code, specifications, reviews, and operational observations so that the theory can handle it.&lt;/p&gt;

&lt;p&gt;Feature addition is read as an operation that extends an existing architecture into a larger one while preserving the existing architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ExistingCore X
  -&amp;gt; ExtendedArchitecture X'
  -&amp;gt; FeatureView F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a good feature extension, the existing core is preserved inside the extension, the feature view can be extracted in an explainable way, and interactions from the feature to the core pass through declared interfaces. Conversely, hidden dependencies, boundary policy violations, abstraction leakage, runtime exposure, and semantic mismatch are treated not as impressions, but as &lt;code&gt;ObstructionWitness&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;To count, remove, preserve, or explicitly decline to conclude about these obstructions, AAT makes &lt;code&gt;ProofObligation&lt;/code&gt; and &lt;code&gt;Certificate&lt;/code&gt; explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;ProofObligation&lt;/span&gt; (&lt;span class="n"&gt;State&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;) (&lt;span class="n"&gt;Witness&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;formalUniverse&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;requiredLaws&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;invariantFamily&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;witnessUniverse&lt;/span&gt; : &lt;span class="n"&gt;Witness&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;exactnessAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;operationPreconditions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;conclusion&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An obligation is not discharged merely because it exists. It is discharged only when the visible assumptions imply the conclusion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;AssumptionsHold&lt;/span&gt; (&lt;span class="n"&gt;P&lt;/span&gt; : &lt;span class="n"&gt;ProofObligation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Witness&lt;/span&gt;) : &lt;span class="kt"&gt;Prop&lt;/span&gt; :=
  &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;formalUniverse&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
  &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requiredLaws&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
  &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
  &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exactnessAssumptions&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
  &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operationPreconditions&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;Discharged&lt;/span&gt; (&lt;span class="n"&gt;P&lt;/span&gt; : &lt;span class="n"&gt;ProofObligation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Witness&lt;/span&gt;) : &lt;span class="kt"&gt;Prop&lt;/span&gt; :=
  &lt;span class="n"&gt;AssumptionsHold&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conclusion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same is true for certificates. For example, in repair synthesis, if we say "there is no solution", solver failure alone is not enough. Only when a valid certificate exists do we use soundness to conclude that no satisfying architecture exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;NoSolutionCertificate&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Constraint&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; (&lt;span class="n"&gt;Certificate&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;)
    (&lt;span class="n"&gt;C&lt;/span&gt; : &lt;span class="n"&gt;SynthesisConstraintSystem&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Constraint&lt;/span&gt;)
    (&lt;span class="n"&gt;cert&lt;/span&gt; : &lt;span class="n"&gt;Certificate&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;valid&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;sound&lt;/span&gt; : &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NoArchitectureSatisfies&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;
  &lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;exactnessAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;

&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;sound_of_valid&lt;/span&gt;
    (&lt;span class="n"&gt;pkg&lt;/span&gt; : &lt;span class="n"&gt;NoSolutionCertificate&lt;/span&gt; &lt;span class="n"&gt;Certificate&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt;)
    (&lt;span class="n"&gt;hValid&lt;/span&gt; : &lt;span class="n"&gt;ValidNoSolutionCertificate&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;) :
    &lt;span class="n"&gt;NoArchitectureSatisfies&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nonConclusions&lt;/code&gt; is not decoration. Even if a static split is proved, runtime flatness or semantic flatness does not automatically follow. Even if no obstruction is found in one observation universe, that does not imply there is no obstruction in every universe. Making this boundary explicit is necessary if we want to treat the theory as testable rather than as a collection of engineering anecdotes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ArchitectureSignature&lt;/code&gt; is also not intended to collapse architecture quality into a single score. It is a multi-axis diagnosis for reading multiple invariant and obstruction families axis by axis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;ArchitectureSignatureV1&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;core&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureSignatureV1Core&lt;/span&gt;
  &lt;span class="n"&gt;weightedSccRisk&lt;/span&gt; : &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;Nat&lt;/span&gt;
  &lt;span class="n"&gt;projectionSoundnessViolation&lt;/span&gt; : &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;Nat&lt;/span&gt;
  &lt;span class="n"&gt;lspViolationCount&lt;/span&gt; : &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;Nat&lt;/span&gt;
  &lt;span class="n"&gt;nilpotencyIndex&lt;/span&gt; : &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;Nat&lt;/span&gt;
  &lt;span class="n"&gt;runtimePropagation&lt;/span&gt; : &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;Nat&lt;/span&gt;
  &lt;span class="n"&gt;relationComplexity&lt;/span&gt; : &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;Nat&lt;/span&gt;
  &lt;span class="n"&gt;empiricalChangeCost&lt;/span&gt; : &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="n"&gt;Nat&lt;/span&gt;
  &lt;span class="n"&gt;deriving&lt;/span&gt; &lt;span class="n"&gt;DecidableEq&lt;/span&gt;, &lt;span class="n"&gt;Repr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some axes are &lt;code&gt;Option Nat&lt;/code&gt; because an unmeasured value must not be treated as zero. &lt;code&gt;none&lt;/code&gt; does not mean "no problem". It means "not measured in this universe / extractor / bridge".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;not_axisAvailableAndZero_of_axisValue_none&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureSignatureV1&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureSignatureV1Axis&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    (&lt;span class="n"&gt;hNone&lt;/span&gt; : &lt;span class="n"&gt;axisValue&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;none&lt;/span&gt;) :
    &lt;span class="o"&gt;¬&lt;/span&gt; &lt;span class="n"&gt;axisAvailableAndZero&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this perspective, a signature is not a convenient bag of metrics. It is a multi-axis invariant relative to a law universe. For selected required law axes, there are also bridge theorems connecting lawfulness and zero signature axes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;architectureLawful_iff_requiredSignatureAxesZero&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Obs&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
    (&lt;span class="n"&gt;X&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureLawModel&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;Obs&lt;/span&gt;)
    [&lt;span class="n"&gt;DecidableEq&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;] [&lt;span class="n"&gt;DecidableEq&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;] [&lt;span class="n"&gt;DecidableEq&lt;/span&gt; &lt;span class="n"&gt;Obs&lt;/span&gt;]
    [&lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;] [&lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;]
    [&lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;boundaryAllowed&lt;/span&gt;]
    [&lt;span class="n"&gt;DecidableRel&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abstractionAllowed&lt;/span&gt;] :
    &lt;span class="n"&gt;ArchitectureLawful&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;↔&lt;/span&gt;
      &lt;span class="n"&gt;RequiredSignatureAxesZero&lt;/span&gt; (&lt;span class="n"&gt;ArchitectureLawModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signatureOf&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AAT does not treat every claim at the same level. It separates definitions, proved theorem packages, bounded bridge theorems, tooling-side evidence, and empirical hypotheses. It also records which universe, observation, coverage, and exactness assumptions each claim is relative to.&lt;/p&gt;

&lt;p&gt;The dynamics part below follows the same discipline. The important point is that AAT is not using mathematical vocabulary for atmosphere. It is trying to make the assumptions, conclusions, and non-conclusions part of the type-level structure.&lt;/p&gt;

&lt;p&gt;So far, AAT gives us vocabulary for a single architectural state and operations acting on it.&lt;/p&gt;

&lt;p&gt;But in AI-assisted development, the central issue is not only a single operation. Requirements, existing code, review, CI, and AI agents change which operation is likely to be selected next, and this selection is repeated many times.&lt;/p&gt;

&lt;p&gt;So we need to view AAT operations not only as one-time proof targets, but also as transformations repeatedly selected over time. This is where a chaos-game-like reading enters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formalizing Attractor Dynamics
&lt;/h2&gt;

&lt;p&gt;From here, I use AAT vocabulary to rewrite "field", "force", "dissipation", and "observation" in a more mathematical form.&lt;/p&gt;

&lt;p&gt;This is not merely a metaphor that says "AI development is kind of like a chaos game". It is an attempt to place PR force, operation support, trajectory, and basin candidates on top of the AAT vocabulary of state, operation, invariant, obstruction, proof obligation, certificate, and signature.&lt;/p&gt;

&lt;p&gt;At this stage, I should be careful: this is not a finished theorem of real-world software development. It is a way to organize phenomena that practitioners can feel, in a structure that may eventually support measurement and verification.&lt;/p&gt;

&lt;p&gt;The minimal loop of the dynamics can be read as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;architecture field
  -&amp;gt; operation distribution
  -&amp;gt; accepted / rejected transitions
  -&amp;gt; signature trajectory
  -&amp;gt; updated architecture field
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The position is that architecture quality is not only a property of a snapshot. It is also a property of the future operation distribution and the signature trajectory.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. State, Operation, Observation
&lt;/h3&gt;

&lt;p&gt;Let the architectural state be &lt;code&gt;X_t&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Feature addition, repair, split, protection, migration, and refactoring are operations acting on that state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_{t+1} = op_t(X_t)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operation &lt;code&gt;op_t&lt;/code&gt; is not selected uniformly at random.&lt;/p&gt;

&lt;p&gt;The current codebase, requirements, prompt, review policy, CI, design boundaries, and organizational judgment all change which operations are likely to be selected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;op_t ~ P(operation | X_t, control_t)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This probability expression is notation for the practical reading. The formal core of AAT is not a probability distribution. It is finite or bounded operation support, bounded scripts, accepted transition predicates, and explicit preservation assumptions.&lt;/p&gt;

&lt;p&gt;In Lean, for example, operation support is represented not as a probability distribution, but as a finite list of candidate operations for each state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;FiniteOperationKernel&lt;/span&gt;
    (&lt;span class="n"&gt;State&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;) (&lt;span class="n"&gt;OperationId&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;support&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;
  &lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;weightSourceBoundary&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;normalizationBoundary&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Weights, normalization, and completeness of AI-generated proposals are not mixed into the theorem here. Boundaries such as &lt;code&gt;weightSourceBoundary&lt;/code&gt; and &lt;code&gt;normalizationBoundary&lt;/code&gt; record what is outside the formal claim, and the probabilistic reading remains outside that core.&lt;/p&gt;

&lt;p&gt;Likewise, repeated operation sequences are first treated as bounded scripts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;BoundedOperationScript&lt;/span&gt; (&lt;span class="n"&gt;OperationId&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;operations&lt;/span&gt; : &lt;span class="n"&gt;List&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;
  &lt;span class="n"&gt;operationFamily&lt;/span&gt; : &lt;span class="n"&gt;OperationId&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;operationsInFamily&lt;/span&gt; :
    &lt;span class="o"&gt;∀&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;, &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="err"&gt;∈&lt;/span&gt; &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;operationFamily&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;
  &lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This boundary helps avoid confusing probabilistic interpretation with preservation claims over finite support.&lt;/p&gt;

&lt;p&gt;Instead of observing the entire state directly, we map it into signature space through an observation function &lt;code&gt;Obs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sigma_t = Obs(X_t)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;sigma_t&lt;/code&gt; is the Architecture Signature.&lt;/p&gt;

&lt;p&gt;It contains multi-axis observations such as dependency direction, boundaries, abstraction, runtime exposure, and semantic mismatch.&lt;/p&gt;

&lt;p&gt;In Lean, even the observation itself is packaged. The package contains not only the observation function, but also coverage and non-conclusion boundaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;SignatureObservation&lt;/span&gt; (&lt;span class="n"&gt;State&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;) (&lt;span class="n"&gt;Sig&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;observe&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;
  &lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When architectural evolution is mapped into observation space, we get a signature trajectory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;SignatureTrajectory&lt;/span&gt; (&lt;span class="n"&gt;O&lt;/span&gt; : &lt;span class="n"&gt;SignatureObservation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;) :
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ArchitectureEvolution&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;, &lt;span class="n"&gt;_&lt;/span&gt;, &lt;span class="n"&gt;ArchitecturePath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; [&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observe&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;]
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;, &lt;span class="n"&gt;_&lt;/span&gt;, &lt;span class="n"&gt;ArchitecturePath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cons&lt;/span&gt; &lt;span class="n"&gt;_step&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observe&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; :: &lt;span class="n"&gt;SignatureTrajectory&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A change moves the signature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Delta_t = sigma_{t+1} - sigma_t
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;Delta_t&lt;/code&gt; can be read as the force applied by the PR or operation.&lt;/p&gt;

&lt;p&gt;However, not every axis admits simple subtraction. For numeric axes, we may read it as a signed delta. For other axes, we read it as a before / after comparison, the appearance of a witness, or a change in state classification.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. PR Force Model
&lt;/h3&gt;

&lt;p&gt;With this structure, a PR becomes more than a diff.&lt;/p&gt;

&lt;p&gt;A PR can be read as a force applied to the codebase in the selected Architecture Signature space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRForce(PR) = sigma(after(PR)) - sigma(before(PR))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The word force here does not mean physical force. It means an observed change in signature, relative to which axes are observed and which differences are defined.&lt;/p&gt;

&lt;p&gt;Delta sequences, like trajectories, are relative to finite paths in Lean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;SignatureDeltaSequence&lt;/span&gt;
    (&lt;span class="n"&gt;O&lt;/span&gt; : &lt;span class="n"&gt;SignatureObservation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;) (&lt;span class="n"&gt;D&lt;/span&gt; : &lt;span class="n"&gt;SignatureDelta&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt; &lt;span class="n"&gt;Delta&lt;/span&gt;) :
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ArchitectureEvolution&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt; &lt;span class="n"&gt;Delta&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;, &lt;span class="n"&gt;_&lt;/span&gt;, &lt;span class="n"&gt;ArchitecturePath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; []
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;, &lt;span class="n"&gt;_&lt;/span&gt;, &lt;span class="n"&gt;ArchitecturePath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cons&lt;/span&gt; (&lt;span class="n"&gt;Y&lt;/span&gt; := &lt;span class="n"&gt;Y&lt;/span&gt;) &lt;span class="n"&gt;_step&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;between&lt;/span&gt; (&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observe&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;) (&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observe&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;) ::
        &lt;span class="n"&gt;SignatureDeltaSequence&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For selected additive deltas, the sum of step deltas agrees with the endpoint delta. This is proved as a theorem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;netSignatureDelta_telescopes&lt;/span&gt; [&lt;span class="n"&gt;Zero&lt;/span&gt; &lt;span class="n"&gt;Delta&lt;/span&gt;] [&lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;Delta&lt;/span&gt;]
    (&lt;span class="n"&gt;O&lt;/span&gt; : &lt;span class="n"&gt;SignatureObservation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;) (&lt;span class="n"&gt;D&lt;/span&gt; : &lt;span class="n"&gt;SignatureDelta&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt; &lt;span class="n"&gt;Delta&lt;/span&gt;)
    (&lt;span class="n"&gt;law&lt;/span&gt; : &lt;span class="n"&gt;AdditiveSignatureDeltaLaw&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;) :
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; (&lt;span class="n"&gt;plan&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureEvolution&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;) &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;NetSignatureDelta&lt;/span&gt; (&lt;span class="n"&gt;SignatureDeltaSequence&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;) &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;EndpointSignatureDelta&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this theorem is finite path calculus under the assumption that the selected delta satisfies an additive law. It does not claim that unobserved axes, incident risk, review quality, or actual PR outcomes can all be added this way.&lt;/p&gt;

&lt;p&gt;The force has multiple components.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Force component&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feature force&lt;/td&gt;
&lt;td&gt;The force that moves product functionality forward.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repair force&lt;/td&gt;
&lt;td&gt;The force that repairs existing breakage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coupling force&lt;/td&gt;
&lt;td&gt;The force that increases or decreases coupling.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boundary force&lt;/td&gt;
&lt;td&gt;The force that preserves or violates boundaries.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type force&lt;/td&gt;
&lt;td&gt;The force that adds type information or creates type holes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test force&lt;/td&gt;
&lt;td&gt;The force that increases or decreases observability through tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debt force&lt;/td&gt;
&lt;td&gt;The force that pushes the system toward a bad basin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refactor force&lt;/td&gt;
&lt;td&gt;The force that helps it escape a bad basin.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A good PR has not only feature force, but also stabilizing force.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v_PR = v_feature + v_stabilize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A risky PR moves the feature forward locally while quietly adding small debt force.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v_PR = v_feature + v_debt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In AI-generated PRs, the especially important case is one where tests pass and the specification is satisfied, but small &lt;code&gt;v_debt&lt;/code&gt;, &lt;code&gt;v_coupling&lt;/code&gt;, &lt;code&gt;v_type_hole&lt;/code&gt;, or &lt;code&gt;v_entropy&lt;/code&gt; accumulates repeatedly. It may be hard to see in a single PR. But as a trajectory, the system is moving toward a bad basin. I call this the Local Correctness Trap.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Three Classes of Force
&lt;/h3&gt;

&lt;p&gt;The earlier discussion about product managers, product owners, review, and CI/CD becomes clearer if we separate force by observability.&lt;/p&gt;

&lt;p&gt;Force can be divided into three classes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Force class&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Main evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ObservedForce&lt;/td&gt;
&lt;td&gt;Before / after signature delta of PRs that were actually merged.&lt;/td&gt;
&lt;td&gt;PRs, ArchSig reports, drift ledger.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LatentForce&lt;/td&gt;
&lt;td&gt;Invisible force by which requirements, design, prompts, and local code style shape which PRs are proposed.&lt;/td&gt;
&lt;td&gt;Requirements, prompts, proposal logs, case studies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DissipatedForce&lt;/td&gt;
&lt;td&gt;Raw force that was rejected, corrected, or weakened by review / CI / types / policy.&lt;/td&gt;
&lt;td&gt;CI failures, requested changes in review, rejected proposals.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This classification makes AI-assisted development more concrete.&lt;/p&gt;

&lt;p&gt;If we look only at merged PRs, we see only ObservedForce.&lt;/p&gt;

&lt;p&gt;But in an era where AI can generate many proposals, the force that was not merged also matters. Force removed by review, rejected by CI, or reshaped before merge matters.&lt;/p&gt;

&lt;p&gt;To understand how well a dissipative system is working, we need DissipatedForce.&lt;/p&gt;

&lt;p&gt;To understand what kind of PR distribution is created by upstream requirements or prompts, we need LatentForce.&lt;/p&gt;

&lt;p&gt;In Lean, the separation between accepted and rejected changes appears as a damping / control schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;DampingControlSchema&lt;/span&gt; (&lt;span class="n"&gt;State&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;) (&lt;span class="n"&gt;Sig&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;observation&lt;/span&gt; : &lt;span class="n"&gt;SignatureObservation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;
  &lt;span class="n"&gt;invariant&lt;/span&gt; : &lt;span class="n"&gt;SafeRegion&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;
  &lt;span class="n"&gt;accepted&lt;/span&gt; :
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ArchitectureTransition&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;rejected&lt;/span&gt; :
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ArchitectureTransition&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;acceptedPreservesInvariant&lt;/span&gt; :
    &lt;span class="o"&gt;∀&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; (&lt;span class="n"&gt;t&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureTransition&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;),
      &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StepPreservesSafeRegion&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;invariant&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this proves is limited: transitions classified as &lt;code&gt;accepted&lt;/code&gt; preserve the explicitly stated &lt;code&gt;invariant&lt;/code&gt;. The existence of rejected changes does not prove that the whole future of the codebase is safe.&lt;/p&gt;

&lt;p&gt;On top of this schema, it is proved that accepted finite evolutions create trajectories inside the selected invariant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;acceptedEvolution_preserves_selectedInvariant&lt;/span&gt;
    (&lt;span class="n"&gt;control&lt;/span&gt; : &lt;span class="n"&gt;DampingControlSchema&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;) :
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; (&lt;span class="n"&gt;plan&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureEvolution&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;) &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;StateInSafeRegion&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invariant&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AcceptedEvolution&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;SignatureTrajectoryInSafeRegion&lt;/span&gt;
          &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invariant&lt;/span&gt; (&lt;span class="n"&gt;SignatureTrajectory&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. A Chaos-Game-Like Correspondence
&lt;/h3&gt;

&lt;p&gt;This is where the chaos-game-like reading appears.&lt;/p&gt;

&lt;p&gt;The similarity is that we have multiple transformations, one of them is selected at each step, and a state trajectory is produced.&lt;/p&gt;

&lt;p&gt;The difference is that, in software development, neither the set of transformations nor their likelihood is fixed. Requirements, review, CI, design boundaries, existing examples, and AI agent behavior all affect which operation is likely to be selected next.&lt;/p&gt;

&lt;p&gt;So the goal is not to claim that software development literally is the classical chaos game. The goal is to use AAT vocabulary to handle the structure where multiple operations are repeatedly selected and the resulting trajectory tends to move toward certain regions.&lt;/p&gt;

&lt;p&gt;The correspondence is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chaos-game side&lt;/th&gt;
&lt;th&gt;AAT / development side&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State &lt;code&gt;X_t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Architecture state / codebase field.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transformation &lt;code&gt;f_i&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Architecture operation / PR / patch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transformation selection&lt;/td&gt;
&lt;td&gt;Operation selection by developer / AI / requirement / review.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trajectory&lt;/td&gt;
&lt;td&gt;Architecture Signature trajectory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attractor&lt;/td&gt;
&lt;td&gt;Signature region where the system tends to stay.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basin&lt;/td&gt;
&lt;td&gt;Initial or surrounding states likely to fall into that attractor.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control input&lt;/td&gt;
&lt;td&gt;Prompt, review policy, CI, type checker, architecture rule.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As a formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X_{t+1} = f_{i_t}(X_t)
i_t ~ P(. | X_t, control_t)
Y_t = sigma(X_t)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is not to assert probability or attractors as strong theorems too early.&lt;/p&gt;

&lt;p&gt;What we should handle in practice is first an attractor candidate or basin candidate relative to a finite observation window, bounded horizon, selected signature axes, and selected operation support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;finite observed trajectory
  + selected signature region
  + bounded operation support
  -&amp;gt; attractor / basin candidate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not an escape into weak claims. It is a boundary that makes measurement and falsification possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Support Shaping
&lt;/h3&gt;

&lt;p&gt;The mathematical core of Attractor Engineering is support shaping.&lt;/p&gt;

&lt;p&gt;This is not just about "blocking bad PRs". It is about changing the set of operations that can naturally be selected next, and changing how likely they are to be selected.&lt;/p&gt;

&lt;p&gt;In short, Attractor Engineering is a theory of designing the geometry of the operation distribution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;Supports&lt;/span&gt;
    (&lt;span class="n"&gt;kernel&lt;/span&gt; : &lt;span class="n"&gt;FiniteOperationKernel&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;)
    (&lt;span class="n"&gt;X&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;) (&lt;span class="n"&gt;op&lt;/span&gt; : &lt;span class="n"&gt;OperationId&lt;/span&gt;) : &lt;span class="kt"&gt;Prop&lt;/span&gt; :=
  &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="err"&gt;∈&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the current architecture state &lt;code&gt;X&lt;/code&gt;, the set of operations that can naturally be selected is called operation support.&lt;/p&gt;

&lt;p&gt;Good design changes this support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;SupportOperationsPreserveSafeRegion&lt;/span&gt;
    (&lt;span class="n"&gt;kernel&lt;/span&gt; : &lt;span class="n"&gt;FiniteOperationKernel&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;)
    (&lt;span class="n"&gt;sem&lt;/span&gt; : &lt;span class="n"&gt;OperationTransitionSemantics&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;)
    (&lt;span class="n"&gt;O&lt;/span&gt; : &lt;span class="n"&gt;SignatureObservation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;) (&lt;span class="n"&gt;R&lt;/span&gt; : &lt;span class="n"&gt;SafeRegion&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;) : &lt;span class="kt"&gt;Prop&lt;/span&gt; :=
  &lt;span class="o"&gt;∀&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; (&lt;span class="n"&gt;op&lt;/span&gt; : &lt;span class="n"&gt;OperationId&lt;/span&gt;),
    &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Supports&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationPreservesSafeRegion&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strong form of support shaping says: operations that remain in support preserve the selected safe region. In practical terms, we reduce bad operations, increase good operations, make correct paths easier to choose, and make dangerous shortcuts less convenient.&lt;/p&gt;

&lt;p&gt;This idea is packaged on the Attractor Engineering side as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="n"&gt;structure&lt;/span&gt; &lt;span class="n"&gt;AttractorEngineeringSupportPackage&lt;/span&gt;
    (&lt;span class="n"&gt;State&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;) (&lt;span class="n"&gt;Sig&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;) (&lt;span class="n"&gt;OperationId&lt;/span&gt; : &lt;span class="kt"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;) &lt;span class="n"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;observation&lt;/span&gt; : &lt;span class="n"&gt;SignatureObservation&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;
  &lt;span class="n"&gt;kernel&lt;/span&gt; : &lt;span class="n"&gt;FiniteOperationKernel&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;
  &lt;span class="n"&gt;semantics&lt;/span&gt; : &lt;span class="n"&gt;OperationTransitionSemantics&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;
  &lt;span class="n"&gt;targetRegion&lt;/span&gt; : &lt;span class="n"&gt;SafeRegion&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt;
  &lt;span class="n"&gt;supportPreserves&lt;/span&gt; :
    &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SupportOperationsPreserveSafeRegion&lt;/span&gt; &lt;span class="n"&gt;semantics&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;targetRegion&lt;/span&gt;
  &lt;span class="n"&gt;coverageAssumptions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;measurementBoundary&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
  &lt;span class="n"&gt;nonConclusions&lt;/span&gt; : &lt;span class="kt"&gt;Prop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this package, if a bounded script uses only operations from finite support, and the starting point is in the target region, then the observed trajectory remains in the target region. This is stated as a theorem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;supportPackage_preserves_targetTrajectory&lt;/span&gt;
    (&lt;span class="n"&gt;package&lt;/span&gt; : &lt;span class="n"&gt;AttractorEngineeringSupportPackage&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;Sig&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;)
    (&lt;span class="n"&gt;script&lt;/span&gt; : &lt;span class="n"&gt;BoundedOperationScript&lt;/span&gt; &lt;span class="n"&gt;OperationId&lt;/span&gt;)
    &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; (&lt;span class="n"&gt;plan&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureEvolution&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;)
    (&lt;span class="n"&gt;hStart&lt;/span&gt; :
      &lt;span class="n"&gt;StateInSafeRegion&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targetRegion&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;)
    (&lt;span class="n"&gt;hRealizes&lt;/span&gt; : &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RealizesEvolution&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;semantics&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;)
    (&lt;span class="n"&gt;hSupport&lt;/span&gt; :
      &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScriptUsesSupport&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;) :
    &lt;span class="n"&gt;SignatureTrajectoryInSafeRegion&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targetRegion&lt;/span&gt;
      (&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TargetTrajectory&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, good APIs, good examples, clear ownership, narrow modules, and domain states represented in types increase the local discoverability of good operations.&lt;/p&gt;

&lt;p&gt;Conversely, a huge common module, implicit global context, ambiguous services, and overly convenient helpers increase the local convenience of bad operations.&lt;/p&gt;

&lt;p&gt;From this viewpoint, refactoring is not only cleaning up the current structure. It is also a basin-reshaping operation that changes the future operation distribution.&lt;/p&gt;

&lt;p&gt;In the measurement layer, one tooling-side metric candidate to watch here is &lt;code&gt;SupportRiskMass&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SupportRiskMass(C) =
  sum over op in support(C) of weight(op) * risk(op, C)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here again, it is important not to reduce &lt;code&gt;risk&lt;/code&gt; to a simple 0 / 1.&lt;/p&gt;

&lt;p&gt;In AAT terms, we should distinguish at least:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;safe-preserving proved
safe-preserving measured
safe-preserving estimated
unsafe witness measured
unmeasured
unavailable
private
notComparable
outOfScope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unmeasured must not be read as zero. This is a central principle in both ArchSig and AAT.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The Same Signature Does Not Imply the Same Future
&lt;/h3&gt;

&lt;p&gt;An important point is that two states can have the same current Architecture Signature but different future operation distributions.&lt;/p&gt;

&lt;p&gt;For example, two modules might show the same number of dependency violations, the same test coverage, and the same complexity. But one may have a good canonical example nearby, while the other may contain many bad shortcuts.&lt;/p&gt;

&lt;p&gt;Even if the current observation values are the same, future PRs may be attracted in different directions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Obs(X) = Obs(Y)
  does not imply
OperationSupport(X) = OperationSupport(Y)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why architecture quality should not be judged by snapshot metrics alone. The current value can be the same while the future force field is different. Attractor Engineering treats this future force field as a design object.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Accepted Preservation and Support Preservation Are Different
&lt;/h3&gt;

&lt;p&gt;There is another important separation.&lt;/p&gt;

&lt;p&gt;Suppose review and CI ensure that merged PRs preserve a safe region.&lt;/p&gt;

&lt;p&gt;Even then, unsafe operations may still remain inside operation support.&lt;/p&gt;

&lt;p&gt;This separation is not just a warning. It appears on the Lean side as a finite counterexample. Accepted-step invariant preservation can hold, and an accepted safe step can exist, while operations that do not preserve the safe region remain in support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;acceptedPreservation_not_supportPreservation_counterexample&lt;/span&gt; :
    (&lt;span class="o"&gt;∃&lt;/span&gt; (&lt;span class="n"&gt;t&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureTransition&lt;/span&gt; &lt;span class="n"&gt;ExampleState&lt;/span&gt; &lt;span class="n"&gt;safeState&lt;/span&gt; &lt;span class="n"&gt;safeState&lt;/span&gt;),
      &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AcceptedStep&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
        &lt;span class="n"&gt;StepPreservesSafeRegion&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invariant&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;) &lt;span class="o"&gt;∧&lt;/span&gt;
    (&lt;span class="o"&gt;∀&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; : &lt;span class="n"&gt;ExampleState&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; (&lt;span class="n"&gt;t&lt;/span&gt; : &lt;span class="n"&gt;ArchitectureTransition&lt;/span&gt; &lt;span class="n"&gt;ExampleState&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;),
      &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AcceptedStep&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;StepPreservesSafeRegion&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invariant&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;) &lt;span class="o"&gt;∧&lt;/span&gt;
    (&lt;span class="o"&gt;∃&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;,
      &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Supports&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
        &lt;span class="o"&gt;¬&lt;/span&gt; &lt;span class="n"&gt;semantics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OperationPreservesSafeRegion&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt;
          &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invariant&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the difference between guardrails and attractor engineering.&lt;/p&gt;

&lt;p&gt;Strong guardrails may stop bad PRs.&lt;/p&gt;

&lt;p&gt;But a field where bad PRs are produced in large numbers every time is still not a good field.&lt;/p&gt;

&lt;p&gt;A good field is one where bad operations are less likely to appear in the first place, and good operations are natural, easy to imitate, observable, and low-friction.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. PRs Are Non-Commutative
&lt;/h3&gt;

&lt;p&gt;PRs are generally non-commutative.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR_2 o PR_1 != PR_1 o PR_2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, this only makes sense when both orders can be applied. Even then, the same two PRs can produce different final signatures depending on merge order.&lt;/p&gt;

&lt;p&gt;This matters in an era where AI agents can generate multiple PRs in parallel.&lt;/p&gt;

&lt;p&gt;Even when individual PRs are locally correct, their order can change boundaries, types, tests, and semantic alignment.&lt;/p&gt;

&lt;p&gt;I call this merge order sensitivity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MergeOrderSensitivity(a, b, X) =
  distance(
    sigma(b(a(X))),
    sigma(a(b(X)))
  )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not merely a merge conflict issue. It is the non-commutativity of operation algebra branching the signature trajectory. We will need this viewpoint when evaluating teams of AI agents as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Observe the Shape of the Trajectory
&lt;/h3&gt;

&lt;p&gt;Architecture Signature should be read not only as a current value, but also as a trajectory.&lt;/p&gt;

&lt;p&gt;Even if the endpoint is safe, the path may have passed through a bad region.&lt;/p&gt;

&lt;p&gt;Even if net delta is zero, there may have been large churn inside the path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;endpoint safe
  does not imply path safe

net force zero
  does not imply no excursion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also proved in Lean as a small finite counterexample. In a trajectory such as &lt;code&gt;0 -&amp;gt; 2 -&amp;gt; 0&lt;/code&gt;, both endpoints are the same safe state. The endpoint delta and net delta can both appear to be zero, while the path passed through an unsafe region.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lean"&gt;&lt;code&gt;&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;netSignatureDelta_eq_zero&lt;/span&gt; :
    &lt;span class="n"&gt;NetSignatureDelta&lt;/span&gt; (&lt;span class="n"&gt;SignatureDeltaSequence&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;signedNatDelta&lt;/span&gt;
      &lt;span class="n"&gt;excursionPlan&lt;/span&gt;) &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;theorem&lt;/span&gt; &lt;span class="n"&gt;endpointSafe_and_zeroDelta_but_not_pathSafe&lt;/span&gt; :
    &lt;span class="n"&gt;EndpointSignatureDelta&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;signedNatDelta&lt;/span&gt; &lt;span class="n"&gt;excursionPlan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
      &lt;span class="n"&gt;StateInSafeRegion&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;safeRegion&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
      &lt;span class="n"&gt;StateInSafeRegion&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;safeRegion&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;∧&lt;/span&gt;
      &lt;span class="o"&gt;¬&lt;/span&gt; &lt;span class="n"&gt;SignatureTrajectoryInSafeRegion&lt;/span&gt; &lt;span class="n"&gt;safeRegion&lt;/span&gt;
          (&lt;span class="n"&gt;SignatureTrajectory&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="n"&gt;excursionPlan&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trajectories have shapes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trajectory type&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stable Orbit&lt;/td&gt;
&lt;td&gt;The system returns to a safe region after small changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift&lt;/td&gt;
&lt;td&gt;The system slowly shifts toward a bad region.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spiral Debt&lt;/td&gt;
&lt;td&gt;It appears to return, but over the long run moves closer to a bad basin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sudden Phase Shift&lt;/td&gt;
&lt;td&gt;A signature changes sharply after a particular PR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oscillation&lt;/td&gt;
&lt;td&gt;Feature additions and refactorings alternate between good and bad.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basin Capture&lt;/td&gt;
&lt;td&gt;After some point, the system gets captured by a bad structure.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What ArchSig really wants to observe is this trajectory.&lt;/p&gt;

&lt;p&gt;Not just the evaluation of one PR, but the resulting motion produced by a group of PRs.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Expanding Observation Can Suddenly Reveal Badness
&lt;/h3&gt;

&lt;p&gt;With coarse observation, a codebase may look safe.&lt;/p&gt;

&lt;p&gt;But if we add more observation axes, a hidden obstruction may suddenly appear as nonzero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;coarse observation:
  safe

refined observation:
  hidden obstruction appears
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can call this an observability expansion shock.&lt;/p&gt;

&lt;p&gt;The important point is that this does not necessarily mean the architecture got worse. It may simply mean that an axis that used to be invisible has become visible.&lt;/p&gt;

&lt;p&gt;That is why ArchSig must distinguish &lt;code&gt;unmeasured&lt;/code&gt; from &lt;code&gt;zero&lt;/code&gt;. When something that was not measured becomes visible, we must separate "the architecture got worse" from "the observation became better".&lt;/p&gt;

&lt;h3&gt;
  
  
  About the Lean Formalization
&lt;/h3&gt;

&lt;p&gt;The structure above is not built only from metaphor. Some of the core vocabulary of AAT has been implemented as Lean definitions and theorems under &lt;code&gt;Formal/Arch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The repository is &lt;a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2" rel="noopener noreferrer"&gt;AlgebraicArchitectureTheoryV2&lt;/a&gt;. The proved API is summarized in the &lt;a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2/blob/main/docs/aat/lean_theorem_index.md" rel="noopener noreferrer"&gt;Lean definition and theorem index&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The vocabulary used in the second half of this article mainly corresponds to &lt;code&gt;Formal/Arch/Evolution/SignatureDynamics.lean&lt;/code&gt; and &lt;code&gt;Formal/Arch/Evolution/AttractorEngineering.lean&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The role of Lean formalization is not to give this theory an aura of correctness. Its role is to record, with boundaries, what can be said under which universe, observation, coverage, and exactness assumptions.&lt;/p&gt;

&lt;p&gt;It is important that counterexamples live in the same place as proved theorems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proved:
  accepted evolution preserves selected invariant
  bounded sampled support-preserving script stays in target region
  selected additive delta telescopes over finite path

proved counterexamples:
  endpoint safe + zero delta does not imply path safe
  accepted preservation does not imply support preservation
  unmeasured axis is not available-zero evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conversely, the fact that something is proved in Lean does not mean that a real-world code extractor is complete, or that every runtime / semantic obstruction has already been observed. With this boundary, AAT can separate what is formally known, what depends on measurement, and what remains an empirical research question.&lt;/p&gt;

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

&lt;p&gt;The discovery of Attractor Engineering changes how I see software architecture: from a static blueprint to a field that guides future changes.&lt;/p&gt;

&lt;p&gt;If software architecture is read as an algebraic structure, feature additions and refactorings become operations.&lt;/p&gt;

&lt;p&gt;When those operations are repeated, the architecture state draws a trajectory.&lt;/p&gt;

&lt;p&gt;We can then ask where that trajectory tends to go, and where it tends to stay. This is where attractors and basins enter the picture.&lt;/p&gt;

&lt;p&gt;Architecture design in the era of AI-assisted development can be described as creating a field where future changes gather in good places and can escape bad places.&lt;/p&gt;

&lt;p&gt;Harness engineering becomes the engineering of receiving AI's change force, dissipating unwanted components, and guiding the system toward good attractors.&lt;/p&gt;

&lt;p&gt;ArchSig is the tool for observing that trajectory.&lt;/p&gt;

&lt;p&gt;The essence of AI-assisted development is not only producing PRs faster.&lt;/p&gt;

&lt;p&gt;It is deciding where fast force should converge.&lt;/p&gt;

&lt;p&gt;A codebase is a field.&lt;/p&gt;

&lt;p&gt;A PR is a force.&lt;/p&gt;

&lt;p&gt;CI/CD is a dissipative system.&lt;/p&gt;

&lt;p&gt;Product managers, product owners, engineers, reviewers, and AI agents are participants in the field.&lt;/p&gt;

&lt;p&gt;ArchSig is an observer of the trajectory.&lt;/p&gt;

&lt;p&gt;With this framing, development in the AI era is no longer just automation. It becomes field design.&lt;/p&gt;

&lt;p&gt;As a design theory for that purpose, Attractor Engineering may be a useful direction for both practice and research.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>What is EBICS?</title>
      <dc:creator>Andr Sv</dc:creator>
      <pubDate>Sat, 09 May 2026 11:24:18 +0000</pubDate>
      <link>https://open.forem.com/andr_sv_03db554060c8042d4/what-is-ebics-5gd6</link>
      <guid>https://open.forem.com/andr_sv_03db554060c8042d4/what-is-ebics-5gd6</guid>
      <description>&lt;p&gt;If you’ve ever wondered how massive companies move millions of dollars across different banks without losing their minds (or their money), the answer is likely EBICS. Short for &lt;strong&gt;Electronic Banking Internet Communication Standard&lt;/strong&gt;, it is essentially a universal, highly secure language that businesses and banks use to talk to each other. &lt;br&gt;
What exactly is EBICS? Think of EBICS as a secure, high-speed bridge. It started in Germany and has since become the gold standard across Europe—especially in France, Austria and Switzerland for remote data transmission. Instead of every bank using its own clunky, proprietary system, EBICS provides a single, unified gateway. It relies on heavy-duty XML encryption and digital signatures to make sure every byte of data is authenticated and hasn't been messed with. EBICS simplifies the dialogue between businesses and financial institutions, turning complex multi-bank relationships into a streamlined digital workflow.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>cybersecurity</category>
      <category>learning</category>
      <category>networking</category>
    </item>
    <item>
      <title>Get Real Row Counts in GBase 8s Without UPDATE STATISTICS</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Sat, 09 May 2026 11:24:00 +0000</pubDate>
      <link>https://open.forem.com/michaelfv/get-real-row-counts-in-gbase-8s-without-update-statistics-34le</link>
      <guid>https://open.forem.com/michaelfv/get-real-row-counts-in-gbase-8s-without-update-statistics-34le</guid>
      <description>&lt;p&gt;The &lt;code&gt;nrows&lt;/code&gt; column in &lt;code&gt;systables&lt;/code&gt; is only updated when you run &lt;code&gt;UPDATE STATISTICS&lt;/code&gt;, so it often shows 0 even right after bulk inserts. If you need the real, live row count — especially for fragmented tables — you can query &lt;code&gt;sysmaster:sysptnhdr&lt;/code&gt; or use &lt;code&gt;oncheck&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non‑Fragmented Tables
&lt;/h2&gt;

&lt;p&gt;Join &lt;code&gt;systables.partnum&lt;/code&gt; with &lt;code&gt;sysptnhdr.partnum&lt;/code&gt; in the &lt;code&gt;sysmaster&lt;/code&gt; database. &lt;code&gt;sysptnhdr.nrows&lt;/code&gt; gives the actual row count and &lt;code&gt;npdata&lt;/code&gt; the number of used data pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tabname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partnum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nrows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nrows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npdata&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stores_demo&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;gbaseserver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;systables&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;sysmaster&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;gbaseserver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sysptnhdr&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partnum&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tabname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fragmented Tables
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;sysfragments&lt;/code&gt; to get the partition number (&lt;code&gt;partn&lt;/code&gt;) and join it with &lt;code&gt;sysptnhdr.partnum&lt;/code&gt; to see per‑fragment row counts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nrows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;npdata&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sysfragments&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;sysmaster&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;gbaseserver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sysptnhdr&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partnum&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tabname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"tab1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick Check with oncheck
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;oncheck -pt&lt;/code&gt; command shows row and page counts for a table or fragment immediately, without any statistics update.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oncheck &lt;span class="nt"&gt;-pt&lt;/span&gt; stores_demo:tab1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output includes sections like &lt;code&gt;Fragment ... Number of rows&lt;/code&gt; and &lt;code&gt;Number of pages&lt;/code&gt;. It’s a handy operational tool for verifying data volumes in a gbase database.&lt;/p&gt;

</description>
      <category>gbase</category>
      <category>database</category>
      <category>数据库</category>
    </item>
    <item>
      <title>Responsible Disclosure Case Study: Critical Authorization, Identity and Credential-Exposure Risks Affecting SIPEF-Related Platforms</title>
      <dc:creator>Anonymous Security Researcher</dc:creator>
      <pubDate>Sat, 09 May 2026 11:22:06 +0000</pubDate>
      <link>https://open.forem.com/trustboundarylab/responsible-disclosure-case-study-critical-authorization-identity-and-credential-exposure-risks-19ef</link>
      <guid>https://open.forem.com/trustboundarylab/responsible-disclosure-case-study-critical-authorization-identity-and-credential-exposure-risks-19ef</guid>
      <description>&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;p&gt;In 2026, I privately disclosed multiple high-severity security concerns affecting systems associated with SIPEF Group, a multinational agro-industrial company operating across Southeast Asia, Africa, and Europe.&lt;/p&gt;

&lt;p&gt;The findings included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a severe Broken Access Control condition affecting the GeoSIPEF sustainability and traceability platform;&lt;/li&gt;
&lt;li&gt;publicly indexed credential-exposure indicators associated with enterprise authentication environments;&lt;/li&gt;
&lt;li&gt;indicators potentially consistent with infostealer-related compromise scenarios affecting enterprise identities and sessions;&lt;/li&gt;
&lt;li&gt;and additional security concerns involving a digital vCard/contact-sharing application associated with the broader enterprise ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issues were disclosed privately under responsible disclosure principles. Following notification, the organization acknowledged receipt, initiated internal triage and containment activities, engaged external specialists, and temporarily disabled affected systems during investigation and remediation activities.&lt;/p&gt;

&lt;p&gt;This article intentionally omits exploit-ready details, credentials, sensitive infrastructure information, employee identities, and technical information that could facilitate misuse.&lt;/p&gt;

&lt;p&gt;The purpose of this writeup is to discuss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architectural security lessons;&lt;/li&gt;
&lt;li&gt;identity-centric compromise risks;&lt;/li&gt;
&lt;li&gt;secure authorization design;&lt;/li&gt;
&lt;li&gt;and governance challenges increasingly faced by modern enterprises.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;The investigation began through OSINT-based review of publicly visible exposure indicators and externally indexed authentication-related metadata associated with SIPEF-related systems.&lt;/p&gt;

&lt;p&gt;The observed indicators included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enterprise email addresses;&lt;/li&gt;
&lt;li&gt;authentication-related URLs;&lt;/li&gt;
&lt;li&gt;environment identifiers;&lt;/li&gt;
&lt;li&gt;and credential-exposure records indexed in external exposure-intelligence sources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of publicly visible environment references included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/GP/Account/LogOn;&lt;/li&gt;
&lt;li&gt;production and UAT environment naming patterns;&lt;/li&gt;
&lt;li&gt;Microsoft Online authentication contexts;&lt;/li&gt;
&lt;li&gt;and enterprise-related login references.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No credentials were purchased, unlocked, validated, or used.&lt;/p&gt;

&lt;p&gt;No unauthorized access attempts were performed at any stage.&lt;/p&gt;

&lt;p&gt;The findings were handled strictly within responsible disclosure boundaries.&lt;/p&gt;




&lt;h1&gt;
  
  
  Broken Access Control in GeoSIPEF
&lt;/h1&gt;

&lt;p&gt;One of the most severe findings involved the GeoSIPEF sustainability and traceability platform.&lt;/p&gt;

&lt;p&gt;GeoSIPEF was publicly positioned as a digital sustainability and supply-chain traceability initiative supporting ESG and EUDR-related operational visibility.&lt;/p&gt;

&lt;p&gt;During review, the application appeared to rely on client-side authorization state stored within browser-accessible storage mechanisms rather than enforcing authorization decisions entirely server-side.&lt;/p&gt;

&lt;p&gt;In practical terms, privilege-related state appeared to be trusted on the client side.&lt;/p&gt;

&lt;p&gt;This represents one of the most dangerous anti-patterns in modern web security.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Client-Side Authorization Is Dangerous
&lt;/h1&gt;

&lt;p&gt;Frontend applications must never be trusted as authorization boundaries.&lt;/p&gt;

&lt;p&gt;Anything stored client-side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;localStorage;&lt;/li&gt;
&lt;li&gt;sessionStorage;&lt;/li&gt;
&lt;li&gt;JavaScript variables;&lt;/li&gt;
&lt;li&gt;browser state;&lt;/li&gt;
&lt;li&gt;hidden fields;&lt;/li&gt;
&lt;li&gt;or client-generated role objects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can potentially be modified by authenticated users.&lt;/p&gt;

&lt;p&gt;Authorization decisions must always be enforced server-side.&lt;/p&gt;

&lt;p&gt;If authorization logic depends on tamperable client-side state, authenticated low-privileged users may potentially escalate privileges simply by modifying browser-side values.&lt;/p&gt;

&lt;p&gt;This category falls under:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OWASP A01:2021 — Broken Access Control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Broken Access Control consistently remains one of the highest-impact vulnerability classes because it directly affects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confidentiality;&lt;/li&gt;
&lt;li&gt;integrity;&lt;/li&gt;
&lt;li&gt;authorization boundaries;&lt;/li&gt;
&lt;li&gt;and trust relationships.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Architectural Lessons from GeoSIPEF
&lt;/h1&gt;

&lt;p&gt;The GeoSIPEF case demonstrates several broader architectural lessons relevant across the industry.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Frontends are presentation layers — not trust boundaries
&lt;/h2&gt;

&lt;p&gt;Modern SPAs and JavaScript-heavy applications often push excessive logic client-side.&lt;/p&gt;

&lt;p&gt;While this improves responsiveness and developer velocity, it creates serious risk if developers blur the distinction between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI state
and&lt;/li&gt;
&lt;li&gt;authorization state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser must always be treated as hostile territory.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Sustainability and ESG platforms are now high-value targets
&lt;/h2&gt;

&lt;p&gt;Modern ESG, traceability, and sustainability systems increasingly contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;supplier data;&lt;/li&gt;
&lt;li&gt;operational metrics;&lt;/li&gt;
&lt;li&gt;compliance evidence;&lt;/li&gt;
&lt;li&gt;land-use information;&lt;/li&gt;
&lt;li&gt;audit trails;&lt;/li&gt;
&lt;li&gt;and governance reporting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As organizations digitize sustainability workflows, these systems become increasingly sensitive operational platforms rather than merely “reporting tools.”&lt;/p&gt;

&lt;p&gt;Security maturity must evolve accordingly.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Governance failures are often architectural failures
&lt;/h2&gt;

&lt;p&gt;Many enterprise security incidents do not originate from advanced attackers.&lt;/p&gt;

&lt;p&gt;They originate from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;insecure architectural assumptions;&lt;/li&gt;
&lt;li&gt;weak trust-boundary modeling;&lt;/li&gt;
&lt;li&gt;rushed development;&lt;/li&gt;
&lt;li&gt;insufficient secure-design review;&lt;/li&gt;
&lt;li&gt;and lack of server-side authorization validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most dangerous vulnerabilities are often conceptually simple.&lt;/p&gt;




&lt;h1&gt;
  
  
  Credential Exposure Indicators
&lt;/h1&gt;

&lt;p&gt;Separate from the authorization issue, additional OSINT review identified publicly visible credential-exposure indicators associated with SIPEF-related identities and authentication environments.&lt;/p&gt;

&lt;p&gt;The indicators referenced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Online authentication contexts;&lt;/li&gt;
&lt;li&gt;enterprise email addresses;&lt;/li&gt;
&lt;li&gt;production and UAT naming patterns;&lt;/li&gt;
&lt;li&gt;and ERP-related login environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observed indicators suggested possible exposure involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enterprise identities;&lt;/li&gt;
&lt;li&gt;browser-stored credentials;&lt;/li&gt;
&lt;li&gt;or authentication artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no credentials were unlocked;&lt;/li&gt;
&lt;li&gt;no credentials were validated;&lt;/li&gt;
&lt;li&gt;and no login attempts were performed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The findings were based entirely on publicly visible metadata and exposure indicators.&lt;/p&gt;




&lt;h1&gt;
  
  
  Infostealer Malware and Modern Identity Risk
&lt;/h1&gt;

&lt;p&gt;Further analysis suggested that at least part of the observed exposure patterns may have been consistent with modern infostealer-related compromise scenarios.&lt;/p&gt;

&lt;p&gt;Infostealer malware has become one of the most significant threats facing enterprises today.&lt;/p&gt;

&lt;p&gt;Unlike traditional malware focused solely on destruction or ransomware deployment, infostealers specialize in quietly harvesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;saved browser credentials;&lt;/li&gt;
&lt;li&gt;cookies;&lt;/li&gt;
&lt;li&gt;refresh tokens;&lt;/li&gt;
&lt;li&gt;browser profiles;&lt;/li&gt;
&lt;li&gt;cryptocurrency wallets;&lt;/li&gt;
&lt;li&gt;VPN credentials;&lt;/li&gt;
&lt;li&gt;cloud sessions;&lt;/li&gt;
&lt;li&gt;and authentication artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resulting datasets are frequently aggregated and redistributed through underground ecosystems.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Password Resets Alone Are Sometimes Insufficient
&lt;/h1&gt;

&lt;p&gt;One important industry misconception is that identity compromise equals “password compromise.”&lt;/p&gt;

&lt;p&gt;Modern session-centric compromise changes that equation significantly.&lt;/p&gt;

&lt;p&gt;If session cookies, refresh tokens, or persistent browser sessions are compromised, attackers may potentially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bypass certain MFA workflows;&lt;/li&gt;
&lt;li&gt;inherit already-authenticated sessions;&lt;/li&gt;
&lt;li&gt;or maintain access even after password changes if sessions are not invalidated properly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means organizations increasingly need to treat incidents as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identity compromise events,
not merely&lt;/li&gt;
&lt;li&gt;password reset events.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Potential Enterprise Impact Areas
&lt;/h1&gt;

&lt;p&gt;Modern identity-centric compromise can potentially affect far more than email access.&lt;/p&gt;

&lt;p&gt;Depending on environment integration, risks may extend into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ERP systems;&lt;/li&gt;
&lt;li&gt;VPN environments;&lt;/li&gt;
&lt;li&gt;SaaS platforms;&lt;/li&gt;
&lt;li&gt;document-management systems;&lt;/li&gt;
&lt;li&gt;cloud consoles;&lt;/li&gt;
&lt;li&gt;HR systems;&lt;/li&gt;
&lt;li&gt;procurement systems;&lt;/li&gt;
&lt;li&gt;and financial workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizations should therefore consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;session invalidation;&lt;/li&gt;
&lt;li&gt;token revocation;&lt;/li&gt;
&lt;li&gt;OAuth consent review;&lt;/li&gt;
&lt;li&gt;endpoint forensics;&lt;/li&gt;
&lt;li&gt;privileged access review;&lt;/li&gt;
&lt;li&gt;and identity telemetry analysis.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Security Concerns in a Digital vCard / Contact-Sharing Application
&lt;/h1&gt;

&lt;p&gt;Separate review activities also identified security concerns affecting a digital vCard/contact-sharing application associated with the broader enterprise ecosystem.&lt;/p&gt;

&lt;p&gt;The issues observed were not limited to a single isolated vulnerability pattern, but rather reflected broader concerns around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trust-boundary enforcement;&lt;/li&gt;
&lt;li&gt;client-side assumptions;&lt;/li&gt;
&lt;li&gt;exposure of sensitive business-contact information;&lt;/li&gt;
&lt;li&gt;and insufficient defensive controls around authenticated application behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because digital business-card and contact-sharing platforms frequently integrate with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;corporate identity systems;&lt;/li&gt;
&lt;li&gt;email environments;&lt;/li&gt;
&lt;li&gt;CRM workflows;&lt;/li&gt;
&lt;li&gt;and mobile-device ecosystems,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;security weaknesses in such applications may create disproportionate downstream risk relative to their perceived operational importance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Digital Identity and Contact Platforms Matter
&lt;/h1&gt;

&lt;p&gt;Enterprise contact-sharing systems are often underestimated from a security perspective.&lt;/p&gt;

&lt;p&gt;In reality, they may expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;employee names;&lt;/li&gt;
&lt;li&gt;titles;&lt;/li&gt;
&lt;li&gt;reporting structures;&lt;/li&gt;
&lt;li&gt;phone numbers;&lt;/li&gt;
&lt;li&gt;email addresses;&lt;/li&gt;
&lt;li&gt;organizational relationships;&lt;/li&gt;
&lt;li&gt;and internal business metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This information can become highly valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;phishing campaigns;&lt;/li&gt;
&lt;li&gt;Business Email Compromise;&lt;/li&gt;
&lt;li&gt;social engineering;&lt;/li&gt;
&lt;li&gt;credential-targeting operations;&lt;/li&gt;
&lt;li&gt;and identity correlation activities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even seemingly “low-risk” applications can therefore materially increase enterprise attack surface.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Architectural Weaknesses in Enterprise Applications
&lt;/h1&gt;

&lt;p&gt;Several recurring anti-patterns commonly appear in internally developed or rapidly deployed enterprise web applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;excessive trust in client-side state;&lt;/li&gt;
&lt;li&gt;insufficient server-side authorization validation;&lt;/li&gt;
&lt;li&gt;predictable identifiers or object references;&lt;/li&gt;
&lt;li&gt;inadequate segregation between environments;&lt;/li&gt;
&lt;li&gt;weak session invalidation controls;&lt;/li&gt;
&lt;li&gt;overexposed API responses;&lt;/li&gt;
&lt;li&gt;and insufficient input or access validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These weaknesses often emerge when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;security review occurs too late in the SDLC;&lt;/li&gt;
&lt;li&gt;applications evolve organically without formal architecture review;&lt;/li&gt;
&lt;li&gt;or business functionality is prioritized ahead of trust-boundary modeling.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Identity Exposure and Enterprise Reconnaissance Risk
&lt;/h1&gt;

&lt;p&gt;Attackers increasingly combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;publicly exposed contact information;&lt;/li&gt;
&lt;li&gt;credential-exposure datasets;&lt;/li&gt;
&lt;li&gt;LinkedIn profiling;&lt;/li&gt;
&lt;li&gt;breached browser data;&lt;/li&gt;
&lt;li&gt;and cloud identity information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to construct highly accurate targeting maps of organizations.&lt;/p&gt;

&lt;p&gt;Applications that expose employee relationship structures, contact metadata, or organizational mappings may unintentionally assist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;phishing operators;&lt;/li&gt;
&lt;li&gt;infostealer operators;&lt;/li&gt;
&lt;li&gt;BEC actors;&lt;/li&gt;
&lt;li&gt;or credential-harvesting campaigns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes especially concerning when combined with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;weak MFA adoption;&lt;/li&gt;
&lt;li&gt;session-token theft;&lt;/li&gt;
&lt;li&gt;browser credential storage;&lt;/li&gt;
&lt;li&gt;or credential reuse across platforms.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Incident Response and Forensic Considerations
&lt;/h1&gt;

&lt;p&gt;One of the most important lessons from incidents involving possible infostealer activity is the need to preserve evidence early.&lt;/p&gt;

&lt;p&gt;Organizations sometimes rush immediately into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wiping endpoints;&lt;/li&gt;
&lt;li&gt;rebuilding machines;&lt;/li&gt;
&lt;li&gt;or mass password resets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While containment is important, preserving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;logs;&lt;/li&gt;
&lt;li&gt;endpoint telemetry;&lt;/li&gt;
&lt;li&gt;browser artifacts;&lt;/li&gt;
&lt;li&gt;token history;&lt;/li&gt;
&lt;li&gt;sign-in telemetry;&lt;/li&gt;
&lt;li&gt;and authentication trails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;is critical for understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;infection vectors;&lt;/li&gt;
&lt;li&gt;lateral movement;&lt;/li&gt;
&lt;li&gt;dwell time;&lt;/li&gt;
&lt;li&gt;and post-compromise activity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Responsible Disclosure Process
&lt;/h1&gt;

&lt;p&gt;The findings described in this article were disclosed privately and in good faith under responsible disclosure principles.&lt;/p&gt;

&lt;p&gt;The disclosure emphasized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;non-exploitation;&lt;/li&gt;
&lt;li&gt;minimal disclosure;&lt;/li&gt;
&lt;li&gt;avoidance of sensitive-data publication;&lt;/li&gt;
&lt;li&gt;and coordinated remediation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The organization acknowledged the report and initiated internal investigation and containment activities.&lt;/p&gt;

&lt;p&gt;No public disclosure was performed during the initial remediation period.&lt;/p&gt;




&lt;h1&gt;
  
  
  Broader Industry Lessons
&lt;/h1&gt;

&lt;p&gt;This case reflects broader trends increasingly affecting enterprises worldwide.&lt;/p&gt;

&lt;p&gt;Modern enterprise security challenges are shifting away from traditional perimeter-only threats and toward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identity compromise;&lt;/li&gt;
&lt;li&gt;token theft;&lt;/li&gt;
&lt;li&gt;cloud-session abuse;&lt;/li&gt;
&lt;li&gt;authorization failures;&lt;/li&gt;
&lt;li&gt;and trust-boundary weaknesses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizations must increasingly prioritize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secure-by-design architecture;&lt;/li&gt;
&lt;li&gt;server-side authorization enforcement;&lt;/li&gt;
&lt;li&gt;identity governance;&lt;/li&gt;
&lt;li&gt;secure SDLC practices;&lt;/li&gt;
&lt;li&gt;endpoint hygiene;&lt;/li&gt;
&lt;li&gt;and modern session-management controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most damaging failures are often not exotic zero-days.&lt;/p&gt;

&lt;p&gt;They are fundamental trust-model mistakes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Security is not merely a tooling problem.&lt;/p&gt;

&lt;p&gt;It is fundamentally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an architectural problem;&lt;/li&gt;
&lt;li&gt;a governance problem;&lt;/li&gt;
&lt;li&gt;and a trust-boundary problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As enterprises accelerate digital transformation initiatives around sustainability, compliance, ERP modernization, cloud identity integration, and business-platform consolidation, secure-design maturity becomes increasingly critical.&lt;/p&gt;

&lt;p&gt;Responsible disclosure remains one of the most important mechanisms available for improving security outcomes while minimizing harm.&lt;/p&gt;

&lt;p&gt;The goal of disclosure should never be humiliation.&lt;/p&gt;

&lt;p&gt;The goal should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remediation;&lt;/li&gt;
&lt;li&gt;accountability;&lt;/li&gt;
&lt;li&gt;architectural improvement;&lt;/li&gt;
&lt;li&gt;and stronger security maturity across the industry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reposted on Medium - &lt;a href="https://medium.com/p/af7f9c24585c" rel="noopener noreferrer"&gt;https://medium.com/p/af7f9c24585c&lt;/a&gt; and Substack - &lt;a href="https://trustboundarylab.substack.com/p/responsible-disclosure-case-study" rel="noopener noreferrer"&gt;https://trustboundarylab.substack.com/p/responsible-disclosure-case-study&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>devdiscuss</category>
      <category>learning</category>
    </item>
    <item>
      <title>A VIC x AiSAQ Implementation Brings AI to Your Files Without Breaking the Bank</title>
      <dc:creator>Ross Peili</dc:creator>
      <pubDate>Sat, 09 May 2026 11:18:32 +0000</pubDate>
      <link>https://open.forem.com/arpa/a-vic-x-aisaq-implementation-brings-ai-to-your-files-without-breaking-the-bank-1mic</link>
      <guid>https://open.forem.com/arpa/a-vic-x-aisaq-implementation-brings-ai-to-your-files-without-breaking-the-bank-1mic</guid>
      <description>&lt;p&gt;We’re generating more data than ever, and AI‑powered search is great—until your dataset gets huge and your RAM starts crying for mercy. Most vector search systems rely on expensive DRAM to keep indexes fast, but that approach doesn’t scale. &lt;a href="https://github.com/kioxia-jp/aisaq-diskann" rel="noopener noreferrer"&gt;KIOXIA’s &lt;strong&gt;AiSAQ&lt;/strong&gt;&lt;/a&gt; (All‑in‑Storage ANNS with Product Quantization) flips the script: it runs approximate nearest neighbor search directly on SSD, slashing DRAM usage by &lt;strong&gt;3,200×&lt;/strong&gt; in billion‑scale workloads. The &lt;a href="https://github.com/ARPAHLS/vic_aisaq_demo" rel="noopener noreferrer"&gt;&lt;code&gt;vic_aisaq_demo&lt;/code&gt;&lt;/a&gt; repo from &lt;strong&gt;ARPA Hellenic Logical Systems&lt;/strong&gt; puts this tech into a practical, local‑first retrieval pipeline that’s as auditable as it is efficient.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: &lt;code&gt;vic_aisaq_demo&lt;/code&gt; combines tiered metadata filtering with flash‑optimized vector search to keep memory low and answers relevant. It’s a live demo of storage‑aware AI for edge and controller‑style environments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Problem: DRAM Is the Bottleneck
&lt;/h2&gt;

&lt;p&gt;Graph‑based nearest neighbor search (like HNSW) is fast, but it keeps key index structures in DRAM. With billion‑scale datasets, memory costs explode. Even compressed representations can still require tens of gigabytes of RAM. &lt;a href="https://github.com/kioxia-jp/aisaq-diskann" rel="noopener noreferrer"&gt;KIOXIA’s AiSAQ technology&lt;/a&gt; changes that by moving those compressed vectors to flash storage, consuming as little as &lt;strong&gt;10 MB&lt;/strong&gt; of DRAM during search without sacrificing recall.&lt;/p&gt;

&lt;p&gt;But low DRAM is only half the story. You also need a retrieval strategy that doesn’t waste time parsing irrelevant files.&lt;/p&gt;

&lt;h2&gt;
  
  
  How &lt;code&gt;vic_aisaq_demo&lt;/code&gt; Works: Tiered Retrieval Meets Flash‑Native Search
&lt;/h2&gt;

&lt;p&gt;The demo builds on two open‑source building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/ARPAHLS/lc0_vic" rel="noopener noreferrer"&gt;&lt;code&gt;lc0_vic&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; – a tiered retrieval controller that plans and orchestrates search in layers (L0 → L1 → L2).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/kioxia-jp/aisaq-diskann" rel="noopener noreferrer"&gt;&lt;code&gt;aisaq-diskann&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; – a flash‑oriented ANN backend optimized for low‑DRAM environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The execution flow is refreshingly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Librarian / Plan&lt;/strong&gt; – Turn a natural‑language question into retrieval intent using a lightweight LLM (e.g., &lt;a href="https://ollama.com/library/qwen2.5" rel="noopener noreferrer"&gt;qwen2.5:0.5b&lt;/a&gt; via Ollama).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L0 Metadata Filter&lt;/strong&gt; – Narrow down candidate files by extension, size, time, or path hints. Cheap and fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L1 Vector Search&lt;/strong&gt; – Run native AiSAQ ANN search over embeddings to find semantically similar content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L2 Deep Read&lt;/strong&gt; – Parse only the top few files and extract evidence snippets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ranked Response&lt;/strong&gt; – Return paths, scores, and run metrics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tiered approach keeps deep parsing affordable at scale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Benchmark results&lt;/strong&gt; show latency remains stable as dataset size grows, while DRAM footprint stays near zero. The funnel chart below visualises how each tier slashes the candidate pool:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv08c072sk9m1a1rq3t7y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv08c072sk9m1a1rq3t7y.png" alt="Tier Funnel" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s how the pipeline shifts results from superficial matching to true semantic evidence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i386j3iivqd3c0ndfpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i386j3iivqd3c0ndfpt.png" alt="Match Type Comparison" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The repo is built to be &lt;strong&gt;reproducible and local‑first&lt;/strong&gt;. You’ll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WSL (Ubuntu)&lt;/strong&gt; for building AiSAQ binaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; running locally (or over the network) with two models:

&lt;ul&gt;
&lt;li&gt;Planner model: &lt;a href="https://ollama.com/library/qwen2.5" rel="noopener noreferrer"&gt;&lt;code&gt;qwen2.5:0.5b&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Embedding model: &lt;a href="https://ollama.com/library/embeddinggemma" rel="noopener noreferrer"&gt;&lt;code&gt;embeddinggemma&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Python 3.13 and the usual suspects (see &lt;code&gt;requirements.txt&lt;/code&gt;)&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Once you’ve built the AiSAQ index from a sample drive, a query like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/run_query.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Find the Q3 2025 contract that mentions penalty clauses"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--aisaq-root&lt;/span&gt; /home/&lt;span class="nv"&gt;$USER&lt;/span&gt;/aisaq-diskann
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…will return ranked files with evidence snippets, tier labels, and latency metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;vic_aisaq_demo&lt;/code&gt; isn’t just a toy. It demonstrates a realistic, storage‑aware retrieval pattern that could run on devices with tight memory budgets—think edge gateways, embedded controllers, or even future SSD firmware that embeds intelligence directly on the drive. The &lt;a href="https://github.com/rosspeili/computational_storage_landscape" rel="noopener noreferrer"&gt;Computational Storage Landscape report&lt;/a&gt; maps this evolution, and this repo is one of the first runnable examples that puts those ideas into practice.&lt;/p&gt;

&lt;p&gt;The two charts below summarise that systems trade‑off and scaling behaviour:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2stpp8klpocea6q8vzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2stpp8klpocea6q8vzd.png" alt="Latency vs Dataset Size" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43swbbmrzcyhduowwl8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43swbbmrzcyhduowwl8n.png" alt="DRAM Footprint by Method" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The takeaway? You don’t need a cluster of DRAM‑heavy servers to run effective semantic search. Sometimes the smartest storage is the one that knows what &lt;em&gt;not&lt;/em&gt; to load into memory.&lt;/p&gt;

&lt;p&gt;Check out the full repo: &lt;strong&gt;&lt;a href="https://github.com/ARPAHLS/vic_aisaq_demo" rel="noopener noreferrer"&gt;ARPAHLS/vic_aisaq_demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>computationalstorage</category>
      <category>vectorsearch</category>
      <category>edgeai</category>
      <category>lowmemoryretrieval</category>
    </item>
    <item>
      <title>Image to PDF in the Browser - No Libraries, No Backend</title>
      <dc:creator>TechMind</dc:creator>
      <pubDate>Sat, 09 May 2026 11:13:20 +0000</pubDate>
      <link>https://open.forem.com/techmind-click/image-to-pdf-in-the-browser-no-libraries-no-backend-2g9h</link>
      <guid>https://open.forem.com/techmind-click/image-to-pdf-in-the-browser-no-libraries-no-backend-2g9h</guid>
      <description>&lt;p&gt;As Steve Jobs said, "&lt;strong&gt;&lt;em&gt;Design is not just what it looks like and feels like. Design is how it works.&lt;/em&gt;&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;Converting an image to PDF should work like this: open tool → upload image → get PDF. That is it. No backend calls, no third-party APIs, no file size limits from a server.&lt;/p&gt;

&lt;p&gt;Here is how it actually works in the browser — and why &lt;a href="https://www.techmind.click/blogs/how-to-convert-image-to-pdf-free-no-app-needed" rel="noopener noreferrer"&gt;TechMind.click&lt;/a&gt; built it this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Approach - Canvas + jsPDF
&lt;/h2&gt;

&lt;p&gt;The cleanest client-side image-to-PDF conversion uses the HTML5 Canvas API and jsPDF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jsPDF&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jspdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;imageToPDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// A4 dimensions in mm&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;jsPDF&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;orientation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;landscape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;portrait&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWidth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHeight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Calculate scaled dimensions preserving aspect ratio&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;pageWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;pageHeight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imgWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imgHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Center on page&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageWidth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;imgWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageHeight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;imgHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JPEG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;imgWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imgHeight&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Multiple Images
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;multipleImagesToPDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;jsPDF&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWidth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHeight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;imageFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fileToDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageFiles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getImageDimensions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;pageWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;pageHeight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;dataUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JPEG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageWidth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageHeight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileToDataURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;res&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getImageDimensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;res&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Client-Side Matters
&lt;/h3&gt;

&lt;p&gt;As Alan Turing would appreciate — the most elegant solution solves the problem with the least complexity. Client-side conversion means:&lt;/p&gt;

&lt;p&gt;Zero server costs — no file storage, no bandwidth for uploads&lt;br&gt;
Privacy — user files never leave their device&lt;br&gt;
Speed — no round-trip to a server&lt;br&gt;
Offline capable — works without internet after initial page load&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Manual Fix
&lt;/h3&gt;

&lt;p&gt;For non-developers or one-off conversions, &lt;a href="https://www.techmind.click/" rel="noopener noreferrer"&gt;TechMind.click&lt;/a&gt; has this built in — upload image, download PDF, done. Uses the same browser-based approach — no server, no storage.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What is your preferred client-side PDF library? jsPDF vs pdf-lib — drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>containers</category>
    </item>
    <item>
      <title>Desconstruindo o Streaming do Naver: Como Construir um Downloader de Alta Performance com HLS e WebAssembly</title>
      <dc:creator>yqqwe</dc:creator>
      <pubDate>Sat, 09 May 2026 11:12:21 +0000</pubDate>
      <link>https://open.forem.com/yqqwe/desconstruindo-o-streaming-do-naver-como-construir-um-downloader-de-alta-performance-com-hls-e-4nh1</link>
      <guid>https://open.forem.com/yqqwe/desconstruindo-o-streaming-do-naver-como-construir-um-downloader-de-alta-performance-com-hls-e-4nh1</guid>
      <description>&lt;p&gt;Para o usuário comum, baixar um vídeo parece ser apenas uma questão de encontrar um link .mp4. No entanto, para desenvolvedores que lidam com plataformas gigantes como o Naver (incluindo Naver TV, Sports e arquivos do V LIVE), a realidade é uma infraestrutura fragmentada, protegida e altamente otimizada via Adaptive Bitrate Streaming (ABS).&lt;br&gt;
Ao desenvolver o Naver Video Downloader, enfrentei desafios técnicos que foram muito além do simples web scraping. Neste artigo, vou detalhar a arquitetura de entrega de vídeo do Naver e as soluções de engenharia que implementamos para alcançar uma extração sem perdas e de alta velocidade.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. O Desafio Central: O Vídeo "Invisível"
&lt;/h2&gt;

&lt;p&gt;O Naver não serve arquivos de vídeo estáticos. Eles utilizam o protocolo HLS (HTTP Live Streaming).&lt;br&gt;
1.1 O Fluxo Fragmentado&lt;br&gt;
Quando você reproduz um vídeo no Naver, seu navegador não está baixando um único arquivo; ele está baixando centenas de pequenos segmentos .ts (Transport Stream).&lt;br&gt;
• Master Playlist (.m3u8): Um arquivo de manifesto que lista todas as resoluções disponíveis (1080p, 720p, etc.).&lt;br&gt;
• Media Playlist: Um sub-manifesto para uma resolução específica contendo as URLs dos segmentos individuais de 2 a 5 segundos.&lt;br&gt;
1.2 A Barreira de Autenticação: VodSeed e Tokens Dinâmicos&lt;br&gt;
A API interna do Naver (vod_play_info) é o "cérebro" do player. Para obter o link .m3u8, você precisa de um vid (Video ID) e um inkey (Session Key). Essas chaves são frequentemente geradas via JavaScript ofuscado e possuem um TTL (Time To Live) muito curto. Tentar acessar uma URL de segmento sem a assinatura correta resulta em um erro 403 Forbidden.&lt;br&gt;
 &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://twittervideodownloaderx.com/naver_downloader_po" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;twittervideodownloaderx.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  2. Engenharia do Mecanismo de Extração
&lt;/h2&gt;

&lt;p&gt;Para automatizar isso, nosso motor deve emular um "handshake" entre o player oficial do Naver e seu backend.&lt;br&gt;
2.1 Interceptação de Metadados&lt;br&gt;
Implementamos uma lógica de análise headless que:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Escaneia a página de destino em busca do vid — geralmente oculto em um objeto JSON &lt;strong&gt;PRELOADED_STATE&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Simula a chamada de API para os servidores VOD do Naver usando um conjunto rotativo de cabeçalhos que imitam impressões digitais de navegadores reais.&lt;/li&gt;
&lt;li&gt; Analisa o XML/JSON retornado para encontrar a fonte M3U8 de maior bitrate (geralmente 1080p).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Superando o CORS: Arquitetura de Proxy Transparente
&lt;/h2&gt;

&lt;p&gt;Os navegadores aplicam a Política de Mesma Origem (SOP). Um script em seu-site.com não pode buscar dados binários diretamente dos domínios do naver.com devido às restrições de CORS (Cross-Origin Resource Sharing).&lt;br&gt;
3.1 Proxy de Streaming de Alta Taxa de Transferência&lt;br&gt;
Para resolver isso, construímos um Proxy de Streaming Transparente usando Node.js.&lt;br&gt;
• O Fluxo: O cliente solicita um segmento através do nosso proxy. Nosso servidor o busca no CDN do Naver, remove os cabeçalhos CORS restritivos e injeta Access-Control-Allow-Origin: *.&lt;br&gt;
• Zero-Latency Piping: Em vez de baixar o segmento inteiro para o nosso servidor primeiro, usamos Stream Piping. Os dados são enviados ao usuário conforme chegam, o que significa que nosso servidor atua como um "tubo passivo", mantendo o uso de RAM constante, independentemente do tamanho do vídeo.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Muxing no Lado do Cliente com FFmpeg.wasm
&lt;/h2&gt;

&lt;p&gt;É aqui que a mágica acontece. Unir 500 arquivos .ts individuais em um servidor é intensivo em CPU e caro. Em vez disso, descarregamos o trabalho para o computador do usuário via WebAssembly (WASM).&lt;br&gt;
4.1 Remuxing vs. Transcoding&lt;br&gt;
Os segmentos de vídeo no fluxo HLS do Naver já estão codificados em H.264. Codificá-los novamente perderia qualidade e levaria muito tempo. Usando o FFmpeg.wasm, realizamos o Remuxing:&lt;br&gt;
• Utilizamos a flag -c copy no FFmpeg.&lt;br&gt;
• Isso instrui o motor a apenas mudar o "container" de TS para MP4 sem tocar nos pacotes de vídeo subjacentes.&lt;br&gt;
• Resultado: Qualidade 1080p nativa, processada em segundos diretamente na memória RAM do navegador do usuário.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdo7f7eoza7ixstp6olw4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdo7f7eoza7ixstp6olw4.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Otimizações de Performance
&lt;/h2&gt;

&lt;p&gt;5.1 Controle de Concorrência Assíncrona&lt;br&gt;
Baixar 500 segmentos um por um é lento. Baixar todos de uma vez aciona o limite de taxa (rate-limiting) do CDN. Implementamos um Async Promise Pool para manter exatamente 5 a 10 downloads simultâneos, maximizando a largura de banda sem ser bloqueado.&lt;br&gt;
JavaScript&lt;br&gt;
// Lógica conceitual para download paralelo&lt;br&gt;
async function downloadWithPool(urls, limit) {&lt;br&gt;
  const pool = new Set();&lt;br&gt;
  for (const url of urls) {&lt;br&gt;
    if (pool.size &amp;gt;= limit) await Promise.race(pool);&lt;br&gt;
    const promise = fetchSegment(url).then(() =&amp;gt; pool.delete(promise));&lt;br&gt;
    pool.add(promise);&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
5.2 Alinhamento de Dados Sequenciais&lt;br&gt;
Os segmentos HLS devem ser unidos na ordem exata especificada no arquivo .m3u8. Mesmo um único segmento ausente pode dessincronizar o tempo de áudio e vídeo. Nosso motor implementa uma Camada de Validação de Sequência que tenta baixar novamente chunks que falharam e garante que o buffer binário esteja perfeitamente alinhado antes da fase final de muxing.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Conclusão: Engenharia para Privacidade e Velocidade
&lt;/h2&gt;

&lt;p&gt;Construir um downloader para uma plataforma tão complexa quanto o Naver é uma aula de arquitetura web moderna. Ao combinar proxies Node.js, análise de HLS e WebAssembly, criamos uma ferramenta que é rápida, eficiente e focada na privacidade.&lt;br&gt;
Se você está procurando uma maneira confiável de salvar conteúdo do Naver na qualidade original 1080p, experimente nossa ferramenta: 👉 &lt;a href="https://twittervideodownloaderx.com/naver_downloader_po" rel="noopener noreferrer"&gt;Naver Video Downloader&lt;/a&gt;&lt;br&gt;
Destaques Técnicos:&lt;br&gt;
• Qualidade Nativa: Sem compressão adicional; cópia 1:1 do stream original.&lt;br&gt;
• Potencializado por WASM: Todo o processamento ocorre no lado do cliente para máxima privacidade.&lt;br&gt;
• Sem Instalação: Funciona inteiramente no navegador usando padrões web modernos.&lt;br&gt;
Dúvidas sobre análise de HLS ou WebAssembly? Vamos discutir nos comentários abaixo!&lt;/p&gt;

&lt;p&gt;Tags: #JavaScript #WebDev #NodeJS #WebAssembly #FFmpeg #Naver #Streaming #Architecture&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>naver</category>
      <category>downloader</category>
    </item>
    <item>
      <title>📢Angular’s 7‑Layer Component Architecture 🗂️</title>
      <dc:creator>abdelaaziz ouakala</dc:creator>
      <pubDate>Sat, 09 May 2026 11:11:12 +0000</pubDate>
      <link>https://open.forem.com/abdelaaziz_ouakala/angulars-7-layer-component-architecture-125l</link>
      <guid>https://open.forem.com/abdelaaziz_ouakala/angulars-7-layer-component-architecture-125l</guid>
      <description>&lt;p&gt;📢 𝐌𝐨𝐬𝐭 𝐀𝐧𝐠𝐮𝐥𝐚𝐫 𝐟𝐨𝐥𝐝𝐞𝐫𝐬 🗂️  𝐥𝐨𝐨𝐤 𝐥𝐢𝐤𝐞 𝐚 𝐣𝐮𝐧𝐤 𝐝𝐫𝐚𝐰𝐞𝐫 🤨 . 𝐇𝐞𝐫𝐞 𝐢𝐬 𝐭𝐡𝐞 𝐚𝐫𝐜𝐡𝐢𝐭𝐞𝐜𝐭𝐮𝐫𝐞 𝐈 𝐮𝐬𝐞 𝐟𝐨𝐫 𝟏𝟎𝟎+ 𝐜𝐨𝐦𝐩𝐨𝐧𝐞𝐧𝐭 𝐚𝐩𝐩𝐬.&lt;/p&gt;

&lt;p&gt;This is what happens when every component, directive, and service lives in one chaotic folder. It looks harmless at first — until onboarding slows, bugs multiply, and scalability collapses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6o9iz8g2qfksjj77c81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6o9iz8g2qfksjj77c81.png" alt="JUNK DRAWER DETECTED: SCALABILITY AT RISK!" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🚨 The Hook&lt;/strong&gt;&lt;br&gt;
Most Angular apps don’t fail because of performance — they fail because of folder chaos.&lt;br&gt;
When your src/app looks like a junk drawer, you’re not just messy… you’re building a maintenance nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧩 The Problem&lt;/strong&gt;&lt;br&gt;
Flat folders = high cognitive load.&lt;br&gt;
When a new engineer joins and sees 50+ components in one directory, they aren’t onboarding — they’re firefighting.&lt;/p&gt;

&lt;p&gt;The infamous shared/components folder becomes a dumping ground. Over time, this erodes scalability, testability, and maintainability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🏗️ The 7‑Layer UI System&lt;/strong&gt;&lt;br&gt;
To solve this, I move teams from “Flat Chaos” → 7‑Layer UI System.&lt;br&gt;
This isn’t about aesthetics; it’s about enforcing the Single Responsibility Principle (SRP) at the architectural level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Hierarchy&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elements → Atomic UI (Buttons, Inputs). Pure, dumb, zero logic.&lt;/li&gt;
&lt;li&gt;Blocks → UI Compositions (Cards, Hero sections).&lt;/li&gt;
&lt;li&gt;Features → Smart layer (Signals, APIs, service injection).&lt;/li&gt;
&lt;li&gt;Dialogs → Wizards, Modals, isolated flows.&lt;/li&gt;
&lt;li&gt;Layouts → Dashboards, Nav shells.&lt;/li&gt;
&lt;li&gt;Views → Route orchestration. Where features meet the router.&lt;/li&gt;
&lt;li&gt;Domains → Business boundaries (Billing, Auth, Analytics).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;📂 Folder Structure Example&lt;/strong&gt;&lt;br&gt;
Here’s how it looks in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/app/
└── modules/
    └── billing/                &amp;lt;-- [L7: Domain]
        ├── layouts/            &amp;lt;-- [L5: Layout]
        ├── views/              &amp;lt;-- [L6: View]
        │   └── invoice-list/
        ├── features/           &amp;lt;-- [L3: Feature]
        │   └── payment-form/
        ├── dialogs/            &amp;lt;-- [L4: Dialog]
        ├── blocks/             &amp;lt;-- [L2: Block]
        │   └── invoice-card/
        └── elements/           &amp;lt;-- [L1: Element]
            └── status-badge/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure scales naturally to 100+ components without collapsing under cognitive load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Order Restored: The 7‑Layer Architecture”&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Each folder now has a clear purpose — from atomic Elements to business Domains. The hierarchy enforces boundaries, reduces cognitive load, and makes scaling feel effortless.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhb8hgmed6rakbv9d2nh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhb8hgmed6rakbv9d2nh4.png" alt=" the clean, layered Angular folder structure that contrasts perfectly" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📊 Architecture Diagram Ideas&lt;br&gt;
Two visuals make this system click:&lt;/p&gt;

&lt;p&gt;Pyramid of Responsibility → Elements at the wide base (many, simple), Domains at the peak (few, complex).&lt;/p&gt;

&lt;p&gt;Dependency Flow → Arrows only downward. Blocks can use Elements, but Elements never depend on Features.&lt;/p&gt;

&lt;p&gt;These diagrams reinforce the mental model: granular → functional → contextual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Key Benefits&lt;/strong&gt;&lt;br&gt;
Breaking it down:&lt;/p&gt;

&lt;p&gt;Scalability → Independent vertical features (e.g., feat-auth, feat-users) allow teams to work in parallel.&lt;/p&gt;

&lt;p&gt;Testability → Dumb components and pure services are easy to unit test.&lt;/p&gt;

&lt;p&gt;Maintainability → Clear boundaries ensure changes in one layer don’t ripple into others.&lt;/p&gt;

&lt;p&gt;This aligns with Clean Architecture principles while staying pragmatic for frontend teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚡ Contrarian Engineering Opinion&lt;/strong&gt;&lt;br&gt;
“The shared/ folder is where good code goes to die.”&lt;/p&gt;

&lt;p&gt;If you can’t categorize a component into a specific domain or layer, you don’t understand its purpose yet.&lt;br&gt;
Shared folders encourage laziness and blur boundaries — the exact opposite of scalable architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📚 Case Study&lt;/strong&gt;&lt;br&gt;
On one enterprise project, the team started with a flat components/ folder. Within months:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Onboarding slowed to a crawl.&lt;/li&gt;
&lt;li&gt;Testing became inconsistent.&lt;/li&gt;
&lt;li&gt;Refactors broke unrelated features.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After migrating to the 7‑Layer system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;New engineers onboarded in days, not weeks.&lt;/li&gt;
&lt;li&gt;Unit tests mapped cleanly to Elements/Blocks.&lt;/li&gt;
&lt;li&gt;Features scaled independently with Nx libraries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difference wasn’t performance — it was cognitive clarity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Conclusion&lt;/strong&gt;&lt;br&gt;
Performance isn’t what kills Angular projects. Cognitive load does.&lt;br&gt;&lt;br&gt;
By enforcing the 7‑Layer UI System, you prevent “God Components,” reduce chaos, and scale cleanly past 100+ components.&lt;/p&gt;

&lt;p&gt;How do YOU structure Angular apps at scale?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nx libraries&lt;/li&gt;
&lt;li&gt;Domain‑driven folders&lt;/li&gt;
&lt;li&gt;Or the “hope for the best” flat structure?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop your approach — and your battle scars — in the comments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🌐 Connect With Me&lt;/strong&gt;&lt;br&gt;
If you enjoyed this deep dive into Angular architecture and want more insights on scalable frontend systems, follow my work across platforms:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://www.linkedin.com/in/abdelaaziz-ouakala/" rel="noopener noreferrer"&gt;LinkedIn &lt;/a&gt;— Professional discussions, architecture breakdowns, and engineering insights.&lt;br&gt;
📸 &lt;a href="https://www.instagram.com/ouakala_abdelaaziz/" rel="noopener noreferrer"&gt;Instagram &lt;/a&gt;— Visuals, carousels, and design‑driven posts under the Terminal Elite aesthetic.&lt;br&gt;
🧠 &lt;a href="https://ouakala-abdelaaziz.epizy.com/" rel="noopener noreferrer"&gt;Website &lt;/a&gt;— Articles, tutorials, and project showcases.&lt;br&gt;
🎥 &lt;a href="https://www.youtube.com/@ProgrammingMasteryAcademy" rel="noopener noreferrer"&gt;YouTube &lt;/a&gt;— Deep‑dive videos and live coding sessions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Angular #SoftwareArchitecture #WebDevelopment #Nx #Frontend #CleanCode #ProgrammingTips
&lt;/h1&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
      <category>nx</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Prepare a Legacy Codebase for AI-Assisted Refactoring</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Sat, 09 May 2026 11:09:29 +0000</pubDate>
      <link>https://open.forem.com/137foundry/how-to-prepare-a-legacy-codebase-for-ai-assisted-refactoring-18k5</link>
      <guid>https://open.forem.com/137foundry/how-to-prepare-a-legacy-codebase-for-ai-assisted-refactoring-18k5</guid>
      <description>&lt;p&gt;Jumping into a legacy codebase with an AI coding assistant and no preparation produces predictably mixed results. The AI generates plausible-looking refactors that miss critical business logic embedded in unexpected places. You spend more time verifying output than the AI saved you in generation time. And the refactored code, while cleaner-looking, may have subtle behavioral changes that surface in production six weeks later.&lt;/p&gt;

&lt;p&gt;The difference between this outcome and a productive AI-assisted modernization session is preparation. Specifically: giving the AI the context it needs to reason correctly about your specific codebase rather than reasoning from generic patterns.&lt;/p&gt;

&lt;p&gt;This guide covers the preparation steps that make AI-assisted legacy refactoring significantly safer and more productive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Establish Scope and Document It
&lt;/h2&gt;

&lt;p&gt;Before any AI interaction, define the boundary of what you are working on. Legacy codebases have a way of expanding scope because everything touches everything. Resist this.&lt;/p&gt;

&lt;p&gt;Choose a specific module, class, or set of related functions as your working scope. Write a plain-language description of what that scope is responsible for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scope: the discount calculation module (discount.py, approximately 400 lines)
This module is responsible for: calculating the final price a customer pays
after applying applicable discounts, promotions, and loyalty tier benefits.

It is NOT responsible for: fetching customer tier data (done by customer_service.py),
validating promo codes (done by promo_validator.py), or applying tax (done post-discount
by tax_calculator.py).

The most important business constraint: discounts do not stack additively.
A customer with a 20% loyalty discount and a 15% promo code gets 20% off, 
not 35% off. This is intentional and must be preserved in any refactoring.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This description becomes the context header you paste before every AI prompt related to this module. It costs you twenty minutes to write; it saves you from explaining the same context to the AI repeatedly and catching errors that stem from the AI not knowing the "discounts don't stack" rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Audit Dependencies Before Touching Anything
&lt;/h2&gt;

&lt;p&gt;AI coding assistants will generate refactored code that changes function signatures, return types, or module interfaces without knowing what depends on them. Before you start refactoring, you need a dependency map.&lt;/p&gt;

&lt;p&gt;For Python codebases, tools like &lt;a href="https://python.org" rel="noopener noreferrer"&gt;Python's built-in ast module&lt;/a&gt; and import analysis scripts can generate call graphs. For JavaScript, &lt;a href="https://eslint.org" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; and module analysis tools serve a similar purpose. &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; advanced search can help you find all internal references to a specific function across a large repository.&lt;/p&gt;

&lt;p&gt;The AI can help with this phase, but its output should be treated as a starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Identify all the places this function is called in the following files.
For each call site, note:
1. The file and line number
2. How the return value is used (stored, compared, iterated over, etc.)
3. Whether the caller passes keyword arguments or positional arguments

[target function] [relevant surrounding files]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the AI's output carefully. Dynamic call patterns (calling functions stored in dictionaries, factory patterns, monkey-patching) will not appear in AI dependency analysis. These need manual identification.&lt;/p&gt;

&lt;p&gt;The dependency map serves a critical purpose: before you change a function signature or return type, you know what you need to update. Without it, you are refactoring blind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Create a Test Baseline
&lt;/h2&gt;

&lt;p&gt;Legacy code with no tests is the most dangerous to refactor because you have no automated way to verify that behavior is preserved. Before any refactoring, use AI to generate an initial test suite for the module you are working on.&lt;/p&gt;

&lt;p&gt;This is one of the highest-value uses of AI assistance in legacy modernization. Even imperfect AI-generated tests are faster to produce than writing them from scratch, and they provide a safety net that makes subsequent refactoring significantly lower-risk.&lt;/p&gt;

&lt;p&gt;Important: AI-generated tests tend to cover the happy path and obvious error cases well, and miss edge cases that emerged from production incidents. After getting the AI-generated test suite, review your issue tracker, &lt;a href="https://git-scm.com" rel="noopener noreferrer"&gt;Git&lt;/a&gt; blame history, and incident reports for the module. Add tests for any bugs that were fixed in the module's history - those are the edge cases most likely to be reintroduced by refactoring.&lt;/p&gt;

&lt;p&gt;Once your test baseline is in place, configure your CI pipeline to run these tests on every commit. This gives you immediate feedback when a refactoring breaks behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Identify and Document the Critical Paths
&lt;/h2&gt;

&lt;p&gt;Not all code in a legacy system is equally risky to modify. The critical paths are the execution flows that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle money or anything irreversible (payments, emails sent, database deletes)&lt;/li&gt;
&lt;li&gt;Run under high load or in performance-sensitive paths&lt;/li&gt;
&lt;li&gt;Have known security relevance (authentication, authorization, input validation)&lt;/li&gt;
&lt;li&gt;Have produced incidents or bugs in the past&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the paths where AI-generated refactors need the most careful human review. Document them explicitly before starting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Critical paths in discount.py:
1. Lines 145-190: Final discount application to cart total - this writes to the order record
2. Lines 210-230: Promo code validation bypass for internal employee accounts - security-relevant
3. Lines 280-310: Bulk discount calculation - runs for every item in large orders, performance-sensitive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When AI-generated refactors touch lines in this list, they get extra review. When they do not, you can move faster. This simple classification reduces the time you spend being careful about everything and focuses attention where it matters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdas4v3zf1lc0tfiudxie.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdas4v3zf1lc0tfiudxie.jpeg" alt="A chalkboard with handwritten formulas and diagrams being worked through" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Bernice Chan on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Set Up a Safe Experimentation Environment
&lt;/h2&gt;

&lt;p&gt;Before merging any AI-assisted refactoring, you need a way to run the original and refactored code side-by-side and compare behavior. The ideal setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A feature branch where AI-assisted changes are isolated&lt;/li&gt;
&lt;li&gt;Your test baseline running against both the original and the refactored code&lt;/li&gt;
&lt;li&gt;If the module has external side effects (database writes, external API calls), a way to stub those out for comparison testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.martinfowler.com" rel="noopener noreferrer"&gt;Martin Fowler's&lt;/a&gt; branch-by-abstraction pattern is useful for large-scale refactoring: introduce a seam that lets you run old and new implementations in parallel and compare results before fully switching.&lt;/p&gt;

&lt;p&gt;For simpler modules, a straightforward A/B test in a staging environment - routing a portion of traffic to the refactored implementation - gives you confidence before full deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;The preparation sequence - scope definition, dependency audit, test baseline, critical path identification, safe environment setup - takes time. On a module of moderate complexity, expect to spend a day on preparation before writing a line of refactored code.&lt;/p&gt;

&lt;p&gt;That investment pays back quickly. With context documents, a test baseline, and a dependency map in hand, each AI-assisted refactoring session produces output that is faster to review, safer to merge, and less likely to produce production incidents.&lt;/p&gt;

&lt;p&gt;For the full framework on running these sessions - including prompting patterns for the refactoring phase itself - the guide on &lt;a href="https://137foundry.com/articles/ai-coding-assistants-legacy-code-modernization" rel="noopener noreferrer"&gt;using AI coding assistants for legacy code modernization&lt;/a&gt; covers the end-to-end process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt; works with engineering teams on legacy modernization assessments and implementation. The &lt;a href="https://137foundry.com/services/ai-automation" rel="noopener noreferrer"&gt;137Foundry AI automation services&lt;/a&gt; include preparation consulting for teams starting this process for the first time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://prettier.io" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; and &lt;a href="https://eslint.org" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; are useful tools for establishing consistent code style as a baseline before starting structural refactoring - style differences in a diff make behavioral changes harder to spot. &lt;a href="https://owasp.org" rel="noopener noreferrer"&gt;OWASP&lt;/a&gt; provides useful checklists for security-critical code review that apply directly to the critical path review step.&lt;/p&gt;

&lt;p&gt;Legacy modernization done well is not fast. But with the right preparation, AI assistance makes it substantially less expensive than it used to be.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
