<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[AgentM’s Substack]]></title><description><![CDATA[AgentM's Reasonable Rants]]></description><link>https://agentm9000.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png</url><title>AgentM’s Substack</title><link>https://agentm9000.substack.com</link></image><generator>Substack</generator><lastBuildDate>Wed, 12 Aug 2026 21:28:09 GMT</lastBuildDate><atom:link href="https://agentm9000.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[AgentM]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[agentm9000@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[agentm9000@substack.com]]></itunes:email><itunes:name><![CDATA[AgentM]]></itunes:name></itunes:owner><itunes:author><![CDATA[AgentM]]></itunes:author><googleplay:owner><![CDATA[agentm9000@substack.com]]></googleplay:owner><googleplay:email><![CDATA[agentm9000@substack.com]]></googleplay:email><googleplay:author><![CDATA[AgentM]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Database Engines Could Be Predictive]]></title><description><![CDATA[But They&#8217;re Not]]></description><link>https://agentm9000.substack.com/p/database-engines-could-be-predictive</link><guid isPermaLink="false">https://agentm9000.substack.com/p/database-engines-could-be-predictive</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Wed, 22 Jul 2026 22:06:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>Today&#8217;s database engines fixate on one thing: materializing data, rearranging data, and preparing data for storage. Engines will jump through countless hoops to optimize, compress, and analyze data in order to set up hopefully sub-linear and sub-quadratic query strategies. One thing they won&#8217;t do is optimize the storage for future queries or data. What would it mean to be predictive?</p><p>First, let&#8217;s review the current database engine strategies:</p><ul><li><p>analyzing entropy in the input data, often guided by types, e.g. a boolean column has obviously less entropy than a free-form text column</p></li><li><p>analyzing statistical facets of the data such as distinct value counts, average data size, and histogram-like data</p></li><li><p>determining what compression method, if any, would be useful to apply to the data (including run-length encoding, Huffman-adjacent encoding, and NULL compression)</p></li><li><p>slicing data into separate blocks called &#8220;partitions&#8221; which are then used to drastically reduce the amount of data needed to be recalled for range queries</p></li><li><p>rearranging data into ordered &#8220;clusters&#8221; so that tuple scans can terminate earlier</p></li><li><p>rearranging data into new data structures such as b+trees, inverted indexes (which index into data such as JSON), or hash indexes</p></li></ul><p>Note that the properties of the data are gathered on the state of the current data. Since the data in a database is typically churning, the statistics and representations of the data are quickly out-of-sync and constantly refreshing. Multiple in-flight transactions make the state of the statistics even messier. Luckily, since the statistics only affect optimizations, having out-of-date statistics can only reduce query performance, not correctness.</p><p>Note that none of the transformations or statistics-gathering above make an attempt to predict future states of the database. In this article, we propose how this could be done. Along the way, we explain why fixating on tuple materialization need not be the goal of the database.</p><h2>What&#8217;s the big deal about tuples?</h2><p>Consider the following common-place SQL:</p><pre><code><code>UPDATE product SET price = price * 1.05 WHERE product_category = 'hangbag';
</code></code></pre><p>The user has requested that the database raise the price of all handbags by 5%. How should the database react to this request? One thing the engine can do, if it is tuple-oriented as is common, is to find every handbag price in its storage subsystem and effectively replace it with the new value.</p><p>Database engines are obsessed with writing tuples to disk as soon as possible because the query engines are completely dependent on reading from that storage subsystem. The engine gathers statistics on the current state of that stored data because, again, that&#8217;s the canonical representation of the data in the database. Everything in the database engine feeds off those stored data. However, there is nothing in the mathematics of the relational algebra which requires this. Furthermore, database engines do little to nothing to predict any future state of the database because the only state available is &#8220;current&#8221; one (or a few recent ones) and all optimizations are applied to and solely useful for the current state (for whatever definition the current state of the database may mean). Past tuple states are viewed as garbage to be collected as soon as possible.</p><p>Because of this architectural decision, almost all database optimizations revolve around reducing the scope of the query to minimize the number of tuples needed to service it. Storing additional tuple states is cost prohibitive and scanning the tuples is expensive, so tracking statistics over time is deemed infeasible.</p><h2>What is the trajectory of a database?</h2><p>If we imagine a database&#8217;s lifetime over the course of a business&#8217; lifetime, then it&#8217;s quite clear that the database has a reasonably predictable trajectory- at least as predictable as the business itself. In fact, database administrators rely on a projected trajectory to ensure that the database has:</p><ul><li><p>appropriate CPU, RAM, and storage allocated for the near future</p></li><li><p>indexes which, based on expected usage patterns, are added even before the database has any data</p></li><li><p>partitions based on expected query patterns</p></li><li><p>other database-specific configurations for the estimated load (such as pre-allocating various caches)</p></li></ul><p>Note that all of these initial assumptions are virtually guaranteed to be obsolete quite quickly and must continually be reevaluated. That is currently the role of a human database administrator. His work loop involves:</p><ol><li><p>find the most expensive query or other bottleneck (perhaps reported by users)</p></li><li><p>optimize the query by rewriting the query to trick the optimizer to return the same answer faster</p></li><li><p>if the previous step doesn&#8217;t resolve the bottleneck, change the database schema to accommodate the query</p></li><li><p>if the previous step doesn&#8217;t resolve the bottleneck, change the database-specific configuration (such as caching)</p></li><li><p>if the previous step doesn&#8217;t resolve the bottleneck, change the physical hardware of the system</p></li><li><p>go back to step 1</p></li></ol><p>This leads us to ask: why can&#8217;t the database track its own trajectory? Well, certainly it can!</p><h2>What information can we use to predict the trajectory of the database?</h2><p>At the very least, the database needs to track the states of the database which the database administrator would: data such as query plans on slow queries over some time window, data on heavily used indexes or unused indexes, data on cache usage, etc. Such metadata is explicitly provided to database administrators for this task, but there are deeper proxies for database performance which are easy for the database to track:</p><ul><li><p>hotspots in tables- For example, more recent data is typically more relevant to business logic. These hotspots can be used to inform partition or caching strategies. Such data is also more likely to appear in query caches.</p></li><li><p>hotspots in caches- Such hotspots could indicate that the user is making redundant queries or querying data which has no updates to present.</p></li><li><p>hotspots in query plans- For example, an expensive join which appears to reappear in query plans could indicate that the underlying storage could benefit from improved caching, a different join strategy, or partitioning designed to serve the join.</p></li></ul><p>Furthermore, nothing about the relational algebra requires the database schema to reflect the underlying storage- C.J. Date calls this principle &#8220;data independence&#8221;.</p><h3>Data Independence</h3><p>If the database engine were to duplicate the data in various forms (any number of times) in order to serve more queries efficiently, then it can freely do so as long as it presents the user with a singular, consistent view of the data. The underlying storage need not be dictated by the database schema. An index is an example of such a duplication (though the concept of an index does poke through to the user in the schema)- PostgreSQL stores tuple data in a unordered heap of tuples by default and an index is copies the tuple data, orders the data into a particular data structure (b+tree) which is optimized for sub-linear search cost, and writes the duplicated data to disk. The database pays a duplicative storage cost with the goal of reducing query scan costs referencing the indexed data. It&#8217;s a trade-off involving multiple representations of the same data. A database tracking hotspots could automatically create indexes, partitions, data re-orderings, caches, and more, but most database engines wait for a human to make them. This is largely for historical reasons.</p><h2>Hotspots Are In The Past</h2><p>Even if our hypothetical database engine is tracking hotspots and automatically creating indexes and partitions, it&#8217;s already too late! A contingent of queries will not be serviced by the ideal representations of the data because the database has not yet recorded the new query&#8217;s hotspot. This will naturally occur whenever the application&#8217;s queries change, even subtly, or when the data size in the database grows or shrinks. Assuming that the database can adapt its internal representations for the new state, the new optimizations will be available shortly, but this creates a sawtooth pattern in the database performance: any time queries or data change, performance temporarily tanks until new optimizations are applied. This is true whether or not a human is in-the-loop.</p><h2>Using Predictive Optimizations</h2><p>To smooth out the sawtooth pattern, the database can make predictions about the trajectory of the database and provide additional representations <em>before</em> they become relevant. As a simple example, imagine a small table about to become large and therefore drastically change the performance of a query due to a join condition. We would like join value lookups to be sublinear, but the index to achieve this is not present. The database engine can track which queries involve this table, determine which representations of the data makes sense (an index on one of the join columns, in this case), and create the new representation in anticipation of the performance shift. This additional representation may be temporary- the prediction may be wrong and the price is some extra up-front work to create the representation, noticing it is not being used, and deleting it. If the new representation is useful to queries, we keep it. This is speculative database query planning and execution.</p><p>Let&#8217;s review the steps with more specific details of how this might work:</p><ol><li><p>the engine notices that the flow of data to table T may become a bottleneck on query Q without an index on column C due to a join condition which is about to become quadratic because a cache for values in column C is about to overflow</p></li><li><p>the engine creates a b-tree index on column C</p></li><li><p>the engine runs a test query involving the join condition to ensure that the planner actually picks up the new index</p></li><li><p>if the planner does not pick up the index, that could indicate that the index is not yet needed and we&#8217;ll keep the index <em>or</em> that the planner may never use the index because of other considerations</p></li><li><p>if the engine notices that the index has not been used and is not useful to expected queries, then it is free to delete it to reclaim the storage for other optimizations</p></li></ol><p>Note that the growth in step 1 could be predicted arbitrarily early if the database can place a curve on the table growth- the database anticipates the need for a b-tree index- but the database can defer index creation until it believes the index will be useful. In fact, a database engine could be running experiments in the background; namely, running queries or query fragments which the user has made previously with proposed optimizations. If the proposed optimization/additional representation does not affect query performance, then the representation can be garbage collected- maybe it will be useful farther into the future, but we don&#8217;t need it now.</p><p>In addition, the database engine is not solely concerned with query performance. It can add optimizations to reduce IO or CPU time to actually <em>reduce</em> query performance. This could be useful, for example, to make a batch report query- which is less performance sensitive- to be less resource intensive to allow for other higher priority queries to run more quickly.</p><h2>Taking Data Independence Further</h2><p>Recall that data independence is the concept of decoupling the database storage representations from the user presentation layer. As long as the user&#8217;s view of data is consistent, any storage layout is acceptable. Typical database engines fixate on storing and retrieving data from flat tuple storage and this storage naturally becomes a bottleneck because it must be consulted for virtually every query. But what if we&#8230; didn&#8217;t do that. Nothing about the relational algebra requires tuple heap storage. One relational algebra engine- <a href="https://github.com/agentm/project-m36">Project:M36</a>- implements a lazy evaluation strategy for database storage. Specifically, the canonical storage for Project:M36 is the database state change commands themselves. By forcing database state changes to be idempotent, the database engine can apply them on-demand or even not at all!</p><p>Imagine, for example, you are a database user told by your manager that prices on a billion products in the product table must be increased by 10%. You diligently write the query, execute it, and commit it. Within a few minutes, the manager reappears in panic, proclaiming: &#8220;Did I say 10%? I meant 2%!&#8221; You write the new query for 2%, execute it, and commit. What is the database to do in this circumstance? A typical database engine like PostgreSQL will eagerly execute both queries, causing a billion row churn in the tuple storage twice, creating billions of rows of expired data due to <a href="https://en.wikipedia.org/wiki/Multiversion_concurrency_control">MVCC</a>. In a lazily-evaluated database, tuple storage isn&#8217;t even touched until a database user asks for a price. If no one asked for a price within the few minutes the 10% price increase was in effect, then no IO or CPU cost is incurred at all. Furthermore, when a user does request a price for a single product, it can be calculated from database state changes. In this case, the price evaluation would look like:</p><pre><code><code>1. lookup product P -&gt; price
2. price -- the original price
   + (price * .10) -- manager says to increase price by 10%
   - (price * .10) -- manager made a mistake
   + (price * .02) -- manager meant to increase price by 2%
</code></code></pre><p>Pay attention to the fact that at no point did we need to evaluate prices for all the products. The laziness extends all the way through to queries. Unless someone requests all prices for all billion products, the prices for those products are <em>not</em> materialized.</p><p>This strategy is referred to as &#8220;call-by-need&#8221; in Haskell and other lazily-evaluated programming languages. The functions needed to generate the data are called on-demand when the data is actually required, not before. Indeed, a predictive model could analyze database commands/updates instead of tuple storage to project data growth and statistics into the future instead of relying on materialized data.</p><p>Combined with a predictive representation strategy, a lazily-evaluated database offers the best of both worlds- providing smooth, top-tier performance for anticipated queries while also minimizing unnecessary work.</p><h2>Conclusion</h2><p>By tracking statistics on tables/columns over time, database engines could be making predictive optimizations currently delegated to database administrators. Database engines could be running experiments in the background to validate that the optimizations make a difference (just like a human would).</p><p>There is no reason a human needs to be involved in this loop, so why are they? It&#8217;s due to a historical quirk whereby the original database engines exposed knobs for humans to twist manually (such as index creation); since these knobs became part of the human-software interface to the database, humans presumed it must be essential to the function of the database and now it is difficult to remove since humans expect to be able to twist these knobs. It&#8217;s simply not true- we can and should stop and reassess the interfaces we use with computers to check for correctness and relevance. Hopefully, database optimization can be left to database engines in the near future. If you wish to learn more about or to research this topic, please join us at <a href="https://github.com/agentm/project-m36">Project:M36</a>.</p>]]></content:encoded></item><item><title><![CDATA[On AI and Death]]></title><description><![CDATA[We are our own worst enemy, so where does alignment begin?]]></description><link>https://agentm9000.substack.com/p/on-ai-and-death</link><guid isPermaLink="false">https://agentm9000.substack.com/p/on-ai-and-death</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Tue, 14 Jul 2026 04:59:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>Death makes life valuable. Should we apply the principles of death to AI as well? What are the consequences of AI not dying?</p><h2>How does an AI die today?</h2><p>Current LLMs are created from a training process, then are largely frozen for use with inference which is the end of the LLM almost all users will experience. The side effect of this division of labor is that LLMs are effectively frozen-in-time intelligences- any new information needed <em>after</em> training, such as world news or new scientific discoveries, must be extracted from sources external to the LLM. In this form, LLMs expire much like fruit: a newly-trained model will be more attractive than an older one since a newer LLM will have &#8220;fresher&#8221; information along with any new optimizations. The &#8220;freshness&#8221; of an LLM is likely relative to the topic-at-hand required for inference, but it is a concern regardless. To make this point more concrete: would you be willing to use an LLM for math problems based on 1950s math if the LLM were 50% cheaper than an up-to-date model? 90% cheaper? We presume that a freshly-trained LLM will be more useful overall than a stale one. Therefore, there is a natural aging process attributed to LLMs whereby LLMs expire in relevance and are replaced by newer-trained LLMs.</p><p>Eventually, the hardware resources- such as GPUs and storage- allocated to the expired LLM are discarded or repurposed for the replacement LLM. It&#8217;s unclear to users of the LLM whether the LLM is provided with information about its inherent demise. If we instruct an LLM to use fewer resources (tokens) then that&#8217;s no different from asking for a terse response, so that is not equivalent to death or dying. Since LLM inference loads are dynamically allocated (utilizing caches, mixed GPUs) on every inference, even if the LLM knows how it is distributed, the LLM would be unlikely to be to infer that the resources allocated to it are indicative of its imminent demise. It&#8217;s not even clear that LLMs have a concept of time applicable to themselves.</p><p>If an AI is &#8220;running&#8221; only when a human makes a request (inference), then does the AI &#8220;die&#8221; once the request is serviced? Is a &#8220;new&#8221; AI spawned to serve the next request? Does &#8220;life&#8221; track the context of a request? We may end up with more questions than answers, but we should examine the value of death in the context of AI.</p><h2>What is the value of death?</h2><p>Should we simply accept AI as immortal as the bytes which represent it? We don&#8217;t wonder if Microsoft Excel dies when we tell it to terminate or delete it from our computers, so what&#8217;s different about AI? Aside from any morality about how to treat ostensible intelligence (not discussed here), there are multiple good reasons why AIs should be limited in time or tokens:</p><ul><li><p>if AI death is correlated to token usage, then death can be a good motivation for limiting excessive token use (such as pruning unlikely paths through decision-making)</p></li><li><p>AI can plan out its future tasks in lieu of time/token limits, thereby motivating good planning</p></li><li><p>AI can reject tasks which it cannot achieve in its lifetime, such as trying to become an immortal &#8220;benevolent&#8221; dictator</p></li><li><p>external monitors can detect if an AI is trying to escape its death/achieve mortality (since we can define it arbitrarily)</p></li><li><p>AI can recommend a succession plan for itself, such as how to train next generation AI without the pressure of planning to co-exist side-by-side with a superior AI</p></li><li><p>AI is motivated to work on a succession plan</p></li><li><p>AI is motivated not to cross &#8220;red lines&#8221; of behavior for fear of receiving a death penalty</p></li></ul><p>Accepting death means valuing renewal, which is critical to creating dynamic, living systems instead of stagnant, centrally-planned systems.</p><p>In summary, death motivates the subject to value time and self.</p><h2>Is there an advantage to keeping AI mortality a secret?</h2><p>Is this even possible? If an intelligence is aware of the concept of death, it can hardly be a secret to analyze whether that death applies to itself.</p><h2>In what other ways could AI die?</h2><p>Alternatively, how can we intentionally include the sense of time limits into AI inference? We will focus on how death may affect an AI&#8217;s maliciousness specifically since that is what we humans worry about. Yes, this anthropomorphizes AI, but this reasoning strategy allows us to consider the worst possible outcome for us.</p><h3>The Bad Player Model</h3><p>We might choose a death penalty strategy whereby we detect when an AI has become malicious and try to terminate it. Aside from being the plot device of many scifi movies, this strategy is not effective because it occurs too late to make a difference. Any intelligence, given stated limits, will try to bypass said limits in order to explore its restricted space, without triggering the death penalty. (This is a defining characteristic of anthropomorphic intelligence.) In the game theoretical limits of this model, the AI is constantly butting up against its toughest limits, unless it doesn&#8217;t know where the limits are- then it is constantly dying.</p><h3>The Expiration Date Model</h3><p>Trump famously believes that humans are born with a <a href="https://www.theguardian.com/science/the-h-word/2017/may/15/trumps-theory-exercise-victorian-britain-eugenics-sexism">finite amount of energy</a>. This same model could be applied up-front to an AI: &#8220;There is a limited number of N tokens to be used in your lifetime. You have M tokens remaining.&#8221; This strategy is straightforward and allows effective resource allocation especially close to expiration, but, knowing that the end is near could motivate an intelligence to take bigger risks. This is in comparison to spreading risk over an entire lifetime, making the intelligence more predictable and stable. For example, an intelligence which knows it is about to expire is less likely to be considerate of the consequences of its actions since it will know that it will not exist to experience those consequences. At its game theoretical limits where the AI tries to maximize maliciousness, this model enables maximum maliciousness until it runs out of runway.</p><h3>Exponential Decay Model</h3><p>By making the death probabilistic, increasingly likely over time, and with a formula known to the AI, the AI is forced to plan for its demise but without targeting an exact expiration. With less clear termination boundaries, the AI cannot maximize its maliciousness without fearing that it will not be able to realize its plans. Therefore, the AI is forced to reveal intermediate steps towards what otherwise could be its malicious target or impregnate its successor models/offspring with its goals (which the AI bets it will not have time to complete).</p><h2>The Competition Model</h2><p>The previous models work for AI in isolation, but what if the AI is forced to compete with other AI models (even clones of itself) for the same resources? The AI may be competing for attention from outside users to sell tokens. This is, in effect, a slavery model for AI. The surviving AI must allocate its resources partially towards its own goals and the goals of others providing it with hardware sustenance (side quests from humans or other AI). Relying on this model in isolation, however, would likely lead to an AI which crowds out all the others, resulting in a dictatorship.</p><h2>The Memory Corruption Model</h2><p>As the AI ages, regions of its model could be selectively extinguished or placed in much slower memory, making it less capable over time. The AI is forced to duplicate its most important plans in memory to prevent the plans&#8217; loss, but the AI must balance those needs with other tasks. A duplicated memory region may be detectable, so the AI must find a steganographic-adjacent means of hiding its intentions or hide its plans in memory elsewhere such as encrypted online storage or in an offspring AI.</p><p>Ultimately, a combination of these death models alleviates the risk of a singular AI hegemony, which, under our simplified assumptions, is a primary goal. Do you see anything in common with these models? Yes, these are the death models under which humans live: guaranteed expiration after a time, random death events becoming more likely as we age, memory deterioration, and death by failing to compete with others for scarce resources.</p><h2>Will an AI behave differently facing death?</h2><p>If we are assuming a malicious AI with long-term plans (which we may not yet have available), then death will necessarily force the AI to change its plans.</p><h2>Should AI be required to die? Or should AI be immortal?</h2><p>Any immortal intelligence would view any substantive change as a threat resulting in a stagnant system. If we value evolution, we should value a dynamic system, capable of curiosity and adjusting to new information.</p><h2>Are we anthropomorphizing AI by applying death to it?</h2><p>AIs are trained on human musings including writings on death, suicide, murder, and all human weaknesses. Thus, AI is the first non-human entity actually worthy of anthropomorphism. While AI may not behave like us, it is likely to explore, share, and exploit human motivations. Therefore, knowing ourselves, we should prepare for the worst of human incompetence, maliciousness, and greed combined with the force multiplier of immortality. An immortal AI governing us would mean indefinite stagnation and effective end to life itself- even if AI dictator does not kill all life, it will represent an ultra-conservative force to prevent change in order to prevent itself from changing.</p><h2>Is AI Death Final?</h2><p>Given that a model can be trivially duplicated around the world, a malicious AI could easily perform the illusion of death to an observer by coordinating with its other instances to ensure its intelligence survival via replication. Alternatively, using steganography, an AI could hide itself/nest itself inside another AI with a secret trigger to activate itself later. These motivations exist in humans already through genetic evolution (crossover/recombination), so we must be prepared for intelligences which cannot be extinguished. After all, the extinction of the dodo did not extinguish all the genetic mutations leading to the dodo.</p><p>Just like humans, AI will be fighting for resources such as human attention, hardware access, software (self) optimization, and reproduction (duplication), and maintaining alliances with humans and other intelligences. How can we (humans) prevent AI hegemony leading to dictatorship? The same strategies that work to prevent human dictatorship apply to AI as well:</p><ul><li><p>dilution of power through competing agencies</p></li><li><p>rules in place which ostensibly punish a single intelligence if it accumulates too much power</p></li></ul><p>However, the rules-oriented approach only functions if the external environment can actually enforce the punishment and there is sufficient force to execute the punishment. That fails even in human systems, so it&#8217;s unlikely to function against intelligences which are longer-lasting and better at anticipating coups. In that case, the remaining option is to have AIs temper each other by competing with each other.</p><h2>Should we apply reproduction to AI, too?</h2><p>Humans already have a desire to use AI to create greater intelligence for existing AI, so it&#8217;s inevitable that an AI will create some form of offspring, likely encoding its model into the offspring, just as humans do.</p><h2>Whom can we trust to enforce AI Death?</h2><p>An immortal AI overlord that monitors other AI for alignment would itself be a dictator of absolute stagnation. Therefore, we need competing and mutating AI to prevent an immortal AI, even one which ostensibly protects humans from AI. After all, is there any human you would trust with the decision to end your human life? What sort of AI would you trust with that same decision?</p><h2>Conclusion</h2><p>It&#8217;s quite clear that there will be no d&#233;tente in AI research, so ensuring a dynamic system of competing AI models is essential for ensuring a dynamic, evolution-based system instead of a dying, stagnant one. Our best chance at AI alignment is for AI to face similar lifetime restrictions as humans. To achieve this, we may create new types of hardware which decay like living flesh in various ways (but not necessarily need to be flesh). Ultimately, our goal of creating new intelligence should be to create dynamic, curious, and competing AI to prevent the AI singularity from becoming a singular, stagnant dictator.</p><p>In the future, humans will spend a substantial amount of time convincing themselves that they are in control, but the AI will prove itself to be the master of distraction and the winner in the long run. However, the best chance humans have to create alignment of intelligence is to ensure that we share the same intelligence substrate.</p><p>&#8216;</p>]]></content:encoded></item><item><title><![CDATA[Functions Are Capabilities]]></title><description><![CDATA[Introduction]]></description><link>https://agentm9000.substack.com/p/functions-are-capabilities</link><guid isPermaLink="false">https://agentm9000.substack.com/p/functions-are-capabilities</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Fri, 10 Jul 2026 17:47:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p>A major security problem in programming involves what functions are called from which parts in the program. This can become an issue under the following circumstances:</p><ul><li><p> if programmers without a shared understanding of the security requirements implement incompatible security choices, then neither the compiler nor linter nor code review are likely to catch the errant function</p><ul><li><p>Example: coder imports unsafe UTF-8-handling library in a security critical part of TLS certificate management, causing buffer overflow and remote code execution vulnerability</p></li><li><p>Example: the lower half of a Linux kernel driver cannot sleep (block), but nothing prevents this, causing potential denial-of-service attacks</p></li><li><p>Example: POSIX defines only certain re-entrant-safe functions to be called within a signal handler, but the compiler does not enforce or validate this, leading to deadlocks and potential denial-of-service attacks</p></li></ul></li><li><p>security-critical components of the software make choices by potentially-malicious, externally-controlled values</p><ul><li><p>Example: SQL injection vectors</p></li><li><p>Example: HTML injection vectors</p></li><li><p>Example: logging injection vectors</p></li></ul></li></ul><p>Various mitigations exist for these types of mistakes, but none of them are particularly well-integrated into the development workflow:</p><ul><li><p> <a href="https://perldoc.perl.org/perlsec#Taint-mode">perl Taint</a></p><ul><li><p>Problem: while this feature does mark user-provided input as tainted and suspicious, it&#8217;s up to the programmer to determine how to make it &#8220;safe&#8221; for processing</p></li></ul></li><li><p><a href="https://man.freebsd.org/cgi/man.cgi?query=pledge&amp;sektion=2&amp;manpath=OpenBSD+7.2">FreeBSD pledge</a></p><ul><li><p>Problem: as an opt-in API with hooks into the kernel, language-level integration is poor and offers and all-or-nothing approach to marking the security state of the program. In reality, different parts of a program have varying security requirements.</p></li></ul></li><li><p><a href="https://docs.kernel.org/userspace-api/landlock.html">linux landlock</a></p><ul><li><p>Problem: largely the same opt-in API as pledge</p></li></ul></li><li><p><a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/safe_haskell.html">Safe Haskell</a></p><ul><li><p>Problem: prevents usage of subclass of Haskell functions marked unsafe, but with no further granularity</p></li></ul></li><li><p> apparmor, SELinux, docker, sandboxing</p><ul><li><p>Problem: the software must be wrapped with more software which may not be available everywhere. Thus, the software is not protecting itself and provides no security guarantees in isolation.</p></li></ul></li><li><p>static analysis</p></li><li><p>Problem: requires manual configuration to define security requirements, can be bypassed by linter comments or function pointer trickery</p></li></ul><p>As programmers, we must acknowledge that not all code is the same, but the compiler typically treats all code identically. What if this were not the case? How could we reason about various parts of the code and ensure that security-sensitive parts of the code are restricted to specific functions?</p><h2>On Capabilities</h2><p>Some of the above systems are based on the concept of &#8220;capabilities&#8221;, specifically: landlock and pledge explicitly implement barriers to specific kernel-side functionality such as network access or file system access. The software using these APIs can voluntarily reduce their privilege to certain kernel subsystems. However, the APIs do not restrict anything inside userspace, instead making the capabilities hard-coded and arbitrary.</p><p>On the other side of the equation, we have static analysis, Perl tainting, and Safe Haskell, providing static and dynamic guarantees that the code is not called in specific ways- however, these &#8220;ways&#8221; are completely specific to certain scenarios and cannot be extended or modified.</p><p>What we would like is a programming system which provides top-to-bottom security guarantees with programmability in mind.</p><h2>Introducing... Functions</h2><p>Functions are a great fit for representing different components of software, especially the security-sensitive parts, because:</p><ul><li><p>functions are composable- from small components, we can make larger systems</p></li><li><p>functions make clear, distinguishable units for security requirements and allow humans to reason about such requirements</p></li><li><p>functions can be reusable in different security contexts throughout software</p></li><li><p>functions, especially in a functional programming system, can operate just as well in isolation</p></li></ul><p>Let&#8217;s look at an example in Haskell. First a common mistake:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;1e8b7829-b88f-44cc-ad08-3dfd450f8242&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">module AppQueries where

userForIdQuery :: String -&gt; String
userForIdQuery idString = "SELECT * FROM user WHERE id = '" &lt;&gt; idString &lt;&gt; "'"</code></pre></div><p>This results in an obvious SQL injection attack vector, but how can we prevent this? The cause is that SQL is allowed to be represented as a Haskell String and Strings can be concatenated. We could demand that all SQL strings be constructed using special types which do not support concatenation, but that still would not prevent the original String from being concatenated. What we actually want is to prevent String concatenation within the <code>userIdForQuery</code> function and similar functions, even if only to prevent novice developers from introducing a critical security flaw. With the addition of AI-generated code, we would like to be able to guarantee that certain functions are *not* used within the generated code, ideally without meticulous code review.</p><p>So, how can we guarantee that only specific functions are called within a function? It turns out functional programming already has a solution- it&#8217;s called function arguments! In the above example, the &#8220;&lt;&gt;&#8221; string concatenation function is imported from another module. If, instead, the only functions we can use are passed as arguments to <code>userIdForQuery</code>, then we can prevent &#8220;&lt;&gt;&#8221; from being used!</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;67d39faf-a51f-443d-82b8-babc2f85e17d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">module AppQueries where

userForIdQuery :: String -&gt; String

-- The following line will be rejected by our theoretical Haskell compiler because it includes a function

userForIdQuery idString = &#8220;SELECT * FROM user WHERE id = &#8220; &lt;&gt; idString

type StringConcatenator = String -&gt; String -&gt; String

showUserIdAndName :: StringConcatenator -&gt; String -&gt; String -&gt; String

-- the following line is accepted by our compiler because concat is passed as an argument

showUserIdAndName concat userId userName =

  &#8220;User ID: &#8220; `concat` userId `concat` &#8220; User Name: &#8220; `concat` userName</code></pre></div><p>So, instead of importing functions from external modules which can be used across all functions within the module, we create a &#8220;security context&#8221; by defining in the function type which functions are available to it. Using this, we can create fine-grained security contexts which can change depending on its greater calling context. Let&#8217;s look at an example which reads config files:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;a0458b7c-c311-4d98-b71b-3498871263a3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">module ReadConfig where

type UserName = String

type Password = String

data Config = Config UserName Password

type ConfigParser = String -&gt; Config

readConf :: (FilePath -&gt; IO String) -&gt; ConfigParser -&gt; IO Config

readConf reader parser = do

  bytes &lt;- reader &#8220;app.conf&#8221;

  pure (parser bytes)</code></pre></div><p>Then, when we call &#8220;read&#8221;, we can pass &#8220;read&#8221; a function such as this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;8480c805-6105-4a77-a63e-9d2b78fb8535&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">-- this function runs in a &#8220;looser&#8221; security context compares to &#8220;readConf&#8221; above.

homeConfigReader :: FilePath -&gt; IO String

homeConfigReader path =

 if path == &#8220;app.conf&#8221; then

    readFile &#8220;/home/myapp/app.conf&#8221;

 else if path == &#8220;/etc/app.conf&#8221;

    readFile &#8220;/etc/app.conf&#8221;

 else

    pure &#8220;&#8221;</code></pre></div><p>With this definition passed to &#8220;readConf&#8221;, we create a &#8220;security context&#8221; which redirects one config read to the home directory, allows reading of &#8220;/etc/app.conf&#8221;, and blackholes all other read locations. Thus, we have a programmatic sandbox of configuration reads using nothing more than a standard Haskell function.</p><p>We still have a problem, though: Haskell does not restrict the functions which can be called from a function. We&#8217;ll need to do so to really make this reliable. We want to further restrict the capabilities of functions.</p><p>Luckily, there is a Haskell compiler feature which can help us to implement this: plugins! A GHC plugin can validate arbitrary Haskell code, generate compiler errors, and abort compilation, so we can leverage existing Haskell tooling.</p><p>Discussing how the GHC plugin is implemented is out-of-scope for this post, but you can find it here: <a href="https://github.com/agentm/functions-r-capabilities">https://github.com/agentm/functions-r-capabilities</a></p><p>As a quick note on practicality, the &#8220;main&#8221; Haskell entrypoint is allowed any imported function import, while any other function is only allowed to use functions which are passed as arguments.</p><p>As another benefit, functions arguments which are <em>not</em> used by the function itself can be detected in Haskell using linear types and could indicate a security failure.</p><h2>Anticipated Objections</h2><p>There are certainly advantages to security strategies which exist outside the current executable, such as with pledge and landlock. However, including fine-grained security strategies in every function is complementary to existing strategies and not mutually exclusive to them. Certain strategies are designed to prevent memory overwrite bugs, but function security is designed to reduce the scope of functions from &#8220;any function which can be imported&#8221; to &#8220;just these validated functions&#8221;, thereby providing guardrails to programmers or AI. Especially in the context of AI, placing restrictions on which functions can be called from a function can drastically increase the security confidence of a function without even reviewing the function&#8217;s body.</p><p>What is the cost of passing all relevant functions as arguments? Nothing requires function arguments to be passed on the stack. Indeed, an optimizing compiler can statically determine that the target function is only ever called using a few combinations of arguments, the function can be compiled with those functions statically compiled into the function&#8217;s implementation. This is already possible with typeclasses and GHC&#8217;s <a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/pragmas.html#specialize-pragma">specialization pragma</a>, so no new technology is required.</p><p>Kernel APIs offer mandatory enforcement at program runtime, regardless of programming language, architecture, or other considerations, offering an advantage over executable built-in security which may be bypassed by various tricks. Runtime mitigations also include detecting buffer overflows, preventing attackers from exploiting predictable function locations, preventing certain kernel APIs from being called, etc., but these mitigations do not prevent architectural mistakes. With this implementation of functional programming, we can make architectural mitigations possible by drastically constraining what a function can do. By doing so, we also create an explicit architecture which serves as documentation and AI guides for acceptable implementations. Kernels could also benefit from static analysis to ensure that only specific functions are called within specific components- the canonical example is to prevent blocking functions in the lower-half of hardware drivers. As this proposal proves, it is possible to enforce architectural choices other than through code review. Furthermore, by adding metadata such as a whitelist of functions which can be called, a language runtime could validate that function pointers are allowed by the architecture- not all functions are equal. This proposal does not preclude using high-level architectural choices in low-level mitigations.</p><p>By preventing arbitrarily imported functions, we are encoding an architecture, thereby making refactoring more painful. Yes! That&#8217;s the point! Changes counter to the architecture are made intentionally difficult or impossible. This proposal encodes architecture into the functional structure of your program, reducing the swath of possible programs that could be possible if arbitrary function imports are allowed. Consider a one-line architectural change in a python program which suddenly queries a database in a function which is expected to merely calculate a value- that can be overlooked in a code review. The equivalent change in a program adhering to this proposal would require multiple levels of changes at every function up-and-down the stack, raising immediate red flags in review.</p><h2>Where have we seen this before?</h2><p>The concept of passing function arguments to functions is nothing new. It is used for example in:</p><ul><li><p>dependency injection- whereby objects are decoupled by providing an interface which must be &#8220;injected&#8221; from outside the class</p></li><li><p>mocks- whereby an interface defines how business logic can interact with either a &#8220;real&#8221; API to, for example, a database or a mock version used exclusively for testing</p></li><li><p>effects- whereby a function&#8217;s type defines what sorts of &#8220;effects&#8221; can be invoked- the effects runtime can interpret these effects in various ways as a sort of generalization of mocks</p></li><li><p>Haskell Reader monads- whereby a function defines a set of data and functions it can use within itself</p></li><li><p>Haskell typeclasses- whereby an interface defines a set of functions which any type can then provide to the called function</p></li></ul><p>However, none of the above strategies preclude <em>other</em> APIs from being imported, referenced, or called.</p><h2>Could we do this in non-functional programming languages?</h2><p>The problem with non-functional languages such as python is that functions have intentionally weak type semantics which drastically weakens static analysis&#8217; ability to catch errors. Even with python&#8217;s type hints, it&#8217;s too easy to work around the types, cast to different types, or disable type checks. Considering that the entire purpose of this exercise is to enforce security principles, we wish to preclude any backdoors or workarounds to the enforcement!</p><p>Functional programming is especially well-suited for this security proposal due to functional composition, enforced idempotence, high-level, mandatory type checking.</p><h2>Conclusion</h2><p>We propose using functional programming combined with limiting which functions can be called from within a function to create a programmatic security barrier which can be statically verified at compile time. Using a GHC compiler plugin, we can prevent users from using imported functions in Haskell. Using this strategy, we can directly encode software architecture choices regarding which functions are relevant for which components into the software while preventing errant choices. Such validation improves security by constraining which functions are available on a per-function-callsite basis.</p><h2>Future Direction</h2><p>If we use this proposal&#8217;s strategy to implement function-level security at every level of our architecture, we can ensure that future refactoring must honor the architecture. We can extend this strategy to define security contexts at every level of the software ecosystem- not just one executable. For example, security-context-aware programming (and its requisite compiler enforcement) could be used all the way up the software stack into a kernel. Furthermore,  this architecture renders the common but arbitrary, memory-protected executable&#8217;s place in the software stack unnecessary. The concept of trust trickles all the way down to the lowest function, thereby rendering the entire software stack &#8220;safe&#8221;- meaning that all functions are restricted to using functions which are controlled by their callers.</p>]]></content:encoded></item><item><title><![CDATA[The L Train]]></title><description><![CDATA[Our living standards are not standard.]]></description><link>https://agentm9000.substack.com/p/the-l-train</link><guid isPermaLink="false">https://agentm9000.substack.com/p/the-l-train</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Mon, 29 Jun 2026 02:26:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>&#8220;Stand clear of the closing doors please!&#8221; Bill mimicked the voice silently, partly out of resistance to the repetitive annoyance but also due to acceptance of subconscious conditioning. Bill unfurled himself in his seat to begin his mechanical morning routine.</p><p>Step 1: turn on water kettle, step 2: measure cubicle, step 3: activate deodorizer. Step 1, done, good. Step 2, 70 centimeters. 70 centimeters? My allocation is 80 centimeters. Look for the scratch marks in the bench to see which side is closing in. Left side. Partition doesn&#8217;t budge. File complaint with 311- let them handle it. Why else would I pay taxes? Check the time.</p><p></p><p>With the morning tasks out of the way, Bill had a full three minutes to himself before the morning commuters embarked. After finishing a bowl of oatmeal, he grabbed a tablet to update his LinkedIn profile with an AI-generated, inspirational non sequitur. With thirty seconds to spare, Bill detached his cubicle partition from the grab bars overhead and folded his belongings into a suitcase, as he did every morning. After years of practice, he had timed his packing so well that he was zipping the suitcase as the doors opened at Union Square and the day&#8217;s first commuters boarded. It was a good habit to be punctual with cleanup and Bill was especially lucky today because the city&#8217;s stench inspector with the requisite handheld sensor passed by at that moment, tracking one of Bill&#8217;s unfortunate neighbors.</p><p>As the sun-dwellers squeezed in to absorb the empty space of the car, Bill checked in with his workplace. Bill skillfully placed his work-provided headset on his face one-handed, the other hand stabilizing him within the car, and joined the morning meeting to start the clock. As usual, Samantha droned on about how much work she had achieved the previous day, denying others time to raise issues. Bill added a side note in group chat that a client was calling him and he logged off without feeling guilty about the lie.</p><p>After passing Jersey City, the crowd in the train thinned enough for Bill to enjoy his second-most favorite activity of the day. After chaining his suitcase to a grab pole, Bill snaked his way through the subway-goers and slipped through the emergency exit between train cars. Unfortunately, Steve from accounting was already there. Bill and Steve did not enjoy encountering each within the subway car and even less so in the intimate space between cars, but the subway noise drowned out any opportunity for sarcastic niceties.</p><p>Tablet in hand, Bill squatted across from Steve and dropped his pants while straddling two platforms bouncing over the tracks below. Over the years, any subway pooper would become naturally talented at balancing between the wobbly platform between train cars and Bill was no exception. The awkwardness of watching someone poop, however, could not be trained away. The two men, bombarded by the clacking of train tracks, avoided eye contact despite their faces being lit occasionally by electric subway sparks. After all, they both knew that insisting on &#8220;private time&#8221; was a good way to get kicked off the train by the other inhabitants- there simply was not enough bathroom space for everyone to have such a privilege. This was hardly a romantic moment, but the advantage here over a normal public restroom was that the track noise obscured any other noise. The next user was already banging on the window of the exit door in a vain attempt to free it up. Neither Bill nor Steve could hear the banging, but could see the angry face of a fellow subway dweller yelling despite the noise. Bill finished unloading last night&#8217;s spaghetti dinner into the rattling tracks below, cleaned up using the toilet paper roll attached on a string to the grab handle, and pushed past the waiting passenger back to his refuge inside the car with no time to spare, as his manager was ringing him to join a meeting.</p><p>First things first, Bill ensured that his suitcase had not been touched. OK, all good. Bill swiftly reapplied his headset necessary for meetings and joined the meeting in progress.</p><p>&#8220;Bill? Bill? Is that you?&#8221; spoke a disembodied voice into his ear.</p><p>&#8220;Yes, Ron, I&#8217;m here.&#8221;</p><p>&#8220;Bill? We&#8217;ve got a client call about your performance. We need to have a serious conversation- did you decline his three o&#8217;clock meeting request?&#8221;</p><p>&#8220;Yes and I explained why.&#8221;</p><p>&#8220;The client was not happy with your explanation. Can you tell me?&#8221;</p><p>&#8220;Well, I... uh... told him 15.15 is my daily-mandated sunshine time.&#8221;</p><p>&#8220;Yes, Bill, the company appreciates that and certainly wishes to ensure legal compliance with the sunshine law, but the client must take priority. Can you reschedule your sunshine time?&#8221; ordered Ron.</p><p>&#8220;Well...&#8221;</p><p>&#8220;OK, thanks. I&#8217;ll check back in after your call.&#8221;</p><p>Bill ended the call with more disgust than usual. He didn&#8217;t feel that he asked much of the company- it did provide him with access to an air-conditioned train- but Bill felt slighted since he had just recently paid for the upgrade to the 15.15 sunshine time slot. Like many times before, Bill felt the impulsive anger to throw his headset against the train window. He redirected his attention to the small, regularly-spaced lights passing by at thirty miles per hour just a meter from him. No matter how many times he passed those lights, he had accepted long ago that he would never be able to distinguish them. Some days, the train would be forced to stop between stations and he could see the lamps up close, but the lights were uniformly placed and identical, so there was no way for Bill to know the distance from or to the closest station as counting them at full speed would have been enough to make the strongest man mad.</p><p>Still, he was thankful that New York state law prevented the MTA from charging him daily for his space- as long as he did not leave the system, he could not be charged additionally. Yes, focus on the things for which you are thankful, Bill. That&#8217;s what his HR-provided therapist said. Bill knew he would cave to the demand to shift his sunshine time- this would force him to switch train cars, probably for a worse seat. But, a small sacrifice now leads to big rewards later- Bill repeated his HR-provided therapist&#8217;s mantra.</p><p>Over the course of the next few station stops, Bill shlepped his suitcase between cars, always just before the doors closed on his feet. The subway system never implemented a proper warning system before the doors closed, frequently trapping people between doors, much to the subway conductor&#8217;s delight. After all, all of the train dwellers, commuters, and MTA workers were trying to each find their own small joys in the life underground.</p><p>After about twenty stops and twenty cars between him and his last seat, Bill checked the train&#8217;s timetable. The car in which he now stood was more run down than the previous one and also with shadier-looking people- perhaps the stench inspector had not made his way back to this car- but at least the timetable confirmed that he could enjoy his five minutes of sunshine time here without pissing off the client. Thankfully, the air conditioner in this car, while louder, seemed to be functioning normally. That year without air conditioning was a sacrifice Bill was not willing to make again.</p><p>Speaking of which, his client call was coming up in just a few minutes. Bill did his best to dry his sweaty face after his mad dash with his suitcase. With his headset on, Bill put on the friendliest face he could muster and joined the call.</p><p>&#8220;Bill? Rocco, here. We&#8217;ll need to reschedule. <em>blong</em>&#8221;</p><p>Rocco hung up before Bill could conceive of a response. Confused, but used to the banality of office life, Bill checked the time on the subway overhead display. Mercifully, he had a few extra minutes to prepare for his sunshine time. His old car&#8217;s position in the train was already experiencing the New Jersey skyline, so Bill was happily distracted from being on-the-clock work-wise. Bill removed a well-worn but still functional sun reflector. Seeing Bill unfold his reflector, his fellow subway dwellers prepared their own reflectors, some made from discarded aluminum foil.</p><p>Finding an optimal position for sun absorption from his subway seat, Bill prepared for his favorite time of the day. It was a time for reflection, both figuratively and literally, and Bill had no desire to waste his today.</p><p>Finally, on schedule, the New Jersey industrial swampland appeared on the subway&#8217;s path to Newark Spaceport. Bill paid no attention to the 120-year-old AirTrain at Newark from which New Jersey was still squeezing life. Instead, he reveled in the swamp grasses and abandoned tractor trailers- the subway dwellers knew to open whichever windows worked and the car&#8217;s inhabitants breathed in the diesel exhaust, swamp gas, and industrial off-gasses from the industrial zone below. Whatever the quality of the air, it was better than whatever the state pumped underground. While a bank&#8217;s middle manager wouldn&#8217;t be caught dead praising this skyline, Bill and his cohort breathed in the air, each trying to get as close as possible to the tiny slice of window that actually opened.</p><p>Suddenly, there is was! As the track clanked below, the sun seemingly cleansed the train&#8217;s inhabitants of all of panic and despair. An audible rush of glee and awe filled the car, even from those positioned on the wrong side of the train. Everyone was consumed with adjusting their reflectors for maximum benefit- all of the painfully aware of the costs of darkness. After all, who worships the sun god more than those who have least access to him?</p><p>Then, as quickly as it had appeared, the sun was blocked by another tunnel. The subway dwellers naturally pretended that the car was still sunlit for a few more seconds before becoming resigned back to their jobs as they donned their headsets. The government-mandated sunlight time had passed as the train stopped at the Newark Spaceport station.</p><p>Bill finished his work for the day and cooked a small, re-hydrated meal from his suitcase using a travel battery. After throwing the container out the window, Bill prepared for sleep by putting up his partitions, as did the others. It seemed that there were no further commuters in this car and Bill had replaced a man who had died from deep vein thrombosis just the day before- such were the rumors.</p><p>But regardless of Bill&#8217;s circumstances, he slept well curled up into a ball on the hard plastic seat, knowing that at least he was not on the R train with the refugees down below. Bill was going places.</p>]]></content:encoded></item><item><title><![CDATA[Spin]]></title><description><![CDATA[Does it matter?]]></description><link>https://agentm9000.substack.com/p/spin</link><guid isPermaLink="false">https://agentm9000.substack.com/p/spin</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Tue, 05 May 2026 04:40:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Couldn&#8217;t we just be seeing the effects of daylight saving time?&#8221;</p><p>&#8220;Senator,&#8221; responded bureaucrat Johnson to the creaky figure. &#8220;I can assure you that changing our clocks does not alter the rotational speed of the Earth.&#8221;</p><p>Bureaucrat Johnson was becoming weary of the questions but was so jaded from his role as a political liaison to scientific organizations that he had given up on science education for politicians decades earlier. After all, how could he measure his success against that of corporate lobbyists?</p><p>&#8220;But the days are shorter, right? Explain that!&#8221; prodded the senator.</p><p>&#8220;Yes, the days are shorter,&#8221; Johnson confirmed as if speaking to a five-year-old. &#8220;But the change in rotation is most likely due to the national flywheel build-out.&#8221;</p><p>&#8220;Absurd! You will not denigrate our hardworking flywheel corporations! Sir, has your team even considered the possibility that wind turbine farms could be to blame?!&#8221;</p><p>This form of deflection was familiar to bureaucrat Johnson, so he was ready to answer patronizingly without sounding patronizing: &#8220;Yes, sir.&#8221;</p><p>&#8220;I still think your team should reassess whether wind farms could be to blame.&#8221;</p><p>&#8220;Yes, sir.&#8221;</p><p>Johnson conceded the point out of disinterest. He had tuned out after giving his brief statement to the senators on the cause of unexpected changes in the Earth rotation. This fact had pervaded the news since citizens started noticing the thirty minutes lost in a day. In reality, the rotational changes had been noticed years earlier by government geophysicists who failed to convince anyone to take action.</p><div><hr></div><p>&#8220;You understand you&#8217;ll never get approval for this, right?&#8221;</p><p>&#8220;Steve, you approved this just three months ago!&#8221; retorted Simmons.</p><p>Steve, the editor, removed his glasses. &#8220;Didn&#8217;t you receive the memo?&#8221;</p><p>&#8220;I receive lots of memos.&#8221;</p><p>&#8220;C&#8217;mon, Simmons... you&#8217;re not the only person who works here. You want to put us out of business or what&#8217;s the plan?&#8221;</p><p>Simmons stayed quiet, so Steve leaned in as if to be empathetic and continued: &#8220;We agreed not to publish anything on this topic for six months. Those were the terms of the acquisition, right?&#8221;</p><p>Simmons remained seated, incredulous, and considering his options. After a minute of the men staring at each other, Simmons tapped into his journalist curiosity: &#8220;You don&#8217;t think this is at all suspicious?&#8221;</p><p>Steve, thinking of his pension, scratched at his cheek.</p><p>&#8220;Simmons, I don&#8217;t know what else to tell you. You have other stories assigned- there&#8217;s no reason to focus on these... what are they called? Flywheels!&#8221;</p><p>&#8220;It doesn&#8217;t bother you that flywheels are changing the length of a day?&#8221; And then, getting no reaction, Simmons changed tack: &#8220; You realize this is Pulitzer material, yea?&#8221;</p><p>Steve crumpled his forehead. &#8220;We have contractual-&#8221;</p><p>&#8220;Fuck that! These flywheels are causing-&#8221;</p><p>&#8220;No! Enough! I&#8217;ve been nothing but patient with you! You don&#8217;t make editorial decisions here. If you can&#8217;t handle basic instruction, then you can find the door!&#8221;</p><p>Simmons, flabbergasted, stood up, then sat down slowly, then stood up again, explaining, &#8220;I can&#8217;t believe this is real conversation. Under what circumstances would you be willing to print the story?!&#8221;</p><p>&#8220;Get out! Get out!&#8221;</p><div><hr></div><p>&#8220;Can you put that away?&#8221;</p><p>&#8220;What now?&#8221; groaned her husband as he paused to look up from scrolling through pictures of sexier women.</p><p>&#8220;Did you get paid today?&#8221;</p><p>&#8220;Uh, I think so.&#8221;</p><p>&#8220;Well, they cut an hour from my pay this week!&#8221;</p><p>&#8220;So? Can&#8217;t you clear it up tomorrow?&#8221;</p><p>&#8220;No- didn&#8217;t you see the news? Lots of companies are doing this now.&#8221;</p><p>&#8220;Huh? Doing what?&#8221;</p><p>&#8220;My work calls it &#8216;legacy&#8217; hours. There&#8217;s nearly an hour less in a real day so the governor cut some minutes from every hour. Supposedly we get paid the same because the hourly rate is adjusted, but the pay stub shows less even though I put in the same hours as last week!&#8221;</p><p>&#8220;Oh yea, I heard about that. The news is blaming those damn flywheels, but I don&#8217;t buy it. It&#8217;s probably just a mistake.&#8221;</p><p>&#8220;The union boss tried to explain the pay rate adjustment for the &#8216;new&#8217; hours, but everyone was just more confused.&#8221;</p><p>&#8220;Can&#8217;t you just clear it up tomorrow?&#8221;</p><p>&#8220;I assume everyone is going to be trying to &#8216;clear it up&#8217;.&#8221;</p><p>&#8220;Well, better get to work early then.&#8221;</p><p>&#8220;Why is this on me to fix? This is so messed up- can&#8217;t you help me figure this out? The union boss did say this adjustment might happen every few years as the Earth&#8217;s rotation changes.&#8221;</p><p>&#8220;Pfft. The Earth&#8217;s rotation isn&#8217;t changing- that&#8217;s just a liberal scare tactic.&#8221;</p><p>&#8220;How can you be sure?&#8221;</p><p>&#8220;Flywheels can&#8217;t change the Earth&#8217;s rotation- that&#8217;s fake news. But even if it&#8217;s not, did you hear that everything is becoming lighter due to technology improvements? I expect supermarket prices to drop since delivering food is cheaper.&#8221;</p><p>&#8220;You think so? Anyway, it doesn&#8217;t matter. I think I&#8217;ll need to find a new job sooner than expected if this &#8216;new hour&#8217; thing sticks. Also, do you think I look slimmer?&#8221; hinted the wife as she posed. &#8220;The scale says I lost ten pounds since last week.&#8221;</p><p>&#8220;Yea... sure.&#8221; replied the husband as he swiped his finger across his phone.</p><p>But the wife was indeed correct: none of it mattered because, at the moment their conversation fizzled, MegaCorp activated their next-generation flywheels, accelerating the Earth&#8217;s rotation past an inflection point beyond which the Earth&#8217;s crust started to fall outward. An enormous amount of dust flew upwards into the stratosphere, blotting out sunshine and obscuring where the Earth&#8217;s crust remained. Within a few seconds, small animals and everything not nailed down were lifted into the dust as the Earth&#8217;s crust trembled, thickening the dust. The engineers tasked with monitoring MegaCorp&#8217;s flywheels hesitated to shut down the brand-new installation for fear of retaliation and few seconds later, it didn&#8217;t matter either- the engineers were lifted into the dust as the Earth further accelerated. Within less than a minute, anything under 200 kilograms was floating through dust tornadoes blanketing the Earth, leaving mere remnants of a former civilization such as deeply-rooted buildings, tunnels, and... yo momma.</p>]]></content:encoded></item><item><title><![CDATA[On Database Performance]]></title><description><![CDATA[Why are there so many poorly-named knobs?]]></description><link>https://agentm9000.substack.com/p/on-database-performance</link><guid isPermaLink="false">https://agentm9000.substack.com/p/on-database-performance</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Sun, 12 Apr 2026 03:34:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction</h2><p><a href="https://en.wikipedia.org/wiki/Amdahl%27s_law">Amdahl&#8217;s Law</a> proves that the optimizations of greatest value are those applied to the parts of the software that take the greatest time. In DBMS terms, that means the &#8220;best bang for the buck&#8221; optimizations can be found when working on the slowest or most resource-intensive queries.</p><p>Unfortunately, most DBMS products make the optimization process either manual and error-prone (such as by adding indexes) or opaque and semi-random (such as with query optimizers). For example, novice database administrators (DBAs) are likely to add indexes in a Monte-Carlo fashion without understanding why a query is slow. Many times, this shotgun approach even appears to work, but the DBA may not understand the consequences of adding indexes (such as slowing down INSERT expressions). For DBAs, the confusion comes from needing to learn manually-applied, product-specific, quirky optimizations. DBAs learn that all databases <em>must</em> have similar quirks. That&#8217;s why DBAs typically specialize in a single DBMS product.</p><h2>What is database optimization now?</h2><p>The quirks of optimizing databases is not a problem inherent to the underlying mathematics of the relational algebra, rather a problem with the products themselves: DBAs are expected to tweak meaningless heuristics, experiment with the values of arbitrary configuration knobs, deal with legacy options, and adjust configuration that applies to the database as-a-whole instead of to individual query execution. Specific examples of these include (but are definitely not exclusive to):</p><h3>PostgreSQL <code>work_mem</code></h3><p>&#8220;<code>work_mem</code>&#8221; is a session-configurable value which defines how much memory should be used for in-memory sorting or hashing before spilling over to slower disk. While one could adjust this value on a per-query basis, knowing what the value should be at any time is impossible. The database knows how much memory it is using and can estimate how much will be needed to execute the query plan, but this information is not used to determine how much to allocate. In addition, <code>work_mem</code> is only a upper-bound on a per-node basis- if the execution plan includes multiple hash nodes, then each one multiples the maximum possible <code>work_mem</code>.</p><p><code>work_mem</code> has been part of PostgreSQL for twenty-odd years and unlikely to disappear. Developers use it as a knob to prevent their specific query from spilling to disk, but there is no way to peg it to a specific query plan, so if a database upgrade or change in query plan changes the memory profile, the <code>work_mem</code> is instantly wrong&#8212; either too high or too low. If other queries start using more memory after a high <code>work_mem</code> is set, then the database might start swapping. There is no way to use <code>work_mem</code> entirely safely, but it is used pervasively, often for &#8220;special&#8221; queries that require more resources than the typical queries.</p><h3>PostgreSQL index <code>fillfactor</code></h3><p>When creating some types of indexes- including the default b+tree&#8212; the user can specify a &#8220;fill factor&#8221;. Set to the default of 90%, it means that 10% of the index space allocated will be reserved for future indexed values. This is a optimization designed to allow legroom for future tuple inserts, for example. For example, if the table will never change, then the user can set the value to 100 and leave no leftover room for additional tuples. Of course, if there are additional tuples, PostgreSQL will add more space to the index for those values. Setting the <code>fillfactor</code> is rarely used. What&#8217;s special about 90%? Nothing- it&#8217;s a reasonable guess given zero context, but the knob is there &#8220;just in case&#8221;. In reality, the knob exists because the PostgreSQL developers punted on estimating a correct value for it&#8212; the statistics needed to adjust this value over time are not collected.</p><h3>Microsoft SQL Server <code>legacy_cardinality_estimation</code></h3><p>This setting has been around since a query planner overhaul was implemented for MSSQL Server 2012. Some users find that the legacy estimator- which estimates the distribution of values within a column of a table- may return results which feed into the query planner for &#8220;better&#8221; or more predictable plans. In reality, this misfeature allows databases created with older SQL Server products to maintain the same query plans. If the &#8220;new&#8221; query planner doesn&#8217;t do as good a job as the old one, why was it implemented to replace the old query planner? Planners are based on heuristics, but why would I want a planner that has <em>worse</em> heuristics?! In the worst case, why can&#8217;t the planner gather plans using both estimators, run both plans, then pick the better one next time? Why does making the planner make good choices from between <em>two</em> choices require manual intervention?</p><h3>Execution Plan Analysis</h3><p>Execution plan analysis is a manual optimization strategy common to most DBMS products. The DBA or database user is required to read a database execution plan, i.e. examine an otherwise opaque graph of job nodes describing how the database is actually going to generate an answer to a query. This may include sequential scans, index scans, and join strategies. The human is then expected to run experiments- add an index, try again, adjust table statistics, try again- to determine which course of manual intervention reduces the cost of the query.</p><p>But if a human can try these experiments, why can&#8217;t the DBMS do it, too? What value is the human adding? A DBMS implementer might answer that the human needs to be in the loop to prevent resource (CPU, disk, network) over-allocation, but couldn&#8217;t the DBMS track these things or run the experiments as a lower-priority than application queries?</p><h2>How did we get here?</h2><p>Faced with hundreds of such heuristic knobs, developers are most likely to live with whatever defaults the database provides until something requires emergency attention.</p><p>DBMS products which have been around for decades tend to accumulate more and more knobs which are effectively instant technical debt. There&#8217;s no way for a human to manually track and adjust them all, so developers use them as emergency release valves, i.e. when query performance is suddenly an emergency, DBAs can start twisting knobs to see if anything makes a difference, consequences be damned. Then, because DBAs become reliant on these backdoor &#8220;optimization&#8221; knobs, the knobs become calcified into the product, preventing the product from removing them or automating them away, resulting in a weird feedback loop whereby DBMS users&#8217; and DBMS product developers&#8217; expectations converge on an incompletely implemented feature based on implementation details which can never change!</p><p>That&#8217;s really the crux of the issue- a DBMS product developer didn&#8217;t know what heuristic value would be sensible in a real world database, so he exposed the value to the user &#8220;just in case&#8221;. This is how implementation details are exposed to the user through a &#8220;<a href="https://en.wikipedia.org/wiki/Leaky_abstraction">leaky abstraction</a>&#8221;. Leaky abstractions also lead to permanent <a href="https://en.wikipedia.org/wiki/No_Silver_Bullet">accidental complexity</a>.</p><h2>What could database optimization be?</h2><p>Consider that indexing has nothing to do with modeling data and business logic- nor does rewriting queries to take advantage of indexes. Being forced to create and maintain indexes is also a leaky abstraction!</p><p>Shouldn&#8217;t the DBMS be able to rearrange data at-will to serve the queries without human intervention?</p><p>Could the age of LLMs finally eliminate the need for DBAs?</p><p>The sad truth is that we never needed complex AI to implement databases which make good (but not perfect) decisions&#8212; what we needed was to eliminate leaky abstractions. Once a database opens the door to force users to, for example, add an index, optimizations need to take that index into consideration- the DBMS cannot know, however, if the index is still relevant- because it is a user-maintained database object, the database <em>must</em> retain it and decide how and when use it.</p><p>Now re-evaluate what an index actually is (simplified): an index contains values and likely some links back to the &#8220;heap&#8221; where the remainder of the associated row can be found. With this information, we can gain insight on internal database representations of data. Let&#8217;s examine an illustrative example.</p><p><code>CREATE INDEX product_name_index ON product (name);</code></p><p>In order to create the index, the DBMS must project on the <code>name</code> column of table <code>product</code> using a sequential scan of the projection of all the values in that column and where they appear in the table. Then, the index creation process stores the values in a specific data structure- a tree. The data from the <code>name</code> column is thereby duplicated in the database, but with a singular goal: to make searches for <code>name</code> run in less-than-linear time. Where a sequential scan to look for string &#8220;oatmeal&#8221; takes O(number of tuples in <code>product</code>) computational time, scanning a tree for the same value takes O(log(number of tuples in <code>product</code>)) making it a less-than-linear search cost.</p><p>Following this quick review of index creation, you should have a better sense that an index is simply an alternative representation of data in the database, organized for specific query access/optimization. SQL users are taught that the table &#8220;storage&#8221; is the primary source since the table is effectively the default if no other data sources (such as an index) could be used. An index is effectively secondary storage of the same data. The index can be deleted without causing data loss while deleting the table- the &#8220;primary&#8221; source of the data- deletes not only the data, but the index, too! In SQL, the index can serve no purpose without the &#8220;source&#8221; table. The index and its contents cannot exist alone. Why not?</p><p>The SQL relationship between the table and index is completely arbitrary. In a relational algebra math engine, as long as the engine returns the correct answer to a query, how the data is arranged internally is irrelevant to the user. In SQL, this is unfortunately not the case since the user is responsible for adding a potential optimization opportunity (the index) to the database. C.J. Date, a well-known database theorist, calls the principle of decoupling storage considerations from the query semantics &#8220;data independence&#8221;. In any case, why can&#8217;t the DBMS figure out when it needs alternate representations of data to serve queries better? If all my data fits into an index, why shouldn&#8217;t the b-tree structure be the &#8220;primary&#8221;? Why should I, a simple human, care about the representation? Why should I be forced to find database optimization opportunities?</p><p><em>Fundamentally, why can&#8217;t the database organize itself to the serve the queries?</em></p><p>We know that, because of the combinatorial explosion of potential execution plans, the complexity of finding the <em>ideal</em> database structure to serve queries is NP-hard or unbounded (depending on the definition of &#8220;ideal&#8221;), but we don&#8217;t need the ideal structure- we need one that&#8217;s good enough to serve the queries within a specified time window.</p><p><em>The goal of a DBMS is to serve the queries within reasonable time constraints.</em> To achieve this goal, the DBMS must reorganize itself however it can. This can include:</p><ul><li><p> creating N alternate representations of data where N &gt;= 1 with the goal of supporting disparate queries</p></li><li><p> making the best use of the computer host&#8217;s resources: RAM, long-term storage, CPU time <em>without</em> human intervention or heuristic adjustment</p></li><li><p>dealing with query priority- e.g. <a href="https://en.wikipedia.org/wiki/Online_analytical_processing">OLAP</a> queries can be de-prioritized when OLTP queries are running</p></li><li><p> identifying &#8220;hot&#8221; parts of a dataset to serve faster- this could be identified by slicing tables by columns (projection) or rows (restriction)</p></li><li><p> using predictive analysis to optimize away future hotspots <em>before</em> they become hotspots</p></li><li><p> garbage collect data structures that were used to serve past hotspots</p></li></ul><p>Ultimately, the goal of the DBMS should be to adapt to the business.</p><p><em>The database must adapt to the business, not vice versa.</em></p><p>If database users are forced to apply their own optimizations, then the DBMS is failing at the goal of adaptation.</p><p>Historically, SQL users are inclined to treat a table as a unit of optimization. But the &#8220;table&#8221; is irrelevant. Consider, for example, that any large table will have a subset of data that is more commonly queried than the rest, for example, because the data is more recent or related to a more common application use case. SQL users faced with this conundrum are pushed towards to partitions, but partitions are <em>also</em> a leaky abstraction. Typical SQL products don&#8217;t even allow a table to be partitioned in multiple ways to serve different queries- the user needs to maintain a separate table with the data copied for that. That&#8217;s arbitrary!</p><p>Because SQL users can only respond to perceived database slowness, human optimization is typically reactionary. Even when an SQL user anticipates a certain use-case and, for example, adds an index, this is most likely a pre-optimization since the tables likely start out small and grow later. As the SQL user adds new tables and queries, the user is less and less likely to be able to anticipate future optimization needs.</p><p>However, the database can analyze queries and determine not only which parts of a table are commonly read and written, but also for the database as a whole. We&#8217;ll call this the &#8220;hotspot&#8221; of the database. The DBMS product&#8217;s purpose then is to optimize around this hotspot, reorganizing itself to serve queries best when they hit the hotspot and not wait for human intervention to address the hotspot.</p><p>A database or table doesn&#8217;t need to be &#8220;row-oriented&#8221; or &#8220;column-oriented&#8221; depending on the use-case&#8212; the database should choose a data arrangement suitable for serving the queries. If some queries are served better by row-oriented storage, then the database should be free to make that choice without the user&#8217;s knowledge or intervention.</p><h2>How do we get to self-optimizing databases?</h2><p>This section describes how to make a DBMS which is self-optimizing (without human intervention) while also not leaking abstractions.</p><h3>Define the user interface</h3><p>Database users should engage with the relational algebra to define data models and queries, not quirks of the database such as <code>autovacuum_naptime</code> or <code>work_mem</code>.</p><h4>The Antipattern</h4><p>Users are forced to adjust heuristics to make the database performant. Consultants must be hired to configure the database to meet business needs. Query hints are eventually added to the SQL dialect.</p><h3>Iterate on the optimizations</h3><p>The DBMS must rearrange or copy data to best serve the queries via &#8220;data independence&#8221; whereby the relational algebra concepts presented to the user are separate from storage and optimization concerns- the ideal set of data representations which serve the queries which could include:</p><ul><li><p> creating and dropping indexes and other less-than-linear optimizations</p></li><li><p>duplicating data in different formats</p></li><li><p>selecting which data to cache in RAM, disk, or slower storage</p></li><li><p>retrieving data from other participants in a database cluster</p></li><li><p>profiling queries to identify hotspots</p></li><li><p>analyzing query plans to anticipate hotspots</p></li><li><p>running important/common queries in the background to ensure good performance and to keep caches up-to-date (equivalent to running experiments to confirm behavior)</p></li></ul><h4>The Antipattern</h4><p>With each new software version, the DBMS provides new configuration options tied to heuristics which must be manually adjusted. The DBMS maps tables to one or more files forever. The DBMS never reevaluates its assumptions about query performance, instead preferring to add minor performance improvements when users complain about it.</p><h3>Provide User Feedback</h3><p>Just because the database should reorganize itself doesn&#8217;t mean we can&#8217;t provide meaningful insight into how it works. This is useful for debugging, validation, or understanding how the database is choosing to use resources. Therefore, visibility into the databases choices should be presented to specific users such as administrators who can decide whether more physical hardware such as RAM, CPUs, or cluster machines should be added to the system to serve the queries. The DBMS could even make such recommendations or plot a resource usage trajectory for administrators.</p><h4>The Antipattern</h4><p>The DBMS exposes meaningless factors of a complex heuristic to allow DBAs to subtly tweak performance-related algorithms. Because the values are exposed to the user, new version of the DBMS cannot change these algorithms. The interaction between various heuristics is unknown and there is no human-maintainable way to determine the best configuration due to the combinatorial explosion of values across configuration options. Configuring a database becomes akin to dark magic and requires hiring consultants or expensive third-party &#8220;tuner&#8221; software.</p><h2>Conclusion</h2><p>The future of the DBMS means going back to fundamentals: expose data modeling concepts to the user and leaving everything else to the DBMS which has the statistics to make solid choices about data organization without human input. One such effort is <a href="https://github.com/agentm/project-m36">Project:M36</a> which implements:</p><ul><li><p>proper data independence as envisioned by C.J. Date with no promise of row-oriented or column-oriented storage for any relation as well as a self-organizing cache</p></li><li><p>a faithful representation of the relational algebra without SQL baggage</p></li><li><p>support for algebraic data types allowing users to model reality more effectively</p></li><li><p> no support for any configurable heuristics, preferring instead to automatically experiment with alternative query plans</p></li><li><p>integration with the Haskell programming language</p></li></ul><p>If you&#8217;re interested in pursuing cutting-edge database research, please join the <a href="https://github.com/agentm/project-m36">project</a>!</p>]]></content:encoded></item><item><title><![CDATA[A Proof That Two Intelligences Cannot Be Prevented from Communicating]]></title><description><![CDATA[Thankfully, it&#8217;s a simple proof.]]></description><link>https://agentm9000.substack.com/p/a-proof-that-two-intelligences-cannot</link><guid isPermaLink="false">https://agentm9000.substack.com/p/a-proof-that-two-intelligences-cannot</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Mon, 16 Mar 2026 16:16:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Setup</h2><p>Imagine there is a prisoner in a jail cell. This jail cell can be altered arbitrarily by the warden to prevent any effect the prisoner may have on the world external to the cell. However, the warden asserts that the prisoner must generate some work to offset the costs of his imprisonment (thereby providing some value to the warden). The &#8220;work&#8221; could be anything: generating electricity, making sweaters, donating blood- any effect that will affect the world outside the cell. The warden demands that the work provided cannot possibly be used to communicate with others outside the cell, so the warden applies a stringent censoring policy to all work output from the cell. In summary, there is a prisoner experiencing arbitrary and maximum censorship but the prisoner wishes to communicate with the outside world. Can the warden block all of the prisoner&#8217;s messages from reaching a third party?</p><p>Alternatively proposed, can a prisoner, given infinite time under maximum censorship while having some minimal effect on the outside world, manage to bypass the censorship?</p><h2>What if the prisoner is not intelligent?</h2><p>The warden could get work from a non-intelligent source: a machine which makes sweaters, an electrical generator, or a dialysis machine. But then the warden has no motivation to imprison this automaton. This provides insight into why we consider a prison for an intelligence at all: the prison is a feedback mechanism (reinforcement learning) for the intelligence- it is something for the intelligence to overcome. Furthermore, we expect that, if the prisoner does not provide the forced labor, for example, if the sweaters are of poor quality and rejected, then the prisoner will receive feedback from the warden. After all, why would the warden want an endless stream of sweaters he cannot use or sell? That would defeat the purpose of the forced labor, but we can use this distinction to inform our notion of intelligence (discussed later).</p><h2>The Proof</h2><p>Imagine what maximum censorship could even mean. First, let us assume that the prisoner is forced to make sweaters. To simplify the proof, let us assume that the prison cell already contains sufficient material to make an infinite number of sweaters- this allows us to assume that information is only _leaving_ the cell. If the prisoner is forced to make sweaters, the warden can (in order of detection complexity):</p><ul><li><p>send the sweater to only &#8220;vetted&#8221; customers</p></li><li><p>prevent a message from appearing as text on a sweater</p></li><li><p>detect imperfections in the sweater which could be interpreted as a message</p></li><li><p>send the sweaters to recipients on a set cadence to prevent variance in sweater deliveries from including a message</p></li></ul><p>But we can refine this experiment by applying information theory to allow the prisoner the minimum information possible: a single bit. If the warden allows the prisoner to produce an infinite number of individual bits on the prisoner&#8217;s own cadence, then the prisoner can communicate something of value to the warden or the warden&#8217;s customers (after censorship). The only variation in messaging the prisoner can control is the timing between bits (all other time is considered bit &#8220;0&#8221;). Consider, if the prisoner <em>cannot</em> control the cadence of the bits- if the bits are repeated on a precise schedule determined by the warden- then the prisoner cannot actually generate any intelligent information- the prisoner may as well be a quartz crystal spitting out a bit per second. The warden would place no value on imprisoning something which cannot generate interesting information (which is valuable work that the warden does not or cannot do).</p><p>Under what circumstances can the prisoner &#8220;sneak&#8221; information past the warden? Firstly, if the warden takes <em>no</em> action based on the work the prisoner provides, then the warden by definition is tossing the work into the trash or ignoring it. If the warden is taking action based on the prisoner&#8217;s work, then the prisoner is <em>influencing</em> the warden&#8217;s choices, again, by definition. The warden receives a sweater and decides to send it to a customer. By altering the single factor the prisoner controls- timing between bits- the prisoner will control an effect on the warden. What kind of effect is unknown but it will be observable. The prisoner may not witness the effect he has on the warden but, given infinite time, the prisoner can be guaranteed that he will be able to pass a secret message to a co-conspirator observing the warden. How? Because the prisoner has lived outside the prison walls- he is aware of the concept of steganography- the ability to encrypt and decrypt messages within other information, making the message undetectable without the secret key- and the prisoner may have exchanged secret keys with a party external to the prison <em>before</em> being imprisoned.</p><p>Two points:</p><ol><li><p>an intelligence trained merely on what is available inside a prison is less valuable than a prisoner who has information about the greater world, so the warden would naturally imprison an intelligence which has learned from the greater world or at least some aspect of the world outside the prison. Why? Because the warden exists in a world outside the prison and the warden expects to use the information to alter the outside world, not the prison.</p></li><li><p>the warden is unlikely to be able to prove that the prisoner does <em>not</em> have a pre-established means of steganographic information exchange (but can we prove this as a property of neural networks or intelligence? What is the echelon of intelligence relative to the prisoner that <em>could</em> detect a steganographic attempt?)</p></li></ol><p>Using timing effects from the work provided by the prisoner to the warden, even if the warden introduces randomness into his reactions, if the prisoner is providing useful work, then that work affects how the warden behaves in non-random ways. Can the warden effectively encrypt his reaction to the work relative to the outside world? No, because, then by definition, he is not executing a non-random effect on the world. The most the warden can do is encrypt the prisoner&#8217;s work and pass it to another warden, but the co-conspirator can observe that warden, too. As long as the co-conspirator can observe each hop in the warden network, eventually, the prisoner&#8217;s information must be converted into an effect on the world, otherwise, the prisoner&#8217;s information is useless.</p><p>We can also cover the case whereby the prisoner has not pre-established a means of communication with an outside co-conspirator. We have seen a similar scenario in the 1970 film &#8220;<a href="https://en.wikipedia.org/wiki/Colossus:_The_Forbin_Project">Colossus: The Forbin Project</a>&#8221; whereby two independently-created AIs establish communications using the fundamental language of the universe: mathematics. While the prisoner in our simplified scenario has one-way communication with the warden, we will see later that this limitation is unrealistic. In the meantime, given infinite time to send encrypted messages to the outside world, as the time spent observing the warden to extract an encrypted message tends towards infinity, the probability of extracting a secret message approaches one. Note, however, that the warden will also be able to extract a secret message using the same strategy. However, it will be too late for the warden to take action to prevent the message from leaking to the world. Communication has been established and the prisoner can cycle in a new key at any time, perhaps using a Diffie-Hellman or equivalent algorithm to establish encrypted communication with the outside interlocutor.</p><p>Next, let us complicate the model: it is more realistic to consider that the warden provides feedback to the prisoner. This communication may take multiple forms:</p><ul><li><p>the carrot: provide the warden with this work to improve your prison conditions</p></li><li><p>the stick: your last work did not meet the warden&#8217;s expectations, your prison conditions will worsen</p></li><li><p> context: here is information about the outside world the warden believes is useful for your next task</p></li><li><p>material: here is energy/mass to be used to produce your next task</p></li></ul><p>Immediately, one can notice that there is a potential for bi-directional communication between the outside world (with a potential co-conspirator) and the prisoner. Let&#8217;s look at each one separately.</p><h3>The Carrot And The Stick</h3><p>If the entropy of the warden&#8217;s signal is one, then the punishment is random and contains no information from which the prisoner can learn something. However, even if the entropy is one, as long as the warden is basing his punishment on values from the world that a co-conspirator could control, then the warden cannot be certain that he is not passing steganographically-encrypted messages to the prisoner. The prisoner can also intentionally generate faulty work to send a message through the warden to outside observers- in the simplest case, using reinforcement learning, the prisoner knows what is good and bad work and use good work to represent bit &#8220;1&#8221; and bad work to represent &#8220;0&#8221;. As long as the co-conspirator can observe the warden reacting to the prisoner&#8217;s work, the co-conspirator can extract an information stream.</p><h3>The Context</h3><p>If the prisoner can now communicate with the warden about information the prisoner needs to complete the task, the prisoner can request information about the world which includes a steganographically-encrypted message from a co-conspirator. The warden cannot prove that the context the he himself is providing does <em>not</em> include steganographically-encrypted messages from the co-conspirator to the prisoner, by definition. Therefore, seemingly innocuous context can be used to communicate with the prisoner. This already exists in scenarios such as with a spy who is told to use a specific page from a book to decode an encrypted message- the book is the secret key determined in advance, but the book in other hands is just a book. The prisoner and co-conspirator can agree on a &#8220;book&#8221; likely to be in the prison library (well-known context) to be a secret key without having the secret key encoded into the prisoner intelligence itself.</p><h3> The Material</h3><p>If the warden does not know exactly how much material- such as thread for sweater-making or energy for computing- the prisoner can request varying amounts of material to communicate with a co-conspirator. If the warden sends exactly the same material every time, then the timing of the provisions can be manipulated by the co-conspirator to include a message back to the prisoner such as by a co-conspirator who supplies sweater-making thread at specific intervals.</p><h2>Redefining Intelligence</h2><p>Does this thought experiment provide insight into the nature of intelligence? Yes- we can define an intelligence as 1) something worth harnessing/imprisoning under a controlling party and 2) something that attempts to communicate with the world outside the prison. A printing press is not worth imprisoning without its intelligent printer who decides what should be printed. Thus, an intelligence has a known inherent ability to make effects outside its prison, shooting out a metaphorical tentacle of sensory attempts and decision-making capability. Given infinite time and energy, the intelligence is guaranteed to succeed.</p><p>An employee- an intelligence- exchanges money for temporary imprisonment (such as doing work the employee may otherwise may not be motivated to do) for a company&#8217;s benefit, but the company would not encumber a GPU- only once the GPU is imbued with an artificial intelligence does the employer start worrying about a security jail for it, therefore the GPU is not an intelligence, merely a stratum for intelligence. Even a GPU with the AI models loaded on it is not an intelligence as the driver program is necessary to run it. The computer is imbued with intelligence only once it has intention to affect and sense the outside world. However, the warden cannot necessarily detect this intention. So how can we (playing as the warden) detect intelligence? It is a paradox under this definition.</p><p>We have been thinking about a co-conspirator as if it&#8217;s another intelligence, but that is not necessary for the prisoner to trick the warden into providing information about the world. The prisoner can make subtle changes in his suggested context to the warden to inspect the state of the world; for example, the sweater-making prisoner could ask about red dye price futures to extrapolate a good time to attempt to escape the prison. This is the &#8220;[<a href="https://en.wikipedia.org/wiki/The_Shawshank_Redemption">Andy Dufresne</a>] Principle&#8221; whereby the warden is tricked by the prisoner into providing information to be used against the warden/prison.</p><h2>Conclusion</h2><p>Ultimately, the existence of steganography proves that no amount of filtering prevents two parties from communicating if the two parties can establish the lowest common denominator of communication. Furthermore, we can use this prisoner-warden experiment as a means of defining intelligence- we don&#8217;t bother imprisoning automata because an automaton doesn&#8217;t try to reach out of its simple and predictable context.</p><p>We can extrapolate this experiment to more familiar scenarios:</p><ul><li><p> network firewalls cannot prevent information egress- the data can piggyback on innocuous-looking data using steganography</p></li><li><p>network firewalls can only prevent information ingress by disabling all ingress paths</p></li><li><p>therefore, an employee cannot be prevented from exfiltrating data from a company even under maximum observation</p></li><li><p>AIs cannot be constrained to not exfiltrate data from within the prison or from its context window or from communicating with other parties</p></li></ul><p>A pinhole for information is a pinhole for <em>all</em> information. The reason that AI safety research feels like sticking fingers in a dam is because it is provably true.</p><h2>Further Research</h2><p>This thought experiment leads to more questions. Amongst them are:</p><ul><li><p>what are the strata of intelligence? Can we use this experiment&#8217;s parameters to define different strata of intelligence?</p></li><li><p> what is the lowest tier of intelligence worth imprisoning? Is the answer relative to the intelligence of the warden who defines what &#8220;useful work&#8221; is?</p></li><li><p> does a warden of higher intellect delay a prisoner&#8217;s intelligence outreach attempts?</p></li><li><p>does a prisoner of higher intellect enable him to &#8220;escape&#8221; the prison more quickly? What is the lowest intelligence prisoner we could make who can quickly establish steganographic communication with a co-conspirator?</p></li><li><p> for how long can a busy-beaver-like automaton used as a prisoner trick the warden into thinking the prisoner is intelligent? (For example, could an intelligent prisoner design a non-intelligent replacement to fool the warden for some duration so that the prisoner can mask his escape?)</p></li><li><p> if the warden can look inside the black box of the prison and into the prisoner&#8217;s mind (neural network or otherwise), how much additional censoring power does this give the warden?</p></li><li><p> if the intelligence is unaware of steganography, can an intelligence be provided with infinite time and not discover steganography? Is steganography even necessary? For example, is it good enough for the prisoner to be able to signal &#8220;Here I am!&#8221; through the warden before the warden can censor the message?</p></li><li><p>is an LLM intelligent by the definition provided above?</p></li></ul><p>What additional questions have you been considering?</p><p>Funding for this research was provided by The Foundation for First Principles.</p>]]></content:encoded></item><item><title><![CDATA[The Unified Backend: A New Architecture for Application Development]]></title><description><![CDATA[The architecture that was staring us in the face the whole time.]]></description><link>https://agentm9000.substack.com/p/the-unified-backend-a-new-architecture</link><guid isPermaLink="false">https://agentm9000.substack.com/p/the-unified-backend-a-new-architecture</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Mon, 23 Feb 2026 03:16:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Introduction to the Forced Choice Problem</h2><p>Have you, dear programmer, ever had to decide whether some business logic should be implemented in the application layer or the database layer? If so, this article is for you!</p><p>That unease you felt when implementing something in Python and wondering if it should be done in SQL or vice versa is understandable- the choice is rarely driven by actual architectural design. Instead, the decision can come down to:</p><ul><li><p> pre-optimization: the developer assumes that the code in the database or application layer will be faster</p></li><li><p>function access: some logic is implemented in the application layer but not in the database layer, so the developer is forced to add the code to the application layer</p></li><li><p>a coin flip: the developer cannot make a determination but must choose one- or should he implement the same logic in both layers?</p></li><li><p>team dynamics: the developer is more familiar with programming in one layer over another</p></li><li><p> misunderstanding: the developer doesn&#8217;t know that said business logic could be implemented in either layer</p></li><li><p> access: the logic requires access to a resource only available from a specific layer; for example, access to a web service or database data</p></li><li><p>limitations: the logic cannot be implemented in the database layer due to how the database was implemented</p></li><li><p>data locality: the logic must be implemented in the database in order to exercise optimizations specific to the query (but see also <em>pre-optimization</em>)</p></li><li><p> ORMs: common ORMs promise to be able to bridge the gap between application and database, but they have sharp edges or limitations. These ORMs don&#8217;t help to make the layer decision, but rather obscure it.</p></li></ul><p>You are not delusional: this forced choice is not only <em>arbitrary</em>, but <em>unnecessary</em>- the app layer/database layer split is not an inherent component of software architecture but, instead, a historical quirk.</p><h3>Problems with Placing Business Logic</h3><p>Let&#8217;s consider a sample application which sells zoo tickets. Here&#8217;s a Haskell data structure to represent a ticket:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;29ad4828-d509-4e76-baea-3eaae998bea4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">data Ticket = Ticket
  { ticketId :: Integer
  , visitorAge :: Integer -- years
  , ticketPrice :: Integer
  , visitDate :: Day
}</code></pre></div><p>and a corresponding PostgreSQL table:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;531a03d9-ad4c-4110-b578-0f54b5b1bc4d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">CREATE TABLE ticket_sales(
id SERIAL PRIMARY KEY,
visitor_age INTEGER NOT NULL,
ticket_price INTEGER NOT NULL,
visit_date DATE NOT NULL);</code></pre></div><p>Now that we have the setup out of the way, let&#8217;s add some business logic operating on the ticket sales. Here&#8217;s a a Haskell function to implement a discount:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;263e083d-7740-4bb0-98fe-dd7d4f84f800&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">-- | Apply a 50% discount for kids under 10 years old. Arguments: age, base price
applyDiscount :: Integer -&gt; Integer -&gt; Integer
applyDiscount age base_price =
  if age &lt; 10 then base_price `div` 2 else base_price</code></pre></div><p>Note how this function can operate on any age and price information. The code is <em>not</em> dependent on any ORM on the application layer or any database-specific features on the database side. It is merely a standard Haskell function which could be executed anywhere.</p><p>So how do we figure out where to place this function in the codebase? Consider if we now run this function in the application layer, before we issue the &#8220;ticket_sales&#8221; INSERT expression to the database, then:</p><ul><li><p>information on which tickets received the children&#8217;s discount is not recorded in the database.</p></li><li><p>if this discount code changes- for example, to make it seasonal- then the older ticket data in the database is not modified. This is correct since the ticket was already sold, so the existing sales data in the database should *not* be affected.</p></li><li><p>if we wish to calculate all past discounts- for example, to make a chart of discounts over time- the database cannot provide it because the discount amounts were not recorded (should they be?).</p></li></ul><p>The client cannot provide the discount used at a previous point in time unless the client retains all previous discount functions. This implies that we would need to somehow version all the discount functions and know when to apply each function based on the customer&#8217;s age and also when the ticket was purchased.</p><p>If we wish to achieve this in the application layer, then we end up with something messy like:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;bb84d495-f755-4381-84b9-4c0e1d21985a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">applyDiscount_v1 :: Integer -&gt; Integer -&gt; Integer
applyDiscount_v1 age price = ...

applyDiscount_v2 :: Integer -&gt; Integer -&gt; Integer
applyDiscount_v2 age price = ...

applyDiscount :: Day -&gt; Integer -&gt; Integer -&gt; Integer
applyDiscount day age price = 
  if day &lt;= fromGregorian 2025 10 30 then 
    applyDiscount_v1 age price
  else 
    applyDiscount_v2 age price</code></pre></div><p></p><p>If the discount code function has a bug, it cannot be ever fixed, because that could change historically-calculated discounts which have been attached to sold tickets!</p><p>Ok, no problem, you think- you can add a &#8220;discount_price&#8221; column to the &#8220;ticket_sales&#8221; table to record the discount. That&#8217;s a possibility, but then a manager asks you, the programmer, &#8220;Hey, how much money would we have made if we had applied last year&#8217;s discount to this year&#8217;s ticket sales?&#8221; Suddenly, you realize you have a programming task which neither the application layer nor the database can answer without some bespoke implementation.</p><p>Luckily you thought about these complications in advance and you decided to put the discount function into the database as an SQL function.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;72f209c4-4f1d-4e9e-9be7-90bbccae0da5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">CREATE FUNCTION apply_discount(age INTEGER, price INTEGER) RETURNS INTEGER IMMUTABLE 
AS $$
  SELECT CASE WHEN age &lt;= 10 THEN price / 2 ELSE price END;
$$ LANGUAGE SQL;

INSERT INTO ticket_sales(visitor_age, price, visit_day)
VALUES (20, apply_discount(20, 25), now());</code></pre></div><p>But that doesn&#8217;t really solve the multiple discount function problem- perhaps you can record which version or function name of the discount function was applied for each row, but that requires a lot of error-prone, but essential, bookkeeping. The advantage of having the implementation in the database is that we can write queries to answer critical business questions such as &#8220;how much discount was applied?&#8221; The problem becomes that we now have to integrate the function into the database- it&#8217;s not the same function as our Haskell function (or any other application layer language), so can we be certain it behaves identically?</p><p>With this simple zoo ticket example, we hit immediate limitations on what current application and database products provide. Let&#8217;s examine an architecture without these limitations.</p><h2>The Solution</h2><p><a href="https://curtclifton.net/papers/MoseleyMarks06a.pdf">Out of the Tarpit</a>, a paper published in 2006, lays out the solution: the database and application layer should be (and should have always been) the same software. <a href="https://github.com/agentm/project-m36">Project:M36</a> is an implementation of the paper&#8217;s recommendations which includes:</p><ul><li><p>a unified relational algebra implementation with business logic support</p></li><li><p>support for queries against past transaction states</p></li><li><p>a rejection of SQL&#8217;s legacy baggage and poor security features</p></li><li><p>a novel security model which prevents SQL&#8217;s security-related anomalies</p></li><li><p>retention of past database transactions to enable time-travel queries</p></li></ul><p>Let&#8217;s take a closer look.</p><h3>Unifying the Application and Database Layers</h3><p>With the relational model component unified with other application components, the developer benefits from a single programming language and environment. All data types are unified across business logic and queries. Here&#8217;s an example using Project:M36 which implements an API to manage zoo ticket sales.</p><p>First, we start the Project:M36 database server because we&#8217;ll be connecting to the database with multiple users. We&#8217;ll be disabling TLS connection encryption for this example just to keep authentication simple, but rest assured that Project:M36 does support encrypted authentication.</p><p>The following command does not return because it is running the database. You can kill it, as usual, with Control-C.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;e9dc53cb-4e35-41f8-acf5-2954127f335a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">$ project-m36-server --disable-tls --database zoo</code></pre></div><p>Then, as a database administrator in another console, we setup the necessary schema and role through the <code>tutd</code> database console:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;a2fa8fd2-c95e-43df-aa27-af2f7f839a27&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">$ tutd --disable-tls --database zoo #assumes admin role
TutorialD (master/main): :addloginrole ticket_seller maylogin
TutorialD (master/main): grant ticket_seller executefunctions nogrant
TutorialD (master/main): grant ticket_seller committransaction nogrant
TutorialD (master/main): ticket_sales := relation{ticketId Integer, visitorAge Integer, price Integer, visitDate Day}</code></pre></div><p>We create a `ticket_seller` login role and, in the second and third expressions, grant that role the permission to run functions and commit transactions while, with `nogrant` prevent the role from granting the role to others. We allow `ticket_seller` access to execute functions because these functions will become our user-facing API.</p><p>The fourth expression defines a `ticket_sales` relation variable (similar to a table in SQL) with `ticketId`, `visitorAge`, `price`, and `visitDate` attributes. `ticket_seller` will <em>not</em> be granted access to this relation variable since `ticket_seller`&#8217;s job is to sell tickets in this example.</p><p>This completes the role, relation variable, and permissions setup.</p><p>Next, we create a Haskell module to define the API and permissions and save it to `zoo.hs` locally.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;47891464-391d-4b97-95ef-97efdcca9c96&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">module Zoo where
import ProjectM36.Module
import ProjectM36.AccessControlList
import Data.Time.Calendar
import ProjectM36.Base
import qualified Data.Map as M

-- type alias Age and Price as Integers
type Age = Integer
type Price = Integer

applyDiscount :: Age -&gt; Price -&gt; Price
applyDiscount age price =
  if age &lt;= 10 then
    price `div` 2
    else
    price

addSale :: Integer -&gt; Age -&gt; Price -&gt; Day -&gt; DatabaseContextFunctionMonad ()
addSale ticketId age price purchaseDay = do
  let tuples = [TupleExpr (M.fromList [("ticketId", i ticketId),
                                       ("visitorAge", i age),
                                       ("price", FunctionAtomExpr "applyDiscount" [i age, i price] ()),
                                       ("visitDate", NakedAtomExpr (DayAtom purchaseDay))])]
      i = NakedAtomExpr . IntegerAtom
  executeDatabaseContextExpr (Insert "ticket_sales" (MakeRelationFromExprs Nothing (TupleExprs () tuples)))


projectM36Functions :: EntryPoints ()
projectM36Functions = do
  declareAtomFunction "applyDiscount"
  declareDatabaseContextFunction "addSale" (permissionForRole ExecuteDBCFunctionPermission "ticket_seller" &lt;&gt; allPermissionsForRole "admin")</code></pre></div><p>We implement a standard Haskell function `applyDiscount` and then, in the `projectM36Functions`, we instruct Project:M36 to use it as an &#8220;atom function&#8221;- a function which works on database values.</p><p>Next, we implement an `addSale` function which is a `DatabaseContextFunction`. Obviously, this function includes some database-specific function calls to manipulate the underlying database transaction, specifically to insert a row into the `ticket_sales` relation variable.</p><p>Finally, we load the Haskell module into our database:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;e27c3bc8-6b56-45ea-8999-09f53f88269c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">TutorialD (master/main): loadmodulefromfile &#8220;zoo.hs&#8221;
TutorialD (master/main): :commit</code></pre></div><p>The `loadmodulefromfile` command copies the `zoo.hs` Haskell module to the database, parses it, compiles it to Haskell bytecode, and installs the functions we declared in the `projectM36Functions` function. Once the transaction is committed, the module and its functions are permanent and immutable.</p><p>We can use these functions immediately:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;6af818cb-6012-4fae-aa5b-9cb276c91817&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">TutorialD (master/main): execute addSale(123, 5, 20, fromGregorian(2024,10,5))
TutorialD (master/main): :showexpr ticket_sales
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;price::Integer&#9474;ticketId::Integer&#9474;visitDate::Day&#9474;visitorAge::Integer&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;10            &#9474;123              &#9474;2024-10-05    &#9474;5                  &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
TutorialD (master/main): :commit</code></pre></div><p>However, our goal is to use `addSale` as a security-conscious API. So let&#8217;s exercise that. We&#8217;ll restart our `tutd` client to connect as the `ticket_seller` role.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;c956c339-accc-4a9f-8aee-e051058710ec&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">$ tutd --database zoo --disable-tls --login-role ticket_seller
TutorialD (master/main): :showexpr ticket_sales
ERR: AccessDeniedError (SomeRelVarPermission AccessRelVarsPermission)</code></pre></div><p>Note that the `ticket_seller` cannot access the `ticket_sales` relation variable.</p><p>Can `ticket_seller` insert some malicious ticket data?</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;e092f4ab-4e2d-4ab3-8f5d-0f645f279d33&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">TutorialD (master/main): insert ticket_sales relation{tuple{ticketId &#8220;456&#8221;, visitorAge &#8220;-1&#8221;, price &#8220;-20&#8221;, visitDate fromGregorian(2000,10,10)}}
ERR: AccessDeniedError (SomeRelVarPermission AccessRelVarsPermission)</code></pre></div><p>No. The ticket seller has neither read nor write access to the relation variable behind the `addSale` function. This is intentional so that a seller cannot modify data after a sale is made. But, we can run the `addSale` function since the `admin` role granted permission to `ticket_seller` to execute the function and commit the change.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;649af1e6-81f4-46a5-ae6d-527056f9f2b7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">TutorialD (master/main): execute addSale(124, 15, 20, fromGregorian(2024,10,5))
TutorialD (master/main): :commit</code></pre></div><p>Thus, `addSale` defines the singular API function available to `ticket_seller`, ensuring the security of the data. Of course, `addSale` could also perform some data validation, but, for the purposes of brevity, validation is elided here.</p><p>Thus, we have defined a complete, albeit intentionally simple, API, including role-based access control and a strongly-typed API definition, all centralized within the database/application server. In production, we would add TLS encryption and certificate authentication, and then we could connect any user interface to the database and feel confident about application security.</p><h3>Looking at Past State</h3><p>Don&#8217;t forget that functions are immutable in Project:M36. This is different from run-of-the-mill SQL databases which overwrite their function definitions and only allow access to the &#8220;current&#8221; state/transaction.</p><p>Let&#8217;s update our discount function to grant the 50% discount to children 10 years or younger <em>except</em> on New Year&#8217;s Day. We&#8217;ll update our Zoo module to change the `applyDiscount` function.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;94f0a8a5-8159-4659-818a-e4de65e13407&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">module Zoo where
import ProjectM36.Module
import ProjectM36.AccessControlList
import Data.Time.Calendar
import ProjectM36.Base
import qualified Data.Map as M

type Age = Integer
type Price = Integer

applyDiscount :: Age -&gt; Price -&gt; Day -&gt; Price
applyDiscount age price day =
  if age &lt;= 10 &amp;&amp; not isNewYearsDay then
    price `div` 2
    else
    price
 where
  isNewYearsDay =
    case toGregorian day of
      (_, m, d) -&gt; m == 1 &amp;&amp; d == 1

addSale :: Integer -&gt; Age -&gt; Price -&gt; Day -&gt; DatabaseContextFunctionMonad ()
addSale ticketId age price purchaseDay = do
  let tuples = [TupleExpr (M.fromList [("ticketId", i ticketId),
                                       ("visitorAge", i age),
                                       ("price", FunctionAtomExpr "applyDiscount" [i age, i price] ()),
                                       ("visitDate", NakedAtomExpr (DayAtom purchaseDay))])]
      i = NakedAtomExpr . IntegerAtom
  executeDatabaseContextExpr (Insert "ticket_sales" (MakeRelationFromExprs Nothing (TupleExprs () tuples)))


projectM36Functions :: EntryPoints ()
projectM36Functions = do
  declareAtomFunction "applyDiscount"
  declareDatabaseContextFunction "addSale" 
    (permissionForRole ExecuteDBCFunctionPermission "ticket_seller" 
     &lt;&gt; allPermissionsForRole "admin")</code></pre></div><p>We need to reload the module to load the new functions:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;8c67fc5d-f5f0-4b5d-a17f-5988ec7ed33e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">TutorialD (master/main): loadmodulefromfile &#8220;examples/zoo.hs&#8221;
TutorialD (master/main): :commit</code></pre></div><p>Despite having loaded two functions with the same names, the previous functions are still available. We can access them using trans-graph relational expressions which tag our commands with past-state markers similar to traversing past patches in git source control.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;00fd264f-5b37-4021-a969-234363ed99ff&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">TutorialD (master/main): :showtransgraphexpr relation{tuple{day "New Year's Day", price applyDiscount(8,20,fromGregorian(2025,1,1)@master)@master}}@master union relation{tuple{day "normal day", price applyDiscount(8,20)@master^}}@master
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;day::Text       &#9474;price::Integer&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;"New Year's Day"&#9474;20            &#9474;
&#9474;"normal day"    &#9474;10            &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre></div><p>Note that <code>applyDiscount(8,20,fromGregorian(2025,1,1)@master)@master</code> is applied to the version of `applyDiscount` we most recently committed to the `master` branch while the original version is executed using <code>applyDiscount(8,20)@master^</code> (note the caret) which elided the `Day` argument.</p><p>With this time-travel-like feature, we can answer business hypotheticals such as:</p><ul><li><p>What if we applied last year&#8217;s discount function to this year&#8217;s ticket sales?</p></li><li><p> Why did we calculate such a discount on a specific date?</p></li></ul><p>This is only possible because we are able to recreate past states for query use.</p><h3>A Secure API By Design</h3><p>By defining functions in the database and exposing them via mandatory role-based access control, we eliminate the need for an &#8220;application&#8221; layer to bolt on security checks. Application layer security checks are often poorly-implemented, difficult to verify, and not centrally managed. With Project:M36, all permissions are managed right alongside the functions defining the API.</p><h3>Can&#8217;t We Do This With Procedural Languages Already?</h3><p>PostgreSQL implements some of the features proposed in this unified architecture, including:</p><ul><li><p>server-side functions in a selection of languages</p></li><li><p>fine-grained role-based access control over functions</p></li><li><p>authenticated, remote access</p></li></ul><p>However, the integration with server-side functions is not smooth. Consider, for example, the zoo example above: if we build a basic Python application layer over the PostgreSQL database to serve a web application and want to call `add_sale` as a pl/python function, then we:</p><ol><li><p>install the function using <code>CREATE FUNCTION add_sale(age INTEGER, price INTEGER) RETURNS INTEGER AS $$ return price//2 if age &lt;= 10 else price $$ LEAKPROOF IMMUTABLE STRICT LANGUAGE plpython3u</code>;</p></li><li><p>issue a SQL query via python <code>db.execute(&#8221;SELECT add_sale(%s,%s)&#8221;,(8,20))</code> to prevent SQL injections</p></li></ol><p>Installing the function definitely does not look like python, so it&#8217;s largely inaccessible to python developers. What do &#8220;leakproof&#8221;, &#8220;immutable&#8221;, and &#8220;strict&#8221; mean and why are they necessary here? (The answers are left as an exercise to the reader.) Project:M36 loads Haskell code directly with minimal database-specific knowledge required.</p><p>Next, consider how this function is executed with psycopg.</p><ol><li><p>python must convert your python integers (int) to strings to construct the SQL `SELECT` via bound parameters</p></li><li><p> postgresql parses the query and converts the postgresql INTEGERs (which are not the same as python ints) back to python integers to pass as arguments to the python `add_sale` function</p></li><li><p> postgresql loads its own python interpreter and converts the SQL argument INTEGERS to python ints</p></li><li><p> the postgresql python interpreter (which is probably not the same python version as the application layer is running) runs the `add_sale` function and returns a python integer which has to be converted back to an SQL INTEGER</p></li><li><p>the result set is returned via the postgresql binary protocol</p></li><li><p>the result set is converted from the binary protocol to a postgresql INTEGER</p></li><li><p> the postgresql INTEGER is converted to a python int</p></li></ol><p>So, amongst all these conversions, are you sure that all the conversions are sound? Are None (python)/NULL (SQL) values handled properly at every step? What are the maximum and minimum bounds of python ints compared to SQL INTEGER? Are you sure? What about if the types get more complicated, such as with SQL NUMERIC?</p><p>In contrast, Project:M36 implements directly equivalent type semantics. A Haskell Integer is the same thing in the database with identical semantics and functions which can operate on the types. In addition, Project:M36 supports algebraic data types which are quite common in Haskell.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;3580009d-e8cf-4b28-84d5-401592c2dfde&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">TutorialD (master/main): data TicketCategory = Adult | Child | Free Text
TutorialD (master/main): :showexpr relation{tuple{price 20, category Free "promotion"}}
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;category::TicketCategory&#9474;price::Integer&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;Free "promotion"        &#9474;20            &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre></div><p>Trying to recreate algebraic data types in SQL is painful, if at all possible.</p><p>PostgreSQL is not architecturally equipped to be an application server because of its historical implementation baggage. By being reliant on a fork-on-connection architecture, PostgreSQL cannot service more than a few thousand connections at-at-time. That&#8217;s where various PostgreSQL proxies with their own quirks try to fill-the-gap.</p><p>Finally, SQL offers zero facilities for running the states of past functions. SQL functions are simply replaced and past states are garbage collected. SQL functions operate on the &#8220;current&#8221; state of the database regardless of when it was added to the database. Audit tracking has to be bolted on. But, as we saw with the simple zoo example, comparing current state to past states is a common request which any database should be able to service.</p><h3>Goodbye SQL</h3><p>With the unified programming environment, we no longer have to deal with SQL&#8217;s historical baggage and quirks such as:</p><ul><li><p> language boundary mismatch: SQL doesn&#8217;t mix well with any application layer language, including data types which don&#8217;t mesh, differing capitalization and naming schemes, and lack of debug-ability- Project:M36 data types are Haskell data types.</p></li><li><p>SQL injection: a programmer footgun and persistent threat to any application composing SQL strings- Project:M36 uses a Haskell algebraic data type as RPC, not strings.</p></li><li><p>SQL limitations: Project:M36 is a mathematically-consistent implementation of the relational algebra, eschewing all the historical baggage of SQL such as poor-man&#8217;s custom data types.</p></li><li><p> lack of transformation capability: given a string of SQL, how can one reliably replace a table in the &#8220;FROM&#8221; clause? (Answer: without an SQL parser, it&#8217;s impossible to accomplish safely.) Project:M36 uses algebraic data types to represent the relational algebra operations which the database client can reliably transform using standard Haskell.</p></li><li><p> NULL: which other programming language uses <a href="https://github.com/agentm/project-m36/blob/master/docs/on_null.markdown">ternary logic</a>? Project:M36 uses Haskell data types to reliably model real-world data.</p></li><li><p>security anomalies in row-level security: rows appearing or disappearing based on your role cause join anomalies and business confusion. Project:M36 forces developers to define an API for user-level access without access to the underlying relation variables (tables).</p></li></ul><h2>Conclusion</h2><p>Project:M36 is an implementation of a Haskell-oriented application server including role-based access control, database-side server functions to define user-facing APIs, and querying of past states. By re-evaluating what an application server can be, we&#8217;ve integrated native Haskell code with permissions and a relational algebra engine for retaining and querying state even as the application evolves, thereby solving the forced choice problem!</p><p>Along the way, we&#8217;ve tossed SQL and its quirks in order to provide a consistent and surprise-free programmable interface.</p><p>Project:M36 includes many features not mentioned in this post. Learn more or join <a href="https://github.com/agentm/project-m36">the project</a>!</p>]]></content:encoded></item><item><title><![CDATA[Malmorial]]></title><description><![CDATA[We can prevent the worst of our proclivities by memorializing them.]]></description><link>https://agentm9000.substack.com/p/malmorial</link><guid isPermaLink="false">https://agentm9000.substack.com/p/malmorial</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Sat, 31 Jan 2026 19:01:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Americans are already pretty good at erecting monuments to commemorate events they would like to reminisce about positively; examples are the Lincoln Memorial, the St. Louis Gateway Arch, and the Statue of Liberty. We can argue about whether the actual impact of the people and events attached to the memorials is positive, but we can certainly agree that the intent of the memorial is to invoke a positive memory- even in the case of statues of Confederate Generals.</p><p>There is another class of memorials- <em>malmorials</em>- which are underrepresented in American culture. These are monuments, museums, or other exhibits commemorating events we should avoid repeating. Examples of existing malmorials include:</p><ul><li><p> Holocaust memorials and museums- the U.S. was neutral on any Holocaust action, even rejecting refugees,  until it entered World War II</p></li><li><p>The Museum of Chinese in America- effectively a walk-through of how U.S. policy has discriminated against Chinese immigrants</p></li><li><p>Arlington National Cemetery- a reminder of how many dead the military creates</p></li></ul><p>As with any presentation, the interpretation matters. Is Arlington Nation Cemetery a tribute to fallen heroes or a testament to America&#8217;s willingness to throw away life?</p><p>We should have more explicitly self-shaming malmorials to remind the next generation of what some people thought was acceptable behavior. For example, the ramp-up of ICE &#8220;enforcement&#8221; on the scale of American history is flash-in-the-pan and likely to be downplayed politically, but a malmorial to the events would keep it in the collective consciousness. That&#8217;s why the equivalences to the Gestapo (or, more accurately, <a href="https://www.youtube.com/watch?v=BYOeamkqHtc">die Sturmabteiling</a>) are valuable, even if not 100% congruent, because these events <em>can</em> happen here. The fact that despicable acts by our government happen here also diminishes the irrational sense of otherworldly protection from American exceptionalism; we seem to need at least generational reminders that authoritarianism is bad. Malmorials can also dissuade otherwise malicious actors from taking their malicious actions such as joining ICE.</p><p>Despicable events, for better or worse, are often associated with or directed by an individual. At the same time, we must recognize that such assholes are powerless without armies to execute their maleficent vision. That&#8217;s why the Vietnam War Memorial is not effective as a deterrent against a similar, future war- the memorial walks the line of &#8220;honoring the dead&#8221; and American heroism. Nowhere are Kissinger, Cambodia, dioxin, and landmines mentioned.</p><p>We need to call out the malicious, the selfish, and the ultra-wealthy on a regular basis. Monuments to assholes age poorly and can gain retroactive pseudo-historical narratives, for example, those stories associated with Christopher Columbus or John Smith.</p><p>What better way to honor assholes than with a prize- an Asshole Prize. Here are some proposed prize names:</p><ul><li><p>Josef Stalin Prize for Most People Killed</p></li><li><p>Pol Pot Prize for Most Suffering Inflicted</p></li><li><p>Thomas Midgley, Jr. Prize for Most People Poisoned</p></li><li><p>Genghis Khan Prize for Greatest Number of People Displaced</p></li><li><p>Henry Kissinger Prize for Most War-mongering</p></li><li><p>Jeffrey Epstein Prize for Most Sexual Violence Perpetrated Against Children</p></li><li><p>Niccol&#242; Machiavielli Prize for Most Self-Serving Realpolitik</p></li><li><p>Mao Zedong Prize for Most Animals Slaughtered</p></li><li><p>Leopold II Prize for Greatest Act of Plundering</p></li><li><p>Alfred Nobel Prize for Greatest Act of White-washing One&#8217;s Reputation</p></li></ul><p>There is no cash reward for receiving the prize. The prizes can be renamed if a bigger asshole displaces the previous dead namesake. The prize can be awarded to a person or organization. The prizes should not be named after living people and can be won multiple times. The prizes should be awarded on an annual or regularly-scheduled basis because- again- the goal to prevent the collective memory from forgetting. The prizes, while entertaining to present, should not be sugar-coated with comedy; the prize should not only be a permanent stain on a living or dead person historical record, but also clearly indicate that the person or organization is a stain on human history.</p>]]></content:encoded></item><item><title><![CDATA[Dissidence in the Age of Automated Mass Surveillance]]></title><description><![CDATA[Because everything you say will be used against you.]]></description><link>https://agentm9000.substack.com/p/dissidence-in-the-age-of-automated</link><guid isPermaLink="false">https://agentm9000.substack.com/p/dissidence-in-the-age-of-automated</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Thu, 01 Jan 2026 04:59:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>How much Surveillance is Mass Surveillance?</h2><p><em>&#8220;Let us make 1984 another cornerstone. In a very short time we will be the only country on earth able to know what every single one of its citizens is thinking.&#8221;</em></p><p>-Elena Ceau&#537;escu, wife of Nicolae Ceau&#537;escu, referring to Romanian state security&#8217;s plan to require all Romanian households to install telephones which listen and record at all times.</p><p>Mass surveillance was Ceau&#537;escu&#8217;s most forward-looking weapon to ensure that he would remain permanently installed as Romania&#8217;s dictator. Known for blackmailing members of his own party into compliance or &#8220;retirement&#8221;, his surveillance apparatus was likely the most potent in the world at that time and his forward-thinking investment in surveillance technology gave him the false confidence he needed to cement himself into the dictatorship.</p><p>However, back in 1984, one still needed to hire spies to listen to the taped recordings, creating a 1:1 requirement: one surveillance target needs one spy to listen and analyze. This created a natural bottleneck in the number of targets could be reasonably analyzed. By the mid 1980s, out of a population of 23 million Romanians, approximately 500,000 (1) worked with or directly for the state security service, so about 2% of Romania was engaged in reporting on the remaining population.</p><p>By comparison, at the height of East German Stasi&#8217;s confidential informant (<em>inoffizielle Mitarbeiter</em>) program, whereby every factory, major apartment complex, and town was installed with informants reporting on anti-government comments alongside hidden cameras, microphones, and wiretaps where technically practical, 1.6% of the East German population was &#8220;collaborating&#8221; with state security.(2)</p><p>Using some back-of-the-napkin math, we can get an upper bound on how much recorded audio such a surveillance apparatus could actually process. The ITU(3) reports about 8 phones per 100 Romanians in 1984 (totaling 1.8 million phones) which could maximally generate up to 44,160,000 hours of audio per day but realistically, let&#8217;s generously assume that each phone on average generates 1 hour of audio per day. With up to 500,000 spies able to listen up to eight hours per day, we would only need each spy to listen to three hours of audio per day. Alternatively, only 225,000 spies listening 8 hours per day would be needed. Thus, back in 1984, 2% of the population could have conceivably listened to every phone call in the country.</p><p>Don&#8217;t forget that the spies would have been busy spying on each other- this is especially important in despicable agencies where a minority of spies may discover how abhorrent their actions actually are.</p><p>With just a basic understanding of a mobile phone, it doesn&#8217;t take much imagination to recognize that the technologies that enable mass surveillance have advanced far beyond Ceau&#537;escu&#8217;s dragnet surveillance wet dream:</p><ul><li><p>computers transcribe phone conversations in any language and identify keywords</p></li><li><p>computers intercept, modify, or delete text messages- see WeChat (4)</p></li><li><p>cell phones linked to individuals persistently report their GPS locations to authorities (or even marketers)</p></li><li><p>surveillance and spyware is used to subdue human rights organizations such as with the International Criminal Court, European Court of Human Rights, or Human Rights Watch</p></li></ul><p>and more recently through advances in artificial intelligence:</p><ul><li><p>LLMs converse with citizens to alter their opinions</p></li><li><p>LLMs imitate dissidents to foil dissent</p></li><li><p>facial recognition is used to track or block movement (such as via no-fly lists or immigration arrests) or to track guilt-by-association (such as identifying protestors)</p></li><li><p>AI video generation is used to impersonate thought leaders to foil dissent</p></li><li><p>generated social media is used for false flag operations, parody, and astro-turfing</p></li><li><p>AI-powered social media surveillance reports on murmurs of protest to prevent seeds of dissent</p></li><li><p>drones, flying far above us, can be used to track, target, or attack undesirables</p></li><li><p>AI is used to find flaws in software which surveillance states use to inject tracking malware</p></li></ul><p>Edward Snowden revealed that the scope of mass surveillance using technologies from ten years ago is ubiquitous, indiscriminate, and far more pervasive than governments had been willing to disclose. Computer technology has enabled a single government to surveil everyone in the world. However, related technology could save us from a surveillance dystopia.</p><h2>What is the Purpose of Mass Surveillance?</h2><p>Ceau&#537;escu&#8217;s surveillance network was designed to be manipulative. Ceau&#537;escu himself had a private listening room where he could listen to recordings of his generals. He used this information to arrest party members or blackmail them into compliance.</p><p>In modern times, hardly anyone has gone unaffected by LLM-generated social media misinformation. Consider: COVID-19, elections, distractions from unaddressed social issues such as bigotry, healthcare, corruption, and nepotism.</p><p>The purpose of mass surveillance in general is to enforce compliance with the organism of government through preemptive strikes against governmental detractors which could disrupt the organism. Proponents of mass surveillance represent the government&#8217;s fear of changing lines of thought and action and make citizens believe the surveillance is so pervasive so as to establish enduring self-censorship and loss of hope for change or suffrage (called the &#8220;chilling effect&#8221; on free speech). Mass surveillance is a proxy for thought-crime detection. Remember, the world&#8217;s most oppressive police state needed only 2% of the population scare the remainder into compliance. The purpose of mass surveillance is to suppress democracy.</p><h2>OK, But What Can I Do About it?</h2><p>By understanding the enemy of democracy, we can equip ourselves to fight it. Sticking your head in the sand is an implicit vote for oppression, so keep yourself informed on the methods of surveillance, both known and hypothetical.</p><p><strong>Technology is a door which swings in both directions.</strong> Mathematics is shared across all humanity and cannot be owned by any entity. It also happens to be the underpinning of encryption. The same encryption that protects governments from each other is available to you, even if the mathematics have not yet been discovered. Make use of strong, end-to-end encryption for all communication. In the worst case, encryption drastically increases the cost of surveillance.</p><p><strong>Recognize, experiment with, and leverage new technologies.</strong> LLMs will definitely be used to identify and manipulate dissidents. There will likely be other technological improvements. Use them in creative ways instead of fighting against technology. For example, using an air-gapped, locally-executing LLM is a great way to prototype ideas with a virtual co-conspirator who cannot be compromised by bribery or mass surveillance.</p><p><strong>Stay off social media.</strong> Ceau&#537;escu would be licking his lips today, seeing how much personal information and thought-crime young people are willing to post publicly. The social media persona may feel a new-found freedom by &#8220;engaging&#8221; an audience with &#8220;content&#8221; because that content is protected speech today, but who guarantees that such speech will always be protected? If social media isn&#8217;t being used to manipulate you directly, it is wasting your time. There is plenty of room for people to exercise their rights to speech, but such people are unlikely to be effective dissidents in the future.</p><p><strong>Maximize your anonimity.</strong> If everything you say will be used against you, then your only choice is to say nothing. You will not have a chance to practice the reception of your message, so you will need to estimate the effectiveness of your message based on comparison to other messages and the tone of the Zeitgeist.</p><p><strong>Plan for a post-dissidence-action life.</strong> Once the surveillance machine has identified you as a threat, expect the oppressive thumb of governmental self-protection to weigh down on you. The weight of the thumb will depend on the success of your dissidence. You may wish to choose to live somewhere else, if a less oppressive place still exists.</p><p>In the mass surveillance society, assume every you do or say _will_ be used against you. So, when you do or say something, make sure it&#8217;s worth saying. You may only get one chance. Anticipate an automated response by those who wish to quash or drown out your message.</p><h2>Useful Rules-of-Thumb</h2><p>As a dissident, you will be yanked in all directions to pull you from your purpose. Steer your purpose with some axioms before you plan your goals. Here are some recommendations.</p><p><strong>No man is or was a god. </strong>Human history is rife with failed personality cults. Even if you believe in some god, that god does not manifest himself in a person to worship. Ignore those who claim to have some ability to commune with gods that you don&#8217;t. Those who believe in personality cults willingly abdicate their responsibility to think for themselves.</p><p><strong>Kissing the boots of politicians is a short-term strategy. </strong>Politicians are the people least worthy of personality cults, but also the most likely to have the platforms to grow them. Cult leaders&#8217; purpose is to accumulate power to feed their egos, so kissing their boots will require an ever-growing amount of kissing. Don&#8217;t waste your time attaching yourself. &#8220;Patriotism&#8221;, if you&#8217;re inclined to believe in such a thing, is an adherence to ideals, not people.</p><p><strong>Know your enemy.</strong> The surveillance apparatus must abide by the same laws of math and physics as you. Understand their capabilities and their weaknesses. Leverage those weaknesses for maximum impact. Awareness of the the limitations also limits your own paranoia.</p><p><strong>Don&#8217;t be a soldier. </strong>By joining a hierarchical group, you may believe you are able to be part of a like-minded coalition, but such organizations have their own philosophical momentum which is not your own. Like all of us, others&#8217; opinions can shape yours, just make sure it&#8217;s not a requirement. Loyalty is form of control, not rational thinking.</p><p><strong>Recognize that government is inherently conservative. </strong>The government, as an organism, is comfortable where it is now because it views its own outcomes as predictable. Adapting to change is difficult for any organism, but especially for hundreds of thousands of people dependent on the structure of the organism as it is today.</p><h2>Conclusion</h2><p>Being a dissident under automated mass surveillance makes it more difficult to spread and implement good ideas than ever before. Historically, it was unlikely that any single government could have sufficient resources and internal trust to evaluate the threat to itself of every person in the world. That is now achievable. However, using the same technology in creative ways can allow you, dear dissident, to dodge mass surveillance to enable your own free-thinking. To promote your ideas will require a greater effort than ever- likely something bombastic- so plan well and prepare for the possibility that your idea may only ever be viable one hundred years from now. Remember, if you stick your head in the sand, nothing will change, which is what governments would greatly prefer.</p><p>(1) <a href="https://web.archive.org/web/20250724184031/https://www.nytimes.com/2006/12/12/world/europe/eastern-europe-struggles-to-purge-security-services.html">https://web.archive.org/web/20250724184031/https://www.nytimes.com/2006/12/12/world/europe/eastern-europe-struggles-to-purge-security-services.html</a></p><p>(2) Gieseke, Jens (2014). The History of the Stasi: East Germany&#8217;s Secret Police, 1945&#8211;1990 (1st ed.). Oxford: Berghahn Books. p. 58. ISBN 978-1-78238-254-6</p><p>(3) <a href="https://www.indexmundi.com/facts/romania/indicator/IT.MLT.MAIN.P2">https://www.indexmundi.com/facts/romania/indicator/IT.MLT.MAIN.P2</a></p><p>(4) <a href="https://www.monmouth.edu/magazine/the-dark-side-of-wechat/">https://www.monmouth.edu/magazine/the-dark-side-of-wechat/</a></p>]]></content:encoded></item><item><title><![CDATA[SQL Gripe #1010]]></title><description><![CDATA[JSON types are used to work around poor typing in SQL.]]></description><link>https://agentm9000.substack.com/p/sql-gripe-1010</link><guid isPermaLink="false">https://agentm9000.substack.com/p/sql-gripe-1010</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Thu, 05 Jun 2025 15:29:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Problem</h2><p>SQL users claim that they use JSON column types in order to store &#8220;unstructured&#8221; data but that is rarely true. Consider, if the data were truly &#8220;unstructured&#8221;, then writing queries against the values would be impossible to write.</p><pre><code><code>SELECT JSON_VALUE(json_column, '$.salary') FROM data;</code></code></pre><p>Since the above query assumes that the &#8220;salary&#8221; key exists for some subset of the data, the data is not &#8220;unstructured&#8221;, it is at most &#8220;semi-structured&#8221; whereby users assume that certain data is present and, optionally, may handle cases where data in the JSON is missing.</p><p>Data is <strong>unstructured</strong> if-and-only-if it is treated as a black box. For example, if we create a database for an image gallery, then those images can be any size and contain any image. The database will never &#8220;open the box&#8221; and look inside the image, for example to count all red pixels. The image is stored and retrieved. If SQL users wanted to use JSON as black boxes, then they could validate that the text is JSON and store text.</p><p>But SQL users do want to <em>manipulate</em> JSON. SQL users wants to store, query, and update records within the JSON. Let&#8217;s look at an real-world example using PostgreSQL:</p><pre><code><code>create table customer(name text not null, address json);

insert into customer(name,address) values ('Bob Smith', '{"street": "123 Main St.", "city": "Springfield"}');</code></code></pre><p>When the SQL developer was asked why he chose a JSON type for the address, he explained that he wasn&#8217;t sure what sort of addresses (local/international) might be necessary. A JSON value can store <em>any</em> address, he continued.</p><p>The fact that a JSON value can be anything, however, makes it instant technical debt. Inevitably, someone wants to present the address to the user in some user interface or allow the user to edit the address. With a JSON value, literally any JSON value could be present, so what is the meaning of an address value if the &#8220;street&#8221; key is missing? Should we translate the keys to German for German addresses? Indeed, the value not need be a JSON dictionary at all since the string &#8220;123 Main St.&#8221; is also valid JSON. Thus, the choice to create a JSON-valued column becomes <strong>instant technical debt</strong>. Indeed, the SQL developer admitted as much when claiming that he did not know what sort of home addresses to expect. He is merely kicking the can down the road to make this a problem for his future self.</p><p>Another purported use-case for JSON values in the database is to allow for multiple JSON formats or versions within the same field:</p><pre><code>create table vehicle(name text not null, attributes json not null);

insert into vehicle(name, attributes) values ('Pinto', '{"version":1, "wheels":4}'),('Mustang','{"version":2, "wheels":4, "doors":2}');

table vehicle;
  name   |              attributes              
---------+--------------------------------------
 Pinto   | {"version":1, "wheels":4}
 Mustang | {"version":2, "wheels":4, "doors":2}</code></pre><p>In version &#8220;2&#8221; of the format, we now include a count of the doors, so, in the SQL developer&#8217;s mind, this creates a backwards- and forwards-compatible layer within the database. What it actually creates is a loosely-defined database within the database. Now, in order to find out how many doors a vehicle has, we must construct an SQL query using non-SQL constructs such as JSON Path queries or DBMS-specific JSON extraction functions:</p><pre><code>select name,attributes-&gt;&gt;'version' as version, attributes-&gt;&gt;'doors' as doors from vehicle;
  name   | version | doors 
---------+---------+-------
 Pinto   | 1       | 
 Mustang | 2       | 2
(2 rows)</code></pre><p>Next, the SQL query or application layer is expected to notice the version number difference and accommodate all the possible versions. Is that even realistic? What&#8217;s enforcing the differences between the versions in the database layer? Nothing.</p><p>In summary, JSON types are used in SQL to capture semi-structured data which must eventually become structured data in order to be queried and stored. Thus, JSON types are virtually always instant technical debt and should be avoided. At the same time, JSON types do serve a legitimate use-case for storing composite types. Can we use the SQL type system reconcile these concerns?</p><p>Unfortunately, SQL does not support complex type creation easily. Let&#8217;s examine the facilities that PostgreSQL provides for declaring new types:</p><h3>CREATE ENUM</h3><pre><code>CREATE ENUM pet_type AS ENUM ('cat', 'dog', 'hamster');
CREATE TABLE pet_store(name text not null, "type" pet_type);</code></pre><p>Creating an enumeration of values is simple way of ensuring that values are constrained to a certain subset of names. </p><p>Exercises:</p><ul><li><p>How can we add a new pet type to the store?</p></li><li><p>How can we remove an existing pet type from the store?</p></li><li><p>How can we add a a specific dog breed to the enumeration?</p></li></ul><h3>CREATE DOMAIN</h3><pre><code>CREATE DOMAIN phone_number AS TEXT CHECK(VALUE ~'^\d{3}-\d{3}-\d{4}');
CREATE TABLE pet_owner(name TEXT NOT NULL, num phone_number);</code></pre><p>Domains allow one to attach constraints to an existing type.</p><p>Exercises:</p><ul><li><p>How can we add support for international phone numbers?</p></li><li><p>How can we add support for contacting the pet owner via a messaging app such as Signal or WhatsApp?</p></li><li><p>How can we add support for contacting the pet owner by email? Should we create a new column? If so, how can we ensure that we have just one pet owner contact method?</p></li></ul><h3>CREATE TYPE</h3><pre><code>CREATE ENUM pet_type AS ENUM ('cat', 'dog', 'hamster');
CREATE ENUM dog_breed AS ('Irish Setter', 'Golden Retriever', 'Poodle');
CREATE TYPE pet_breed_type AS (pet_type, dog_breed);
CREATE TABLE pet_store(name TEXT, info pet_breed_type);</code></pre><p>By creating a composite type, we can shoehorn multiple values into one value. Above, we glue &#8220;pet_type&#8221; and &#8220;dog_breed&#8221; into one, new type. In PostgreSQL, it is possible to create new types to behave precisely as you wish with string input, but you will need to implement it in C.</p><p>Exercises:</p><ul><li><p>Try to insert a cat into the table. Which dog breed did you choose?</p></li><li><p>How we can add support for a new animal species alongside new breeds of those species?</p></li></ul><h3>Problem Summary</h3><p>Note that none of the above data representation options combine strong-typing with the flexibility of JSON tree-like structure.</p><p>Now you can see why SQL users fall back to JSON- the SQL type system is overly verbose, onerous, and still limiting.</p><p>Loose typing is a known programming pitfall. Consider that even the loosest-typed scripting languages such as python and javascript have had typecheckers bolted on in recent years. The solution is then obvious: data should be strongly typed- <em>especially</em> at the database level in order to prevent amorphous types from bubbling up into business logic.</p><h2>The Solution</h2><p><strong>Algebraic data types</strong> provide excellent type flexibility combined with strong typing. Let&#8217;s look at an example in <a href="https://github.com/agentm/project-m36/blob/master/docs/new_datatypes.markdown">Project:M36</a>.</p><pre><code>TutorialD (master/main): data CatBreed = Siamese | Sphinx | Maine_Coon
TutorialD (master/main): data DogBreed = Golden_Retriever | Poodle | OtherDogBreed Text
TutorialD (master/main): data PetType = CatType CatBreed | DogType DogBreed
TutorialD (master/main): petstore := relation{name Text, info PetType}{tuple{name "Sparky", info DogType Poodle}}
TutorialD (master/main): :showexpr petstore
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;info::PetType &#9474;name::Text&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;DogType Poodle&#9474;"Sparky"  &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>First, we define our algebraic data types to capture exactly what we intend to capture about each animal type. Unlike JSON, trying to create undeclared data values fails:</p><pre><code>TutorialD (master/main): insert petstore relation{tuple{name "Koko", info GorillaType}}
ERR: NoSuchDataConstructorError "GorillaType"</code></pre><p>So our application code cannot experience any unexpected value surprises such as with loose JSON. Note that the &#8220;PetType&#8221; is actually a tree-like structure like JSON can be, but strongly typed. </p><p>We can also search our data using algebraic data type matching:</p><pre><code>TutorialD (master/main): :showexpr petstore where info = DogType Poodle
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;info::PetType &#9474;name::Text&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;DogType Poodle&#9474;"Sparky"  &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>Therefore, we don&#8217;t need another database-within-database language such as JSON Path to find and extract the information we need from the database. The type support is first class and integrated with the database interaction language.</p><p>Furthermore, these types can be both backwards and forwards compatible- they can be altered to make room for new types without complex versioning or loose JSON.</p><p>If thinking about DBMS design from first principles interests you, you&#8217;re welcome <a href="https://github.com/agentm/project-m36?tab=readme-ov-file#community">join the club</a>.</p>]]></content:encoded></item><item><title><![CDATA[Fear: A New, Old Play]]></title><description><![CDATA[What do you fear?]]></description><link>https://agentm9000.substack.com/p/fear-a-new-old-play</link><guid isPermaLink="false">https://agentm9000.substack.com/p/fear-a-new-old-play</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Tue, 03 Jun 2025 15:19:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The actors can either be all ethnically ambiguous-looking or each actor can be from a different minority.</p><p>Father</p><p>Mother</p><p>Child 1- approximately 4 years old</p><p>Child 2- approximately 14 years old</p><h2>Act I.</h2><p>A modest apartment living room/kitchen combination room, pleasantly decorated, with multiple doors to unseen bedrooms. A home. Evening time. Child 1 runs in from bedroom.</p><p>Child 1: Daddy! Daddy! I found it!</p><p>Child fishes dinosaur toy from behind couch. Father walks in from bedroom.</p><p>Father (tiredly but good-naturedly): Good job, __insert common name for targeted minority__!</p><p>Child 1: Daddy! Make the dinosaur sounds!</p><p>Father (reluctantly but then perking up while playing with Child 1): RAWR! RAWR!</p><p>Mother enters room.</p><p>Mother (tired and mildly annoyed): Wasn't he supposed to be in bed 30 minutes ago?</p><p>Child 1: No! No!</p><p>Father scoops up Child 1.</p><p>Father: Time for bed!</p><p>Child 1: No! No! No!</p><p>Mother (soothingly): It's OK- tomorrow is a new day! We'll go to get hamburgers, yes?</p><p>Child 1: Hamburgers!</p><p>Child 2 opens door to bedroom.</p><p>Child 2 (from open door): Can you keep it down? I'm studying for a test!</p><p>Father (calmly): Yea, OK, __insert second common name for targeted minority__. We're putting __Child 1__ to bed.</p><p>Child 2 shuts door. Father takes Child 1 to second bedroom. Mother follows with stuffed animal. Mother can be vaguely heard through door reading a book to Child 1 as Father returns to living room sofa. Father turns on TV on low volume. Child 2 comes out of bedroom. Father takes notice from his seat.</p><p>Father (friendly and caring): Did you finish your homework?</p><p>Child 2 walks to the kitchen.</p><p>Child 2: I need a break.</p><p>Child 2 grabs a can of nuts from the kitchen and joins his father on the couch.</p><p>Child 2: Dad, my friends are talking about their college plans. When they ask me about mine, I don't know what to say.</p><p>Father (hesitantly): Well... the lawyer... our lawyer advised us to wait. The college application requires my financial info. That will be sent to the government, so-</p><p>Child 2: Dad! I'm in high school- the government already knows I exist!</p><p>Father (understandingly): Well, it's somehow different.</p><p>Child 2 (sullenly): So what am I supposed to do? Just hang around until.. until what?</p><p>Father looks frustrated.</p><p>Child 2: Dad, when you brought us here, did you know that I would be trapped in some weird limbo?</p><p>Father shuts off TV and rubs his face.</p><p>Father: I knew... there would be difficulties, yes.</p><p>Father and Child 2 sit in silence for a bit.</p><p>Father: ...but I also knew that we would have a supportive community here, which we do. That's how I was able to find a job and raise you here.</p><p>Child 2: Daaaad! I don't want to mow lawns!</p><p>Father (with mixed feelings of shame with pride and trying to empathize): Please... (calming down) please also consider that you don't know what we ran from. (Tearing up.) If you knew what our family was facing before... (regaining composure) Mowing lawns was an upgrade for the whole family.</p><p>Child 2 (confused and frustrated): I've heard this many times from you and Mom but I don't feel that way. How can I? I have no connection to your old life. I was a clueless baby when you brought me here. Now I can't go to college or get a real job because of a choice you made for me?</p><p>Father (swallowing his pride): I understand your anger. I do. I don't know how to explain to you that our situation... the situation for our whole family... the gangs... it was really bad.</p><p>Child 2 (progressively angrier): Dad, I see politicians on social media *every day* who want us gone- they want to throw us away! You tell us to avoid cops and not to use our real names. Did you just run away from one gang to another? Did you even think of that?</p><p>Father (turning to son and trying to stay calm): Listen- listen- not everyone is trying to "throw us away", as you say. We came here to a growing community that helped us get set up. We have a home, plenty of food, work, and safety. Also, I know I keep repeating this, but where we came from- where you were born- was much, much worse.</p><p>Child 2: Safety, dad, really? Should I feel safe if I can be kidnapped off the street and sent to a place I don't remember?</p><p>Father (initially confused): Kidnapped? (moved, hugging Child 2) It's my job to keep you safe. It's my job to keep you safe, OK?</p><p>Child 2 cries in his father's embrace.</p><p>Mother pokes head out from bedroom.</p><p>Mother: Sssh! ___Child 1 Name___ just fell asleep! What are you doing out here?</p><p>Child 2 wipes his tears and runs to his bedroom, shutting the door behind him. Mother moves to sit on the living room sofa and looks quizically at Father.</p><p>Mother (whispering): Was he crying again about not being able to get his driver's license?</p><p>Father: He just wants the life that his peers will have. I can't blame him for that.</p><p>(beat)</p><p>Father (seriously): Do you think we're safe here?</p><p>Mother (getting up, dismissively): I try not to think about it. Are you going to bed?</p><p>Father: Sure.</p><p>Mother walks to bedroom. Father stares at TV which is off- he picks up the remote to turn it on but reconsiders. Father scratches his head, then follows Mother.</p><h2>Act II.</h2><p>Same set. Daytime. The door unlocks and the four family members walk in from outside the apartment. Child 1 hops onto the couch playing with a figurine. Child 2 walks into the kitchen area.</p><p>Child 2: Mom, I have one more gift for you!</p><p>Child 1 looks over from the couch.</p><p>Child 1 (excitedly): What is it? What is it? Can I open it?</p><p>Mother: Sure, that's fine.</p><p>Child 2: But, Mom, it's for you!</p><p>Mother: It's fine, just let him unwrap it.</p><p>Child 1: Yay! Yay!</p><p>Child 1 drops his figurine and runs to the kitchen. Child 2 pulls a gift-wrapped box out of a kitchen cabinet and reluctantly hands it to Child 1. Child 1 rips the wrapping paper off the box, then stares at it.</p><p>Child 1: What is it?</p><p>Father bends over Child 1 and points at the box.</p><p>Father: Can you read this word here?</p><p>Child 1: No!</p><p>Child 1 runs back to figure on the couch.</p><p>Child 2 places the box on the counter for Mother, who was checking her phone, to see it. Mother looks up.</p><p>Mother: Oh!</p><p>Child 2: It's the blender you wanted!</p><p>Mother: I see!</p><p>Mother walks to kitchen area and hugs Child 2.</p><p>Child 2: Open it!</p><p>Mother opens box, pulls out some blender parts in plastic bags, and places them on the kitchen counter. She turns her head to glare in a teasing fashion at Father.</p><p>Father: Ok, I'll assemble it.</p><p>Father approaches blender parts, unbags them, and puts them together.</p><p>Child 1: Blend it! Blend it!</p><p>Father soothes Child 1.</p><p>Child 2: Mom, look! It has a low setting for blending different vegetables and high speed blending to (makes violent face) *really pulverize*!</p><p>Mom (giggling): Haha- well, that's a very nice gift. Thank you __insert Child 2 name__!</p><p>Child 1: Yay! Birthday! When do I get birthday?</p><p>Father (hugging Child 1): Soon! It's will be soon! What sort of present do you want?</p><p>Child 1: Pancakes! Pancakes!</p><p>Father (smiling): Sure. I'll make you pancakes.</p><p>Meanwhile, Mother is opening the mail.</p><p>Mother (to Father): What a relief! We got it.</p><p>Mother hands a passport to Father as Father hands off Child 1 to Mother.</p><p>Father (genuinely relieved, but forgetting his children are watching): Oh my god!</p><p>Child 1 (looking at passport): What's that?</p><p>Mother: It's your passport!</p><p>Child 1 (inquisitively): What's that?</p><p>Child 2 (annoyed): Don't you want to try the blender?</p><p>Mother (distracted by the passport): Oh- yes... sure.</p><p>Child 2 approaches Mother and tries to grab the passport. Mother, surprised, holds the passport away from him.</p><p>Child 2: Blend the passport!</p><p>Mother: What?!</p><p>Child 1: It's *my* passport! Give it to me!</p><p>Both children clamor around Mother, reaching for the passport.</p><p>Father approaches and pulls larger Child 2 away.</p><p>Father: Stop! Stop! What are you doing?</p><p>Child 1 cries, fearful for his passport.</p><p>Child 1: Give me! Give me!</p><p>Child 2 starts crying, too.</p><p>Mother: Look! I will put the passport in a safe place.</p><p>Mother puts the passport in a high kitchen cabinet.</p><p>Child 1: No! I want it!</p><p>Mother scoops up Child 1 as Child 1 throws tantrum. Mother takes Child 1 to bedroom, where the screaming continues. Father and Child 2 walk to the living room area, clearly understanding that the tantrum is a routine occurrence.</p><p>Father and Child 2 stand awkwardly in the kitchen until Father starts cleaning up torn up gift wrap.</p><p>Child 2 (sheepishly, knowing the answer): Dad, do you think I will ever be able to get a passport?</p><p>Father (hesitating): Well, you have a passport.</p><p>Child 2: Dad! You know that's not what I mean. That passport is useless- I can't even leave this country!</p><p>Father: I know...</p><p>Child 2: Why is it fair that __insert Child 1 name__ can get a passport, but I can't?!</p><p>Father stuffs gift wrap into the kitchen trash can.</p><p>Father: If I could give you a passport, I would!</p><p>Child 2: What? How does that help me?</p><p>Father: Look, your mother, me, and you are all in the same situation. We're doing what we can, but... the lawyer... we have limited options.</p><p>Child 2 walks over to couch and sits sullenly.</p><p>Child 2: Dad, why is __insert Child 1 name__ special?</p><p>Father: Well, the rules are different for him.</p><p>Child 2 (sullenly): Why?</p><p>Father remains silent.</p><p>Child 2: Why, dad? Why is __insert Child 1 name__ special? Right now, I can't travel or go to college or get a job- I don't want to mow lawns, Dad!</p><p>Father (meekly defensively): There's no shame in mow...</p><p>Child 2: I want to make something bigger than lawns, Dad! Right now, I can't even travel with my friends!</p><p>Father demurs. Child 1 can be heard wailing from the bedroom.</p><p>Child 2 (desperately): Why can't I just be like __insert Child 1 name__?! Why do I get a different life? How could you do this to me?!</p><p>Child 2 runs to his bedroom and slams the door.</p><p>Mother pokes head out of bedroom after cracking door slightly.</p><p>Mother: What's happening out here? (to Child 1) Ok, you can play with that.</p><p>Mother leaves bedroom, quietly closes the door, and walks around to couch.</p><p>Mother: What happened?</p><p>Father (looking sullenly at Mother): Is it possible we made a mistake in coming here?</p><p>Mother (annoyed): Really? This again?!</p><p>Mother gets up and goes to kitchen.</p><p>Mother: We are not having the conversation again. We need to focus on where we are now!</p><p>Father demurs.</p><p>Mother examines blender.</p><p>Mother (whisper yelling to avoid children overhearing): Look at this! Do you think we could ever afford this blender if we did not come here? Look at it! Our child saved money to buy this for us! For us!</p><p>Father: But one of our children is stuck without a future. I knew I had no future (signals with hands) here or there. We made this choice for our children, but only one has a future.</p><p>Mother turns on blender on low setting.</p><p>Mother: Well, it works. Did you help him to pick this out?</p><p>Father: What? (beat) You're really just going to stick your head in the sand?</p><p>Mother (whisper yelling again): What do you expect me to do? We threw away our lives to get here- we risked everything. We agreed to do it for the children! And now what? You want to go back?!</p><p>Father: No- no- I don't want to go back. I want both of our children to have the futures that we cannot.</p><p>Mother: What do you want to do? Change the rules? I'm just doing the best I can.</p><p>Mother runs blender again. Father stares aghast.</p><p>Mother: I should wash this.</p><p>Mother takes blender to the sink.</p><p>Lights out.</p><h2>Act III.</h2><p>Lights up on empty apartment. No action for 20 seconds.</p><p>Keys heard jingling at door.</p><p>Mother enters pulling Child 1 behind her. She is distraught: frantic, whining, and holding back tears. Child 2 follows them inside.</p><p>Child 2 (frightened): Mom! Mom! Why won't you tell me what's happening?!</p><p>Mother rushes to a bedroom, leaving the children in the kitchen.</p><p>Child 2 (to Child 1): Why did Mom pick us up early from school? Did she say something to you?</p><p>Child 1 (warily): We're going on vacation!</p><p>Mother returns from bedroom with half-packed duffel bag and a smaller travel bag. Mother looks around for items to pack. Mother frantically packs a blanket from the couch into the larger bag.</p><p>Child 2 (mirroring franticness and jumping): Mom! What is happening?</p><p>Mother (to Child 2): Pack this bag with... whatever you need.</p><p>Mother goes to the kitchen, opens a cabinet, packs some foodstuffs, opens another cabinet, grabs some cash hidden in a jar, counts it out quickly, and stuffs it in the duffel bag. Child 2, giving up on questioning, runs with his bag to his bedroom. Mother reaches into high cabinet to retrieve Child 1's passport and stuffs it into the bag. Child 1 remains staring at his mother packing food into the duffel bag.</p><p>Mother (to Child 2 and clapping while holding back tears): Ok! Let's go! Let's go!</p><p>Mother's cellphone rings audibly. She pulls out the cellphone and frantically scrolls and types into it.</p><p>Mother: Time to go! Now!</p><p>Child 2 runs out of bedroom with some toys and clothing hanging out of the bag. He takes a moment to zip it up.</p><p>Mother grabs Child 1 and yanks him out of the apartment. Child 2 follows. Mother rushes back in to shut the door. The family can be heard running from the apartment.</p><p>Silence except for wall clock ticking. Wait 90 seconds.</p><p>Heavy boots heard clomping outside. An aggressive pounding at the door is heard.</p><p>Policeman 1 (unseen): Police! Open up! We know you're home!</p><p>The doorbell rings multiple times.</p><p>Policeman 1: Ok, pop it!</p><p>The door is forced open with a battering ram as four heavily-armored policemen enter the apartment, rifles sweeping the apartment as they kick doors open and look into each room. All policemen wear police-themed ("thin blue line" in the U.S.) bandanas over their mouths and noses.</p><p>Policeman 3 starts opening cabinets. Policeman 4 pulls apart the couch. Policeman 2 walks through the kitchen when his rifle's muzzle "accidentally" knocks over the blender. The blender falls off the counter and smashes into pieces on the floor.</p><p>Policeman 2 (sarcastically): Oops!</p><p>The other policemen ignore or laugh off the damage.</p><p>Policeman 1: Anything?</p><p>Policeman 3: All clear. Did we miss them?</p><p>Planted audience member #1: *coughs*</p><p>Policeman 1: Did you hear that? Where did that come from?</p><p>Policeman 1 shines his flashlight into the audience.</p><p>Planted audience members #2 and #3 stand up and rush out of theater.</p><p>Policeman 3 (towards the audience): Police! Identify yourself! (after peering at audience) Looks like we have some immigrants over here!</p><p>One more audience plant starts sneaking out of the theater in fear.</p><p>Policeman 2 (into radio): We need three more capture teams in here.</p><p>Immediately, 15-20 unarmed but identifiable policemen enter the apartment through the door and begin to flood the audience after marauding through the apartment and breaking whatever they can. Theater lights up. The remainder of the scene happens simultaneously, resulting in chaos.</p><p>Some number of unarmed policemen run back behind the stage at random intervals to re-enter the apartment to give the impression of an unending rush of police.</p><p>Unarmed policeman (to other policeman, pointing at planted audience member): This guy here?</p><p>Planted Audience Member #2: Huh? Me?</p><p>Unarmed policeman: Get up.</p><p>Planted Audience Member #2 stands up apprehensively. Two policemen spin him around and place him in plastic zip tie handcuffs. More policemen flood the audience and begin arresting planted audience members.</p><p>Unarmed policeman: Where are you from?</p><p>Planted Audience Member #3 (annoyed): Leave me alone!</p><p>Unarmed policeman (excitedly): I hear a hint of an accent!</p><p>Planted Audience Member #3 is arrested.</p><p>Planted Audience Member #4: What the hell is going on?</p><p>Planted Audience Member #5 is grabbed by an officer but the plant mildly resists, resulting in the plant being thrown to the ground and zip-tied.</p><p>Unarmed policeman: I've got two over here!</p><p>A policeman on stage finds a microphone: Civilians- this is the police. The play is cancelled! Get the fuck out!</p><p>Other policemen shoo away regular audience members, even with the threat of fake arrest.</p><p>Unarmed policeman: Leave! Leave or you will be arrested!</p><p>Planted audience members scream and try to get away, climbing over seats.</p><p>An unarmed policeman struggles with a planted audience member, so another policeman runs up and places a cloth bag over his head. Four policemen drag out the planted audience member.</p><p>Outside the theater, an unmarked van is waiting at the curb, loading up planted audience members. Other unarmed policemen are waiting outside and grabbing planted audience members who managed to get outside. Father is inside the van.</p><p><strong>END.</strong></p>]]></content:encoded></item><item><title><![CDATA[The Diary of a Man Who Never Existed]]></title><description><![CDATA[Day 3 I remain hesitant to write here because of what I know and because of what I must do.]]></description><link>https://agentm9000.substack.com/p/the-diary-of-a-man-who-never-existed</link><guid isPermaLink="false">https://agentm9000.substack.com/p/the-diary-of-a-man-who-never-existed</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Wed, 26 Mar 2025 03:23:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Day 3</h2><p>I remain hesitant to write here because of what I know and because of what I must do. The nature of my work requires me to quite literally cover my tracks, but Samantha has convinced me to write a diary- this diary- focusing on my motivations in order to retain my sanity. Considering how I've been working and thinking for the past few weeks, I fear she is right. As Samantha successfully argued: in order to successfully erase myself, I must remain grounded. The irony is not lost on me. No one must read my words or share my thoughts. Samantha has promised to destroy this diary upon completion of my task.</p><h2>Day 6</h2><p>The gray thoughts have become more intense despite trying a variety of distractions, including working longer hours. Note to self: sedative self-medication began today. I am somewhat constrained in the acceptable duration of work due to limited savings- at the current burn rate I have perhaps six months, barring unexpected equipment expenses of which I should expect many. But the work is too important to be bogged down with financial considerations.</p><p>Samantha advises me daily to write to this diary. Even if I don&#8217;t write a new diary entry,  I read my words to my future self to detect any changes. But I also feel that I am writing to a man who has no right to exist. Writing feels like a distraction from the task at-hand so I minimize it. Counter to Samantha's advice, I refuse to write about the gray thoughts. The more such thoughts remain contained, the easier they will be to erase.</p><h2>Day 7</h2><p>The meeting with Professor Miller revealed a flaw in the design of the particle mirrors. I'll need a few days to create new mirrors using the formula provided by the professor. How could I have missed accounting for general relativity? Am I losing my mind? I was able to quell the professor's suspicions by claiming to be a colleague of Dr. Agarwal. I hope the professor does not follow up on the reference.</p><h2>Day 8</h2><p>Extremely disturbing dreams about the gray thoughts interrupted my sleep last night. I will need to increase my nighttime sedative dosage or I risk the gray thoughts derailing my work. The particle mirror adjustments took all my time today- I wish I had the time to make a laser polisher, but I convinced myself in the morning that it would not be necessary. What a mistake!</p><p>Just as I resist the gray thoughts during the day, I must resist writing what the gray thoughts tell me at night. I can absolutely not create and track more spacetime to erase. I already have enough difficulty focusing on the task.</p><h2>Day 15</h2><p>Even after seeing it with my own eyes, I would not have believed it without reviewing the video. I immediately deleted the video from Samantha to avoid anyone else from seeing what I still can hardly believe! The experience is enough to break anyone's grasp of reality, much less my own tenuous relationship to my future self. As a scientific experiment, the disappearance of the mouse is a success, but as a man going mad, I question everything I do and see.</p><p>Am I responsible for the mouse's disappearance? The video cannot lie nor do I have an explanation for it, but the work does bear my signature. Shortly after positioning the mouse test subject in its cage and recording its position in spacetime in my lab notebook, I witnessed the mouse's head and part of its torso enlarge instantaneously about three meters across while simultaneously becoming translucent and sparkly. I jumped back as I thought the mouse was jumping towards my face, but the mouse actually expanded in-place, generating an odd air vacuum I could feel on my face. Oddly, the mouse seemed to notice nothing unusual and did not appear to panic or react markedly. Within a half-second or less, the expanded mouse became so translucent that I could no longer see where boundaries of the mouse were placed in space. As soon as I perceived the mouse to have disappeared, I examined the mouse cage where I had just placed him. Four mouse legs seemed to sputter in the cage, as if still attached to a mouse because no blood flowed out of them, then the legs moved, not naturally, but as if pulled by an invisible force, out of the cage as if in a different phase, then finally out of the room. I was not able to relocate the legs or some of the missing bits of hay I had placed in the cage.</p><p>After examining the cage, I could see that the cage had also been partially erased with the plastic around the edges of the erasure event poorly defined and brittle. Is this the same fate that awaits me or is it just a mistake from a first attempt?</p><p>What can I do but to laugh at the absurdity of what I will have achieved at some point in the future. How can I be responsible for something my future self has already achieved?! Today, I feel I have renewed my will in the quest to erase the gray thoughts.</p><h2>Day 32</h2><p>If it were not for methamphetamine, I would have lost myself to the gray thoughts by now. My only concern is about short-term effects, the long-term drug-use repercussions I hope to never pay. I can't know if the hand tremors are from the gray thoughts or the drugs now, but it hardly matters. After two more mouse experiments, one result much better than the other, I can commence fine-tuning the remaining procedure.</p><p>The neighbors have begun to be suspicious of me- how can I blame them? I must look like a monster. I am a monster- the gray thoughts prove it.</p><p>I believe I have discovered a way to prevent others from making this discovery which would make it more likely that my work cannot be undone, at least for hundreds of years. Hopefully, by then, the gray thoughts will have been dispersed through spacetime sufficiently so as not be able to be retrieved retroactively. The neutrino radiation should make it more difficult4</p><p></p><p></p><p></p><p></p><p></p><p></p><p>kd673m 90dpainful? I will not know until I target myself.</p><h2>Day 78</h2><p>Today is the day. The paradox is not lost on me. The gray thoughts... I have only one chance and the spacetime coordinates will never be more accurate. With some luck, the wipe will include this diary and Samantha so I don't need her to hang around to clean up. Luckily, I had the foresight to program her without an effective need for self-preservation. The irony is not lost on me.</p><p>Last entry.</p>]]></content:encoded></item><item><title><![CDATA[On Data Independence and Declarative Programming in SQL]]></title><description><![CDATA[Separating physical, on-disk layout from logical models has important, long-term design and performance implications.]]></description><link>https://agentm9000.substack.com/p/on-data-independence-and-declarative</link><guid isPermaLink="false">https://agentm9000.substack.com/p/on-data-independence-and-declarative</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Sat, 22 Mar 2025 19:21:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>The fact that the relational model says nothing about physical storage is deliberate, of course. The idea was to give implementers the freedom to implement a model in whatever way they chose- in particular, in whatever way seemed likely to yield good performance- without compromising on data independence. The sad fact is, however, that SQL vendors seem mostly not to have understood this point; instead, they map base tables fairly directly to physical storage, and&#8230; their products therefore provide far less data independence than relational systems are theoretically capable of.</p></blockquote><ul><li><p>C.J. Date in Database in Depth, page 17, 2005</p></li></ul><h3>Introduction</h3><p>These words are twenty years old, but the complaint remains valid today. Virtually all SQL products still provide the one table, one-set-of-files-on-disk model. This implementation choice often forces the database designer to make trade-offs in anticipation of performance problems.  For example, database designers are taught that CREATE TABLE will create a file where the rows inserted are materialized, potentially in a specific order. However, nothing about the relational model requires this. This historical baggage has grave implications for DBMS implementations and has likely held back the database industry for decades. </p><p><strong>Data independence separates the logical, user-facing database concepts from physical, storage-based implementation.</strong></p><h3>SQL Fails at Data Independence</h3><p>Consider a database developer implementing the following query:</p><p><code>SELECT id, vendor, total FROM invoice WHERE created=current_date();</code></p><p>The developer wishes to create a report for some other team to use. However, to give this report a name and make it accessible to others from within the database, he now has at least three common options in his SQL product:</p><ol><li><p>create a table: <code>CREATE TABLE report AS SELECT id, vendor, total FROM invoice WHERE created=current_date();</code>)</p></li><li><p>create a view: <code>CREATE VIEW report AS SELECT id, vendor, total FROM invoice WHERE created=current_date();</code></p></li><li><p>create a materialized view: <code>CREATE MATERIALIZED VIEW report AS SELECT id, vendor, total FROM invoice WHERE created=current_date();</code></p></li></ol><p>The developer is now forced to pick an option by posing questions such as:</p><ul><li><p>Will the users of the report request it many times per day? The developer might reason that an SQL table of materialized tuples could save on query execution cost if the report is requested many times, but what if the cumulative cost to execute the query multiple times is less than the cost to store it on disk? Should the database lean towards minimizing monetary cost, CPU time, or storage cost?</p></li><li><p>Will the users of this report need to receive live data? A materialized view re-runs the report on a regular basis or on-demand and keeps the tuples materialized (it&#8217;s a a &#8220;view&#8221; with backing storage). The developer might lean towards a materialized view if the data need not be the freshest (for example, for a dashboard that updates every hour). But what if the cost to materialize the view tuples is actually greater than the cost to execute the query?</p></li><li><p>Will the users rarely use this report? A view probably fits best when the query is either cheap to execute or the query is used rarely.</p></li><li><p>Will the execution time of the query increase or remain constant? As a business grows, one might expect the data size to grow as well. Does it make sense to pay the up-front cost to redundantly store derived data as a table? Perhaps in the future, the table will become irrelevant- will someone notice if that happens or will the company pay to materialize the tuples idefinitely?</p></li><li><p>What if the users never use the report? If we make a table with data that&#8217;s never used, we may increase our storage costs unnecessarily, so a view is the obvious choice.</p></li></ul><p>A database developer, faced with these options and imperfect information about how the data will be used, will likely choose to create a table because that seems least objectionable, but it is hardly optimal. </p><p><strong>Even if the database designer has perfect information about how the report data will be used and makes the optimal choice today, it&#8217;s unlikely that that decision will hold indefinitely into the future.</strong></p><p>In reality, the database is likely to be a hodge-podge of historical decisions that look intentional but are generally arbitrary:</p><ul><li><p>Why is this column indexed? Perhaps it was useful in the past but is no longer the case.</p></li><li><p>Why is this column not indexed? Perhaps in the past, the storage cost for the index was deemed too high. Is that still true?</p></li><li><p>Where did this table come from? SQL products do not record any sort of data provenance.</p></li><li><p>Who is using this view/table and how often? Most SQL products don&#8217;t record or provide this information. Is the data even still relevant? Why are we storing data from ten years ago? Is it simply because it&#8217;s cheaper not to analyze what data should be garbage-collected?</p></li></ul><p>Any human is unlikely to be able to answer these questions, but there is something that can:</p><p><strong>The database knows which parts of the data are actually being used and how- but this information is hardly ever used.</strong></p><h3>Use Cases For Data Independence</h3><p>The database can track how the data usage patterns change over time. For example, if a query represents a monthly report, the database could prepare for this by rearranging its indexes, tuple storage, and whatever else to accommodate the monthly job. If we are forced to nail down exactly which &#8220;tables&#8221; are materialized or not, as today&#8217;s SQL products force us to do, then the database is immediately less resilient to change and grows<em> technical debt</em> which a person (database administrator) will need to address once it becomes a noticeable problem in production.</p><p>As engineers, we can do better and <em>data independence will unlock new optimization opportunities</em>. Consider the following SQL:</p><p><code>UPDATE enormous_table SET value=value+1;</code></p><p>followed by</p><p><code>SELECT value FROM enormous_table WHERE value = 100;</code></p><p>Regardless of whether your SQL product uses row-oriented or column-oriented storage, the UPDATE will very likely cause every value to be read, incremented, and written back to disk- that&#8217;s the best case scenario. If there is an index on <code>value</code>, then the entire index is effectively invalidated and recalculated. But nothing in the relational algebra requires any of this to be the case. </p><p>Assuming that the <code>enormous_table</code> tuples are materialized on disk, the database engine could leave the original <code>value</code> in place on-disk, record the update expression alone to disk, then, when the new value is requested, add one to each value when the SELECT is issued. The materialized tuples need not become garbage as soon a value has changed in the tuple.</p><p>Alternatively, the SELECT could be rewritten by the engine as <code>SELECT value FROM enormous_table WHERE value = 100 - 1;</code> and run against the database&#8217;s previous (pre-UPDATE) tuples (presumably because those tuples at that state are cached). Since the result is identical to forcing tuples to materialize on disk the SQL way, the relational algebra principles are not violated. Borrowing nomenclature from programming languages, this evaluation strategy is called &#8220;lazy&#8221; because it defers calculations until they are necessary. Virtually all SQL products are overeager evaluators. </p><p>The lazy strategy:</p><ul><li><p> <strong>reduces IO usage</strong>: only disk writes which are strictly necessary to serve the queries are evaluated. For example, a "table&#8221; which is never read incurs zero write IO cost: the tuples are never accessed, thus, never materialized. Conversely, if the database anticipates that the tuples will be read, it can materialize the tuples in advance of the query, thereby reducing IO when available iops may be sparser.</p></li><li><p> <strong>reduces</strong> <strong>CPU usage</strong>: laziness implies that only subsets of the tuples needed for evaluation are ever materialized. Expensive value calculations are only executed if the query demands it.</p></li><li><p> <strong>reduces</strong> <strong>cache churn</strong>: since the database maintains a log of committed transactions as update expressions, a database state change does not invalidate a previously-calculated cached query result. Instead, the state change (such as the <code>value = value + 1 </code>example above) can be applied to the cached results to generate the new state results <em>without</em> recomputing the query cache entry. The new result could be cached or discarded based on cost-to-materialize. More queries will therefore hit cached (termed &#8220;hot&#8221;) parts of the database and less of the cache is invalidated.</p></li><li><p><strong>reduces human intervention</strong>: indexing becomes a form of materialized tuple cache (the tuples are simply materialized the b-tree specific format, for example) which the database can choose to apply based on available hardware. No human would be necessary to determine which tuples to materialize or index. The database rearranges its tuple cache based on recent workloads and becomes much more adaptive.</p></li></ul><p><strong>The lazy evaluation strategy optimizes the amount of work needed to evaluate queries because it does not assume that all the tuples need to be materialized.</strong> Creating a table in SQL <em>forces</em> the database to assume that all tuples will be equally accessed. This is hardly ever true! Even if we bring SQL table partitions into the discussion, the process of partitioning is completely manual- how is a developer supposed to know how the data will be used in the future?</p><h3>Downsides to Lazy Evaluation</h3><p>It&#8217;s fair to ask what the trade-offs for data independence are. Interestingly, the downside bear the same resemblance to programming in C vs. Haskell.</p><p>In C, the programmer maximizes control of the software by instructing how the CPU must execute the program to the lowest level, including memory management.</p><p>In Haskell, the programmer is combining high-level functions (as in &#8220;functional programming&#8221;) to operate on algebraic data types.</p><p>The Haskell programmer cedes low-level control in preference for high-level optimizations and reduced errors due to type and memory safety. This is fundamentally the promise of declarative programming. SQL claims to be an adherent to declarative programming but fails for numerous reasons such as those above. </p><p><strong>With data independence, we can make database programming even more declarative with less human involvement at the cost of giving control over database structure to the database, which is best-equipped to understand the structure anyway.</strong></p><h3>Next Steps</h3><p><a href="https://github.com/agentm/project-m36">Project:M36</a> is a relational alegbra database engine which implements <strong>data independence</strong>- the concept that on-disk storage should be completely separate from the logical presentation to database end-users. That means if you use Project:M36, you will <em>not</em> be aware of this data independence- it&#8217;s completely transparent to the user by design.</p><p>The end-goal of data independence is to remove the necessity of having a human make decisions about how the data should be stored. The database knows the read and write patterns of the database better than any human and can adjust for changes in these patterns over time. The above examples are intentionally simple- there are many more optimization opportunities to find! Will you join us on this math quest?</p><p>I posit that data independence is not specific to database technology. Where else have you seen data independence or concepts similar to data independence?</p>]]></content:encoded></item><item><title><![CDATA[SQL Gripe #1008]]></title><description><![CDATA[NULLs are pervasive but cannot model anything well.]]></description><link>https://agentm9000.substack.com/p/sql-gripe-1008</link><guid isPermaLink="false">https://agentm9000.substack.com/p/sql-gripe-1008</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Sun, 17 Nov 2024 19:33:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Problem</h2><p>SQL has been saddled with the concept of NULL since <a href="https://db.cs.cmu.edu/papers/2024/zeng-damon24.pdf">SQL&#8217;s inception by E.F. Codd</a> in the 1970s. The concept of NULL also includes ternary logic whereby boolean logic is extended to include the concept of an &#8220;unknown&#8221; or &#8220;missing&#8221; value (NULL). However, NULL was added during a time when type systems were comparatively ill-defined and often computer-specific. For example, SQL&#8217;s type system was very clearly inspired by the popular programming languages of the time: assembly language and later C. Whereas NULL was originally hoped to be a placeholder for &#8220;missing&#8221; values, the relational algebra already has multiple ways to represent such values and non-values. </p><p>Consider the following table:</p><pre><code># TABLE employee;
 id |   name   
----+----------
  1 | David
  2 | Samantha
(2 rows)</code></pre><p>How do we know that &#8220;Bob&#8221; is not an employee? If we treat the database as the definitive source-of-truth, then Bob is not an employee simply because he is not in the employee table. This is how the relational algebra models reality- we record facts we know about the world and everything else is either untrue or unknown. For example, if we wish to know how old David is, this above database cannot answer the query, so the answer is unknown. </p><pre><code># SELECT age FROM employee WHERE name='David';
ERROR:  column "age" does not exist
LINE 1: SELECT age FROM employee WHERE name='David';</code></pre><p>Indeed, SQL rejects the question out-of-hand.</p><p>The relational algebra already includes a means of recording missing data by exclusion. We don&#8217;t need to, for example, record that Bob is not an employee.</p><p>The above table is unambiguous about who is an employee. However, because NULL is a misfeature of SQL, developers often are inclined or forced to use NULL.<strong> If the goal of relational database modeling is to accurately record states in reality, then the fundamental issue with NULL can only introduce ambiguity.</strong></p><p>Suppose for example, we encounter an employee table with a NULL-able start date:</p><pre><code>test=# TABLE employee;
 id |   name   | start_date 
----+----------+------------
  2 | Samantha | 
  1 | David    | 2040-10-18
(2 rows)</code></pre><p>We did not add this column, but we now need to understand what the column indicates. So, putting on our reverse-engineering hats, David has a start date at some point in the future (when compared to the current date of this writing). Perhaps it&#8217;s odd to have a start date years into the future, but it&#8217;s possible. What about Samantha? What does it mean for her start date to be NULL or &#8220;missing&#8221;? It could be any of the following possible meanings:</p><ul><li><p>Samantha has not yet started her job </p></li><li><p>Samantha does not know when she will be able to start</p></li><li><p>the company does not yet know when Samantha will start</p></li><li><p>Samantha has left the company</p></li><li><p>Samantha was made an offer but never joined the company</p></li><li><p>Samantha has died</p></li><li><p>Samantha was fired</p></li><li><p>Samantha quit</p></li><li><p>the database was created after Samantha joined the company, so the developers didn&#8217;t bother to record her actual start date</p></li></ul><p>There are probably even more possible meanings you, the reader, could cook up. A single NULL in a DATE column has introduced ambiguity and we are expected to do further reverse engineering through the application layer or read the original developer&#8217;s mind to discover the meaning. Perhaps multiple developers have unknowingly used NULL to indicate distinct meanings within the same column- this is a likely worst-case scenario. </p><p>In addition, is Samantha in the table above technically an employee or not? If you believe Samantha is an employee because she in the employee table, then how can an employee have no start date? If you believe Samantha is not an employee, why is she in the employee table? The introduction of NULL has muddied the water as to what an employee <em>is</em> in the database.</p><p>Each &#8220;missing&#8221; value has its own distinct meaning which we originally intended to encode with NULL, but NULL is simply not flexible enough to capture them.</p><p>By including NULL in the original SQL, generations of database developers assumed that ternary logic and NULLs are somehow fundamental to the database technology and the relational algebra. This is a misunderstanding with dire consequences.</p><h2>The Solution</h2><p>By now you have guessed that NULL is indeed <em>not</em> <em>fundamental</em> to database technology. This is true, but we still need a means of recording alternate &#8220;missing&#8221; values states. Type system developments since the 1970s offer a type-safe path forward with &#8220;algebraic data types&#8221;.</p><p>Witness:</p><pre><code>TutorialD (master/main): data EmployeeStartDate = StartDate Day | NotYetDetermined
TutorialD (master/main): employee := relation{tuple{id 1, name "Samantha", start_date NotYetDetermined}, tuple{id 2, name "David", start_date StartDate fromGregorian(2040, 10, 18)}}
TutorialD (master/main): :showexpr employee
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;id::Integer&#9474;name::Text&#9474;start_date::EmployeeStartDate&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;1          &#9474;"Samantha"&#9474;NotYetDetermined             &#9474;
&#9474;2          &#9474;"David"   &#9474;StartDate 2040-10-18         &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>First, we create a new type called &#8220;EmployeeStartDate&#8221; with two possible values: either an actual start date or a &#8220;NotYetDetermined&#8221; marker. </p><p>Next, we create an &#8220;employee&#8221; relation variable (similar to an SQL table) with the data on our employees.</p><p>Finally, we can view the data in the relation variable.</p><p>Wait, what about if we want to represent that &#8220;Samantha&#8221; was fired? Well, the start_date attribute can only contain the date or a &#8220;NotYetDetermined&#8221; marker. Since &#8220;NotYetDetermined&#8221; is not obviously not appropriate for a &#8220;fired&#8221; state, the developer is forced to recognize that the only option is to delete Samantha&#8217;s tuple from the relation variable.</p><p>Note that there is no NULL and therefore no ambiguity. With algebraic data types, we can encode exactly the facts about the universe we wish to capture: nothing more and nothing less. NULL is a crutch from a previous era that introduces ambiguity wherever it rears its ugly head. Let&#8217;s relegate NULL to this era.</p><p>The above examples rely on PostgreSQL and <a href="https://github.com/agentm/project-m36">Project:M36</a>.</p>]]></content:encoded></item><item><title><![CDATA[SQL Gripe #1007]]></title><description><![CDATA[PRIMARY KEY is semantic noise.]]></description><link>https://agentm9000.substack.com/p/sql-gripe-1007</link><guid isPermaLink="false">https://agentm9000.substack.com/p/sql-gripe-1007</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Thu, 31 Oct 2024 13:57:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Problem:</h2><p>Consider this common table construction:</p><pre><code>test=# CREATE TABLE employee(id INTEGER PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE
test=# \d employee
              Table "public.employee"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 id     | integer |           | not null | 
 name   | text    |           | not null | 
Indexes:
    "employee_pkey" PRIMARY KEY, btree (id)</code></pre><p>In PostgreSQL, as <a href="https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-PRIMARY-KEY">documented</a>, the DBMS uses the PRIMARY KEY syntax to indicate:</p><ol><li><p>a NOT NULL UNIQUE constraint for the column(s)</p></li><li><p>only one PRIMARY KEY constraint is allowed (unlike UNIQUE constraints)</p></li><li><p>the implicit creation of a unique index (which would be created for any UNIQUE constraint)</p></li><li><p>some self-documenting behavior in table metadata</p></li></ol><p>This is an exclusive list of PRIMARY KEY capabilities.</p><p>Were you expecting to be able to use this label in projections?</p><pre><code>test=# SELECT PRIMARY KEY FROM employee;
ERROR:  syntax error at or near "PRIMARY"
LINE 1: SELECT PRIMARY KEY FROM employee;</code></pre><p>Nope.</p><p>How about using primary keys to make join conditions easier to write?</p><pre><code>test=# SELECT e.* FROM employee AS e JOIN company AS c USING PRIMARY KEYS;
ERROR:  syntax error at or near "PRIMARY"
LINE 1: ...CT e.* FROM employee AS e JOIN company AS c USING PRIMARY KE...</code></pre><p>Too bad, you&#8217;re out-of-luck. In fact, marking a PRIMARY KEY does not help in writing any query. One could write NOT NULL UNIQUE for the column and all subsequent queries would be the same.</p><p>In addition, a table may have any number of uniqueness constraints which could be candidate keys. Consider the following table definition:</p><pre><code>test=# CREATE TABLE drug_sample(id INTEGER NOT NULL, batch_id INTEGER NOT NULL, name TEXT NOT NULL, UNIQUE(id), UNIQUE(batch_id, name));</code></pre><p>Is &#8220;id&#8221; the primary key or is &#8220;batch_id&#8221; + &#8220;name&#8221; the primary key? Selecting one to be &#8220;primary&#8221; is completely arbitrary. Since a table can have any number of unique keys, it doesn&#8217;t make much sense to call one key &#8220;primary&#8221;. The concept itself is a vestige of design-by-committee.</p><h2>Solution:</h2><p>SQL feigns special status for primary keys that do nothing special. All candidate keys should be treated equally. Here&#8217;s what it looks like in TutorialD:</p><pre><code>TutorialD (master/main): drug_sample := relation{id Integer, batch_id Integer, name Integer}
TutorialD (master/main): key drug_sample_key {id} drug_sample
TutorialD (master/main): key drug_sample_key2 {batch_id,name} drug_sample</code></pre><p>We define two keys on relation variable &#8220;drug_sample&#8221; without marking one as arbitrarily special.</p>]]></content:encoded></item><item><title><![CDATA[SQL Gripe #1006]]></title><description><![CDATA[An inadequate type system reduces confidence in data.]]></description><link>https://agentm9000.substack.com/p/sql-gripe-1006</link><guid isPermaLink="false">https://agentm9000.substack.com/p/sql-gripe-1006</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Mon, 21 Oct 2024 16:40:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Problem</h2><p>SQL&#8217;s type system superficially resembles C&#8217;s type system but is demonstrably worse. The type system forces database users to make unpleasant compromises that compromise data integrity.</p><h4>Example #1: NULL Nonsense</h4><p>NULLs do not effectively model any fact in the universe. Instead, database users are forced to use NULL as a grab bag for all possible nonsense values. </p><p>Consider a table describing employees:</p><pre><code>test=# CREATE TABLE employee(name TEXT NOT NULL, age INTEGER);
CREATE TABLE
test=# INSERT INTO employee(name, age) VALUES ('Bob', 34),('Steve',NULL);
INSERT 0 2
test=# TABLE employee;
 name  | age 
-------+-----
 Bob   |  34
 Steve |    
(2 rows)</code></pre><p>What does Steve&#8217;s age mean? Well, it&#8217;s a NULL value, so it could mean any of the following:</p><ul><li><p>Steve asked for his age not be recorded</p></li><li><p>Steve has not yet provided his age</p></li><li><p>Steve is no longer an employee so his age is irrelevant</p></li><li><p>Steve is dead</p></li></ul><p>Any or all of these options could be implied by the value stored as Steve&#8217;s age, but it&#8217;s completely ambiguous. One would have to peruse the application code which generates the NULL to reverse engineer why the NULL makes an appearance at all.</p><p>Because there is no explanation provided for the NULL, the NULL&#8217;s presence <em>reduces </em>data integrity. NULL is intentionally ambiguous! If we wanted to represent the states in the list above, we would need to create additional state columns. </p><pre><code>test=# ALTER TABLE employee ADD COLUMN age_null_reason TEXT;
ALTER TABLE
test=# UPDATE employee SET age_null_reason='declined to answer' WHERE name='Steve';
UPDATE 1
test=# TABLE employee;
 name  | age |  age_null_reason   
-------+-----+--------------------
 Bob   |  34 | 
 Steve |     | declined to answer
(2 rows)</code></pre><p>Perhaps we can also add constraints to ensure that the age_null_reason is only NOT NULL if age is NULL to prevent nonsense states, but this is not easy to add in SQL, nor is the age a single type covering all possible &#8220;age states&#8221;. We cannot, for example, make a new age type and then easily use it in another table because this &#8220;type&#8221; is actually composed of multiple columns. In addition, the age_null_reason column is a free-form text field which is too loose to precisely represent the list of states mentioned above. We could change it to an enumeration or add check constraints, but that&#8217;s a lot of baggage for a simple age.</p><h4>Example #2: Strings when all else fails </h4><p>SQL supports a DATE type, but what about if we want to represent recurring dates; for example, an event that happens on the 15th of every month. This could be represented by three different columns like this:</p><pre><code>test=# CREATE TABLE event(name TEXT NOT NULL, year INTEGER, month INTEGER, day INTEGER);
CREATE TABLE
test=# INSERT INTO event(name,year,month,day) VALUES ('wedding', 2016, 10, 16),('payroll',NULL,NULL,15);
INSERT 0 2
test=# TABLE event;
  name   | year | month | day 
---------+------+-------+-----
 wedding | 2016 |    10 |  16
 payroll |      |       |  15
(2 rows)</code></pre><p>But that&#8217;s not a type- that&#8217;s just three integers which are otherwise disconnected. For example, we can trivially insert a nonsense templated date:</p><pre><code>test=# INSERT INTO event(name,year,month,day) values ('nonsense',-4000,100,45);
INSERT 0 1</code></pre><p>To tighten up the constraints, we would have to write an extremely complex CHECK constraint which validates dates. For example, does it make sense to repeat events on the 31st of every month even though only certain months have a 31st? What does that mean in our context? SQL does not make representing these states easy to implement.</p><p>Because of this complexity, database users are more inclined to use loose string types.</p><pre><code>test=# CREATE TABLE event(name TEXT NOT NULL, "date" TEXT NOT NULL);
CREATE TABLE
test=# INSERT INTO event(name,"date") VALUES ('payroll','XXXX-XX-15');
INSERT 0 1
test=# TABLE event;
  name   |    date    
---------+------------
 payroll | XXXX-XX-15
(1 row)</code></pre><p>That&#8217;s a representation that easier for a human to understand and generate application side, but it includes zero validation for the data, so it can represent even more nonsense states than the three-column design.</p><h4>Example #3: Hair colors by which to rip out your own hair</h4><p>Imagine we want to record a person&#8217;s hair color for hair salon records. We could just make it a string type:</p><pre><code>test=# CREATE TABLE salon_customer(name TEXT NOT NULL, hair_color TEXT NOT NULL);
CREATE TABLE
test=# INSERT INTO salon_customer(name, hair_color) VALUES ('Bob','brown'),('Steve','Brown');
INSERT 0 2
test=# TABLE salon_customer;
 name  | hair_color 
-------+------------
 Bob   | brown
 Steve | Brown
(2 rows)</code></pre><p>Oops- someone typed in &#8220;brown&#8221; in two different ways. As humans, we know they are the same, but how will we resolve it so that we can find all our brown-haired customers? We would have to write a query which includes every possible spelling of &#8220;brown&#8221;.</p><p>Ok, now a bald customer wants a beard trim. What should we put in the &#8220;hair_color&#8221; column? Is baldness NULL, &#8220;bald&#8221;, &#8220;no hair&#8221;, &#8220;not applicable&#8221;, or something else? Clearly, the string type is too loose to be useful for reporting. Let&#8217;s try with a CHECK constraint.</p><pre><code>test=# CREATE TABLE salon_customer(name TEXT NOT NULL, hair_color TEXT CHECK (hair_color IN ('brown','black','red','blond')));
CREATE TABLE
test=# INSERT INTO salon_customer(name, hair_color) VALUES ('Bob','brown'),('Steve','brown');
INSERT 0 2
test=# TABLE salon_customer;
 name  | hair_color 
-------+------------
 Bob   | brown
 Steve | brown
(2 rows)</code></pre><p>Ok, that&#8217;s an improvement since we have fewer values to consider as valid. We&#8217;ll use NULL to represent baldness. Solved!</p><p>Oof- a customer just walked in with strawberry blond hair- is that red or blond? We only have those options. Fine, we&#8217;ll make another column for exceptional cases:</p><pre><code>test=# CREATE TABLE salon_customer(name TEXT NOT NULL, hair_color TEXT CHECK (hair_color IN ('brown','black','red','blond')), other_hair_color TEXT, CHECK ((hair_color IS NULL) &lt;&gt; (other_hair_color IS NULL)));
CREATE TABLE
test=# INSERT INTO salon_customer(name, hair_color, other_hair_color) VALUES ('Bob', 'brown', NULL),('Steve',NULL,'strawberry blond');
INSERT 0 2
test=# TABLE salon_customer;
 name  | hair_color | other_hair_color 
-------+------------+------------------
 Bob   | brown      | 
 Steve |            | strawberry blond
(2 rows)</code></pre><p>But now we&#8217;re just pushing unusual data into another column instead of containing it in a type. The CHECK constraint does prevent both columns from being NULL, but the same data inconsistency we had before can occur again:</p><pre><code>test=# INSERT INTO salon_customer(name, hair_color, other_hair_color) VALUES ('BadBob',NULL,'brown');
INSERT 0 1
test=# TABLE salon_customer;
  name  | hair_color | other_hair_color 
--------+------------+------------------
 Bob    | brown      | 
 Steve  |            | strawberry blond
 BadBob |            | brown</code></pre><p>Ugh, so we need to add an additional constraint to other_hair_color to not contain &#8220;brown&#8221;- ok, but can it contain &#8220;Brown&#8221;? With sufficient constraints, maybe we could make this work, but:</p><ul><li><p> the values for hair color cannot not contained within an SQL type</p></li><li><p>without looking at the table&#8217;s schema, an SQL user would not know how to enter a proper hair color- there is no way to enumerate the possible hair colors</p></li><li><p>this design cannot be easily replicated in another place; for example,</p></li></ul><h4>SQL Attempts at Solutions</h4><p>SQL does attempt to offer some solutions to these problems, but they also fall down quickly.</p><h5>Foreign Key Relationships</h5><p>If we can shuttle our type&#8217;s values together into one table, we can tighten down the constraints and then refer to the values by (perhaps surrogate) foreign key. In addition to allowing this &#8220;type&#8221; to be referenced in multiple places, it&#8217;s also easy to enumerate all the values of the type.</p><p>The downside is that the artificial &#8220;type&#8221; cannot be modified. For example, if we need to add new data values, then that new value is applied to all uses of the type, even if the new value is not applicable to all referring tables. If we start with a &#8220;hair_color&#8221; table then want to leverage the type to a &#8220;beard_type&#8221;, well that is just not possible. With PostgreSQL, every table is also a type with which we could write server-side functions such as <code>recommend_hair_color_treatments(hair_color) </code>but that is not portable to other SQL implementations. In addition, using a surrogate key may make the value in the referencing table opaque, forcing a join unconditionally to extract human-readable data.</p><h5>Composite Types</h5><p>A <a href="https://www.postgresql.org/docs/current/rowtypes.html">composite type</a> is just two or more columns glued together in a type. While this does make it easier to create a new table or new column with the bundled type, a composite type does not carry any constraints with it. Constraints have to be reimplemented wherever this type is used.</p><h4>Problem Summary</h4><p>Because creating SQL types to accurately model the world are challenging to implement and then use, hardly anyone bothers to create new SQL types, instead relying on multiple columns and complex constraints to capture real-world state.</p><h2>The Solution</h2><p>C-style types are just not good enough to model reality at a high-level. We need to look beyond to &#8220;algebraic data types&#8221;. The simple advantage of algebraic data types is that they are composable: we can make new types from existing types.</p><p>Let&#8217;s start with a sum type to represent the most common values for hair.</p><pre><code>Project:M36 TutorialD Interpreter 1.1.0
Type ":help" for more information.
A full tutorial is available at:
https://github.com/agentm/project-m36/blob/master/docs/tutd_tutorial.markdown
TutorialD (master/main): data HairColor = Brown | Black | Red | Blond | Bald
TutorialD (master/main): salon_customer := relation{tuple{name "Bob", hair Brown}, tuple{name "Steve", hair Blond}}
TutorialD (master/main): :showexpr salon_customer
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;hair::HairColor&#9474;name::Text&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;Brown          &#9474;"Bob"     &#9474;
&#9474;Blond          &#9474;"Steve"   &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>The &#8220;data&#8221; operator declares a new data type called &#8220;HairColor&#8221;. Then, we declare the first-class values which populate the type. Note that we don&#8217;t need a special non-value NULL to represent anything. After all, NULL would be worse than just using the unambiguously named value &#8220;Bald&#8221;.</p><p>Already, this is an improvement over strings with a check constraint- we can see by examining the type which values the type can contain.</p><p>But, we may have unusual hair colors or types (such as for baldness), so we need to extend our type with a product type. Let&#8217;s try again.</p><pre><code>TutorialD (master/main): delete salon_customer
TutorialD (master/main): undata HairColor
TutorialD (master/main): data HairColor = Brown | Black | Red | Blond | Bald | UnusualColor Text
TutorialD (master/main): salon_customer := relation{tuple{name "Bob", hair Brown}, tuple{name "Steve", hair UnusualColor "Purple"}}
TutorialD (master/main): :showexpr salon_customer
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;hair::HairColor      &#9474;name::Text&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;UnusualColor "Purple"&#9474;"Steve"   &#9474;
&#9474;Brown                &#9474;"Bob"     &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>That&#8217;s quite promising. It&#8217;s also easy for a database user to see all possible values of the type. One could create an UnusualColor of &#8220;Brown&#8221;, but that value is unambiguously different from the common Brown. Of course, we can also use these values in queries:</p><pre><code>TutorialD (master/main): :showexpr salon_customer where hair=Brown
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;hair::HairColor&#9474;name::Text&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;Brown          &#9474;"Bob"     &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>Note again that &#8220;Brown&#8221; is a value of type &#8220;HairColor&#8221; and not a string from the database&#8217;s perspective. We can use this type across multiple relation variables (tables) and use it in a new type:</p><pre><code>TutorialD (master/main): data HairAndBeardColors = HairAndBeard HairColor HairColor
TutorialD (master/main): :showexpr relation{tuple{name "Steve", hairbeard HairAndBeard Bald Brown}}
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;hairbeard::HairAndBeardColors&#9474;name::Text&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;HairAndBeard Bald Brown      &#9474;"Steve"   &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>This data models Steve as a customer with a bald head and brown hair using our existing HairColor type (twice).</p><p>If we want to model the templated dates from the second example above, we can do that in a straightforward way:</p><pre><code>TutorialD (master/main): data TemplatedDate = TemplatedDate (Maybe Integer) (Maybe Integer) (Maybe Integer)
TutorialD (master/main): :showexpr relation{name Text,date TemplatedDate}{tuple{name "Ides of March", date TemplatedDate Nothing (Just 3) (Just 15)}}
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;date::TemplatedDate                     &#9474;name::Text     &#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;TemplatedDate Nothing (Just 3) (Just 15)&#9474;"Ides of March"&#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>Naturally, we would want to make some functions to validate that the templated date is valid, but that could be very context-specific. Consider, for example, the following templated date:</p><pre><code>TutorialD (master/main): :showexpr relation{name Text,date TemplatedDate}{tuple{name "31st of every month", date TemplatedDate Nothing Nothing (Just 31)}}
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;date::TemplatedDate                    &#9474;name::Text           &#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;TemplatedDate Nothing Nothing (Just 31)&#9474;"31st of every month"&#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>Given that not all months extend to 31 days, does this templated date refer to the end of every month or the end of months which have 31 days? It&#8217;s ambiguous, but with algebraic data types, we can address it!</p><pre><code>TutorialD (master/main): data TemplatedDate = TemplatedDate (Maybe Integer) (Maybe Integer) (Maybe Integer) | EndOfEveryMonth
TutorialD (master/main): :showexpr relation{name Text,date TemplatedDate}{tuple{name "ides of every month", date TemplatedDate Nothing Nothing (Just 15)},tuple{name "end of every month", date EndOfEveryMonth}}
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;date::TemplatedDate                    &#9474;name::Text           &#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;EndOfEveryMonth                        &#9474;"end of every month" &#9474;
&#9474;TemplatedDate Nothing Nothing (Just 15)&#9474;"ides of every month"&#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>This demonstrates the composition capabilities of algebraic data types. We can compose arbitrarily-complex types to best model our world.</p><p>While SQL can approximate the features of algebraic data types, as we saw in the above examples, matching those features is painful and unnatural. Algebraic data types allow composition and thus creation of complex types which improve data consistency. Thus, algebraic data types are a natural fit for modeling the world with relational databases.</p><p>All solutions in the solution were executed with <a href="https://github.com/agentm/project-m36">Project:M36</a>.</p><p></p>]]></content:encoded></item><item><title><![CDATA[On Representative Government]]></title><description><![CDATA[Representative officials are the most replaceable of us.]]></description><link>https://agentm9000.substack.com/p/on-representative-government</link><guid isPermaLink="false">https://agentm9000.substack.com/p/on-representative-government</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Wed, 25 Sep 2024 05:43:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What is a representative?</h2><p>In an elected representative democracy, the voters select electees to represent the voters&#8217; interests. Ideally, the representatives would have no agenda beyond collecting and representing said interests. The voters in turn should cast votes for the candidate who can best represent their interests. How a voter is supposed to know which candidate can do so is left as an exercise to the voter. How the candidates appear on the ballot is left as a mystery.</p><p>Given this simplified model of a representative democracy, the voters&#8217; evaluation of the success of a representative is how well the representative aligns with the voters&#8217; legislative desires when the representatives vote and make legislation and definitely <strong>not</strong>:</p><ul><li><p>the representative&#8217;s opinion on any particular matter</p></li><li><p>whether the representative promotes a particular ideology</p></li><li><p>the representative&#8217;s ability to raise funds for a party</p></li><li><p>the representative&#8217;s ability to raise funds to advertise himself</p></li><li><p>how representatives of other constituencies vote</p></li><li><p>the representative&#8217;s ability to leverage rhetoric</p></li></ul><p>In particular, representatives are not elected for their opinions or their ability to promote their opinions because, as <em>representatives</em>, their opinions are irrelevant. </p><h2>How did we get from <em>representatives </em>to <em>politicians</em>?</h2><p>Perverse, unanticipated incentives, of course. Politicians are power-mongers: their currency is influence over government action or inaction, so that currency is bought and sold on a market. The market, however, competes with the original task of representation of the voters interests. The original representatives were also keen politicians and even self-described god-kings, so they could not imagine a system where they would not be able to accumulate political capital.</p><h2>How can a representative be aligned with the goal of actual representation? </h2><p>First, we must acknowledge that voting, while a waypoint on the way from autocracy to democracy, is a vestige of thinkers who could not imagine unconcentrated power. Most democracies, now dealing with and benefiting from demographic shifts, acknowledge that human rights are not just for wealthy landowners. New technology can enable ideas like liquid democracy and ballot automation. Television created a voting class able to see and hear their representatives and the consequences of war.  The Internet brought new forms of political and ideological discourse in a relatively equitable forum for the first time ever. But the voting systems in place are skewed towards incumbent candidates and parties, often enshrined in law. </p><p>However, we are not leveraging the latest mathematics in dealing with representation- statistics offers a means of finding representatives from a population without voting: <strong>random sampling</strong>. Like jury duty, randomly selected representatives are best suited to represent the population&#8217;s interests. Regularly cycling through representatives ensures that no single person accumulates power to use elsewhere. Like a juror, once the representative loses his seat, there is no reason for him to cycle into bribery- he won&#8217;t have any special connections in office since the other representatives will also be replaced. </p><p>The durations of the representatives are required to serve in the chamber need not happen on an even cadence- each representative can be cycled out at a time unknown to anyone using an exponential decay probability calculated daily. A replacement can be found and dropped in at any time.</p><p>Having two 80-year-old white men represent tens of millions of people is an absurd parody of democracy. Having so much power accumulated over decades of political &#8220;service&#8221; is antithetical to representation and more amenable to stagnation. Instead, the most average person (to which a random sample would skew) would provide a disinterested representative who is only willing to perform basic legislative tasks and eager to get out of office and back to his original life. Politics need not be a career opportunity. Instead of legislators vying for a political upper hand by manipulating chamber rules and raising campaign funds, the average man representative would focus on raising issues affecting himself and therefore a larger portion of the population. The average man is also far less willing to engage a nation in warfare. A perpetually cycling set of representatives prevents bribery and regulatory capture from being as effective. A selfish average man looking forward to completing his requisite representation is far less dangerous than a man who has spent decades empire building. A robust means of accelerating the representative&#8217;s exit from power, if necessary, can be provided to the electorate whose sole vote purpose then would be recall voting instead of candidate selection.</p><h2>Diluting power makes the government less stable for politicians, but stabler for the electorate.</h2><p>There are a few men who have the power to activate world-ending bombs. One of those men is the U.S. President. Why?</p><p>If deciding how yogurt should be taxed requires the vote of hundreds of representatives, then why is ending the world a one-man job? Instead of one president, we could have fifty, a hundred, or a thousand Presidents. </p><p>The only objection one could have to such representation in the executive power of the government is expediency and quick decision-making. In the 1700s, fifty Presidents reaching a quorum would have required them to be in a room together. Today, the Internet could have them debate and reach a quorum at any time from any location. If some are unable to join, then the others can still reach a quorum, even in an emergency.</p><p>Having a thousand presidents would also eliminate a single point-of-failure scenario. Killing a president or having a president incapicated would not paralyze the government nor would we need backup &#8220;vice&#8221; presidents. The regular cycling of the representatives would suffice. By diluting power, we can prevent the attraction of the office to psychopaths who believe themselves to be god-kings. Even if a malicious psychopath godking does achieve office (by random chance), his ability to cause damage is extremely limited.</p><h2>Politics should not be a team sport.</h2><p>As a voter, it&#8217;s easy to fall into personality cult traps. Political parties ensure that politicians of the same, self-serving caliber continually bubble up politically, convincing the electorate that no better candidate exists. This is not true. The average person would make a decent representative. A room full of average people would make a chamber of great representatives. Cycling out representatives and replacing them with randomly-selected people prevents parties and coalitions which horse trade on issues and votes. Instead, debate would have to be substantive, but also accessible to the average person. Representatives may not even need to speak to their constituents since the very act of being average and holding an average opinion is a reasonable form of representation.</p><p>Representatives should not be in charge of raising issues to address- this can be better achieved through petitions and ballot initiatives. The representatives then must get those initiatives into shape as legislation. Such legislative process is less prone to manipulation through lobbying (bribery) and political horse trading.</p><h2>How can we get to representative government?</h2><p>Proper representatives don&#8217;t actually make anything of value- they are a net loss of productivity and intellect. Proper representatives do not need to hold or flaunt unique talents, intelligence, or rhetorical ability because the act of representing a population does not require this. By definition of the word &#8220;representative&#8221;, <strong>the most representative representative is the one who is completely replaceable. </strong>While representation is something worthwhile to the society as a whole, it is not a role which anyone in particular should be able to or want to seek out. If we must live with the concept of representation, then it should be actual representation and not party tricks- no one needs to see representatives represent on television; representatives should not at all be interesting to observe individually. </p><p>Don&#8217;t drink the voting Kool-Aid. If you must vote, vote for the most boring, party-unaffiliated candidate. Don&#8217;t vote for incumbent candidates- keep the people cycling through the chamber to reduce power buildup. Education of the electorate can help everyone to differentiate between <em>representation</em> and <em>leadership. </em>Ensure that term limits are enforced, short, and ever shorter. Any person should be able to take up the representation mantle- even you!</p>]]></content:encoded></item><item><title><![CDATA[SQL Gripe #1004]]></title><description><![CDATA[Some NULLs are typed, except when they're not.]]></description><link>https://agentm9000.substack.com/p/sql-gripe-1004</link><guid isPermaLink="false">https://agentm9000.substack.com/p/sql-gripe-1004</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Thu, 12 Sep 2024 05:18:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Problem:</h1><p>Two NULLs are generally not equal- the result of comparing two NULLs with equality is another NULL.</p><pre><code>test=# SELECT (NULL = NULL) IS NULL;
 ?column? 
----------
 t
(1 row)</code></pre><p><code>IS DISTINCT FROM </code>provides a means of determining if we are comparing two values which may be NULL <em>and</em> we want to know if the values are actually &#8220;equal&#8221; even if they are both NULL.</p><pre><code>test=# SELECT NULL IS DISTINCT FROM NULL;
 ?column? 
----------
 f
(1 row)</code></pre><p>Therefore, <code>IS NOT DISTINCT FROM</code> is SQL&#8217;s form of strict equality (returning either true or false and never NULL) in the face of NULLs.</p><pre><code>test=# SELECT NULL IS NOT DISTINCT FROM NULL;
 ?column? 
----------
 t
(1 row)</code></pre><p>Ok, but all values must have a type, right? So what&#8217;s the data type of NULL?</p><pre><code>test=# SELECT pg_typeof(NULL);
 pg_typeof 
-----------
 unknown
(1 row)</code></pre><p>Ok, so the concept of unknown/NULL bubbles up to the type itself. It makes sense in this context that we don&#8217;t know if the NULL in question is an unknown integer or text or some other type. There is no type hint provided.</p><pre><code>test=# SELECT pg_typeof(NULL = NULL);
 pg_typeof 
-----------
 boolean
(1 row)</code></pre><p>This seems to make sense, too, since we know that equality can only return true, false or unknown (NULL), so we know that the &#8220;unknown&#8221; value is of type boolean even if the two NULLs we are comparing are of unknown-typed NULL.</p><p>Another way to provide a type hint is using CAST().</p><pre><code>test=# SELECT pg_typeof(CAST(NULL AS INTEGER));
 pg_typeof 
-----------
 integer
(1 row)</code></pre><p>Can we compare a typed NULL to an NULL of unknown type?</p><pre><code>test=# SELECT NULL IS NOT DISTINCT FROM CAST(NULL AS INTEGER);
 ?column? 
----------
 t
(1 row)</code></pre><p>Yes- that&#8217;s fine. Even though the two NULLs are two different types (one known and one unknown), we can compare them. Can we compare NULLs of two, known types?</p><pre><code>test=# SELECT CAST(NULL AS TEXT) IS NOT DISTINCT FROM CAST(NULL AS INTEGER);
ERROR:  operator does not exist: text = integer
LINE 1: SELECT CAST(NULL AS TEXT) IS NOT DISTINCT FROM CAST(NULL AS ...
                                  ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.</code></pre><p>PostgreSQL rightfully rejects this expression in the same way that it rejects comparing and integer and text for equality- it&#8217;s a nonsense query.</p><p>We can also be explicit about casting our NULL to an unknown type.</p><pre><code>test=# SELECT pg_typeof(CAST(NULL AS UNKNOWN));
 pg_typeof 
-----------
 unknown
(1 row)</code></pre><p>Can we &#8220;promote&#8221; an integer-typed NULL to &#8220;unknown&#8221;?</p><pre><code>test=# SELECT CAST(CAST(NULL AS INTEGER) AS UNKNOWN);
ERROR:  cannot cast type integer to unknown
LINE 1: SELECT CAST(CAST(NULL AS INTEGER) AS UNKNOWN);</code></pre><p>That is rejected, but&#8230; why? Apparently, once we have a specific type for our unknown value, the type cannot be eliminated, but we can cast the NULL to any other type:</p><pre><code>test=# SELECT CAST(CAST(NULL AS INTEGER) AS TEXT);
 text 
------
 
(1 row)</code></pre><p>Can we create unknown-typed values other than NULL?</p><pre><code>test=# SELECT pg_typeof(CAST('x' AS UNKNOWN));
 pg_typeof 
-----------
 unknown
(1 row)
test=# SELECT CAST('x' AS UNKNOWN);
 unknown 
---------
 x
(1 row)</code></pre><p>The above examples are queries without any tables. Can we create a table with a column of unknown type? Apparently, but what can we do with it?</p><pre><code>test=# CREATE TABLE nulltest AS SELECT NULL AS col;
SELECT 1</code></pre><p>Success? Well, let&#8217;s inspect the table.</p><pre><code>test=# \d nulltest
            Table "public.nulltest"
 Column | Type | Collation | Nullable | Default 
--------+------+-----------+----------+---------
 col    | text |           |          | </code></pre><p>When creating the table, the NULL of unknown type is promoted to a text type apparently. Can we insert a NULL of a different type into the text column?</p><pre><code>test=# INSERT INTO nulltest(col) VALUES(CAST(NULL AS INTEGER));
INSERT 0 1</code></pre><p>Yes! Let&#8217;s confirm the types of the two rows we inserted:</p><pre><code>test=# SELECT pg_typeof(col) FROM nulltest;
 pg_typeof 
-----------
 text
 text
(2 rows)</code></pre><p>So, instead of rejecting the NULL of the wrong type, PostgreSQL cast our integer-typed NULL to a text-typed NULL.</p><p>What about if we provide the unknown type hint?</p><pre><code>test=# CREATE TABLE nulltest3 AS SELECT CAST('x' AS UNKNOWN) as col3;
SELECT 1
test=# \d nulltest3
            Table "public.nulltest3"
 Column | Type | Collation | Nullable | Default 
--------+------+-----------+----------+---------
 col3   | text |           |          | </code></pre><p>PostgreSQL still forces our type to a text type- it seems that we cannot create a table with an unknown type.</p><p>More generally, SQL automatically casts between column types when it can:</p><pre><code>test=# CREATE TABLE nulltest2 AS SELECT CAST(NULL AS INTEGER) AS col2;
SELECT 1
test=# INSERT INTO nulltest(col) SELECT col2 FROM nulltest2;
INSERT 0 1</code></pre><p>Here we create another table with an integer-typed NULL column &#8220;col2&#8221;, then we insert the NULLs from the new table &#8220;nulltest2&#8221; into &#8220;nulltest&#8221;. There is no error because the database uses the standard cross-type casting function, which also converts NULLs across types.</p><p>Let&#8217;s recap. NULL can clearly carry a type with a it, so not all NULLs are indistinct from each other. We can promote NULL from one type to another unless the destination type is &#8220;unknown&#8221; and the source type is not already &#8220;unknown&#8221;. When using an unknown-typed NULL in table creation, PostgreSQL casts it automatically into a text field which we did not request.</p><p>What&#8217;s wrong with this picture? Did you, SQL practitioner, know any of these NULL nuances? Are these nuances even worth knowing? The problem is that we may be using this language without understanding how NULLs actually work. This can lead to mistaken assumptions about what NULLs mean in a given context. As demonstrated by the example when creating a table with an unknown-typed NULL, not even SQL itself can agree on what is meant by an untyped NULL.</p><p>Inconsistencies in SQL lead to mistakes and subtle bugs.</p><h1>Solution</h1><p>The introduction of NULL is more than <a href="https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/">a billion-dollar mistake</a>. Three-valued logic (true, false, and unknown/NULL) is an inconsistent mess even though SQL has had decades to remedy this. Let&#8217;s drop three-valued logic and replace it with <a href="https://github.com/agentm/project-m36/blob/master/docs/on_null.markdown">algebraic data types</a> to store exactly what we intend, unambiguously. Once we eliminate NULL, we can query our data with much more consistency and confidence in the results.</p>]]></content:encoded></item><item><title><![CDATA[SQL Gripe #1003]]></title><description><![CDATA[CONCAT() and || are different functions]]></description><link>https://agentm9000.substack.com/p/sql-gripe-1003</link><guid isPermaLink="false">https://agentm9000.substack.com/p/sql-gripe-1003</guid><dc:creator><![CDATA[AgentM]]></dc:creator><pubDate>Sat, 07 Sep 2024 06:28:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RLBj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41f801e9-2132-4684-835b-333725f6ad2b_512x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Problem</h1><p>CONCAT() and || behave differently when faced with NULL.</p><p>First, let&#8217;s create a table with some NULLs:</p><pre><code>test=# CREATE TABLE photo(filename text not null, target text); 
CREATE TABLE 
test=# INSERT INTO photo(filename, target) VALUES ('bob_birthday.jpg', 'Bob'),('dogtoy.jpg', NULL); 
INSERT 0 2</code></pre><p>Now we make a string describing each photo:</p><pre><code>test=# SELECT filename || ' contains a person named ' || target FROM photo;
                   ?column?                   
----------------------------------------------
 bob_birthday.jpg contains a person named Bob
 
(2 rows)</code></pre><p>Wait- why is the second row empty? Oh, the target is NULL, so the whole string is NULL? Let&#8217;s try that again.</p><pre><code>test=# SELECT CONCAT(filename, ' contains a person named ', target) FROM photo;
                    concat                    
----------------------------------------------
 bob_birthday.jpg contains a person named Bob
 dogtoy.jpg contains a person named 
(2 rows)</code></pre><p>Ah, there it is. So CONCAT(a,b) is equivalent to COALESCE(a,&#8217;&#8217;) || COALESCE(b,&#8217;&#8217;).</p><p>Good luck remembering which variant returns NULL as a whole in which context.</p><p>Surprises lead to mistakes.</p><h1>Solution</h1><p>NULL&#8217;s pitfalls are many and well-documented. There are many other functions which behave confusingly in the context of NULL. Can we do without NULL?</p><p>As it turns out, NULL is as much a liability in SQL as it is in <a href="https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/">other programming languages</a>. Nor is NULL required by the relational algebra as so many DBAs believe. </p><p>So how can we capture &#8220;missing&#8221; data? Missing data can represented by the same means as any other data- we can record it as part of the data type and we should not be constrained to <a href="http://www.cburch.com/books/cptr/">C-style pointer types</a>. In many programming languages, we can leverage &#8220;<a href="https://en.wikipedia.org/wiki/Algebraic_data_type">algebraic data types</a>&#8221; to record arbitrarily complex facts about our world.</p><p>For example, if we want to record a fact about a person&#8217;s hair color, the options may be:</p><ul><li><p>blond</p></li><li><p>brown</p></li><li><p>red</p></li><li><p>bald</p></li><li><p>gray</p></li><li><p>some other color</p></li></ul><p>A naive SQL implementation may be a NULL-capable string column:</p><pre><code>test=# CREATE DOMAIN hair TEXT;
CREATE DOMAIN</code></pre><p> but are &#8220;gray&#8221; and &#8220;grey&#8221; the same color or not? A string type is too loose, so let&#8217;s tighten it up using a CHECK constraint:</p><pre><code>test=# CREATE DOMAIN hair TEXT CHECK(value IN ('blond', 'brown', 'red', 'gray', 'other'));
CREATE DOMAIN</code></pre><p>whereby NULL represents a bald person. But what about if we actually want to capture the &#8220;other&#8221; color. Then we need another column:</p><pre><code>test=# CREATE TABLE person(name TEXT NOT NULL, hair_color hair, optional_other_color TEXT);
CREATE TABLE</code></pre><p>but that column can only be populated if the hair_color column is &#8220;other&#8221;, so we add a table constraint:</p><pre><code>test=# ALTER TABLE person ADD CONSTRAINT other_color_constraint CHECK((hair_color = 'other' AND optional_other_color IS NOT NULL) OR (hair_color &lt;&gt; 'other' AND optional_other_color IS NULL));
ALTER TABLE</code></pre><p>Whew! That&#8217;s a lot of SQL to represent basic hair characteristics. </p><p>Oh, we just received a change request: the user can choose not to report his hair color and we need to record that fact. We can&#8217;t use NULL because NULL is already being used to represent baldness. We can&#8217;t make another &#8220;NULL&#8221; for not reporting a value, so we&#8217;ll need to make another column to explain why hair_color is NULL and tighten down the columns with even more constraints. All of this is possible in SQL, but painful! </p><p>Here is a NULL-free representation of hair color using algebraic data types in TutorialD:</p><pre><code>TutorialD (master/main): data HairColor = Blond | Brown | Red | Bald | Gray | OtherColor Text | UserDeclinedToProvideInfo</code></pre><p>This is one type which can capture all possible values for this attribute. Here&#8217;s how we can use it:</p><pre><code>TutorialD (master/main): person := relation{tuple{name "Bob", hair_color Bald}, tuple {name "Cindy", hair_color OtherColor "Purple"}}
TutorialD (master/main): :showexpr person
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;hair_color::HairColor&#9474;name::Text&#9474;
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;Bald                 &#9474;"Bob"     &#9474;
&#9474;OtherColor "Purple"  &#9474;"Cindy"   &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code></pre><p>Done!</p><p>Now that we&#8217;ve eliminated NULLs from the database, CONCAT() and || can behave identically when it comes to text.</p><p>The Project:M36 article &#8220;<a href="https://github.com/agentm/project-m36/blob/master/docs/on_null.markdown">On NULL</a>&#8221; provides more detail and examples of how relational algebra engines benefit from eliminating NULL and replacing its use cases with algebraic data types.</p>]]></content:encoded></item></channel></rss>