<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Software Safety - Medium]]></title>
        <description><![CDATA[How do we build software that is safe? Exploration of formula specifications, safety science and algorithm ethics. Header art courtesy of Freepik.com - Medium]]></description>
        <link>https://medium.com/software-safety?source=rss----361f7b548b5b---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Software Safety - Medium</title>
            <link>https://medium.com/software-safety?source=rss----361f7b548b5b---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 21 Oct 2026 23:26:10 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/software-safety" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Why Self Driving Cars Are Not Safe]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/software-safety/why-self-driving-cars-are-not-safe-37f05d3e6aaa?source=rss----361f7b548b5b---4"><img src="https://cdn-images-1.medium.com/max/2358/1*UBfoCix0qc2-oXZl0gMRFw.png" width="2358"></a></p><p class="medium-feed-snippet">As long as humans can blame the machine, they will use autonomous features incorrectly.</p><p class="medium-feed-link"><a href="https://medium.com/software-safety/why-self-driving-cars-are-not-safe-37f05d3e6aaa?source=rss----361f7b548b5b---4">Continue reading on Software Safety »</a></p></div>]]></description>
            <link>https://medium.com/software-safety/why-self-driving-cars-are-not-safe-37f05d3e6aaa?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/37f05d3e6aaa</guid>
            <category><![CDATA[ethics]]></category>
            <category><![CDATA[self-driving-cars]]></category>
            <category><![CDATA[tesla]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[safety]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Fri, 08 Jul 2022 15:31:19 GMT</pubDate>
            <atom:updated>2022-07-08T15:31:19.555Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[The Three Levels of Software Safety]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/software-safety/the-three-levels-of-software-safety-2097610ada60?source=rss----361f7b548b5b---4"><img src="https://cdn-images-1.medium.com/max/2600/1*ZTGEGgHCIK5JK0KFL1sX6w.jpeg" width="3000"></a></p><p class="medium-feed-snippet">The more software eats the world, the more critical safety is&#xA0;&#x2026; but what exactly does that mean?</p><p class="medium-feed-link"><a href="https://medium.com/software-safety/the-three-levels-of-software-safety-2097610ada60?source=rss----361f7b548b5b---4">Continue reading on Software Safety »</a></p></div>]]></description>
            <link>https://medium.com/software-safety/the-three-levels-of-software-safety-2097610ada60?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/2097610ada60</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[safety]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Mon, 28 Dec 2020 21:40:42 GMT</pubDate>
            <atom:updated>2020-12-28T03:32:24.095Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[A Software Engineer’s Guide to Cybernetics]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/software-safety/a-software-engineers-guide-to-cybernetics-d57c7def1453?source=rss----361f7b548b5b---4"><img src="https://cdn-images-1.medium.com/max/2600/1*nWc-adZerFK-uNmvSB__6g.jpeg" width="3784"></a></p><p class="medium-feed-snippet">Before &#x2018;cyber&#x2019; was a prefix for everything internet and computers, it was how mathematicians were going to conquer the world.</p><p class="medium-feed-link"><a href="https://medium.com/software-safety/a-software-engineers-guide-to-cybernetics-d57c7def1453?source=rss----361f7b548b5b---4">Continue reading on Software Safety »</a></p></div>]]></description>
            <link>https://medium.com/software-safety/a-software-engineers-guide-to-cybernetics-d57c7def1453?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/d57c7def1453</guid>
            <category><![CDATA[systems-thinking]]></category>
            <category><![CDATA[cybernetics]]></category>
            <category><![CDATA[history]]></category>
            <category><![CDATA[cyber]]></category>
            <category><![CDATA[computer-science]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Mon, 14 Sep 2020 14:26:16 GMT</pubDate>
            <atom:updated>2020-09-14T00:08:49.274Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Introduction to TLA+ Model Checking in the Command Line]]></title>
            <link>https://medium.com/software-safety/introduction-to-tla-model-checking-in-the-command-line-c6871700a6a2?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/c6871700a6a2</guid>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Sat, 05 Sep 2020 04:02:08 GMT</pubDate>
            <atom:updated>2019-01-18T05:12:28.554Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Otherwise known as “wait, you mean I can use the TLA toolbox from the command line?”</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_6aIMM9ub-BjIrPj_K9R2Q.jpeg" /><figcaption><a href="https://www.freepik.com/free-vector/programmers-concept-with-flat-design_2488094.htm">Designed by Freepik</a></figcaption></figure><p>I started playing around with TLA+ and its more programmer friendly variant PlusCal about a year ago. At that time the amount of documentation that was understandable to people like me who do not enjoy reading white papers full of mathematical notation was slim to none. (Today you can buy <a href="https://www.apress.com/us/book/9781484238288">Hillel Wayne’s excellent book Practical TLA+</a>) So I started reading other people’s code (errr…specs) and began piecing together some idea of how things were done.</p><p>TLA+ is a tool to model and verify concurrent systems, finding bugs in them before you have written any code by testing every possible combination of inputs. You might say “well how is that different from fuzzing inputs in more conventional functional tests?” and the answer is because TLA+ also simulates the kind of timing issues commonplace with distributive or concurrent systems. As microservices continue to dominate conversations about modern architecture, the question of what happens when a service sends a request that never arrives or arrives before something else has finished or gets repeated become more and more relevant. TLA+ allows you to test for these scenarios.</p><p>Beyond that it’s a radically different way of thinking about computer programs. It’s not a programming language so one has to abandon certain mental crutches and really think about how we represent what we mean. You can write an excellent model and then write code that doesn’t actually do things the way the model is describing. So you have to think about how your favorite frameworks and libraries actually do what they do.</p><p>I’m a huge fan of anything that might bring unvocalized assumptions up to the surface where people can explore them. It’s hugely difficult to build a complex system of services if two teams are not in sync about their assumptions.</p><p>The best way to think of TLA+ is like drawing a blueprint for software you’re going to build. I’ve seen people use it to test algorithms, but I’ve also seen people use it to model user flows throughout an application.</p><h3>However the Toolbox is Awful</h3><p>When you’re ready to test your models in TLA+ virtually every resource will direct you to the TLA+ Toolbox. The toolbox is a java application that looks a bit like it’s supposed to be an IDE and allows you to translate PlusCal into TLA+, configure models, test them and explore errors. Its UX is awful, simply awful. How you open a TLA file in the toolbox is not intuitive, things will often fail without any errors (or rather the toolbox will do what you’ve told it to do, which is nothing because you needed to click some buttons you didn’t click first). It’s literally exhibit A for why software developers should respect the skills of designers on their teams.</p><p>On top of that I find it a little clunky. I generally don’t work with full scale IDEs if I can avoid it. I prefer the stripped down elegance of TextMate, Sublime or Atom. Sometimes this means I miss out on valuable features, but I’m a person who currently has over thirty tabs open in Chrome, three projects open in TextMate and at least one WiP up in Scrivener…. I like to use my computer’s memory, okay? This is a light day.</p><p>At some point while I was noising around other people’s TLA+ specs to try to understand the language I realized that there seemed to be command line versions of the key tools from the toolbox. Rather than open up the toolkit I could compile PlusCal and test my models from my terminal and even more exciting … from other scripts.</p><h3>Wait… there’s a command line version?</h3><p>Yup! If you’ve downloaded the TLA+ toolbox, you already have the command line tools installed on your computer. To configure them there’s a helpful set of shell scripts you can download <a href="https://github.com/pmer/tla-bin">here</a>.</p><p>One of the first things I did with this knowledge was build a little proof of concept python application that parsed other python programs and tried to write models in PlusCal based on what they were going to do. I wanted to learn more about bytecode and AST and this seemed like a fun way to do it. It was partially successful in that I could write a few simple programs that became working models, but eventually I hit a wall trying to learn two new things at the same time.</p><p>But the potential of command line interfaces is still the same. If model checking can be executed via command line, then it can be executed by other programs. You can update your models and test them automatically the same way you might do with other forms of testing in continuous integration.</p><h3>pcal: Translating PlusCal to TLA+</h3><p>Once you have installed and configured the command line toolbox, you can start translating PlusCal comments in your specs to TLA+ using the pcal command.</p><p>Note that pcal will assume the extension .tla so you can leave it off if you want. For the sake of clarity I will include it.</p><p>The basic command is:</p><pre>pcal myspec.tla</pre><p>Unlike command line tools you might be used to pcal is very particular about order of arguments. The name of the file you want to translate should always be last, any flags should come before it.</p><p>-version does something that might be unexpected. Rather than telling you the version of pcal you have installed, it lets you pick which version of PlusCal the translator will assume it’s translating (this currently goes up to 1.8)</p><p>If you want to see the AST for your PlusCal you can use the -writeAST flag to forego the rest of the translation just to output the AST.</p><p>This is super useful tool but not without its bugs. For example, I understand that-spec and -spec2 are supposed to let you choose between version 1 and 2 of TLA+, but I’ve never been able to get them to work.</p><h3>Defining Models with Configuration files</h3><p>If you’re used to the Toolbox interface, you’re probably wondering how you can configure your invariants and other test cases. The answer is the .cfg file automatically generated for you by the pcal command.</p><p>Normally it will look like this:</p><pre>SPECIFICATION Spec<br>\* Add statements after this line.</pre><p>But there are a variety of parameters you can set before checking your models. The most useful of these areINVARIANT and PROPERTY which specify which invariants and which temporal properties to test respectively.</p><p>Here’s a full list of all the possible configuration settings</p><pre>INVARIANT<br>INVARIANTS<br>PROPERTY<br>PROPERTIES<br>CONSTANT<br>CONSTANTS<br>CONSTRAINT<br>CONSTRAINTS<br>ACTION_CONSTRAINT<br>ACTION_CONSTRAINTS<br>INIT<br>NEXT<br>VIEW<br>SYMMETRY<br>TYPE<br>TYPE_CONSTRAINT</pre><p>I have not been able to figure out what the difference between the singular form of the parameter (eg CONSTANT) and the plural form of the parameter (eg CONSTANTS) is. Both can take multiple values if separated by a space. It might be that there’s some backwards compatibility issue involved here but…. ¯\_(ツ)_/¯</p><p>Make sure you add your custom configuration below the line. If you put them above that comment line, pcal will overwrite them every time you translate.</p><h3>tlc: Checking the Models</h3><p>Now it’s time to run the model checker! Like pcal the .tla extension is optional. The basic command looks like this:</p><pre>tlc myspec.tla</pre><p>And this will produce the following output:</p><pre>TLC2 Version 2.10 of 28 September 2017<br>Running breadth-first search Model-Checking with 1 worker on 4 cores with 910MB heap and 64MB offheap memory (Mac OS X x.x.x x86_64, Oracle Corporation x.x.x_x x86_64).<br>Parsing file myspec.tla<br>Parsing file /var/folders/sx/tb9t1_890xd0hs9y0xb1k5fr0000gn/T/Integers.tla<br>Parsing file /var/folders/sx/tb9t1_890xd0hs9y0xb1k5fr0000gn/T/Naturals.tla<br>Semantic processing of module Naturals<br>Semantic processing of module Integers<br>Semantic processing of module myspec<br>Starting... (2019-01-17 23:06:59)<br>Implied-temporal checking--satisfiability problem has 1 branches.<br>Computing initial states...<br>Finished computing initial states: 25 distinct states generated.<br>Progress(5) at 2019-01-17 23:07:00: 332 states generated, 222 distinct states found, 0 states left on queue.<br>Checking temporal properties for the complete state space with 222 total distinct states at (2019-01-17 23:07:00)</pre><p>This tells you what modules were imported and how many states the model has. If you’re curious the actual spec we’re running here is the wire transfer example in Hillel Wayne’s book. That particular spec has a temporal property that we know will be violated by the algorithm. This is what that looks like:</p><pre>Error: Temporal properties were violated.</pre><pre>Error: The following behavior constitutes a counter-example:</pre><pre>State 1: &lt;Initial predicate&gt;<br>/\ people = {&quot;alice&quot;, &quot;bob&quot;}<br>/\ sender = &lt;&lt;&quot;alice&quot;, &quot;alice&quot;&gt;&gt;<br>/\ receiver = &lt;&lt;&quot;bob&quot;, &quot;bob&quot;&gt;&gt;<br>/\ acc = [alice |-&gt; 5, bob |-&gt; 5]<br>/\ amount = &lt;&lt;1, 1&gt;&gt;<br>/\ pc = &lt;&lt;&quot;CheckAndWithdraw&quot;, &quot;CheckAndWithdraw&quot;&gt;&gt;</pre><pre>State 2: &lt;Action line 55, col 27 to line 61, col 77 of module wire&gt;<br>/\ people = {&quot;alice&quot;, &quot;bob&quot;}<br>/\ sender = &lt;&lt;&quot;alice&quot;, &quot;alice&quot;&gt;&gt;<br>/\ receiver = &lt;&lt;&quot;bob&quot;, &quot;bob&quot;&gt;&gt;<br>/\ acc = [alice |-&gt; 4, bob |-&gt; 5]<br>/\ amount = &lt;&lt;1, 1&gt;&gt;<br>/\ pc = &lt;&lt;&quot;CheckAndWithdraw&quot;, &quot;Deposit&quot;&gt;&gt;</pre><pre>State 3: &lt;Action line 55, col 27 to line 61, col 77 of module wire&gt;<br>/\ people = {&quot;alice&quot;, &quot;bob&quot;}<br>/\ sender = &lt;&lt;&quot;alice&quot;, &quot;alice&quot;&gt;&gt;<br>/\ receiver = &lt;&lt;&quot;bob&quot;, &quot;bob&quot;&gt;&gt;<br>/\ acc = [alice |-&gt; 3, bob |-&gt; 5]<br>/\ amount = &lt;&lt;1, 1&gt;&gt;<br>/\ pc = &lt;&lt;&quot;Deposit&quot;, &quot;Deposit&quot;&gt;&gt;</pre><pre>State 4: &lt;Action line 63, col 18 to line 66, col 68 of module wire&gt;<br>/\ people = {&quot;alice&quot;, &quot;bob&quot;}<br>/\ sender = &lt;&lt;&quot;alice&quot;, &quot;alice&quot;&gt;&gt;<br>/\ receiver = &lt;&lt;&quot;bob&quot;, &quot;bob&quot;&gt;&gt;<br>/\ acc = [alice |-&gt; 3, bob |-&gt; 6]<br>/\ amount = &lt;&lt;1, 1&gt;&gt;<br>/\ pc = &lt;&lt;&quot;Done&quot;, &quot;Deposit&quot;&gt;&gt;</pre><pre>State 5: Stuttering<br>Finished checking temporal properties in 00s at 2019-01-17 23:07:00<br>332 states generated, 222 distinct states found, 0 states left on queue.<br>Finished in 01s at (2019-01-17 23:07:00)</pre><p>If you are already familiar with the Toolbox, this looks pretty much like the output you’d see before, expect without the highlighting.</p><p>By default tlc stops at the first invariant error. If you’d like it to continue, you use the flag -continue. In general tlc&#39;s options are split into two types: MODE-SWITCHES and GENERAL-SWITCHES. Mode switches change how the models are run, from switching from breadth-first to depth-first, to picking a seed number for randomization. General switches allow you to specify different configuration files, how much detail is printed to stdout.</p><p>I hope this is enough to get you started. I will update this post from time to time as I figure out more about how the tools work. <strong>Happy Model Checking!</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c6871700a6a2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/software-safety/introduction-to-tla-model-checking-in-the-command-line-c6871700a6a2">Introduction to TLA+ Model Checking in the Command Line</a> was originally published in <a href="https://medium.com/software-safety">Software Safety</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using TLA+ to Model Cascading Failures]]></title>
            <link>https://medium.com/software-safety/using-tla-to-model-cascading-failures-5d1ebc5e4c4f?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/5d1ebc5e4c4f</guid>
            <category><![CDATA[tla-plus]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[resilience]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Sat, 05 Sep 2020 04:01:46 GMT</pubDate>
            <atom:updated>2019-04-10T11:34:14.411Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5V3vepuG_ahdMLS6zxzaEw.jpeg" /><figcaption>Business vector created by <a href="https://www.freepik.com/free-photos-vectors/business">fullvector</a></figcaption></figure><p>TLA+is traditionally used to model the algorithms that power distributed systems. However, most engineering teams do not build their own message queues, they install and configure multiple solutions developed by other people. The biggest threat to these teams is how all those solutions interact together at scale.</p><p>Typically, engineering teams search for those threats by doing live failure tests. Either running extensive performance tests on a special testing environment, or actually sabotaging their own infrastructure to trigger a response ala Chaos Monkey. These are great practices, but somewhat resource intensive. And because they are resource intensive, organizations tend to put them off.</p><h4>Modeling and its Discontents</h4><p>There’s a popular saying a statistics: <em>All models are wrong, but some are useful</em>.</p><p>I’ve been played around with TLA+ for a while and was curious if it could be used to model the interactions between systems well enough to catch the kinds of complex failures that happen when distributed systems are chained together. I wasn’t sure it was possible to reach a“math proof” level of accuracy between the model and the real life system it was built to reflect but I was eager to try.</p><p>That being said, the purpose of this type of modeling is not to build perfect systems, but to help your team define and clarify assumptions. This kind of modeling forces the team to ask themselves exactly how the various products and services they are using interact and it can help find places where we actually don’t know what the expected behavior is, which is useful in minimizing complex failure but will not totally eliminate it.</p><p>The hard thing about modeling is figuring out when to simplify and when to specify. The whole point is to uncover details that in retrospect seem obvious but were otherwise not top of mind. But if one tries to capture every detail in the model it will likely be too complex to run.</p><h4>Example One: The Naive Cluster</h4><p>Let’s start off with a basic situation. We have three VMs. Each of them has a finite amount of resources, we can think of this as memory, CPU, or I/O but to be frank we don’t need that level of specificity. The important thing is that once the resources are exhausted on any single VM it is no longer healthy.</p><p>We start off with something like this:</p><pre>------------------- MODULE provisioning_naive -------------------<br>EXTENDS TLC, Integers, Sequences</pre><pre>VMs == {&quot;Server1&quot;,&quot;Server2&quot;,&quot;Server3&quot;}</pre><pre>(*--algorithm provisioning<br>variables<br> Cluster = [v \in VMs |-&gt; 4],</pre><pre>define<br> ServersHealthy == \A vm \in VMs: Cluster[vm] &lt;= 9<br>end define;</pre><pre>end algorithm *)<br>==================================================================</pre><p>In this model we’re going to use 0–10 to represent resource use percentage. We could use 0–100, but that would increase the amount of states TLA+ has to check rather dramatically without really providing any additional value. The way TLA+ works is that it runs all possible sets of input looking for places where requirements that you have defined (assertions, invariants, temporal properties) are violated. It is unlikely that for this model an input of 57% will produce an error that an input of 55% didn’t already catch, so we take the opportunity to simplify and use 0–10.</p><p>We then define an invariant called ServersHealthy in which we tell TLA+ that every server in our cluster should be below 90% resource use. We initialize our servers at 40% resource use to start. Why 40%? Here’s a great example of how model checking can lead to interesting conversations about what we really know with any given architecture. We know that when we spin up a new server it is using <em>some</em> memory and <em>some</em> CPU… that number is not 0%, but it’s also not 100%. What number in between is the most accurate? How would we figure that out? Should we be tracking this over time?</p><p>Anyway, let’s say we did all that and we decide on an initial state of 40% utilization. This is where we need to learn how to think about a system of systems as an algorithm instead of an environment. Although in the middle of an outage or complex failure it might seem like things are happening at random, in reality each system has a defined set of rules that govern its behavior and choices, and each system it hands off to has a defined set of rules that govern its behavior and choices. Just because the exact set of inputs is unexpected does not make the behavior random.</p><p>We want this first model to be naive so we’re going to define an incident as a series of events that change the amount of resources being used by one of our VMs. You can think of these as processes eating up CPU or requests using memory, whatever.</p><pre>Events == [target: VMs, size: 0..4]</pre><p>This defines an event as having a target (one of our possible three VMs already defined) and a size (a range from not increasing resource use to increasing it by 4). We’ll also define an incident as being a set of four events. Don’t worry you’ll get to see this altogether in one file at the end.</p><pre>Incidents(set) == set \X set \X set \X set</pre><pre>...</pre><pre>incident \in Incidents(Events)</pre><p>Why four? It’s pretty much an arbitrary number at this point. We want to see how multiple incoming processes/requests affect our VMs so we need more than one, but the more events we have in an incident the more combinations TLA+ has to consider, the longer the full model takes to run. So we settle on four as the sweet spot where we have enough events to see problematic behavior but not so much that we need to grab a coffee and come back to check the model’s results.</p><p>Now the meat and potatoes of our algorithm. As an event occurs it increases the resource use of one of our VMs.</p><pre>macro trigger_event(event) begin<br> Cluster[event.target] := Cluster[event.target] + event.size<br>end macro;</pre><pre>begin<br> while incident /= &lt;&lt;&gt;&gt; do<br>  curr := Head(incident);<br>  incident := Tail(incident);<br>  trigger_event(curr)<br> end while;</pre><pre>end algorithm *)</pre><p>I’m doing this as a macro trigger_event because we’ll want to add more complexity to it soon and breaking it out will keep things clean. Here’s what the full code looks like:</p><pre>------------------ MODULE provisioning_naive --------------------<br>EXTENDS TLC, Integers, Sequences</pre><pre>VMs == {&quot;Server1&quot;,&quot;Server2&quot;,&quot;Server3&quot;}<br>Incidents(set) == set \X set \X set \X set<br>Events == [target: VMs, size: 0..4]</pre><pre>(*--algorithm provisioning<br>variables<br> incident \in Incidents(Events),<br> Cluster = [v \in VMs |-&gt; 4],<br> curr = &quot;&quot;; \* helper: current event</pre><pre>define<br> ServersHealthy == \A vm \in VMs: Cluster[vm] &lt;= 9<br>end define;</pre><pre>macro trigger_event(event) begin<br> Cluster[event.target] := Cluster[event.target] + event.size<br>end macro;</pre><pre>begin<br> while incident /= &lt;&lt;&gt;&gt; do<br>  curr := Head(incident);<br>  incident := Tail(incident);<br>  trigger_event(curr)<br> end while;</pre><pre>end algorithm *)</pre><pre>===================================================================</pre><p>Running this produces a sensible and predictably uninspiring result:</p><pre>Finished computing initial states: 531441 distinct states generated.<br>Error: Invariant ServersHealthy is violated.<br>Error: The behavior up to this point is:<br>/\ incident = &lt;&lt; [target |-&gt; &quot;Server1&quot;, size |-&gt; 2],<br>   [target |-&gt; &quot;Server1&quot;, size |-&gt; 4],<br>   [target |-&gt; &quot;Server1&quot;, size |-&gt; 4],<br>   [target |-&gt; &quot;Server1&quot;, size |-&gt; 4] &gt;&gt;<br>/\ curr = &quot;&quot;<br>/\ pc = &quot;Lbl_1&quot;<br>/\ Cluster = [Server1 |-&gt; 4, Server2 |-&gt; 4, Server3 |-&gt; 4]</pre><pre>State 2: &lt;Action line 67, col 10 to line 74, col 62 of module provisioning_naive&gt;<br>/\ incident = &lt;&lt; [target |-&gt; &quot;Server1&quot;, size |-&gt; 4],<br>   [target |-&gt; &quot;Server1&quot;, size |-&gt; 4],<br>   [target |-&gt; &quot;Server1&quot;, size |-&gt; 4] &gt;&gt;<br>/\ curr = [target |-&gt; &quot;Server1&quot;, size |-&gt; 2]<br>/\ pc = &quot;Lbl_1&quot;<br>/\ Cluster = [Server1 |-&gt; 6, Server2 |-&gt; 4, Server3 |-&gt; 4]</pre><pre>State 3: &lt;Action line 67, col 10 to line 74, col 62 of module provisioning_naive&gt;<br>/\ incident = &lt;&lt;[target |-&gt; &quot;Server1&quot;, size |-&gt; 4], [target |-&gt; &quot;Server1&quot;, size |-&gt; 4]&gt;&gt;<br>/\ curr = [target |-&gt; &quot;Server1&quot;, size |-&gt; 4]<br>/\ pc = &quot;Lbl_1&quot;<br>/\ Cluster = [Server1 |-&gt; 10, Server2 |-&gt; 4, Server3 |-&gt; 4]</pre><p>TLA+ let’s us know that if we have three VMs at 40% of resource use, a process that increases resource use by 20% followed by another increase in resource of 40% on the same server will result in that VM being unhealthy… which is not something you would need to build a model to figure out but is helpful at demonstrating the concept of using TLA+ to model systems integration.</p><p>The core problem with this model is that it doesn’t capture a few essential components of behavior. We have a magical load balancer that apparently has no logic defining which VMs get which traffic, and processes consuming resources indefinitely.</p><p>Let’s change some of that.</p><h4>Example 2: Load Balancing</h4><p>To add a load balancer to the mix we’re first going to change our VM names to integers. There was no particular reason to have their identifiers be strings, except that it made the code a bit easier to read, but by changing them to integers modeling a round robin load balancer becomes just a matter of counting to three over again.</p><p>We don’t need our events directed to a specific VM anymore so we’ll just get rid of that. Then we add some basic conditional logic to trigger_event so that the model directs the next event to the next VM in our round robin rotation. Altogether we have:</p><pre>------------------- MODULE provisioning_load ---------------------<br>EXTENDS TLC, Integers, Sequences</pre><pre>VMs == {1,2,3}<br>Incidents(set) == set \X set \X set \X set<br>Events == [size: 1..4]</pre><pre>(*--algorithm provisioning<br>variables<br> incident \in Incidents(Events),<br> Cluster = [v \in VMs |-&gt; 4],<br> curr = &quot;&quot;, \* helper: current event<br> rrobin = 1;</pre><pre>define<br> ServersHealthy == \A vm \in VMs: Cluster[vm] &lt;= 9<br>end define;</pre><pre>macro trigger_event(event) begin<br> Cluster[rrobin] := Cluster[rrobin] + event.size;<br> if rrobin = 3 then<br>    rrobin := 1<br> else<br>    rrobin := rrobin + 1<br> end if;<br>end macro;</pre><pre>begin<br> while incident /= &lt;&lt;&gt;&gt; do<br>  curr := Head(incident);<br>  incident := Tail(incident);<br>  trigger_event(curr)<br> end while;</pre><pre>end algorithm *)</pre><pre>==================================================================</pre><p>You probably don’t need to run the model to guess what’s going to happen. The fourth event will go back to the first VM and knock it over our limit. To improve our model we need to program in recovering resources when processes end.</p><p>There are a couple of different ways we could approach that. We could give each event a timeout after which its load on the VM is cleared away, but that creates an unhelpful amount of complexity in the model. We’re only doing four events per incident, it’s not likely that timeouts would make enough of an impact to give us any insight we didn’t have before.</p><p>Another way of approaching it is to think of the reclaiming of resources as garbage collection. One event is not one process or request raising the resource use by a single VM, it is just a state change that could have been triggered by any number of things. Maybe Event X increases VM 2 resource use by 20% and Event X is actually a number of processes that happened in an undefined period of time. Then we assume at various points that some of those processes have ended naturally, some have timed out, but that those resources are still “used” because the machine hasn’t released them yet.</p><p>To represent that we have to rework our algorithm into <strong><em>processes</em></strong> because processes are how TLA+ represents concurrency and garbage collection should happen independently of any events happening to the server. Because we’re breaking this up into processes, we’re also going to move the variable initializations around a little bit. Some variables are only relevant to certain processes, it makes sense to keep them local. Any macros we call within the process will be able to access them.</p><pre>-------------------- MODULE provisioning_load ---------------------<br>EXTENDS TLC, Integers, Sequences</pre><pre>VMs == {1,2,3}<br>Incidents(set) == set \X set \X set \X set<br>Events == [size: 1..4]</pre><pre>(*--algorithm provisioning<br>variables<br> Cluster = [v \in VMs |-&gt; 4],</pre><pre>define<br> ServersHealthy == \A vm \in VMs: Cluster[vm] &lt;= 9<br>end define;</pre><pre>macro trigger_event(event) begin<br> Cluster[rrobin] := Cluster[rrobin] + event.size;<br> if rrobin = 3 then<br>  rrobin := 1;<br> else<br>  rrobin := rrobin + 1<br> end if;<br>end macro;</pre><pre>process garbage_collection = &quot;recover resources&quot;<br> variables<br>  Gcollection \in [1..3 -&gt; 1..4],</pre><pre>begin Collect:<br>  with i \in VMs do<br>   if Gcollection[i] &gt;= Cluster[i] then<br>    Cluster[i] := 0;<br>   else<br>    Cluster[i] := Cluster[i] - Gcollection[i];<br>   end if;<br>  end with;<br>end process;</pre><pre>process incoming = &quot;increasing resource usage&quot;<br> variables<br>  incident \in Incidents(Events),<br>  curr = &quot;&quot;, \* helper: current event<br>  rrobin = 1;</pre><pre>begin Increase:<br>  while incident /= &lt;&lt;&gt;&gt; do<br>   curr := Head(incident);<br>   incident := Tail(incident);<br>   trigger_event(curr);<br>  end while;<br>end process;<br>end algorithm; *)</pre><pre>====================================================================</pre><p>What’s important to keep in mind when designing TLA+ models is that the model checker will test <em>all possible combinations of inputs</em>. Not the most likely. So this code will include all the likely scenarios where some VMs free up a lot of resources and some free up only a few, but also scenarios where no one frees up anything.</p><p>Which obviously will put us back where we started, despite that scenario being unlikely.</p><p>Going back to the <em>all models are wrong, but some are useful </em>maxim. Is this a useful level of insight? Not any more than having no garbage collection at all was. It is tempting to force the model to always do garbage collection just to get the model to pass, which is also not useful.</p><p>What we could do instead is reconsider ifServersHealthy should be an invariant the way it is. When we define an invariant, TLA+ will error if that statement is false for any reason, at the end of every step. ServersHealthy defines our model as being in a failure state whenever any of our three VMs are unhealthy, but in reality VMs do occasionally become unhealthy and go offline and it does not generally end Western civilization as we know it.</p><p>If we change the \A (<strong>A</strong>ll VMs are good) in our invariant to \E (at least one good VM <strong>E</strong>xists) the model runs without any failure states:</p><pre>ServersHealthy == \E vm \in VMs: Cluster[vm] &lt;= 9</pre><p>But that’s not enough… We can do better. We can add even more complexity.</p><h4>Example 3: Autoscaling</h4><p>There are lots of things about our models that are still lacking. Obviously if we were to move beyond four events our outcomes would look very different. And we’re missing a big piece of modern cloud architecture: autoscaling. So let’s rethink the model a bit.</p><p>We’re currently representing the VMs in a <strong><em>struct</em></strong> which in TLA is kind of like a hash map. This made assigning resource increases to various VMs super easy but it isn’t the best option when we want to add and remove VMs from the pool. Instead we’re going to store this information in a <strong><em>sequence</em></strong>, basically a list of numbers wherein each number represents the resource use of the VM.</p><pre>(*--algorithm provisioning<br>variables<br>VMs \in [1..2 -&gt; 0..10]</pre><p>[1..2-&gt;0..10] creates a set of sequences that represents all possible combinations of two VMs with a potential resource use from 0% to 100%. Creating a series of traffic patterns as in previous models was a bit easier to conceptualize, but not very efficient. There’s also a question of whether the right combination of state changes will bring both VMs to a place that is unsustainable. In our previous model we survived 4 cycles, but probably would not survive 8. By creating a set of every possible combination we no longer need cycles at all.</p><p>But this change requires us to remove our invariant and replace with something else:</p><pre>define<br> ServersHealthy == &lt;&gt;(\E v \in 1..Len(VMs): VMs[v] &lt;= 9)<br>end define;</pre><p>This is a <strong><em>temporal property</em></strong> it’s defined and configured in much the same way as an invariant but it lets us use &lt;&gt; which in TLA+ means “eventually true”. If we were to keep the invariant then the model would fail as soon as TLA+ gave it the sequence&lt;&lt;10,10&gt;&gt;</p><p>This model doesn’t really load balance… honestly I went back and forth on this. Ultimately when a new VM is added to the pool, what we should expect to see is resource use even out as traffic is distributed across a larger group of machines. But if we aren’t doing cycles anymore I’m not sure that reflecting this behavior in the model is useful. So instead we have three processes: one that kills an unhealthy VM, one that adds a VM to the pool when resource use is high, and one that shuts down VMs when resource use is low.</p><pre>fair process VM_dies = &quot;kill a VM&quot;<br> begin Fail:<br>  await Len(VMs) &gt; 1;<br>  VMs := SelectSeq(VMs, LAMBDA x: x &lt; 8);<br>end process;</pre><pre>fair process scale_up = &quot;autoscale up&quot;<br> variable<br>  load = FALSE;</pre><pre>begin Scale_up:<br>  load := \E v \in 1..Len(VMs): VMs[v] &gt; 6;<br>  if load then<br>   VMs := Append(VMs, 4);<br>  end if;<br>end process;</pre><pre>fair process scale_down = &quot;autoscale down&quot;<br>variables<br> load = FALSE</pre><pre>begin Scale_Down:<br> await Len(VMs) &gt; 1;<br> load := \A v \in 1..Len(VMs): VMs[v] &lt; 4;<br> if load then<br>  VMs := Tail(VMs);<br> end if;<br>end process;</pre><p>There are a few unfamiliar things here. First await tells TLA+ not to run the process until the statement that follows it is true. VM_dies and scale_down will not run if the current pool has less than two VMs in it. This keeps all our servers from being turned off at once.</p><p>Oh except…. our model has found a case where that’s not true:</p><pre>Error: Deadlock reached.<br>Error: The behavior up to this point is:<br>State 1: &lt;Initial predicate&gt;<br>/\ load_ = FALSE<br>/\ pc = ( &quot;kill a VM&quot; :&gt; &quot;Fail&quot; @@<br>  &quot;autoscale up&quot; :&gt; &quot;Scale_up&quot; @@<br>  &quot;autoscale down&quot; :&gt; &quot;Scale_Down&quot; @@<br>  &quot;modify load on VMs&quot; :&gt; &quot;modify_load&quot; )<br>/\ VMs = &lt;&lt;8, 8&gt;&gt;<br>/\ load = FALSE</pre><pre>State 2: &lt;Action line 83, col 9 to line 87, col 38 of module provisioning_autoscale&gt;<br>/\ load_ = FALSE<br>/\ pc = ( &quot;kill a VM&quot; :&gt; &quot;Done&quot; @@<br>  &quot;autoscale up&quot; :&gt; &quot;Scale_up&quot; @@<br>  &quot;autoscale down&quot; :&gt; &quot;Scale_Down&quot; @@<br>  &quot;modify load on VMs&quot; :&gt; &quot;modify_load&quot; )<br>/\ VMs = &lt;&lt;&gt;&gt;<br>/\ load = FALSE</pre><pre>State 3: &lt;Action line 91, col 13 to line 98, col 27 of module provisioning_autoscale&gt;<br>/\ load_ = FALSE<br>/\ pc = ( &quot;kill a VM&quot; :&gt; &quot;Done&quot; @@<br>  &quot;autoscale up&quot; :&gt; &quot;Done&quot; @@<br>  &quot;autoscale down&quot; :&gt; &quot;Scale_Down&quot; @@<br>  &quot;modify load on VMs&quot; :&gt; &quot;modify_load&quot; )<br>/\ VMs = &lt;&lt;&gt;&gt;<br>/\ load = FALSE</pre><p>If both our VMs are at 80% usage and the kill process runs before the scale up process, they will both be killed. VMs := SelectSeq(VMs, LAMBDA x: x &lt; 8); selects a new sequence from the original VMs, picking only the elements where the lambda returns true, in this case only VMs with less than 80% usage … in effect removing unhealthy machines from the pool.</p><p>And the pool doesn’t replace them because \E v \in 1..Len(VMs): VMs[v] &gt; 6; returns false when applied to an empty sequence. So let’s change that by adding and Len(VMs) &lt; 2 \/ to the beginning which will make it true if we have less than 2 VMs in our pool.</p><p>But even though we can control for this behavior by tweaking the model. This is not a fault in our model exactly. Actually it raises a really important question: what does our autoscaling service do when all the servers are unhealthy and it cannot spin up new ones? This actually happened to <a href="https://medium.com/u/e85413579167">Buildkite</a> a few years ago when some <a href="https://building.buildkite.com/outage-post-mortem-for-august-23rd-82b619a3679b">misconfigured settings triggered failure notices on otherwise healthy servers</a>. In fact it’s exactly the sort of issue that tends to trigger cascading failures.</p><p>Right now we’re just going to update the model to prevent this, but if I were modeling a real system what I might want to do instead is build another model that looked at the process of spinning up new servers in detail. This is an important design practice for TLA+, because we are considering every possible state we don’t want to be adding more and more complexity to the same model. Instead we want to <strong>build more models</strong>. Using broad but simple business process models to find the edge cases, then building models that look at how we do those specific components in detail and testing for those edge cases.</p><p>Altogether here’s our finished model:</p><pre>----------------- MODULE provisioning_autoscale -------------------<br>EXTENDS TLC, Integers, Sequences</pre><pre>(*--algorithm provisioning<br>variables<br>VMs \in [1..2 -&gt; 0..10]</pre><pre>define<br> ServersHealthy == &lt;&gt;(\E v \in 1..Len(VMs): VMs[v] &lt;= 9)<br>end define;</pre><pre>fair process VM_dies = &quot;kill a VM&quot;<br> begin Fail:<br>  await Len(VMs) &gt; 1;<br>  VMs := SelectSeq(VMs, LAMBDA x: x &lt; 8);<br>end process;</pre><pre>fair process scale_up = &quot;autoscale up&quot;<br> variable<br>  load = FALSE;</pre><pre>begin Scale_up:<br>  load := Len(VMs) &lt; 2 \/ \E v \in 1..Len(VMs): VMs[v] &gt; 6;<br>  if load then<br>   VMs := Append(VMs, 4);<br>  end if;<br>end process;</pre><pre>fair process scale_down = &quot;autoscale down&quot;<br>variables<br> load = FALSE</pre><pre>begin Scale_Down:<br> await Len(VMs) &gt; 1;<br> load := \A v \in 1..Len(VMs): VMs[v] &lt; 4;<br> if load then<br>  VMs := Tail(VMs);<br> end if;<br>end process;</pre><pre>end algorithm; *)</pre><pre>==================================================================</pre><p>The fair keyword tells TLA+ that it is not necessary for those processes to run in order for the model to complete. We might never need to kill a server, for example, so TLA+ shouldn’t wait for us to have to.</p><h4>Using Modeling as Part of the Development Process</h4><p>All this seems like a lot of work, where does it connect to a normal development process? I see the benefit of modeling being primarily about communication of assumptions and gathering insight into the seams that join complex infrastructure. Good models can help you decide things like:</p><ol><li><strong>Technology selection.</strong> Too often we choose tools based on what we’re already familiar with or what is trendy and cool. Modeling can help you determine what behaviors you’re looking for from the components of your architecture</li><li><strong>Monitoring and alerting strategy.</strong> Knowing how systems are likely to fail can help you figure out what to monitor without having to wait for failure to happen.</li><li><strong>SLOs.</strong> Part of the process of modeling is figuring out what the desired outcomes actually are before you’ve written any code. You can take those conclusions and draft your SLOs from them.</li></ol><p>Make no mistake, complex systems will always have unexpected failures. Nothing can completely prevent them, but modeling can improve your awareness around them and give you an edge in cutting them off before they lead to outages.</p><h4>Other Models To Look At</h4><ul><li><a href="https://www.hillelwayne.com/post/modeling-deployments/">Zero Downtime Deployments</a></li><li><a href="https://learntla.com/concurrency/example/">Rate Limiting</a></li><li><a href="https://medium.com/espark-engineering-blog/formal-methods-in-practice-8f20d72bce4f">Batch Installer</a></li></ul><p>If you’d like to see the code and configuration from my models, <a href="https://github.com/mbellotti/tla-cascading-failure">they’re here on Github</a>. Also thanks as always to the amazing <a href="https://medium.com/u/462061eb878f">Hillel Wayne</a> for helping me track down the occasional TLA+ bug. <a href="https://www.apress.com/us/book/9781484238288">Go buy his book!</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5d1ebc5e4c4f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/software-safety/using-tla-to-model-cascading-failures-5d1ebc5e4c4f">Using TLA+ to Model Cascading Failures</a> was originally published in <a href="https://medium.com/software-safety">Software Safety</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Subtle Power of Booleans]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/software-safety/the-subtle-power-of-booleans-e6d5fa2fcc4a?source=rss----361f7b548b5b---4"><img src="https://cdn-images-1.medium.com/max/2000/1*g2M75eldgkUDALotNuB7FQ.jpeg" width="2000"></a></p><p class="medium-feed-snippet">Programming with truth and statefulness.</p><p class="medium-feed-link"><a href="https://medium.com/software-safety/the-subtle-power-of-booleans-e6d5fa2fcc4a?source=rss----361f7b548b5b---4">Continue reading on Software Safety »</a></p></div>]]></description>
            <link>https://medium.com/software-safety/the-subtle-power-of-booleans-e6d5fa2fcc4a?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/e6d5fa2fcc4a</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[logic]]></category>
            <category><![CDATA[computer-science]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Sat, 05 Sep 2020 04:01:16 GMT</pubDate>
            <atom:updated>2020-03-25T22:06:57.046Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[What Would AI Look Like If We Built It Using System Safety?]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/software-safety/what-would-ai-look-like-if-we-built-it-using-system-safety-a801e6dbae35?source=rss----361f7b548b5b---4"><img src="https://cdn-images-1.medium.com/max/1850/1*7YYszwLEO4PQKoKvUD7kfQ.png" width="1850"></a></p><p class="medium-feed-snippet">How does the science of safety fit into software development?</p><p class="medium-feed-link"><a href="https://medium.com/software-safety/what-would-ai-look-like-if-we-built-it-using-system-safety-a801e6dbae35?source=rss----361f7b548b5b---4">Continue reading on Software Safety »</a></p></div>]]></description>
            <link>https://medium.com/software-safety/what-would-ai-look-like-if-we-built-it-using-system-safety-a801e6dbae35?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/a801e6dbae35</guid>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[safety]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Sat, 05 Sep 2020 04:00:57 GMT</pubDate>
            <atom:updated>2020-05-12T08:59:26.800Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Making AI Better by Making it Slower]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/software-safety/making-ai-better-by-making-it-slower-34e09ba9fcb9?source=rss----361f7b548b5b---4"><img src="https://cdn-images-1.medium.com/max/600/1*wzoQ-qBLJFme-hMJqGZc4Q.png" width="600"></a></p><p class="medium-feed-snippet">The difference between beneficial and destructive AI may be whether making fast thinking faster has any utility for the user.</p><p class="medium-feed-link"><a href="https://medium.com/software-safety/making-ai-better-by-making-it-slower-34e09ba9fcb9?source=rss----361f7b548b5b---4">Continue reading on Software Safety »</a></p></div>]]></description>
            <link>https://medium.com/software-safety/making-ai-better-by-making-it-slower-34e09ba9fcb9?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/34e09ba9fcb9</guid>
            <category><![CDATA[psychology]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Sat, 05 Sep 2020 04:00:38 GMT</pubDate>
            <atom:updated>2020-06-19T05:41:05.791Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Drift Mapping and Software Design]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/software-safety/drift-mapping-and-software-design-6362892c41c2?source=rss----361f7b548b5b---4"><img src="https://cdn-images-1.medium.com/max/1641/1*2g442GkNZ8VsWe0L4VjriQ.png" width="1641"></a></p><p class="medium-feed-snippet">Computers can&#x2019;t avoid automating decisions if software engineers can&#x2019;t figure out what decisions are being made.</p><p class="medium-feed-link"><a href="https://medium.com/software-safety/drift-mapping-and-software-design-6362892c41c2?source=rss----361f7b548b5b---4">Continue reading on Software Safety »</a></p></div>]]></description>
            <link>https://medium.com/software-safety/drift-mapping-and-software-design-6362892c41c2?source=rss----361f7b548b5b---4</link>
            <guid isPermaLink="false">https://medium.com/p/6362892c41c2</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ethics]]></category>
            <dc:creator><![CDATA[Marianne Bellotti]]></dc:creator>
            <pubDate>Sat, 05 Sep 2020 04:00:19 GMT</pubDate>
            <atom:updated>2020-07-13T14:08:49.135Z</atom:updated>
        </item>
    </channel>
</rss>