Refinement without Specification

Hillel Wayne — Computer Things News

Summary

A blog post explaining how to use refinement mappings to preserve external properties during database schema changes, using examples of migrating a boolean column to a nullable timestamp and then to event sourcing.

<p>Imagine we have a SQL database with a <code>user</code> table, and users have a non-nullable <code>is_activated</code> boolean column. Having read <a href="https://ntietz.com/blog/that-boolean-should-probably-be-something-else/" target="_blank">That Boolean Should Probably Be Something else</a>, you decide to migrate it to a nullable <code>activated_at</code> column. You can change any of the SQL queries that read/update the <code>user</code> table but not any of the code that uses the results of these queries. Can we make this change in a way that preserves all external properties? </p> <p>Yes. If an update would set <code>is_activated</code> to true, instead set it to the current date. Now define the <strong>refinement mapping</strong> that takes a <code>new_user</code> and returns an <code>old_user</code>. All columns will be unchanged <em>except</em> <code>is_activated</code>, which will be</p> <div class="codehilite"><pre><span></span><code>f(new_user).is_activated = if new_user.activated_at == NULL then FALSE else TRUE </code></pre></div> <p>Now new code can use <code>new_user</code> directly while legacy code can use <code>f(new_user)</code> instead, which will behave indistinguishably from the <code>old_user</code>. </p> <p>A little more time passes and you decide to switch to an <a href="https://martinfowler.com/eaaDev/EventSourcing.html" target="_blank">event sourcing</a>-like model. So instead of an <code>activated_at</code> column, you have a <code>user_events</code> table, where every record is <code>(user_id, timestamp, event)</code>. So adding an <code>activate</code> event will activate the user, adding a <code>deactivate</code> event will deactivate the user. Once again, we can update the queries but not any of the code that uses the results of these queries. Can we make a change that preserves all external properties?</p> <p>Yes. If an update would change <code>is_activated</code>, instead have it add an appropriate record to the event table. Now, define the refinement mapping that takes <code>newer_user</code> and returns <code>new_user</code>. The <code>activated_at</code> field will be computed like this:</p> <div class="codehilite"><pre><span></span><code>g(newer_user).activated_at = # last_activated_event let lae = newer_user.events .filter(event = &quot;activate&quot; | &quot;deactivate&quot;) .last, in if lae.event == &quot;activate&quot; then lae.timestamp else NULL </code></pre></div> <p class="empty-line" style="height:16px; margin:0px !important;"></p> <p>Now new code can use <code>newer_user</code> directly while old code can use <code>g(newer_user)</code> and the really old code can use <code>f(g(newer_user))</code>.</p> <h3>Mutability constraints</h3> <div class="subscribe-form"></div> <p>I said "these preserve all external properties" and that was a lie. It depends on the properties we explicitly have, and I didn't list any. The real interesting properties for me are mutability constraints on how the system can evolve. So let's go back in time and add a constraint to <code>user</code>:</p> <div class="codehilite"><pre><span></span><code>C1(u) = u.is_activated =&gt; u.is_activated&#39; </code></pre></div> <p>This constraint means that if a user is activated, any change will preserve its activated-ness. This means a user can go from deactivated to activated but not the other way. It's not a particular good constraint but it's good enough for teaching purposes. Such a SQL constraint can be enforced with <a href="https://www.postgresql.org/docs/current/sql-createeventtrigger.html" target="_blank">triggers</a>. </p> <p>Now we can throw a constraint on <code>new_user</code>:</p> <div class="codehilite"><pre><span></span><code>C2(nu) = nu.activated_at != NULL =&gt; nu.activated_at&#39; != NULL </code></pre></div> <p>If <code>nu</code> satisfies <code>C2</code>, then <code>f(nu)</code> satisfies <code>C1</code>. So the refinement still holds.</p> <p>With <code>newer_u</code>, we <em>cannot</em> guarantee that <code>g(newer_u)</code> satisfies <code>C2</code> because we can go from "activated" to "deactivated" just by appending a new event. So it's not a refinement. This is fixable by removing deactivation events, that would work too.</p> <p>So a more interesting case is <code>bad_user</code>, a refinement of <code>user</code> that has both <code>activated_at</code> and <code>activated_until</code>. We propose the refinement mapping <code>b</code>:</p> <div class="codehilite"><pre><span></span><code>b(bad_user).activated = if bad_user.activated_at == NULL &amp;&amp; activated_until == NULL then FALSE else bad_user.activated_at &lt;= now() &lt; bad_user.activated_until </code></pre></div> <p>But now if enough time passes, <code>b(bad_user).activated' = false</code>, so this is not a refinement either.</p> <h3>The punchline</h3> <p>Refinement is one of the most powerful techniques in formal specification, but also one of the hardest for people to understand. I'm starting to think that the reason it's so hard is because they learn refinement while they're <em>also</em> learning formal methods, so are faced with an unfamiliar topic in an unfamiliar context. If that's the case, then maybe it's easier introducing refinement in a more common context like databases.</p> <p>I've written a bit about refinement in the normal context <a href="https://hillelwayne.com/post/refinement/" target="_blank">here</a> (showing one specification is an implementation of another). I kinda want to work this explanation into the book but it might be too late for big content additions like this.</p> <p>(Food for thought: how do refinement mappings relate to database views?)</p>
Original Article
View Cached Full Text

Cached at: 05/16/26, 03:40 AM

# Refinement without Specification Source: [https://buttondown.com/hillelwayne/archive/refinement-without-specification](https://buttondown.com/hillelwayne/archive/refinement-without-specification) Imagine we have a SQL database with a`user`table, and users have a non\-nullable`is\_activated`boolean column\. Having read[That Boolean Should Probably Be Something else](https://ntietz.com/blog/that-boolean-should-probably-be-something-else/), you decide to migrate it to a nullable`activated\_at`column\. You can change any of the SQL queries that read/update the`user`table but not any of the code that uses the results of these queries\. Can we make this change in a way that preserves all external properties? Yes\. If an update would set`is\_activated`to true, instead set it to the current date\. Now define the**refinement mapping**that takes a`new\_user`and returns an`old\_user`\. All columns will be unchanged*except*`is\_activated`, which will be ``` f(new_user).is_activated = if new_user.activated_at == NULL then FALSE else TRUE ``` Now new code can use`new\_user`directly while legacy code can use`f\(new\_user\)`instead, which will behave indistinguishably from the`old\_user`\. A little more time passes and you decide to switch to an[event sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)\-like model\. So instead of an`activated\_at`column, you have a`user\_events`table, where every record is`\(user\_id, timestamp, event\)`\. So adding an`activate`event will activate the user, adding a`deactivate`event will deactivate the user\. Once again, we can update the queries but not any of the code that uses the results of these queries\. Can we make a change that preserves all external properties? Yes\. If an update would change`is\_activated`, instead have it add an appropriate record to the event table\. Now, define the refinement mapping that takes`newer\_user`and returns`new\_user`\. The`activated\_at`field will be computed like this: ``` g(newer_user).activated_at = # last_activated_event let lae = newer_user.events .filter(event = "activate" | "deactivate") .last, in if lae.event == "activate" then lae.timestamp else NULL ``` Now new code can use`newer\_user`directly while old code can use`g\(newer\_user\)`and the really old code can use`f\(g\(newer\_user\)\)`\. ### Mutability constraints I said "these preserve all external properties" and that was a lie\. It depends on the properties we explicitly have, and I didn't list any\. The real interesting properties for me are mutability constraints on how the system can evolve\. So let's go back in time and add a constraint to`user`: ``` C1(u) = u.is_activated => u.is_activated' ``` This constraint means that if a user is activated, any change will preserve its activated\-ness\. This means a user can go from deactivated to activated but not the other way\. It's not a particular good constraint but it's good enough for teaching purposes\. Such a SQL constraint can be enforced with[triggers](https://www.postgresql.org/docs/current/sql-createeventtrigger.html)\. Now we can throw a constraint on`new\_user`: ``` C2(nu) = nu.activated_at != NULL => nu.activated_at' != NULL ``` If`nu`satisfies`C2`, then`f\(nu\)`satisfies`C1`\. So the refinement still holds\. With`newer\_u`, we*cannot*guarantee that`g\(newer\_u\)`satisfies`C2`because we can go from "activated" to "deactivated" just by appending a new event\. So it's not a refinement\. This is fixable by removing deactivation events, that would work too\. So a more interesting case is`bad\_user`, a refinement of`user`that has both`activated\_at`and`activated\_until`\. We propose the refinement mapping`b`: ``` b(bad_user).activated = if bad_user.activated_at == NULL && activated_until == NULL then FALSE else bad_user.activated_at <= now() < bad_user.activated_until ``` But now if enough time passes,`b\(bad\_user\)\.activated' = false`, so this is not a refinement either\. ### The punchline Refinement is one of the most powerful techniques in formal specification, but also one of the hardest for people to understand\. I'm starting to think that the reason it's so hard is because they learn refinement while they're*also*learning formal methods, so are faced with an unfamiliar topic in an unfamiliar context\. If that's the case, then maybe it's easier introducing refinement in a more common context like databases\. I've written a bit about refinement in the normal context[here](https://hillelwayne.com/post/refinement/)\(showing one specification is an implementation of another\)\. I kinda want to work this explanation into the book but it might be too late for big content additions like this\. \(Food for thought: how do refinement mappings relate to database views?\)

Similar Articles

Making Referential Stability a Type

Hacker News Top

A blog post about encoding referential stability as a TypeScript type using phantom brands, so React props can enforce that arrays, callbacks, and objects are stable across renders.

TRACE: An Operational Reasoning Schema for Auditable Agentic Commitments

arXiv cs.AI

This paper introduces TRACE (Typed Reasoning And Commitment Evidence), a typed, versioned schema for recording reasoning traces in agentic systems to enable auditability and improve reasoning quality. It defines a reference writer, measurement regime, and consumer contract, with two worked examples illustrating the approach.

Refining Over Resampling: Test-Time Self-Correction for LLM Reasoning

arXiv cs.AI

A new verifier-free breadth-depth refinement framework improves LLM reasoning at test time by sampling multiple rollouts, iteratively refining each via self-critique, and aggregating with majority voting. It consistently outperforms greedy decoding, majority voting, and verifier-based selection across several math benchmarks and open-weight models.