├── .gitignore ├── .vscode └── settings.json ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── _config.yml ├── _data ├── business-logic │ └── junior-dev │ │ ├── v1.json │ │ └── v2.json ├── caching │ ├── cache-invalidation │ │ ├── v1.json │ │ └── v2.json │ ├── naive-model │ │ ├── always.json │ │ └── eventually.json │ ├── reproducing-the-bug │ │ └── bug.json │ └── working-cache-invalidation │ │ └── v3.json ├── database-blob │ ├── cost-efficent │ │ └── always.json │ ├── improved │ │ ├── multi.json │ │ └── single.json │ ├── naive │ │ ├── multiserver.json │ │ └── small.json │ ├── storage-cleaner-improved │ │ ├── kill.json │ │ └── trace.json │ ├── storage-cleaner-naive │ │ ├── multi.json │ │ └── single.json │ └── storage-cleaner-working ├── tictactoe │ ├── 1everygame │ │ ├── owin.json │ │ ├── stalemate.json │ │ └── xwin.json │ └── 3xwin │ │ └── stalemate.json └── titles.yml ├── _includes ├── code.html ├── head_custom.html ├── requirements.md ├── states.md ├── title_toc.md └── trace.html ├── _plugins └── tla.rb ├── _sass ├── color_schemes │ └── standard.scss └── custom │ └── custom.scss ├── business-logic ├── enterprise-architect │ ├── index.md │ ├── juniorv1.dvi │ ├── juniorv1.pdf │ ├── juniorv1.svg │ ├── juniorv1.tla │ ├── juniorv2.pdf │ ├── juniorv2.tla │ ├── juniorv2snippet.dvi │ ├── juniorv2snippet.pdf │ ├── juniorv2snippet.svg │ ├── juniorv2snippet.tla │ ├── juniorv3.pdf │ ├── juniorv3.tla │ ├── juniorv3snippet.dvi │ ├── juniorv3snippet.pdf │ ├── juniorv3snippet.svg │ ├── juniorv3snippet.tla │ ├── principal.cfg │ ├── principal.dvi │ ├── principal.pdf │ ├── principal.svg │ ├── principal.tla │ ├── spec.cfg │ ├── spec.dvi │ ├── spec.pdf │ ├── spec.svg │ ├── spec.tla │ ├── speccannonical.dvi │ ├── speccannonical.pdf │ ├── speccannonical.svg │ ├── speccannonical.tla │ ├── speccanonical.svg │ ├── specdatamodels.dvi │ ├── specdatamodels.pdf │ ├── specdatamodels.svg │ ├── specdatamodels.tla │ ├── stubs-1.svg │ ├── stubs.dvi │ ├── stubs.pdf │ ├── stubs.svg │ ├── stubs.tla │ └── traced.txt ├── index.md ├── junior-dev │ ├── index.md │ ├── juniorv1.dvi │ ├── juniorv1snippet.svg │ ├── juniorv1snippet.tla │ ├── juniorv2.tla │ ├── juniorv2snippet.dvi │ ├── juniorv2snippet.pdf │ ├── juniorv2snippet.svg │ ├── juniorv2snippet.tla │ ├── juniorv3.tla │ ├── juniorv3snippet.dvi │ ├── juniorv3snippet.pdf │ ├── juniorv3snippet.svg │ ├── juniorv3snippet.tla │ ├── spec.cfg │ ├── specjuniorv1.cfg │ ├── specjuniorv1.pdf │ ├── specjuniorv1.tla │ ├── specjuniorv2.cfg │ ├── specjuniorv2.pdf │ ├── specjuniorv2.tla │ ├── specjuniorv3.cfg │ ├── specjuniorv3.pdf │ ├── specjuniorv3.tla │ ├── v1.json │ ├── v1.trace │ ├── v2.json │ └── v2.trace └── principal-eng │ ├── index.md │ ├── principal.dvi │ ├── principal.pdf │ ├── principal.svg │ ├── principal.tla │ ├── refactored.trace │ ├── spec.tla │ ├── specprincipal.cfg │ ├── specprincipal.pdf │ └── specprincipal.tla ├── caching ├── cache-invalidation │ ├── cacheinvalidation.cfg │ ├── cacheinvalidationv1.dvi │ ├── cacheinvalidationv1.pdf │ ├── cacheinvalidationv1.svg │ ├── cacheinvalidationv1.tla │ ├── cacheinvalidationv2-snippet.pdf │ ├── cacheinvalidationv2.pdf │ ├── cacheinvalidationv2.tla │ ├── cacheinvalidationv2snippet.dvi │ ├── cacheinvalidationv2snippet.pdf │ ├── cacheinvalidationv2snippet.svg │ ├── cacheinvalidationv2snippet.tla │ ├── cacherequirements.tla │ ├── index.md │ ├── v1.json │ ├── v1.trace │ ├── v2.json │ └── v2.trace ├── cpucaches.png ├── index.md ├── naive-model │ ├── always.json │ ├── alwaysconsistent.trace │ ├── alwayseventually.trace │ ├── cacherequirements.dvi │ ├── cacherequirements.pdf │ ├── cacherequirements.svg │ ├── cacherequirements.tla │ ├── eventually.json │ ├── index.md │ ├── naivecache.cfg │ ├── naivecache.dvi │ ├── naivecache.pdf │ ├── naivecache.svg │ └── naivecache.tla ├── reproducing-the-bug │ ├── bug.trace │ ├── cacherequirements.tla │ ├── facebookcacheinvalidation.cfg │ ├── facebookcacheinvalidation.dvi │ ├── facebookcacheinvalidation.pdf │ ├── facebookcacheinvalidation.svg │ ├── facebookcacheinvalidation.tla │ └── index.md ├── working-cache-invalidation │ ├── cacheinvalidationv3.cfg │ ├── cacheinvalidationv3.dvi │ ├── cacheinvalidationv3.pdf │ ├── cacheinvalidationv3.svg │ ├── cacheinvalidationv3.tla │ ├── cacheinvalidationv3snippet.dvi │ ├── cacheinvalidationv3snippet.pdf │ ├── cacheinvalidationv3snippet.svg │ ├── cacheinvalidationv3snippet.tla │ ├── index.md │ ├── v3.json │ └── v3.trace └── xkcd_the_cloud.png ├── database-blob ├── cost-efficent │ ├── always.json │ ├── always.trace │ ├── index.md │ ├── success.dvi │ ├── success.pdf │ ├── success.svg │ └── success.tla ├── improved │ ├── improved.cfg │ ├── improved.dvi │ ├── improved.pdf │ ├── improved.svg │ ├── improved.tla │ ├── improved_small.cfg │ ├── index.md │ ├── multi.json │ ├── multi.trace │ ├── single.json │ └── single.trace ├── index.md ├── naive │ ├── error-discription.png │ ├── error-trace.png │ ├── index.md │ ├── multiserver.json │ ├── multiserver.trace │ ├── naive.cfg │ ├── naive.dvi │ ├── naive.pdf │ ├── naive.svg │ ├── naive.tla │ ├── naive_small.cfg │ ├── process.sh │ ├── small.json │ └── small.trace ├── storage-cleaner-improved │ ├── index.md │ ├── kill.json │ ├── kill.trace │ ├── storagecleanerimproved-killsnippet.dvi │ ├── storagecleanerimproved.cfg │ ├── storagecleanerimproved.dvi │ ├── storagecleanerimproved.pdf │ ├── storagecleanerimproved.svg │ ├── storagecleanerimproved.tla │ ├── storagecleanerimprovedkillsnippet.dvi │ ├── storagecleanerimprovedkillsnippet.pdf │ ├── storagecleanerimprovedkillsnippet.svg │ ├── storagecleanerimprovedkillsnippet.tla │ ├── trace.json │ └── trace.trace ├── storage-cleaner-naive │ ├── index.md │ ├── multi.json │ ├── multi.trace │ ├── single.json │ ├── single.trace │ ├── storagecleanernaive-snippet.dvi │ ├── storagecleanernaive-snippet.pdf │ ├── storagecleanernaive-snippet.svg │ ├── storagecleanernaive-snippet.tla │ ├── storagecleanernaive.cfg │ ├── storagecleanernaive.dvi │ ├── storagecleanernaive.pdf │ ├── storagecleanernaive.svg │ ├── storagecleanernaive.tla │ └── storagecleanernaive_small.cfg ├── storage-cleaner-working │ ├── index.md │ ├── storagecleaner-snippet.dvi │ ├── storagecleaner-snippet.pdf │ ├── storagecleaner-snippet.svg │ ├── storagecleaner-snippet.tla │ ├── storagecleaner.cfg │ ├── storagecleaner.dvi │ ├── storagecleaner.pdf │ └── storagecleaner.tla └── working │ ├── index.md │ ├── working.cfg │ ├── working.dvi │ ├── working.pdf │ ├── working.svg │ ├── working.tla │ ├── working_large.cfg │ ├── working_small.cfg │ ├── working_standard.cfg │ └── workingsingleserver.png ├── favicon.ico ├── favicon.svg ├── index.md ├── learning-material └── index.md ├── logo.png ├── tictactoe ├── 1everygame │ ├── owin.json │ ├── owin.trace │ ├── stalemate.json │ ├── stalemate.trace │ ├── statespace.png │ ├── tictactoe-1.svg │ ├── tictactoe-full.cfg │ ├── tictactoe-owin.cfg │ ├── tictactoe-stalemate.cfg │ ├── tictactoe-xwin.cfg │ ├── tictactoe.dvi │ ├── tictactoe.pdf │ ├── tictactoe.svg │ ├── tictactoe.tla │ ├── xwin.json │ └── xwin.trace ├── 2xstrategy │ ├── safeoptions.svg │ ├── stalemate.json │ ├── stalemate.trace │ ├── statespace.png │ ├── tictactoexstrat-owin.cfg │ ├── tictactoexstrat-snippet.dvi │ ├── tictactoexstrat-snippet.pdf │ ├── tictactoexstrat-snippet.svg │ ├── tictactoexstrat-snippet.tla │ ├── tictactoexstrat-stalemate.cfg │ ├── tictactoexstrat.cfg │ ├── tictactoexstrat.dvi │ ├── tictactoexstrat.pdf │ ├── tictactoexstrat.svg │ └── tictactoexstrat.tla └── 3xwin │ ├── p.svg │ ├── stutteringstep.png │ ├── tictactoexwin-snippet.dvi │ ├── tictactoexwin-snippet.pdf │ ├── tictactoexwin-snippet.svg │ ├── tictactoexwin-snippet.tla │ ├── tictactoexwin.cfg │ ├── tictactoexwin.dvi │ ├── tictactoexwin.pdf │ ├── tictactoexwin.svg │ ├── tictactoexwin.tla │ └── xmustwin.trace └── tools └── index.md /.gitignore: -------------------------------------------------------------------------------- 1 | states/ 2 | *.toolbox/ 3 | .jekyll-cache 4 | _site/ 5 | database-blob/naivedbblob.toolbox/.project 6 | _tools/ 7 | *.log 8 | *.out 9 | *.aux 10 | .sass-cache/ 11 | .jekyll-metadata 12 | /.bundle/ 13 | /vendor/bundle 14 | /lib/bundler/man/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "tlaplus.tlc.modelChecker.options": "-coverage 1" 3 | } -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) {|practicalformalmodeling| "https://github.com/ElliotSwart/practicalformalmodeling.git" } 6 | gem 'jekyll' 7 | gem 'github-pages' 8 | 9 | group :jekyll_plugins do 10 | gem 'kramdown-plantuml' 11 | gem 'just-the-docs' 12 | end -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Elliot Swart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pragmatic Formal Modeling 2 | ## Getting Started 3 | To test: 4 | ``` 5 | bundle install 6 | bundle exec jekyll serve --incremental 7 | ``` 8 | 9 | 10 | To build: 11 | 12 | ``` 13 | bundle install 14 | bundle exec jekyll build --incremental 15 | ``` 16 | 17 | Currently, the page is updated manually on Github pages, due to PlantUML dependency. 18 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: just-the-docs/just-the-docs 2 | theme: just-the-docs 3 | search_enabled: false 4 | title: Pragmatic Formal Modeling 5 | 6 | baseurl: /pragmaticformalmodeling 7 | url: https://elliotswart.github.io 8 | domain: elliotswart.github.io 9 | 10 | 11 | kramdown: 12 | plantuml: 13 | theme: 14 | name: plain 15 | 16 | color_scheme: standard 17 | 18 | author: 19 | name: Elliot Swart 20 | url: https://www.linkedin.com/in/elliotswart/ 21 | 22 | aux_links: 23 | "GitHub Repository": 24 | - "https://github.com/ElliotSwart/pragmaticformalmodeling" 25 | "About the Author": 26 | - "https://www.linkedin.com/in/elliotswart/" 27 | 28 | aux_links_new_tab: true 29 | 30 | 31 | back_to_top: true 32 | back_to_top_text: "Back to top" 33 | 34 | footer_content: "Copyright © 2022 Elliot Swart. Distributed by an MIT license. " 35 | 36 | plugins: 37 | - jekyll-seo-tag 38 | 39 | gh_edit_link: true # show or hide edit this page link 40 | gh_edit_link_text: "Edit this page on GitHub" 41 | gh_edit_repository: "https://github.com/ElliotSwart/pragmaticformalmodeling" # the github URL for your repo 42 | gh_edit_branch: "main" # the branch that your docs is served from 43 | # gh_edit_source: docs # the source that your files originate from 44 | gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately 45 | 46 | exclude: 47 | - "*/states" 48 | - "*.toolbox" 49 | - "_tools" 50 | - "*.aux" 51 | - "*.log" 52 | - "*.dvi" 53 | - "Gemfile" 54 | - "Gemfile.lock" 55 | - "README.md" 56 | - "LICENSE.md" 57 | - /.bundle/ 58 | - /vendor/bundle 59 | - /lib/bundler/man/ 60 | 61 | defaults: 62 | - scope: 63 | path: "" 64 | values: 65 | image: 66 | path: /logo.png 67 | alt: Temporal Logic Symbol for Eventually 68 | height: 150 69 | width: 150 70 | 71 | - scope: 72 | path: caching 73 | values: 74 | description: An accessible deep dive into cache invalidation, going from theory to progressively refined and tested formal models. Learn how to catch cache invalidation design flaws early, and not in production. 75 | - scope: 76 | path: business-logic 77 | values: 78 | description: Trace software requirements into a formal mathematical specification. Use it to verify multiple implementations 79 | - scope: 80 | path: database-blob 81 | values: 82 | description: Use formal modeling to reduce errors in a simple and common distributed system. -------------------------------------------------------------------------------- /_data/business-logic/junior-dev/v1.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0}},"billQueue":[]},"events":[],"month":0}},{"no":2,"name":"StartTrial","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":true,"trialStartTime":0,"billedForMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"starttrial"}],"month":0}},{"no":3,"name":"MonthPasses","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":true,"trialStartTime":0,"billedForMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"starttrial"},{"type":"monthpass"}],"month":1}}] 2 | -------------------------------------------------------------------------------- /_data/business-logic/junior-dev/v2.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":false,"hasCancelled":false,"cancelMonth":0}},"billQueue":[]},"events":[],"month":0}},{"no":2,"name":"StartSubscription","state":{"database":{"users":{"u1":{"subscribed":true,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":false,"cancelMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"}],"month":0}},{"no":3,"name":"MonthPasses","state":{"database":{"users":{"u1":{"subscribed":true,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":false,"cancelMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"}],"month":1}},{"no":4,"name":"CancelSubscription","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":true,"cancelMonth":1}},"billQueue":[{"user":"u1","fee":"CancellationFee"}]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"},{"user":"u1","type":"cancelsubscription"}],"month":1}},{"no":5,"name":"ProcessBills","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":true,"cancelMonth":1}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"},{"user":"u1","type":"cancelsubscription"},{"user":"u1","fee":"CancellationFee","type":"bill"}],"month":1}},{"no":6,"name":"MonthPasses","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":true,"cancelMonth":1}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"},{"user":"u1","type":"cancelsubscription"},{"user":"u1","fee":"CancellationFee","type":"bill"},{"type":"monthpass"}],"month":2}}] 2 | -------------------------------------------------------------------------------- /_data/caching/cache-invalidation/v1.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"inactive","version":0}},"database":{"k1":0},"invalidationQueue":[]}},{"no":2,"name":"CacheStartReadThroughFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"started","version":0}},"database":{"k1":0},"invalidationQueue":[]}},{"no":3,"name":"DatabaseRespondToCacheFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"respondedto","version":0}},"database":{"k1":0},"invalidationQueue":[]}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"respondedto","version":0}},"database":{"k1":1},"invalidationQueue":[{"key":"k1"}]}},{"no":5,"name":"CacheHandleInvalidationMessage","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"respondedto","version":0}},"database":{"k1":1},"invalidationQueue":[]}},{"no":6,"name":"CacheCompleteFill","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"state":"inactive","version":0}},"database":{"k1":1},"invalidationQueue":[]}}] 2 | -------------------------------------------------------------------------------- /_data/caching/cache-invalidation/v2.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":2,"name":"CacheStartReadThroughFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"started"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":3,"name":"DatabaseRespondToCacheFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":5,"name":"CacheIgnoreInvalidationMessage","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[]}},{"no":6,"name":"CacheCompleteFill","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":1},"invalidationQueue":[]}}] 2 | -------------------------------------------------------------------------------- /_data/caching/naive-model/always.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"database":{"k1":0}}},{"no":2,"name":"CacheReadThrough","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":0}}},{"no":3,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":1}}}] 2 | -------------------------------------------------------------------------------- /_data/caching/naive-model/eventually.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"database":{"k1":0}}},{"no":2,"name":"CacheReadThrough","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":0}}},{"no":3,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":1}}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":2}}},{"no":5,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":3}}}] 2 | -------------------------------------------------------------------------------- /_data/caching/reproducing-the-bug/bug.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"cacheVersions":{"k1":{"type":"miss"}},"counter":0,"database":{"k1":0},"invalidationQueue":[]}},{"no":2,"name":"CacheStartFillMetadata","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"startfillmetadata"}},"cacheVersions":{"k1":{"type":"miss"}},"counter":0,"database":{"k1":0},"invalidationQueue":[]}},{"no":3,"name":"DatabaseRespondWithMetadata","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedtometadata"}},"cacheVersions":{"k1":{"type":"miss"}},"counter":0,"database":{"k1":0},"invalidationQueue":[]}},{"no":4,"name":"CacheFillMetadata","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"cacheVersions":{"k1":{"type":"miss"}},"counter":0,"database":{"k1":0},"invalidationQueue":[]}},{"no":5,"name":"DatabaseUpdate","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"cacheVersions":{"k1":{"type":"miss"}},"counter":0,"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":6,"name":"CacheStartFillVersion","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"startfillversion"}},"cacheVersions":{"k1":{"type":"miss"}},"counter":0,"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":7,"name":"DatabaseRespondWithVersion","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":1,"state":"respondedtoversion"}},"cacheVersions":{"k1":{"type":"miss"}},"counter":0,"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":8,"name":"CacheFillVersion","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":1,"state":"inactive"}},"cacheVersions":{"k1":{"version":1,"type":"hit"}},"counter":0,"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":9,"name":"FailUpdateInvalidationMessageIgnore","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":1,"state":"inactive"}},"cacheVersions":{"k1":{"version":1,"type":"hit"}},"counter":1,"database":{"k1":1},"invalidationQueue":[]}}] 2 | -------------------------------------------------------------------------------- /_data/caching/working-cache-invalidation/v3.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":2,"name":"CacheStartReadThroughFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"started"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":3,"name":"DatabaseRespondToCacheFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":5,"name":"CacheHandleInvalidationMessage","state":{"cache":{"k1":{"version":1,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[]}},{"no":6,"name":"CacheEvict","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[]}},{"no":7,"name":"CacheCompleteFill","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":1},"invalidationQueue":[]}}] 2 | -------------------------------------------------------------------------------- /_data/database-blob/cost-efficent/always.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c2":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]},"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[],"serverStates":{"s2":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"},"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}},{"no":2,"name":"ServerStartWrite","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c2":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]},"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s2":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"},"s1":{"metadata":"m1","imageId":"UNSET","state":"started_write","userId":"ui1","image":"u1"}}}},{"no":3,"name":"ServerWriteBlob","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c2":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]},"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s2":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"},"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}}] 2 | -------------------------------------------------------------------------------- /_data/database-blob/improved/multi.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":2,"name":"StartWrite","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"started_write","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":3,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":4,"name":"WriteMetadataAndReturn","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"waiting","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":5,"name":"StartWrite","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"started_write","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":6,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":7,"name":"StartRead","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"started_read","userId":"u1","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":8,"name":"ReadMetadata","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"read_metadata","userId":"u1","metadata":"m2","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":9,"name":"ReadBlobAndReturn","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"},{"userId":"u1","metadata":"m2","image":"i2","type":"READ"}],"serverStates":{"s2":{"state":"waiting","userId":"u1","metadata":"m2","image":"i2"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}}] 2 | -------------------------------------------------------------------------------- /_data/database-blob/improved/single.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[],"serverStates":{"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":2,"name":"StartWrite","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s1":{"state":"started_write","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":3,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s1":{"state":"wrote_blob","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":4,"name":"WriteMetadataAndReturn","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s1":{"state":"waiting","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":5,"name":"StartWrite","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"started_write","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":6,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":7,"name":"FailWrite","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":8,"name":"StartRead","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"started_read","userId":"u1","metadata":"UNSET","image":"UNSET"}}}},{"no":9,"name":"ReadMetadata","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"read_metadata","userId":"u1","metadata":"m2","image":"UNSET"}}}},{"no":10,"name":"ReadBlobAndReturn","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"},{"userId":"u1","metadata":"m2","image":"i2","type":"READ"}],"serverStates":{"s1":{"state":"waiting","userId":"u1","metadata":"m2","image":"i2"}}}}] 2 | -------------------------------------------------------------------------------- /_data/database-blob/naive/multiserver.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":2,"name":"StartWrite","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"started_write","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":3,"name":"WriteMetadata","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":4,"name":"StartRead","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"started_read","userId":"u1","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":5,"name":"ReadMetadata","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"read_metadata","userId":"u1","metadata":"m1","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":6,"name":"ReadBlobAndReturn","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"UNSET","type":"READ"}],"serverStates":{"s2":{"state":"waiting","userId":"u1","metadata":"m1","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}}] 2 | -------------------------------------------------------------------------------- /_data/database-blob/storage-cleaner-naive/single.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}},{"no":2,"name":"ServerStartWrite","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"UNSET","state":"started_write","userId":"ui1","image":"u1"}}}},{"no":3,"name":"ServerWriteBlob","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}},{"no":4,"name":"CleanerStartGetBlobKeys","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_blob_keys","blobKeys":["i1"],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}},{"no":5,"name":"CleanerGetUnusedKeys","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}},{"no":6,"name":"ServerWriteMetadataAndReturn","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}},{"no":7,"name":"ServerStartRead","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"started_read","userId":"ui1","image":"UNSET"}}}},{"no":8,"name":"ServerReadMetadata","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"read_metadata","userId":"ui1","image":"UNSET"}}}},{"no":9,"name":"CleanerDeletingKeys","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"read_metadata","userId":"ui1","image":"UNSET"}}}},{"no":10,"name":"ServerReadBlobAndReturn","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"},{"metadata":"m1","userId":"ui1","image":"UNSET","type":"READ"}],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}}] 2 | -------------------------------------------------------------------------------- /_data/database-blob/storage-cleaner-working: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/_data/database-blob/storage-cleaner-working -------------------------------------------------------------------------------- /_data/tictactoe/1everygame/owin.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","X"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","X"],["_","O","_"],["_","_","_"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","X"],["X","O","_"],["_","_","_"]],"nextTurn":"O"}},{"no":7,"name":"MoveO","state":{"board":[["X","O","X"],["X","O","_"],["_","O","_"]],"nextTurn":"X"}}] 2 | -------------------------------------------------------------------------------- /_data/tictactoe/1everygame/stalemate.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","X"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","X"],["O","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","_"],["_","_","_"]],"nextTurn":"O"}},{"no":7,"name":"MoveO","state":{"board":[["X","O","X"],["O","X","_"],["O","_","_"]],"nextTurn":"X"}},{"no":8,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["O","_","_"]],"nextTurn":"O"}},{"no":9,"name":"MoveO","state":{"board":[["X","O","X"],["O","X","X"],["O","_","O"]],"nextTurn":"X"}},{"no":10,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["O","X","O"]],"nextTurn":"O"}}] 2 | -------------------------------------------------------------------------------- /_data/tictactoe/1everygame/xwin.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","_"],["X","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","O"],["X","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","O"],["X","_","_"],["X","_","_"]],"nextTurn":"O"}}] 2 | -------------------------------------------------------------------------------- /_data/tictactoe/3xwin/stalemate.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","_"],["_","X","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","_"],["_","X","_"],["_","_","O"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","_"],["_","X","X"],["_","_","O"]],"nextTurn":"O"}},{"no":7,"name":"MoveO","state":{"board":[["X","O","_"],["O","X","X"],["_","_","O"]],"nextTurn":"X"}},{"no":8,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["_","_","O"]],"nextTurn":"O"}},{"no":9,"name":"MoveO","state":{"board":[["X","O","X"],["O","X","X"],["O","_","O"]],"nextTurn":"X"}},{"no":10,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["O","X","O"]],"nextTurn":"O"}}] 2 | -------------------------------------------------------------------------------- /_data/titles.yml: -------------------------------------------------------------------------------- 1 | - database-blob: Coordinating a Database and Blob Store 2 | - database-blob-naive: The naive first draft -------------------------------------------------------------------------------- /_includes/code.html: -------------------------------------------------------------------------------- 1 | {% assign namespace = include.path | replace: "/" "" %} 2 | |Next Section | Download Code | Download PDF | 3 | 4 | 5 |
6 | Show Code 7 | Show LaTex 8 |
9 | 10 | {% assign displayPath = include.path %} 11 | {% if include.snippet %}{% assign displayPath = include.snippet %}{% endif %} 12 |
13 | {% highlight tla %} 14 | {% include_relative {{displayPath}}.tla %} 15 | {% endhighlight %} 16 |
17 | 18 | 19 | 22 | 23 |
24 | 25 |
26 | 27 | 87 | -------------------------------------------------------------------------------- /_includes/head_custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /_includes/requirements.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | | ID | Requirement | 4 | |:-------------|:------------------|{% for requirement in include.requirements %}{% assign reqId = forloop.index %} 5 | | {{reqId}} | {{requirement.text}} | {% for subrequirement in requirement.sub%}{% assign subId = forloop.index %} 6 | | {{reqId}}.{{subId}} |        {{subrequirement.text}} |{% for subsub in subrequirement.sub%}{% assign subsubId = forloop.index %} 7 | | {{reqId}}.{{subId}}.{{subsubId}} |                {{subsub.text}} |{% endfor %}{% endfor %}{% endfor %} 8 | 9 |
-------------------------------------------------------------------------------- /_includes/states.md: -------------------------------------------------------------------------------- 1 | {% assign namespace = include.namespace %} 2 | | Next Section | {% if include.model %} Download Model |{% endif %} {% if include.modelcfg %} Download Configuration |{% endif %} 3 | 4 | | State Name | Total States | Distinct States | 5 | |:-------------|:------------------|:------| 6 | {% for state in include.states %}| {{state.name}} | {{state.total}} | {{state.distinct}} | 7 | {% endfor %} 8 | 9 |
-------------------------------------------------------------------------------- /_includes/title_toc.md: -------------------------------------------------------------------------------- 1 | # {{page.title}} 2 | {: .no_toc } 3 | 4 | 1. TOC 5 | {:toc} -------------------------------------------------------------------------------- /_plugins/tla.rb: -------------------------------------------------------------------------------- 1 | Jekyll::Hooks.register :site, :pre_render do |site| 2 | puts "Registering TLA lexer..." 3 | require "rouge" 4 | 5 | class TLA < Rouge::RegexLexer 6 | title "TLA+" 7 | desc "The TLA+ modeling language for systems and programs" 8 | tag 'tla' 9 | aliases 'tlaplus' 10 | filenames '*.tla' 11 | 12 | # FIXME pretty sure this is too restrictive, but it'll do for now 13 | id = /[a-zA-Z_][a-zA-Z0-9_]*/ 14 | 15 | keywords = %w/ 16 | MODULE EXTENDS CONSTANT CONSTANTS VARIABLE VARIABLES 17 | ASSUME THEOREM 18 | LOCAL INSTANCE WITH 19 | IF THEN ELSE TRUE FALSE CASE OTHER 20 | LET IN 21 | ENABLED UNCHANGED 22 | DOMAIN EXCEPT ASSERT 23 | CHOOSE 24 | SUBSET UNION MOD 25 | / 26 | 27 | state :root do 28 | rule %r/\s+/m, Text 29 | 30 | 31 | rule %r/(\[\]\[)([^\]]+)(\]_)/ do |m| 32 | token Operator, m[1] 33 | token Name, m[2] 34 | token Operator, m[3] 35 | end 36 | 37 | rule %r(\\\*(.*)?\n), Comment::Single 38 | rule %r/\-\-.*/, Comment::Single 39 | rule %r/\(\*.*\*\)/, Comment::Single 40 | rule %r/^===+/, Comment::Single 41 | 42 | rule %r/(?:#{keywords.join('|')})\b/, Keyword 43 | rule %r/\\[a-z]+/, Name 44 | rule %r/"(\\\\|\\"|[^"])*"/, Str 45 | rule %r/#{id}'?/, Name 46 | rule %r/[0-9]+\.[0-9]*/, Num::Float 47 | rule %r/[0-9]+/, Num::Integer 48 | 49 | rule %r/[\\~#!%^&*+\|:.,<>=\[\]\(\)\{\}\/_@-]/, Operator 50 | end 51 | end 52 | end -------------------------------------------------------------------------------- /_sass/color_schemes/standard.scss: -------------------------------------------------------------------------------- 1 | $link-color: $blue-000; 2 | 3 | $code-background-color: rgba(255, 255, 255,0); 4 | 5 | $content-width: 850px; 6 | 7 | $font-size-9: $font-size-8; 8 | $font-size-8: 22px; -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv1.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv1.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv1.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv2.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv2snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv2snippet.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv2snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv2snippet.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv2snippet.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE juniorv2 ------------------------------ 2 | 3 | (***************************************************************************) 4 | (* Database Rows *) 5 | (***************************************************************************) 6 | 7 | UserRow == [ 8 | subscribed: BOOLEAN, 9 | \* Forget cancelled 10 | inTrial: BOOLEAN, 11 | trialStartTime: Nat, 12 | billedForMonth: Nat, 13 | hasHadTrialOrSubscription: BOOLEAN, 14 | hasCancelled: BOOLEAN, 15 | cancelMonth: Nat 16 | ] 17 | 18 | StartSubscription(u) == 19 | \* Not subscribed 20 | /\ /\ database.users[u].subscribed = FALSE 21 | /\ \/ database.users[u].inTrial = FALSE 22 | \/ database.users[u].trialStartTime = month 23 | 24 | /\ database' = 25 | [database EXCEPT 26 | !["users"][u].subscribed = TRUE, 27 | !["users"][u].hasHadTrialOrSubscription = TRUE, 28 | !["users"][u].hasCancelled = FALSE 29 | ] 30 | \* Observability required by stub 31 | /\ events' = Append(events, [type |-> "startsubscription", user |-> u]) 32 | /\ UNCHANGED month 33 | 34 | 35 | CancelSubscription(u) == 36 | \* Subscribed 37 | /\ \/ database.users[u].subscribed = TRUE 38 | \/ /\ database.users[u].inTrial = TRUE 39 | /\ database.users[u].trialStartTime < month 40 | /\ database' = 41 | [database EXCEPT 42 | !["users"][u].subscribed = FALSE, 43 | !["users"][u].inTrial = FALSE, 44 | !["users"][u].hasCancelled = TRUE, 45 | !["users"][u].cancelMonth = month, 46 | \* Charge cancellation fee 47 | !["billQueue"] = 48 | Append(database.billQueue, 49 | [user |-> u, fee |-> CancellationFee]) 50 | ] 51 | 52 | \* Observability required by stub 53 | /\ events' = Append(events, [type |-> "cancelsubscription", user |-> u]) 54 | /\ UNCHANGED <> 55 | 56 | StartTrial(u) == 57 | /\ database.users[u].inTrial = FALSE 58 | /\ database.users[u].subscribed = FALSE 59 | /\ database.users[u].hasHadTrialOrSubscription = FALSE 60 | /\ database' = [database EXCEPT 61 | !["users"][u].inTrial = TRUE, 62 | !["users"][u].trialStartTime = month, 63 | !["users"][u].hasHadTrialOrSubscription = TRUE 64 | ] 65 | 66 | \* Observability required by stub 67 | /\ events' = Append(events, [type |-> "starttrial", user |-> u]) 68 | /\ UNCHANGED <> 69 | 70 | 71 | CancelTrial(u) == 72 | \* In active trial 73 | /\ database.users[u].inTrial = TRUE 74 | /\ database.users[u].trialStartTime = month 75 | \* And not subscribed 76 | /\ database.users[u].subscribed = FALSE 77 | /\ database' = [database EXCEPT 78 | !["users"][u].inTrial = FALSE 79 | ] 80 | 81 | \* Observability required by stub 82 | /\ events' = Append(events, [type |-> "canceltrial", user |-> u]) 83 | /\ UNCHANGED <> 84 | 85 | 86 | WatchVideo(u) == 87 | /\ \/ database.users[u].subscribed = TRUE 88 | \/ database.users[u].inTrial = TRUE 89 | \* Remove video access at the end of cancelled month 90 | \/ /\ database.users[u].hasCancelled = TRUE 91 | /\ database.users[u].cancelMonth = month 92 | 93 | \* Observability required by stub 94 | /\ events' = Append(events, [type |-> "watchvideo", user |-> u]) 95 | /\ UNCHANGED <> 96 | 97 | (***************************************************************************) 98 | (* Recurring Operations *) 99 | (***************************************************************************) 100 | 101 | BillSubscribedUsers == 102 | /\ \E u \in USERS: 103 | \* That is subscribed 104 | /\ \/ database.users[u].subscribed = TRUE 105 | \* Subscribed from a trial so bill 106 | \/ /\ database.users[u].inTrial = TRUE 107 | /\ database.users[u].trialStartTime < month 108 | \* Ensure users are not double billed 109 | /\ database.users[u].billedForMonth < month 110 | /\ database' = 111 | [database EXCEPT 112 | \* Add subscription fee 113 | !["billQueue"] = 114 | Append(database.billQueue, 115 | [user |-> u, fee |-> SubscriptionFee]), 116 | !["users"][u].billedForMonth = month 117 | ] 118 | /\ UNCHANGED <> 119 | 120 | 121 | ============================================================================= 122 | \* Modification History 123 | \* Last modified Sun Jun 19 17:44:01 MST 2022 by elliotswart 124 | \* Created Sun Jun 19 16:56:29 MST 2022 by elliotswart 125 | -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv3.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv3snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv3snippet.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/juniorv3snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/juniorv3snippet.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/principal.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SubscriptionFee = SubscriptionFee 5 | CancellationFee = CancellationFee 6 | FailedPaymentFee = FailedPaymentFee 7 | USERS = {u1} 8 | 9 | INVARIANT 10 | TypeOk 11 | StartSubscriptionAccessControl 12 | CancelSubscriptionAccessControl 13 | StartTrialAccessControl 14 | CancelTrialAccessControl 15 | WatchVideoAccessControl 16 | SubscribedNewUsersBilledSubscriptionFee 17 | SubscribedUsersBilledStartOfMonth 18 | SubscribedUsersBilledPostDuePayements 19 | CancelingUsersBilledCancelationFees 20 | 21 | CONSTRAINT 22 | EventLengthLimit -------------------------------------------------------------------------------- /business-logic/enterprise-architect/principal.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/principal.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/principal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/principal.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/spec.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SubscriptionFee = SubscriptionFee 5 | CancellationFee = CancellationFee 6 | FailedPaymentFee = FailedPaymentFee 7 | USERS = {u1} 8 | 9 | INVARIANT 10 | TypeOk 11 | StartSubscriptionAccessControl 12 | CancelSubscriptionAccessControl 13 | StartTrialAccessControl 14 | CancelTrialAccessControl 15 | WatchVideoAccessControl 16 | SubscribedNewUsersBilledSubscriptionFee 17 | SubscribedUsersBilledStartOfMonth 18 | SubscribedUsersBilledPostDuePayements 19 | CancelingUsersBilledCancelationFees 20 | 21 | CONSTRAINT 22 | StateLimit -------------------------------------------------------------------------------- /business-logic/enterprise-architect/spec.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/spec.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/spec.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/spec.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/speccannonical.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/speccannonical.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/speccannonical.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/speccannonical.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/specdatamodels.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/specdatamodels.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/specdatamodels.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/specdatamodels.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/specdatamodels.tla: -------------------------------------------------------------------------------- 1 | --------------------------- MODULE specdatamodels --------------------------- 2 | \* This module will be imported into every implementation 3 | 4 | EXTENDS Sequences 5 | 6 | (***************************************************************************) 7 | (* An ordered event stream of every event that occurs in the system. *) 8 | (* All the specifications will be written based on it. *) 9 | (* This is an observability value that you wouldn't have access to in the *) 10 | (* implementation. We'll only have the API method stubs write to it; no *) 11 | (* implementation may read from it. This will be enforced with code review *) 12 | (***************************************************************************) 13 | VARIABLE events 14 | 15 | \* Represents every potential user in the system 16 | CONSTANT USERS 17 | 18 | \* Constants that should be set to single model values to allow comparisons. 19 | \* Only equality comparisons will be made. 20 | CONSTANTS 21 | SubscriptionFee, 22 | CancellationFee, 23 | FailedPaymentFee 24 | 25 | Fees == {SubscriptionFee, CancellationFee, FailedPaymentFee} 26 | 27 | (***************************************************************************) 28 | (* Event Types: Describes everything that can happen in the system *) 29 | (***************************************************************************) 30 | 31 | MonthPassEvent == [type : {"monthpass"}] 32 | 33 | StartSubscriptionEvent == [type : {"startsubscription"}, user: USERS] 34 | CancelSubscriptionEvent == [type : {"cancelsubscription"}, user: USERS] 35 | 36 | StartTrialEvent == [type : {"starttrial"}, user: USERS] 37 | CancelTrialEvent == [type : {"canceltrial"}, user: USERS] 38 | 39 | WatchVideoEvent == [type : {"watchvideo"}, user: USERS] 40 | 41 | BillEvent == [type : {"bill"}, user: USERS, fee: Fees] 42 | PaymentFailedEvent == [type : {"paymentfailed"}, user: USERS, fee: Fees] 43 | 44 | Event == 45 | MonthPassEvent \union 46 | StartSubscriptionEvent \union 47 | CancelSubscriptionEvent \union 48 | StartTrialEvent \union 49 | CancelTrialEvent \union 50 | WatchVideoEvent \union 51 | BillEvent \union 52 | PaymentFailedEvent 53 | 54 | EventsOk == 55 | events \in Seq(Event) 56 | 57 | 58 | 59 | ============================================================================= 60 | \* Modification History 61 | \* Last modified Fri Jun 17 00:07:38 MST 2022 by elliotswart 62 | \* Created Thu Jun 16 20:19:00 MST 2022 by elliotswart 63 | -------------------------------------------------------------------------------- /business-logic/enterprise-architect/stubs.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/stubs.dvi -------------------------------------------------------------------------------- /business-logic/enterprise-architect/stubs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/stubs.pdf -------------------------------------------------------------------------------- /business-logic/enterprise-architect/traced.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/enterprise-architect/traced.txt -------------------------------------------------------------------------------- /business-logic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Business Logic 4 | nav_order: 4 5 | has_children: true 6 | has_toc: false 7 | --- 8 | 9 | {% include title_toc.md %} 10 | 11 | ## Introduction 12 | 13 | We've previously used TLA+ mostly for distributed systems problems, but you can also used it to model business logic. First we'll go from requirements to a mathematical specification. Then we'll show two implementations of the specification that lead to the satisfaction of the scenario requirements. Those implementations will be the model for the business logic code to be developed. We'll demonstrate how formal specifications can be used to perform tests of implementations, as well as high confidence refactors. 14 | 15 | ## The scenario 16 | 17 | A software design/consulting firm has been contracted to build a website that allows users to watch instructional workout videos online. Let's say it's called MuscleMovies.com. 18 | 19 | It was founded by former gym and magazine executives, so they have lots of ideas on how to maximize their profits _(or gains, if you will)_. We're talking trial memberships that convert into paid memberships, payment processing and cancellation penalties! 20 | 21 | In the following pages, we'll get to be three different people: 22 | - [The Enterprise Architect](enterprise-architect): who distills the requirements into standard form, makes the architectural choices and writes the formal interface specification. 23 | - [The Junior Developer](junior-dev): who is dropped into this project and struggles through the requirements piece by piece. 24 | - [The Principal Engineer](principal-eng): who jumps in and saves the day by reimplementing the requirements in a cleaner fashion. 25 | 26 | It's basically Rashomon, but presented clearly and in chronological order. And with higher stakes, at least according to the MuscleMovies.com CEO. 27 | 28 | > _Don't worry: despite the tone, we're going rigorous with requirements._ 29 | 30 |

31 | 32 | | Next: [Enterprise Architect gets us started](enterprise-architect) | -------------------------------------------------------------------------------- /business-logic/junior-dev/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | parent: Business Logic 4 | title: Junior Developer tries their best 5 | nav_order: 2 6 | v1: 7 | align: left 8 | 2: 9 | note: A trial starts, but user can still subscribe. 10 | 3: 11 | note: A month passes, converting trial into a full subscription. Now you shouldn't be able to start a subscription, but you still can. 12 | v2: 13 | align: left 14 | 2: 15 | note: Subscription states. 16 | 3: 17 | note: Month passes, and you assume user should be billed. 18 | 4: 19 | note: User cancels subscription before monthly billing could take place. 20 | 5: 21 | note: User is not billed. 22 | 6: 23 | note: A month passes, and our system hasn't billed. Out of compliance. 24 | v3: 25 | - name: All States 26 | total: 772157 27 | distinct: 153504 28 | --- 29 | 30 | {% include title_toc.md %} 31 | 32 | ## Introduction 33 | 34 | ### Your bio 35 | 36 | You recently graduated from a good computer science program. Not only do you have a firm grasp on CS fundamentals, you've also taken electives in operating system and compiler design. None of that has been much help here, though. You keep waiting for someone to ask you to traverse a tree or write a new lexer for C, but you're starting to fear that day will never come. 37 | 38 | 39 | ### Your assignment 40 | 1. Try to create a solution that implements the Enterprise Architect's specification. 41 | 2. Use the model checker to get your solution working. 42 | 43 | ## The first attempt 44 | ### Modeling 45 | _Note: We are assuming the junior programmer is fully competent in TLA+ and code style. Only the architectural and/or requirements knowledge will be lacking._ 46 | 47 | You start with a simple and clean solution that meets the requirements as you understood them: 48 | 49 | {% include code.html path="specjuniorv1" snippet="juniorv1snippet"%} 50 | 51 | ### Verification 52 | Your solution does not hold up under test. 53 | {% include trace.html traceconfig=page.v1 constraint="Invariant StartSubscriptionAccessControl is violated." trace=site.data.business-logic.junior-dev.v1 modelcfg="specjuniorv1.cfg"%} 54 | 55 | There are basic logical errors that need to be fixed. 56 | 57 | ## The second attempt 58 | ### Modeling 59 | For this next attempt you work through all the standard logical errors around access control. 60 | 61 | {% include code.html path="specjuniorv2" snippet="juniorv2snippet" %} 62 | 63 | ### Verification 64 | 65 | You test again, running into a billing issue. 66 | 67 | {% include trace.html traceconfig=page.v2 constraint="Invariant SubscribedUsersBilledStartOfMonth is violated." trace=site.data.business-logic.junior-dev.v2 modelcfg="specjuniorv2.cfg" %} 68 | 69 | This is a much more complex business logic error, and it's of the sort that conventional testing may not catch. 70 | 71 | ## The working attempt 72 | ### Modeling 73 | In the final attempt there you add significantly more billing logic. 74 | 75 | {% include code.html path="specjuniorv3" snippet="juniorv3snippet" modelcfg="specjuniorv3.cfg"%} 76 | 77 | The code is starting to look like a nightmare, but hopefully it will work. 78 | 79 | ### Verification 80 | Testing it leads to success! 81 | 82 | {% include states.md states=page.v3 namespace="junior" modelcfg="specjuniorv3.cfg"%} 83 | 84 | ## Next steps 85 | So you've gotten a solution working, but frankly, it looks like it'll be a nightmare to implement. Nested if statements all over the place. The database schema is sloppy. It's time to ask for help. 86 | 87 | > _Note: A valid criticism of the above would be that no one would model business logic like that. And it's true, no one would model something that badly. But people push solutions much worse than this one to production every day. Maybe if they took the time to model, they wouldn't._ 88 | 89 |

90 | 91 | | Next: [ Principal Engineer saves the day](../principal-eng) | -------------------------------------------------------------------------------- /business-logic/junior-dev/juniorv1.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/juniorv1.dvi -------------------------------------------------------------------------------- /business-logic/junior-dev/juniorv2snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/juniorv2snippet.dvi -------------------------------------------------------------------------------- /business-logic/junior-dev/juniorv2snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/juniorv2snippet.pdf -------------------------------------------------------------------------------- /business-logic/junior-dev/juniorv2snippet.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE juniorv2 ------------------------------ 2 | 3 | (***************************************************************************) 4 | (* Database Rows *) 5 | (***************************************************************************) 6 | 7 | UserRow == [ 8 | subscribed: BOOLEAN, 9 | \* Forget canceled 10 | inTrial: BOOLEAN, 11 | trialStartTime: Nat, 12 | billedForMonth: Nat, 13 | hasHadTrialOrSubscription: BOOLEAN, 14 | hasCancelled: BOOLEAN, 15 | cancelMonth: Nat 16 | ] 17 | 18 | StartSubscription(u) == 19 | \* Not subscribed 20 | /\ /\ database.users[u].subscribed = FALSE 21 | /\ \/ database.users[u].inTrial = FALSE 22 | \/ database.users[u].trialStartTime = month 23 | 24 | /\ database' = 25 | [database EXCEPT 26 | !["users"][u].subscribed = TRUE, 27 | !["users"][u].hasHadTrialOrSubscription = TRUE, 28 | !["users"][u].hasCancelled = FALSE 29 | ] 30 | \* Observability required by stub 31 | /\ events' = Append(events, [type |-> "startsubscription", user |-> u]) 32 | /\ UNCHANGED month 33 | 34 | 35 | CancelSubscription(u) == 36 | \* Subscribed 37 | /\ \/ database.users[u].subscribed = TRUE 38 | \/ /\ database.users[u].inTrial = TRUE 39 | /\ database.users[u].trialStartTime < month 40 | /\ database' = 41 | [database EXCEPT 42 | !["users"][u].subscribed = FALSE, 43 | !["users"][u].inTrial = FALSE, 44 | !["users"][u].hasCancelled = TRUE, 45 | !["users"][u].cancelMonth = month, 46 | \* Charge cancellation fee 47 | !["billQueue"] = 48 | Append(database.billQueue, 49 | [user |-> u, fee |-> CancellationFee]) 50 | ] 51 | 52 | \* Observability required by stub 53 | /\ events' = Append(events, [type |-> "cancelsubscription", user |-> u]) 54 | /\ UNCHANGED <> 55 | 56 | StartTrial(u) == 57 | /\ database.users[u].inTrial = FALSE 58 | /\ database.users[u].subscribed = FALSE 59 | /\ database.users[u].hasHadTrialOrSubscription = FALSE 60 | /\ database' = [database EXCEPT 61 | !["users"][u].inTrial = TRUE, 62 | !["users"][u].trialStartTime = month, 63 | !["users"][u].hasHadTrialOrSubscription = TRUE 64 | ] 65 | 66 | \* Observability required by stub 67 | /\ events' = Append(events, [type |-> "starttrial", user |-> u]) 68 | /\ UNCHANGED <> 69 | 70 | 71 | CancelTrial(u) == 72 | \* In active trial 73 | /\ database.users[u].inTrial = TRUE 74 | /\ database.users[u].trialStartTime = month 75 | \* And not subscribed 76 | /\ database.users[u].subscribed = FALSE 77 | /\ database' = [database EXCEPT 78 | !["users"][u].inTrial = FALSE 79 | ] 80 | 81 | \* Observability required by stub 82 | /\ events' = Append(events, [type |-> "canceltrial", user |-> u]) 83 | /\ UNCHANGED <> 84 | 85 | 86 | WatchVideo(u) == 87 | /\ \/ database.users[u].subscribed = TRUE 88 | \/ database.users[u].inTrial = TRUE 89 | \* Remove video access at the end of canceled month 90 | \/ /\ database.users[u].hasCancelled = TRUE 91 | /\ database.users[u].cancelMonth = month 92 | 93 | \* Observability required by stub 94 | /\ events' = Append(events, [type |-> "watchvideo", user |-> u]) 95 | /\ UNCHANGED <> 96 | 97 | (***************************************************************************) 98 | (* Recurring Operations *) 99 | (***************************************************************************) 100 | 101 | BillSubscribedUsers == 102 | /\ \E u \in USERS: 103 | \* That is subscribed 104 | /\ \/ database.users[u].subscribed = TRUE 105 | \* Subscribed from a trial so bill 106 | \/ /\ database.users[u].inTrial = TRUE 107 | /\ database.users[u].trialStartTime < month 108 | \* Ensure users are not double billed 109 | /\ database.users[u].billedForMonth < month 110 | /\ database' = 111 | [database EXCEPT 112 | \* Add subscription fee 113 | !["billQueue"] = 114 | Append(database.billQueue, 115 | [user |-> u, fee |-> SubscriptionFee]), 116 | !["users"][u].billedForMonth = month 117 | ] 118 | /\ UNCHANGED <> 119 | 120 | 121 | ============================================================================= 122 | \* Modification History 123 | \* Last modified Sun Jun 19 17:44:01 MST 2022 by elliotswart 124 | \* Created Sun Jun 19 16:56:29 MST 2022 by elliotswart 125 | -------------------------------------------------------------------------------- /business-logic/junior-dev/juniorv3snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/juniorv3snippet.dvi -------------------------------------------------------------------------------- /business-logic/junior-dev/juniorv3snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/juniorv3snippet.pdf -------------------------------------------------------------------------------- /business-logic/junior-dev/spec.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SubscriptionFee = SubscriptionFee 5 | CancellationFee = CancellationFee 6 | FailedPaymentFee = FailedPaymentFee 7 | USERS = {u1} 8 | 9 | INVARIANT 10 | TypeOk 11 | StartSubscriptionAccessControl 12 | CancelSubscriptionAccessControl 13 | StartTrialAccessControl 14 | CancelTrialAccessControl 15 | WatchVideoAccessControl 16 | SubscribedNewUsersBilledSubscriptionFee 17 | SubscribedUsersBilledStartOfMonth 18 | SubscribedUsersBilledPostDuePayements 19 | CancelingUsersBilledCancelationFees 20 | 21 | CONSTRAINT 22 | StateLimit -------------------------------------------------------------------------------- /business-logic/junior-dev/specjuniorv1.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SubscriptionFee = SubscriptionFee 5 | CancellationFee = CancellationFee 6 | FailedPaymentFee = FailedPaymentFee 7 | USERS = {u1} 8 | 9 | INVARIANT 10 | TypeOk 11 | StartSubscriptionAccessControl 12 | CancelSubscriptionAccessControl 13 | StartTrialAccessControl 14 | CancelTrialAccessControl 15 | WatchVideoAccessControl 16 | SubscribedNewUsersBilledSubscriptionFee 17 | SubscribedUsersBilledStartOfMonth 18 | SubscribedUsersBilledPostDuePayements 19 | CancelingUsersBilledCancelationFees 20 | 21 | CONSTRAINT 22 | StateLimit -------------------------------------------------------------------------------- /business-logic/junior-dev/specjuniorv1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/specjuniorv1.pdf -------------------------------------------------------------------------------- /business-logic/junior-dev/specjuniorv2.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SubscriptionFee = SubscriptionFee 5 | CancellationFee = CancellationFee 6 | FailedPaymentFee = FailedPaymentFee 7 | USERS = {u1} 8 | 9 | INVARIANT 10 | TypeOk 11 | StartSubscriptionAccessControl 12 | CancelSubscriptionAccessControl 13 | StartTrialAccessControl 14 | CancelTrialAccessControl 15 | WatchVideoAccessControl 16 | SubscribedNewUsersBilledSubscriptionFee 17 | SubscribedUsersBilledStartOfMonth 18 | SubscribedUsersBilledPostDuePayements 19 | CancelingUsersBilledCancelationFees 20 | 21 | CONSTRAINT 22 | StateLimit -------------------------------------------------------------------------------- /business-logic/junior-dev/specjuniorv2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/specjuniorv2.pdf -------------------------------------------------------------------------------- /business-logic/junior-dev/specjuniorv3.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SubscriptionFee = SubscriptionFee 5 | CancellationFee = CancellationFee 6 | FailedPaymentFee = FailedPaymentFee 7 | USERS = {u1} 8 | 9 | INVARIANT 10 | TypeOk 11 | StartSubscriptionAccessControl 12 | CancelSubscriptionAccessControl 13 | StartTrialAccessControl 14 | CancelTrialAccessControl 15 | WatchVideoAccessControl 16 | SubscribedNewUsersBilledSubscriptionFee 17 | SubscribedUsersBilledStartOfMonth 18 | SubscribedUsersBilledPostDuePayements 19 | CancelingUsersBilledCancelationFees 20 | 21 | CONSTRAINT 22 | StateLimit -------------------------------------------------------------------------------- /business-logic/junior-dev/specjuniorv3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/junior-dev/specjuniorv3.pdf -------------------------------------------------------------------------------- /business-logic/junior-dev/v1.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0}},"billQueue":[]},"events":[],"month":0}},{"no":2,"name":"StartTrial","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":true,"trialStartTime":0,"billedForMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"starttrial"}],"month":0}},{"no":3,"name":"MonthPasses","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":true,"trialStartTime":0,"billedForMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"starttrial"},{"type":"monthpass"}],"month":1}}] 2 | -------------------------------------------------------------------------------- /business-logic/junior-dev/v1.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | database |-> [ users |-> 9 | ( u1 :> 10 | [ subscribed |-> FALSE, 11 | inTrial |-> FALSE, 12 | trialStartTime |-> 0, 13 | billedForMonth |-> 0 ] ), 14 | billQueue |-> <<>> ], 15 | events |-> <<>>, 16 | month |-> 0 17 | ], 18 | [ 19 | _TEAction |-> [ 20 | position |-> 2, 21 | name |-> "StartTrial", 22 | location |-> "line 30, col 1 to line 30, col 15 of module spec" 23 | ], 24 | database |-> [ users |-> 25 | ( u1 :> 26 | [ subscribed |-> FALSE, 27 | inTrial |-> TRUE, 28 | trialStartTime |-> 0, 29 | billedForMonth |-> 0 ] ), 30 | billQueue |-> <<>> ], 31 | events |-> <<[user |-> u1, type |-> "starttrial"]>>, 32 | month |-> 0 33 | ], 34 | [ 35 | _TEAction |-> [ 36 | position |-> 3, 37 | name |-> "MonthPasses", 38 | location |-> "line 30, col 1 to line 30, col 15 of module spec" 39 | ], 40 | database |-> [ users |-> 41 | ( u1 :> 42 | [ subscribed |-> FALSE, 43 | inTrial |-> TRUE, 44 | trialStartTime |-> 0, 45 | billedForMonth |-> 0 ] ), 46 | billQueue |-> <<>> ], 47 | events |-> <<[user |-> u1, type |-> "starttrial"], [type |-> "monthpass"]>>, 48 | month |-> 1 49 | ] 50 | >> -------------------------------------------------------------------------------- /business-logic/junior-dev/v2.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":false,"hasCancelled":false,"cancelMonth":0}},"billQueue":[]},"events":[],"month":0}},{"no":2,"name":"StartSubscription","state":{"database":{"users":{"u1":{"subscribed":true,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":false,"cancelMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"}],"month":0}},{"no":3,"name":"MonthPasses","state":{"database":{"users":{"u1":{"subscribed":true,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":false,"cancelMonth":0}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"}],"month":1}},{"no":4,"name":"CancelSubscription","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":true,"cancelMonth":1}},"billQueue":[{"user":"u1","fee":"CancellationFee"}]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"},{"user":"u1","type":"cancelsubscription"}],"month":1}},{"no":5,"name":"ProcessBills","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":true,"cancelMonth":1}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"},{"user":"u1","type":"cancelsubscription"},{"user":"u1","fee":"CancellationFee","type":"bill"}],"month":1}},{"no":6,"name":"MonthPasses","state":{"database":{"users":{"u1":{"subscribed":false,"inTrial":false,"trialStartTime":0,"billedForMonth":0,"hasHadTrialOrSubscription":true,"hasCancelled":true,"cancelMonth":1}},"billQueue":[]},"events":[{"user":"u1","type":"startsubscription"},{"type":"monthpass"},{"user":"u1","type":"cancelsubscription"},{"user":"u1","fee":"CancellationFee","type":"bill"},{"type":"monthpass"}],"month":2}}] 2 | -------------------------------------------------------------------------------- /business-logic/junior-dev/v2.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | database |-> [ users |-> 9 | ( u1 :> 10 | [ subscribed |-> FALSE, 11 | inTrial |-> FALSE, 12 | trialStartTime |-> 0, 13 | billedForMonth |-> 0, 14 | hasHadTrialOrSubscription |-> FALSE, 15 | hasCancelled |-> FALSE, 16 | cancelMonth |-> 0 ] ), 17 | billQueue |-> <<>> ], 18 | events |-> <<>>, 19 | month |-> 0 20 | ], 21 | [ 22 | _TEAction |-> [ 23 | position |-> 2, 24 | name |-> "StartSubscription", 25 | location |-> "line 30, col 1 to line 30, col 17 of module spec" 26 | ], 27 | database |-> [ users |-> 28 | ( u1 :> 29 | [ subscribed |-> TRUE, 30 | inTrial |-> FALSE, 31 | trialStartTime |-> 0, 32 | billedForMonth |-> 0, 33 | hasHadTrialOrSubscription |-> TRUE, 34 | hasCancelled |-> FALSE, 35 | cancelMonth |-> 0 ] ), 36 | billQueue |-> <<>> ], 37 | events |-> <<[user |-> u1, type |-> "startsubscription"]>>, 38 | month |-> 0 39 | ], 40 | [ 41 | _TEAction |-> [ 42 | position |-> 3, 43 | name |-> "MonthPasses", 44 | location |-> "line 30, col 1 to line 30, col 17 of module spec" 45 | ], 46 | database |-> [ users |-> 47 | ( u1 :> 48 | [ subscribed |-> TRUE, 49 | inTrial |-> FALSE, 50 | trialStartTime |-> 0, 51 | billedForMonth |-> 0, 52 | hasHadTrialOrSubscription |-> TRUE, 53 | hasCancelled |-> FALSE, 54 | cancelMonth |-> 0 ] ), 55 | billQueue |-> <<>> ], 56 | events |-> <<[user |-> u1, type |-> "startsubscription"], [type |-> "monthpass"]>>, 57 | month |-> 1 58 | ], 59 | [ 60 | _TEAction |-> [ 61 | position |-> 4, 62 | name |-> "CancelSubscription", 63 | location |-> "line 30, col 1 to line 30, col 17 of module spec" 64 | ], 65 | database |-> [ users |-> 66 | ( u1 :> 67 | [ subscribed |-> FALSE, 68 | inTrial |-> FALSE, 69 | trialStartTime |-> 0, 70 | billedForMonth |-> 0, 71 | hasHadTrialOrSubscription |-> TRUE, 72 | hasCancelled |-> TRUE, 73 | cancelMonth |-> 1 ] ), 74 | billQueue |-> <<[user |-> u1, fee |-> CancellationFee]>> ], 75 | events |-> << [user |-> u1, type |-> "startsubscription"], 76 | [type |-> "monthpass"], 77 | [user |-> u1, type |-> "cancelsubscription"] >>, 78 | month |-> 1 79 | ], 80 | [ 81 | _TEAction |-> [ 82 | position |-> 5, 83 | name |-> "ProcessBills", 84 | location |-> "line 30, col 1 to line 30, col 17 of module spec" 85 | ], 86 | database |-> [ users |-> 87 | ( u1 :> 88 | [ subscribed |-> FALSE, 89 | inTrial |-> FALSE, 90 | trialStartTime |-> 0, 91 | billedForMonth |-> 0, 92 | hasHadTrialOrSubscription |-> TRUE, 93 | hasCancelled |-> TRUE, 94 | cancelMonth |-> 1 ] ), 95 | billQueue |-> <<>> ], 96 | events |-> << [user |-> u1, type |-> "startsubscription"], 97 | [type |-> "monthpass"], 98 | [user |-> u1, type |-> "cancelsubscription"], 99 | [user |-> u1, fee |-> CancellationFee, type |-> "bill"] >>, 100 | month |-> 1 101 | ], 102 | [ 103 | _TEAction |-> [ 104 | position |-> 6, 105 | name |-> "MonthPasses", 106 | location |-> "line 30, col 1 to line 30, col 17 of module spec" 107 | ], 108 | database |-> [ users |-> 109 | ( u1 :> 110 | [ subscribed |-> FALSE, 111 | inTrial |-> FALSE, 112 | trialStartTime |-> 0, 113 | billedForMonth |-> 0, 114 | hasHadTrialOrSubscription |-> TRUE, 115 | hasCancelled |-> TRUE, 116 | cancelMonth |-> 1 ] ), 117 | billQueue |-> <<>> ], 118 | events |-> << [user |-> u1, type |-> "startsubscription"], 119 | [type |-> "monthpass"], 120 | [user |-> u1, type |-> "cancelsubscription"], 121 | [user |-> u1, fee |-> CancellationFee, type |-> "bill"], 122 | [type |-> "monthpass"] >>, 123 | month |-> 2 124 | ] 125 | >> -------------------------------------------------------------------------------- /business-logic/principal-eng/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | parent: Business Logic 4 | title: Principal Engineer saves the day 5 | nav_order: 3 6 | 7 | states: 8 | - name: All States 9 | total: 7995363 10 | distinct: 1115416 11 | 12 | 13 | --- 14 | 15 | {% include title_toc.md %} 16 | 17 | ## Your bio 18 | 19 | At the age of three you killed Python and wore it as a hat. You have a resume a mile long from all your successfully completed projects, and a thousand yard stare from the ones you couldn't save. You'll probably be a programmer forever, if only because semi-pro competitive tap dance just doesn't pay the bills. 20 | 21 | ## The assignment 22 | 23 | 1. Simplify the junior developer's solution. 24 | 2. Use the model checker to verify that your elegant solution is, as Einstein would say, "as simple as possible but no simpler." 25 | 26 | ## Modeling the solution 27 | 28 | Using the model checker as a guide, you refactor the solution to denormalize the data model somewhat. Whenever possible, you convert logic to simple database transactions. 29 | 30 | {% include code.html path="specprincipal" snippet="principal"%} 31 | 32 | You use the model checker periodically to ensure that the simplified design still hit requirements. Once you're confident the design is sufficiently simple, you run a final test on your solution. 33 | 34 | ## Verifying the solution 35 | 36 | It passes. 37 | 38 | {% include states.md states=page.states namespace="principal" modelcfg="specprincipal.cfg"%} 39 | 40 | Time to start building! 41 | 42 | ## Design and its effect on automated testing 43 | 44 | When testing requirements, the standard approach is to map each requirement to one or more specific programmatic tests. For straightforward requirements with immediate triggers and effects, this is not a problem. An integration test can trigger the relevant action and observe the effect. For more complex requirements, those that are more woven into the design, this is challenging. Generally, you trace those requirements to a design document which describes how they are to be fulfilled. Senior engineers and/or architects sign off that the design meets the requirements. Then tests are planned to show conformance with the design. 45 | 46 | Formal modeling helps us with this process in two ways: 47 | - The requirements can be explicitly mapped to a model 48 | - A specific design model can be tested against the requirement model 49 | 50 | The requirements can therefore be straightforwardly verified to be implemented by the design model. It's far easier to test for sub-system and component compliance with the design model than with textual requirements. Automated requirement testing can often turn into glorified regression tests if a team is not careful. By testing to the design model instead, a team can ensure that critical system characteristics are maintained without over-specifying behavior. 51 | 52 | ## Retrospective 53 | 54 | Key takeaways from this series: 55 | - Precise requirements documents can be modeled formally. 56 | - Sloppy logic and design are much more apparent in modeled designs than in code. 57 | - Modeled specifications allow you to refactor business logic confidently. 58 | - Modeling business requirements means that you can focus unit and integration testing on testing critical behavior. 59 | 60 | 61 | 62 | | Next: [You made it to the end! Time to learn TLA+](../../learning-material) | -------------------------------------------------------------------------------- /business-logic/principal-eng/principal.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/principal-eng/principal.dvi -------------------------------------------------------------------------------- /business-logic/principal-eng/principal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/principal-eng/principal.pdf -------------------------------------------------------------------------------- /business-logic/principal-eng/refactored.trace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/principal-eng/refactored.trace -------------------------------------------------------------------------------- /business-logic/principal-eng/specprincipal.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SubscriptionFee = SubscriptionFee 5 | CancellationFee = CancellationFee 6 | FailedPaymentFee = FailedPaymentFee 7 | USERS = {u1} 8 | 9 | INVARIANT 10 | TypeOk 11 | StartSubscriptionAccessControl 12 | CancelSubscriptionAccessControl 13 | StartTrialAccessControl 14 | CancelTrialAccessControl 15 | WatchVideoAccessControl 16 | SubscribedNewUsersBilledSubscriptionFee 17 | SubscribedUsersBilledStartOfMonth 18 | SubscribedUsersBilledPostDuePayements 19 | CancelingUsersBilledCancelationFees 20 | 21 | CONSTRAINT 22 | StateLimit -------------------------------------------------------------------------------- /business-logic/principal-eng/specprincipal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/business-logic/principal-eng/specprincipal.pdf -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidation.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | KEYS = {k1} 5 | 6 | INVARIANT 7 | TypeOk 8 | 9 | PROPERTY 10 | AlwaysEventuallyDatabaseAndCacheConsistent 11 | 12 | CONSTRAINT 13 | DatabaseRecordsDoNotExceedMaxVersion -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidationv1.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/cache-invalidation/cacheinvalidationv1.dvi -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidationv1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/cache-invalidation/cacheinvalidationv1.pdf -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidationv2-snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/cache-invalidation/cacheinvalidationv2-snippet.pdf -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidationv2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/cache-invalidation/cacheinvalidationv2.pdf -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidationv2snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/cache-invalidation/cacheinvalidationv2snippet.dvi -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidationv2snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/cache-invalidation/cacheinvalidationv2snippet.pdf -------------------------------------------------------------------------------- /caching/cache-invalidation/cacheinvalidationv2snippet.tla: -------------------------------------------------------------------------------- 1 | -------------------- MODULE cacheinvalidationv2 -------------------- 2 | 3 | \* Cache incorporates the data 4 | CacheCompleteFill(k) == 5 | /\ cacheFillStates[k].state = "respondedto" 6 | \* Either the cache is empty for that key 7 | /\ \/ cache[k] \in CacheMiss 8 | \* or we are filling a newer version 9 | \/ /\ cache[k] \notin CacheMiss 10 | /\ cache[k].version < cacheFillStates[k].version 11 | /\ cacheFillStates' = [cacheFillStates EXCEPT \* Reset to 0 12 | ![k].state = "inactive", 13 | ![k].version = 0 14 | ] 15 | /\ cache' = [cache EXCEPT 16 | ![k] = [ 17 | type |-> "hit", 18 | version |-> cacheFillStates[k].version 19 | ] 20 | ] 21 | /\ UNCHANGED <> 22 | 23 | CacheIgnoreFill(k) == 24 | /\ cacheFillStates[k].state = "respondedto" 25 | \* If we have a newer version in cache, ignore fill 26 | /\ /\ cache[k] \in CacheHit 27 | /\ cache[k].version >= cacheFillStates[k].version 28 | /\ cacheFillStates' = [cacheFillStates EXCEPT \* Reset to 0 29 | ![k].state = "inactive", 30 | ![k].version = 0 31 | ] 32 | \* Don't update cache 33 | /\ UNCHANGED <> 34 | 35 | 36 | CacheHandleInvalidationMessage == 37 | /\ \E message \in invalidationQueue: \* Dequeue invalidation queue in any order 38 | \* Key must be in cache 39 | /\ /\ cache[message.key] \in CacheHit 40 | \* Message needs to be newer than the cache 41 | /\ cache[message.key].version < message.version 42 | \* Update item in cache 43 | /\ cache' = [cache EXCEPT 44 | ![message.key] = [ 45 | type |-> "hit", 46 | \* Update to version in invalidation message 47 | version |-> message.version 48 | ]] 49 | \* Remove message from queue because handled 50 | /\ invalidationQueue' = invalidationQueue \ {message} 51 | 52 | /\ UNCHANGED <> 53 | 54 | CacheIgnoreInvalidationMessage == 55 | /\ \E message \in invalidationQueue: \* Dequeue invalidation queue in any order 56 | \* Ignore invalidation messages for messages not in cache 57 | /\ \/ cache[message.key] \in CacheMiss 58 | \* Or when the cache already has the same or larger version 59 | \/ /\ cache[message.key] \notin CacheMiss 60 | /\ cache[message.key].version >= message.version 61 | \* Remove message from queue to ignore 62 | /\ invalidationQueue' = invalidationQueue \ {message} 63 | \* Don't update cache 64 | /\ UNCHANGED <> 65 | 66 | ============================================================================= 67 | \* Modification History 68 | \* Last modified Wed Jun 15 13:58:25 MST 2022 by elliotswart 69 | \* Created Wed Jun 15 13:58:13 MST 2022 by elliotswart 70 | -------------------------------------------------------------------------------- /caching/cache-invalidation/cacherequirements.tla: -------------------------------------------------------------------------------- 1 | ------------------------- MODULE cacherequirements ------------------------- 2 | 3 | EXTENDS Naturals 4 | 5 | CONSTANTS 6 | KEYS \* The full set of keys in the database 7 | 8 | VARIABLES 9 | database, \* database[key] = DataVersion 10 | cache \* cache[key] = CacheValue 11 | 12 | 13 | \* The maximum number of versions a key can have in this model 14 | MaxVersions == 4 15 | 16 | \* Data Versions are scoped to an individual key. 17 | DataVersion == Nat 18 | 19 | \* Represents the absense of a value in a cache 20 | CacheMiss == [type: {"miss"}] 21 | 22 | \* Represents the presence of a value in a cache, as well as the value 23 | CacheHit == [type : {"hit"}, version: DataVersion] 24 | 25 | DatabaseAndCacheConsistent == 26 | \A k \in KEYS: 27 | \* If the key is in cache 28 | \/ /\ cache[k] \in CacheHit 29 | \* It should be the same version as the database 30 | /\ cache[k].version = database[k] 31 | \* A cache miss is also ok. A cache won't hold everything 32 | \/ cache[k] \in CacheMiss 33 | 34 | \* This means that at some point, the database and cache are consistent 35 | \* It is important to note that this is not eventual consistency. 36 | \* This says it needs to be eventually consistent once 37 | EventuallyDatabaseAndCacheConsistent == <>DatabaseAndCacheConsistent 38 | 39 | \* The cache must be always eventually consistent 40 | \* At any point in time, the cache must be eventually consistent. 41 | AlwaysEventuallyDatabaseAndCacheConsistent == 42 | []EventuallyDatabaseAndCacheConsistent 43 | 44 | \* Used as a state constraint to prevent unbounded testing 45 | \* with infinite versions 46 | DatabaseRecordsDoNotExceedMaxVersion == 47 | \A k \in KEYS: 48 | database[k] < MaxVersions 49 | 50 | ============================================================================= 51 | \* Modification History 52 | \* Last modified Tue Jun 14 22:44:55 MST 2022 by elliotswart 53 | \* Created Tue Jun 14 21:36:26 MST 2022 by elliotswart 54 | -------------------------------------------------------------------------------- /caching/cache-invalidation/v1.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"inactive","version":0}},"database":{"k1":0},"invalidationQueue":[]}},{"no":2,"name":"CacheStartReadThroughFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"started","version":0}},"database":{"k1":0},"invalidationQueue":[]}},{"no":3,"name":"DatabaseRespondToCacheFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"respondedto","version":0}},"database":{"k1":0},"invalidationQueue":[]}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"respondedto","version":0}},"database":{"k1":1},"invalidationQueue":[{"key":"k1"}]}},{"no":5,"name":"CacheHandleInvalidationMessage","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"state":"respondedto","version":0}},"database":{"k1":1},"invalidationQueue":[]}},{"no":6,"name":"CacheCompleteFill","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"state":"inactive","version":0}},"database":{"k1":1},"invalidationQueue":[]}}] 2 | -------------------------------------------------------------------------------- /caching/cache-invalidation/v1.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | cache |-> (k1 :> [type |-> "miss"]), 9 | cacheFillStates |-> (k1 :> [state |-> "inactive", version |-> 0]), 10 | database |-> (k1 :> 0), 11 | invalidationQueue |-> {} 12 | ], 13 | [ 14 | _TEAction |-> [ 15 | position |-> 2, 16 | name |-> "CacheStartReadThroughFill", 17 | location |-> "line 60, col 5 to line 64, col 55 of module cacheinvalidationv1" 18 | ], 19 | cache |-> (k1 :> [type |-> "miss"]), 20 | cacheFillStates |-> (k1 :> [state |-> "started", version |-> 0]), 21 | database |-> (k1 :> 0), 22 | invalidationQueue |-> {} 23 | ], 24 | [ 25 | _TEAction |-> [ 26 | position |-> 3, 27 | name |-> "DatabaseRespondToCacheFill", 28 | location |-> "line 68, col 5 to line 73, col 55 of module cacheinvalidationv1" 29 | ], 30 | cache |-> (k1 :> [type |-> "miss"]), 31 | cacheFillStates |-> (k1 :> [state |-> "respondedto", version |-> 0]), 32 | database |-> (k1 :> 0), 33 | invalidationQueue |-> {} 34 | ], 35 | [ 36 | _TEAction |-> [ 37 | position |-> 4, 38 | name |-> "DatabaseUpdate", 39 | location |-> "line 49, col 5 to line 55, col 43 of module cacheinvalidationv1" 40 | ], 41 | cache |-> (k1 :> [type |-> "miss"]), 42 | cacheFillStates |-> (k1 :> [state |-> "respondedto", version |-> 0]), 43 | database |-> (k1 :> 1), 44 | invalidationQueue |-> {[key |-> k1]} 45 | ], 46 | [ 47 | _TEAction |-> [ 48 | position |-> 5, 49 | name |-> "CacheHandleInvalidationMessage", 50 | location |-> "line 106, col 5 to line 111, col 46 of module cacheinvalidationv1" 51 | ], 52 | cache |-> (k1 :> [type |-> "miss"]), 53 | cacheFillStates |-> (k1 :> [state |-> "respondedto", version |-> 0]), 54 | database |-> (k1 :> 1), 55 | invalidationQueue |-> {} 56 | ], 57 | [ 58 | _TEAction |-> [ 59 | position |-> 6, 60 | name |-> "CacheCompleteFill", 61 | location |-> "line 77, col 5 to line 90, col 48 of module cacheinvalidationv1" 62 | ], 63 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 64 | cacheFillStates |-> (k1 :> [state |-> "inactive", version |-> 0]), 65 | database |-> (k1 :> 1), 66 | invalidationQueue |-> {} 67 | ] 68 | >> -------------------------------------------------------------------------------- /caching/cache-invalidation/v2.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":2,"name":"CacheStartReadThroughFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"started"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":3,"name":"DatabaseRespondToCacheFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":5,"name":"CacheIgnoreInvalidationMessage","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[]}},{"no":6,"name":"CacheCompleteFill","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":1},"invalidationQueue":[]}}] 2 | -------------------------------------------------------------------------------- /caching/cache-invalidation/v2.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | cache |-> (k1 :> [type |-> "miss"]), 9 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "inactive"]), 10 | database |-> (k1 :> 0), 11 | invalidationQueue |-> {} 12 | ], 13 | [ 14 | _TEAction |-> [ 15 | position |-> 2, 16 | name |-> "CacheStartReadThroughFill", 17 | location |-> "line 63, col 5 to line 67, col 55 of module cacheinvalidationv2" 18 | ], 19 | cache |-> (k1 :> [type |-> "miss"]), 20 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "started"]), 21 | database |-> (k1 :> 0), 22 | invalidationQueue |-> {} 23 | ], 24 | [ 25 | _TEAction |-> [ 26 | position |-> 3, 27 | name |-> "DatabaseRespondToCacheFill", 28 | location |-> "line 71, col 5 to line 76, col 55 of module cacheinvalidationv2" 29 | ], 30 | cache |-> (k1 :> [type |-> "miss"]), 31 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedto"]), 32 | database |-> (k1 :> 0), 33 | invalidationQueue |-> {} 34 | ], 35 | [ 36 | _TEAction |-> [ 37 | position |-> 4, 38 | name |-> "DatabaseUpdate", 39 | location |-> "line 50, col 5 to line 58, col 43 of module cacheinvalidationv2" 40 | ], 41 | cache |-> (k1 :> [type |-> "miss"]), 42 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedto"]), 43 | database |-> (k1 :> 1), 44 | invalidationQueue |-> {[key |-> k1, version |-> 1]} 45 | ], 46 | [ 47 | _TEAction |-> [ 48 | position |-> 5, 49 | name |-> "CacheIgnoreInvalidationMessage", 50 | location |-> "line 144, col 5 to line 153, col 53 of module cacheinvalidationv2" 51 | ], 52 | cache |-> (k1 :> [type |-> "miss"]), 53 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedto"]), 54 | database |-> (k1 :> 1), 55 | invalidationQueue |-> {} 56 | ], 57 | [ 58 | _TEAction |-> [ 59 | position |-> 6, 60 | name |-> "CacheCompleteFill", 61 | location |-> "line 90, col 5 to line 108, col 48 of module cacheinvalidationv2" 62 | ], 63 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 64 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "inactive"]), 65 | database |-> (k1 :> 1), 66 | invalidationQueue |-> {} 67 | ] 68 | >> -------------------------------------------------------------------------------- /caching/cpucaches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/cpucaches.png -------------------------------------------------------------------------------- /caching/naive-model/always.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"database":{"k1":0}}},{"no":2,"name":"CacheReadThrough","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":0}}},{"no":3,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":1}}}] 2 | -------------------------------------------------------------------------------- /caching/naive-model/alwaysconsistent.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | cache |-> (k1 :> [type |-> "miss"]), 9 | database |-> (k1 :> 0) 10 | ], 11 | [ 12 | _TEAction |-> [ 13 | position |-> 2, 14 | name |-> "CacheReadThrough", 15 | location |-> "line 50, col 5 to line 60, col 25 of module naivecache" 16 | ], 17 | cache |-> (k1 :> [type |-> "hit", version |-> 0]), 18 | database |-> (k1 :> 0) 19 | ], 20 | [ 21 | _TEAction |-> [ 22 | position |-> 3, 23 | name |-> "DatabaseUpdate", 24 | location |-> "line 37, col 5 to line 39, col 22 of module naivecache" 25 | ], 26 | cache |-> (k1 :> [type |-> "hit", version |-> 0]), 27 | database |-> (k1 :> 1) 28 | ] 29 | >> -------------------------------------------------------------------------------- /caching/naive-model/alwayseventually.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | cache |-> (k1 :> [type |-> "miss"]), 9 | database |-> (k1 :> 0) 10 | ], 11 | [ 12 | _TEAction |-> [ 13 | position |-> 2, 14 | name |-> "CacheReadThrough", 15 | location |-> "line 50, col 5 to line 60, col 25 of module naivecache" 16 | ], 17 | cache |-> (k1 :> [type |-> "hit", version |-> 0]), 18 | database |-> (k1 :> 0) 19 | ], 20 | [ 21 | _TEAction |-> [ 22 | position |-> 3, 23 | name |-> "DatabaseUpdate", 24 | location |-> "line 37, col 5 to line 39, col 22 of module naivecache" 25 | ], 26 | cache |-> (k1 :> [type |-> "hit", version |-> 0]), 27 | database |-> (k1 :> 1) 28 | ], 29 | [ 30 | _TEAction |-> [ 31 | position |-> 4, 32 | name |-> "DatabaseUpdate", 33 | location |-> "line 37, col 5 to line 39, col 22 of module naivecache" 34 | ], 35 | cache |-> (k1 :> [type |-> "hit", version |-> 0]), 36 | database |-> (k1 :> 2) 37 | ], 38 | [ 39 | _TEAction |-> [ 40 | position |-> 5, 41 | name |-> "DatabaseUpdate", 42 | location |-> "line 37, col 5 to line 39, col 22 of module naivecache" 43 | ], 44 | cache |-> (k1 :> [type |-> "hit", version |-> 0]), 45 | database |-> (k1 :> 3) 46 | ] 47 | >> -------------------------------------------------------------------------------- /caching/naive-model/cacherequirements.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/naive-model/cacherequirements.dvi -------------------------------------------------------------------------------- /caching/naive-model/cacherequirements.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/naive-model/cacherequirements.pdf -------------------------------------------------------------------------------- /caching/naive-model/cacherequirements.tla: -------------------------------------------------------------------------------- 1 | ------------------------- MODULE cacherequirements ------------------------- 2 | 3 | EXTENDS Naturals 4 | 5 | CONSTANTS 6 | KEYS \* The full set of keys in the database 7 | 8 | VARIABLES 9 | database, \* database[key] = DataVersion 10 | cache \* cache[key] = CacheValue 11 | 12 | 13 | \* The maximum number of versions a key can have in this model 14 | MaxVersions == 4 15 | 16 | \* Data versions are scoped to an individual key 17 | DataVersion == Nat 18 | 19 | \* Represents the absence of a value in a cache 20 | CacheMiss == [type: {"miss"}] 21 | 22 | \* Represents the presence of a value in a cache, as well as the value 23 | CacheHit == [type : {"hit"}, version: DataVersion] 24 | 25 | DatabaseAndCacheConsistent == 26 | \A k \in KEYS: 27 | \* If the key is in cache 28 | \/ /\ cache[k] \in CacheHit 29 | \* It should be the same version as the database 30 | /\ cache[k].version = database[k] 31 | \* A cache miss is also okay. A cache won't hold everything 32 | \/ cache[k] \in CacheMiss 33 | 34 | \* This means that at some point, the database and cache are consistent. 35 | \* It is important to note that this is not eventual consistency. 36 | \* This only says it needs to be eventually consistent once. 37 | EventuallyDatabaseAndCacheConsistent == <>DatabaseAndCacheConsistent 38 | 39 | \* The cache must be always eventually consistent. 40 | AlwaysEventuallyDatabaseAndCacheConsistent == 41 | []EventuallyDatabaseAndCacheConsistent 42 | 43 | \* Used as a state constraint to prevent unbounded testing 44 | \* with infinite versions. 45 | DatabaseRecordsDoNotExceedMaxVersion == 46 | \A k \in KEYS: 47 | database[k] < MaxVersions 48 | 49 | ============================================================================= 50 | \* Modification History 51 | \* Last modified Tue Jun 14 22:44:55 MST 2022 by elliotswart 52 | \* Created Tue Jun 14 21:36:26 MST 2022 by elliotswart 53 | -------------------------------------------------------------------------------- /caching/naive-model/eventually.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"database":{"k1":0}}},{"no":2,"name":"CacheReadThrough","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":0}}},{"no":3,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":1}}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":2}}},{"no":5,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"hit","version":0}},"database":{"k1":3}}}] 2 | -------------------------------------------------------------------------------- /caching/naive-model/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | parent: Cache Invalidation 4 | title: A naive model of caching 5 | nav_order: 1 6 | always: 7 | 2: 8 | note: The cache reads through and gets version 0 of key 1 9 | 3: 10 | note: The database updates to version 1 of key 1. No longer consistent with cache. 11 | class: text-red-300 12 | open: true 13 | eventually: 14 | 2: 15 | note: The cache reads through and gets version 0 of key 1 16 | 3: 17 | note: Database updated state 18 | 5: 19 | note: Database updated state for the last time 20 | stuttering: 21 | show: true 22 | note: "Cache could do nothing, and remained inconsistent" 23 | class: text-red-300 24 | --- 25 | 26 | {% include title_toc.md %} 27 | 28 | 29 | 30 | ## Designing a naive cache 31 | 32 | In addition to the [parameters described previously](../#initial-parameters-for-all-exercises), we will make a few design decisions: 33 | - Caches will only be filled during reads (as pictured below). 34 | - We will not worry about cache invalidation. 35 | - We will only model a single cache. We are assuming it is either one server, or one consistent replica set. _Note: While there are generally many cache servers, the complexity of cache invalidation (at this level) can be modeled with just one cache and one database._ 36 | 37 | So essentially the only operation we are implementing is the read described in the last section. 38 | ```plantuml 39 | @startuml 40 | participant "Web Server" as Server 41 | participant "Cache" as Cache 42 | participant "Database" as Database 43 | 44 | Server -> Cache: Requests query 45 | 46 | alt cache miss 47 | Cache -> Database: Requests query result 48 | Database --> Cache: Returns data 49 | Cache -> Cache: Caches query result 50 | end 51 | 52 | Cache --> Server: Returns data 53 | 54 | @enduml 55 | ``` 56 | 57 | Now we have all our design parameters. Let's model! 58 | 59 | ## Success criteria 60 | Because caches are so well understood, we can come up with our success criteria before modeling. Note how it defines certain key data models, so success can be defined. 61 | 62 | {% include code.html path="cacherequirements" %} 63 | 64 | > Note: the requirements are their own TLA+ module. In all our models, we will import it. We can think of it a bit like an interface in a standard programming language. 65 | 66 | ## Modeling the cache 67 | We model the cache, importing the cache requirements module rather than redefining the expressions it provides. 68 | 69 | {% include code.html path="naivecache" %} 70 | 71 | ## Verifying the cache 72 | First let's try setting DatabaseAndCacheConsistent as an invariant and see what happens. This says that the cache and the database must always be consistent with each other. 73 | 74 | {% include trace.html traceconfig=page.always constraint="Invariant DatabaseAndCacheConsistent is violated." trace=site.data.caching.naive-model.always %} 75 | 76 | This is what we'd expect. The **Cache** and the **Database** are not always consistent with each other. If this passed, we should doubt the model. 77 | 78 | What if we use the eventually consistent property _AlwaysEventuallyDatabaseAndCacheConsistent_? We get another error. Temporal property violations are not as clear as logical ones. 79 | 80 | {% include trace.html traceconfig=page.eventually constraint="Temporal properties were violated." trace=site.data.caching.naive-model.eventually %} 81 | 82 | Because we have no way of guaranteeing that a key will be evicted from the cache, as soon as the key is set, the cache will not change. 83 | 84 | ## Summary 85 | As we can see, our current model of cache is not eventually consistent with the database. We need to systematically clear the cache of outdated values. We need _cache invalidation_. 86 | 87 |

88 | 89 | | Next: [Adding cache invalidation](../cache-invalidation) | -------------------------------------------------------------------------------- /caching/naive-model/naivecache.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | KEYS = {k1} 5 | 6 | INVARIANT 7 | TypeOk 8 | 9 | PROPERTY 10 | AlwaysEventuallyDatabaseAndCacheConsistent 11 | 12 | CONSTRAINT 13 | DatabaseRecordsDoNotExceedMaxVersion -------------------------------------------------------------------------------- /caching/naive-model/naivecache.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/naive-model/naivecache.dvi -------------------------------------------------------------------------------- /caching/naive-model/naivecache.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/naive-model/naivecache.pdf -------------------------------------------------------------------------------- /caching/naive-model/naivecache.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE naivecache ----------------------------- 2 | 3 | EXTENDS Naturals 4 | 5 | CONSTANTS 6 | KEYS \* The full set of keys in the database 7 | 8 | 9 | VARIABLES 10 | database, \* database[key] = DataVersion 11 | cache \* cache[key] = CacheValue 12 | 13 | \* Imports cache requirements to test against 14 | INSTANCE cacherequirements 15 | 16 | 17 | vars == <> 18 | 19 | \* A cache can hold a hit or a miss for any given key 20 | CacheValue == CacheMiss \union CacheHit 21 | 22 | TypeOk == 23 | \* database is a mapping of keys to a data version 24 | /\ database \in [KEYS -> DataVersion] 25 | \* cache is a mapping of kets to a cache value 26 | /\ cache \in [KEYS -> CacheValue] 27 | 28 | Init == 29 | \* All keys in the database are initialized to their first version 30 | /\ database = [k \in KEYS |-> 0] 31 | \* All keys in the cache are initialized to a miss, i.e. no data in cache 32 | /\ cache = [k \in KEYS |-> [type |-> "miss"]] 33 | 34 | 35 | DatabaseUpdate(k) == 36 | \* The version of that key is incremented, representing a write 37 | /\ database' = [database EXCEPT 38 | ![k] = database[k] + 1] 39 | /\ UNCHANGED cache 40 | 41 | CacheRead(k) == 42 | \* The data is already in the cache 43 | /\ cache[k] \in CacheHit 44 | \* So the cache remains the same 45 | /\ UNCHANGED cache 46 | /\ UNCHANGED database 47 | 48 | CacheReadThrough(k) == 49 | \* The data is not in the cache 50 | /\ cache[k] \in CacheMiss 51 | \* So it is read from the database 52 | /\ cache' = [cache EXCEPT 53 | ![k] = [ 54 | \* Cache value is now a hit 55 | type |-> "hit", 56 | \* Set to whatever version is in database 57 | version |-> database[k] 58 | ] 59 | ] 60 | /\ UNCHANGED database 61 | 62 | CacheEvict(k) == 63 | \* The data is in cache, so can be evicted 64 | /\ cache[k] \in CacheHit 65 | \* cache[k]is turned into a miss 66 | /\ cache' = [cache EXCEPT ![k] = [type |-> "miss"]] 67 | /\ UNCHANGED database 68 | 69 | (***************************************************************************) 70 | (* Fairness: Normally no operation is guaranteed to happen; it just may. *) 71 | (* However, that means that the cache could just stop reading forever. *) 72 | (* And so it would never update. Now that doesn't seem reasonable. *) 73 | (***************************************************************************) 74 | 75 | \* The cache will always be able to... 76 | CacheFairness == 77 | \E k \in KEYS: 78 | \/ CacheRead(k) \* Read 79 | \/ CacheReadThrough(k) \* Write 80 | \* CacheEvict(k) is not here, because CacheEvict is something that 81 | \* may happen. It is not guaranteed 82 | 83 | 84 | 85 | (***************************************************************************) 86 | (* Specification *) 87 | (***************************************************************************) 88 | 89 | Next == 90 | \E k \in KEYS: 91 | \* Database states 92 | \/ DatabaseUpdate(k) 93 | \* Cache states 94 | \/ CacheRead(k) 95 | \/ CacheReadThrough(k) 96 | \/ CacheEvict(k) 97 | 98 | \* Cache fairness is included as part of the specification of system behavior. 99 | \* This is just how the system works. 100 | Spec == Init /\ [][Next]_vars /\ WF_vars(CacheFairness) 101 | 102 | ============================================================================= 103 | \* Modification History 104 | \* Last modified Tue Jun 14 22:51:06 MST 2022 by elliotswart 105 | \* Created Tue Jun 14 20:36:02 MST 2022 by elliotswart 106 | -------------------------------------------------------------------------------- /caching/reproducing-the-bug/bug.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | cache |-> (k1 :> [type |-> "miss"]), 9 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "inactive"]), 10 | cacheVersions |-> (k1 :> [type |-> "miss"]), 11 | counter |-> 0, 12 | database |-> (k1 :> 0), 13 | invalidationQueue |-> {} 14 | ], 15 | [ 16 | _TEAction |-> [ 17 | position |-> 2, 18 | name |-> "CacheStartFillMetadata", 19 | location |-> "line 77, col 5 to line 81, col 53 of module facebookcacheinvalidation" 20 | ], 21 | cache |-> (k1 :> [type |-> "miss"]), 22 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "startfillmetadata"]), 23 | cacheVersions |-> (k1 :> [type |-> "miss"]), 24 | counter |-> 0, 25 | database |-> (k1 :> 0), 26 | invalidationQueue |-> {} 27 | ], 28 | [ 29 | _TEAction |-> [ 30 | position |-> 3, 31 | name |-> "DatabaseRespondWithMetadata", 32 | location |-> "line 84, col 5 to line 90, col 53 of module facebookcacheinvalidation" 33 | ], 34 | cache |-> (k1 :> [type |-> "miss"]), 35 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedtometadata"]), 36 | cacheVersions |-> (k1 :> [type |-> "miss"]), 37 | counter |-> 0, 38 | database |-> (k1 :> 0), 39 | invalidationQueue |-> {} 40 | ], 41 | [ 42 | _TEAction |-> [ 43 | position |-> 4, 44 | name |-> "CacheFillMetadata", 45 | location |-> "line 95, col 5 to line 109, col 72 of module facebookcacheinvalidation" 46 | ], 47 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 48 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "inactive"]), 49 | cacheVersions |-> (k1 :> [type |-> "miss"]), 50 | counter |-> 0, 51 | database |-> (k1 :> 0), 52 | invalidationQueue |-> {} 53 | ], 54 | [ 55 | _TEAction |-> [ 56 | position |-> 5, 57 | name |-> "DatabaseUpdate", 58 | location |-> "line 66, col 5 to line 72, col 67 of module facebookcacheinvalidation" 59 | ], 60 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 61 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "inactive"]), 62 | cacheVersions |-> (k1 :> [type |-> "miss"]), 63 | counter |-> 0, 64 | database |-> (k1 :> 1), 65 | invalidationQueue |-> {[key |-> k1, version |-> 1]} 66 | ], 67 | [ 68 | _TEAction |-> [ 69 | position |-> 6, 70 | name |-> "CacheStartFillVersion", 71 | location |-> "line 114, col 5 to line 118, col 53 of module facebookcacheinvalidation" 72 | ], 73 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 74 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "startfillversion"]), 75 | cacheVersions |-> (k1 :> [type |-> "miss"]), 76 | counter |-> 0, 77 | database |-> (k1 :> 1), 78 | invalidationQueue |-> {[key |-> k1, version |-> 1]} 79 | ], 80 | [ 81 | _TEAction |-> [ 82 | position |-> 7, 83 | name |-> "DatabaseRespondWithVersion", 84 | location |-> "line 121, col 5 to line 127, col 53 of module facebookcacheinvalidation" 85 | ], 86 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 87 | cacheFillStates |-> (k1 :> [version |-> 1, state |-> "respondedtoversion"]), 88 | cacheVersions |-> (k1 :> [type |-> "miss"]), 89 | counter |-> 0, 90 | database |-> (k1 :> 1), 91 | invalidationQueue |-> {[key |-> k1, version |-> 1]} 92 | ], 93 | [ 94 | _TEAction |-> [ 95 | position |-> 8, 96 | name |-> "CacheFillVersion", 97 | location |-> "line 132, col 5 to line 151, col 64 of module facebookcacheinvalidation" 98 | ], 99 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 100 | cacheFillStates |-> (k1 :> [version |-> 1, state |-> "inactive"]), 101 | cacheVersions |-> (k1 :> [version |-> 1, type |-> "hit"]), 102 | counter |-> 0, 103 | database |-> (k1 :> 1), 104 | invalidationQueue |-> {[key |-> k1, version |-> 1]} 105 | ], 106 | [ 107 | _TEAction |-> [ 108 | position |-> 9, 109 | name |-> "FailUpdateInvalidationMessageIgnore", 110 | location |-> "line 243, col 5 to line 250, col 54 of module facebookcacheinvalidation" 111 | ], 112 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 113 | cacheFillStates |-> (k1 :> [version |-> 1, state |-> "inactive"]), 114 | cacheVersions |-> (k1 :> [version |-> 1, type |-> "hit"]), 115 | counter |-> 1, 116 | database |-> (k1 :> 1), 117 | invalidationQueue |-> {} 118 | ] 119 | >> -------------------------------------------------------------------------------- /caching/reproducing-the-bug/cacherequirements.tla: -------------------------------------------------------------------------------- 1 | ------------------------- MODULE cacherequirements ------------------------- 2 | 3 | EXTENDS Naturals 4 | 5 | CONSTANTS 6 | KEYS \* The full set of keys in the database 7 | 8 | VARIABLES 9 | database, \* database[key] = DataVersion 10 | cache \* cache[key] = CacheValue 11 | 12 | 13 | \* The maximum number of versions a key can have in this model 14 | MaxVersions == 2 15 | 16 | \* Data Versions are scoped to an individual key. 17 | DataVersion == Nat 18 | 19 | \* Represents the absense of a value in a cache 20 | CacheMiss == [type: {"miss"}] 21 | 22 | \* Represents the presence of a value in a cache, as well as the value 23 | CacheHit == [type : {"hit"}, version: DataVersion] 24 | 25 | DatabaseAndCacheConsistent == 26 | \A k \in KEYS: 27 | \* If the key is in cache 28 | \/ /\ cache[k] \in CacheHit 29 | \* It should be the same version as the database 30 | /\ cache[k].version = database[k] 31 | \* A cache miss is also ok. A cache won't hold everything 32 | \/ cache[k] \in CacheMiss 33 | 34 | \* This means that at some point, the database and cache are consistent 35 | \* It is important to note that this is not eventual consistency. 36 | \* This says it needs to be eventually consistent once 37 | EventuallyDatabaseAndCacheConsistent == <>DatabaseAndCacheConsistent 38 | 39 | \* The cache must be always eventually consistent 40 | \* At any point in time, the cache must be eventually consistent. 41 | AlwaysEventuallyDatabaseAndCacheConsistent == 42 | []EventuallyDatabaseAndCacheConsistent 43 | 44 | \* Used as a state constraint to prevent unbounded testing 45 | \* with infinite versions 46 | DatabaseRecordsDoNotExceedMaxVersion == 47 | \A k \in KEYS: 48 | database[k] < MaxVersions 49 | 50 | ============================================================================= 51 | \* Modification History 52 | \* Last modified Thu Jun 16 14:30:46 MST 2022 by elliotswart 53 | \* Created Tue Jun 14 21:36:26 MST 2022 by elliotswart 54 | -------------------------------------------------------------------------------- /caching/reproducing-the-bug/facebookcacheinvalidation.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | KEYS = {k1} 5 | 6 | INVARIANT 7 | TypeOk 8 | 9 | PROPERTY 10 | AlwaysEventuallyDatabaseAndCacheConsistent 11 | 12 | CONSTRAINT 13 | DatabaseRecordsDoNotExceedMaxVersion -------------------------------------------------------------------------------- /caching/reproducing-the-bug/facebookcacheinvalidation.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/reproducing-the-bug/facebookcacheinvalidation.dvi -------------------------------------------------------------------------------- /caching/reproducing-the-bug/facebookcacheinvalidation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/reproducing-the-bug/facebookcacheinvalidation.pdf -------------------------------------------------------------------------------- /caching/working-cache-invalidation/cacheinvalidationv3.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | KEYS = {k1, k2} 5 | 6 | INVARIANT 7 | TypeOk 8 | 9 | PROPERTY 10 | AlwaysEventuallyDatabaseAndCacheConsistent 11 | 12 | CONSTRAINT 13 | DatabaseRecordsDoNotExceedMaxVersion -------------------------------------------------------------------------------- /caching/working-cache-invalidation/cacheinvalidationv3.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/working-cache-invalidation/cacheinvalidationv3.dvi -------------------------------------------------------------------------------- /caching/working-cache-invalidation/cacheinvalidationv3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/working-cache-invalidation/cacheinvalidationv3.pdf -------------------------------------------------------------------------------- /caching/working-cache-invalidation/cacheinvalidationv3snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/working-cache-invalidation/cacheinvalidationv3snippet.dvi -------------------------------------------------------------------------------- /caching/working-cache-invalidation/cacheinvalidationv3snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/working-cache-invalidation/cacheinvalidationv3snippet.pdf -------------------------------------------------------------------------------- /caching/working-cache-invalidation/cacheinvalidationv3snippet.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE cacheinvalidationv3 ----------------------------- 2 | 3 | CacheHandleInvalidationMessage == 4 | /\ \E message \in invalidationQueue: 5 | /\ \/ /\ cache[message.key] \in CacheHit 6 | \* Message needs to be newer than the cache 7 | /\ cache[message.key].version < message.version 8 | \* Or not in the cache, but with a pending fill request 9 | \/ /\ cache[message.key] \in CacheMiss 10 | /\ cacheFillStates[message.key].state # "inactive" 11 | \* Update item in cache 12 | /\ cache' = [cache EXCEPT 13 | ![message.key] = [ 14 | type |-> "hit", 15 | \* Update to version in invalidation message 16 | version |-> message.version 17 | ]] 18 | \* Remove message from queue because handled 19 | /\ invalidationQueue' = invalidationQueue \ {message} 20 | 21 | /\ UNCHANGED <> 22 | 23 | CacheIgnoreInvalidationMessage == 24 | /\ \E message \in invalidationQueue: \* Dequeue invalidation queue in any order 25 | \* Ignore invalidation messages for messages not in cache 26 | /\ \/ /\ cache[message.key] \in CacheMiss 27 | \* and a fill is not occurring 28 | /\ cacheFillStates[message.key].state = "inactive" 29 | \* Or when the cache already has the same or larger version 30 | \/ /\ cache[message.key] \notin CacheMiss 31 | /\ cache[message.key].version >= message.version 32 | \* Remove message from queue to ignore 33 | /\ invalidationQueue' = invalidationQueue \ {message} 34 | /\ counter' = counter + 1 35 | /\ UNCHANGED <> 36 | 37 | CacheEvict(k) == 38 | /\ cache[k] \in CacheHit 39 | \* A key with a pending request will not be evicted 40 | /\ cacheFillStates[k].state = "inactive" 41 | /\ cache' = [cache EXCEPT ![k] = [type |-> "miss"]] 42 | /\ counter' = counter + 1 43 | /\ UNCHANGED <> 45 | 46 | ============================================================================= 47 | \* Modification History 48 | \* Last modified Wed Jun 15 13:08:32 MST 2022 by elliotswart 49 | \* Created Tue Jun 14 20:36:02 MST 2022 by elliotswart 50 | -------------------------------------------------------------------------------- /caching/working-cache-invalidation/v3.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":2,"name":"CacheStartReadThroughFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"started"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":3,"name":"DatabaseRespondToCacheFill","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":0},"invalidationQueue":[]}},{"no":4,"name":"DatabaseUpdate","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[{"key":"k1","version":1}]}},{"no":5,"name":"CacheHandleInvalidationMessage","state":{"cache":{"k1":{"version":1,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[]}},{"no":6,"name":"CacheEvict","state":{"cache":{"k1":{"type":"miss"}},"cacheFillStates":{"k1":{"version":0,"state":"respondedto"}},"database":{"k1":1},"invalidationQueue":[]}},{"no":7,"name":"CacheCompleteFill","state":{"cache":{"k1":{"version":0,"type":"hit"}},"cacheFillStates":{"k1":{"version":0,"state":"inactive"}},"database":{"k1":1},"invalidationQueue":[]}}] 2 | -------------------------------------------------------------------------------- /caching/working-cache-invalidation/v3.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | cache |-> (k1 :> [type |-> "miss"]), 9 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "inactive"]), 10 | database |-> (k1 :> 0), 11 | invalidationQueue |-> {} 12 | ], 13 | [ 14 | _TEAction |-> [ 15 | position |-> 2, 16 | name |-> "CacheStartReadThroughFill", 17 | location |-> "line 63, col 5 to line 67, col 55 of module cacheinvalidationv3" 18 | ], 19 | cache |-> (k1 :> [type |-> "miss"]), 20 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "started"]), 21 | database |-> (k1 :> 0), 22 | invalidationQueue |-> {} 23 | ], 24 | [ 25 | _TEAction |-> [ 26 | position |-> 3, 27 | name |-> "DatabaseRespondToCacheFill", 28 | location |-> "line 71, col 5 to line 76, col 55 of module cacheinvalidationv3" 29 | ], 30 | cache |-> (k1 :> [type |-> "miss"]), 31 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedto"]), 32 | database |-> (k1 :> 0), 33 | invalidationQueue |-> {} 34 | ], 35 | [ 36 | _TEAction |-> [ 37 | position |-> 4, 38 | name |-> "DatabaseUpdate", 39 | location |-> "line 50, col 5 to line 58, col 43 of module cacheinvalidationv3" 40 | ], 41 | cache |-> (k1 :> [type |-> "miss"]), 42 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedto"]), 43 | database |-> (k1 :> 1), 44 | invalidationQueue |-> {[key |-> k1, version |-> 1]} 45 | ], 46 | [ 47 | _TEAction |-> [ 48 | position |-> 5, 49 | name |-> "CacheHandleInvalidationMessage", 50 | location |-> "line 126, col 5 to line 144, col 46 of module cacheinvalidationv3" 51 | ], 52 | cache |-> (k1 :> [version |-> 1, type |-> "hit"]), 53 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedto"]), 54 | database |-> (k1 :> 1), 55 | invalidationQueue |-> {} 56 | ], 57 | [ 58 | _TEAction |-> [ 59 | position |-> 6, 60 | name |-> "CacheEvict", 61 | location |-> "line 162, col 5 to line 164, col 65 of module cacheinvalidationv3" 62 | ], 63 | cache |-> (k1 :> [type |-> "miss"]), 64 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "respondedto"]), 65 | database |-> (k1 :> 1), 66 | invalidationQueue |-> {} 67 | ], 68 | [ 69 | _TEAction |-> [ 70 | position |-> 7, 71 | name |-> "CacheCompleteFill", 72 | location |-> "line 90, col 5 to line 108, col 48 of module cacheinvalidationv3" 73 | ], 74 | cache |-> (k1 :> [version |-> 0, type |-> "hit"]), 75 | cacheFillStates |-> (k1 :> [version |-> 0, state |-> "inactive"]), 76 | database |-> (k1 :> 1), 77 | invalidationQueue |-> {} 78 | ] 79 | >> -------------------------------------------------------------------------------- /caching/xkcd_the_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/caching/xkcd_the_cloud.png -------------------------------------------------------------------------------- /database-blob/cost-efficent/always.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c2":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]},"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[],"serverStates":{"s2":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"},"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}},{"no":2,"name":"ServerStartWrite","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c2":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]},"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s2":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"},"s1":{"metadata":"m1","imageId":"UNSET","state":"started_write","userId":"ui1","image":"u1"}}}},{"no":3,"name":"ServerWriteBlob","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c2":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]},"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s2":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"},"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}}] 2 | -------------------------------------------------------------------------------- /database-blob/cost-efficent/always.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | blobStoreState |-> (i1 :> "UNSET" @@ i2 :> "UNSET"), 9 | cleanerStates |-> ( c1 :> [state |-> "waiting", blobKeys |-> {}, unusedBlobKeys |-> {}] @@ 10 | c2 :> [state |-> "waiting", blobKeys |-> {}, unusedBlobKeys |-> {}] ), 11 | databaseState |-> ( ui1 :> [metadata |-> "UNSET", imageId |-> "UNSET"] @@ 12 | ui2 :> [metadata |-> "UNSET", imageId |-> "UNSET"] @@ 13 | ui3 :> [metadata |-> "UNSET", imageId |-> "UNSET"] ), 14 | operations |-> <<>>, 15 | serverStates |-> ( s1 :> 16 | [ metadata |-> "UNSET", 17 | imageId |-> "UNSET", 18 | state |-> "waiting", 19 | userId |-> "UNSET", 20 | image |-> "UNSET" ] @@ 21 | s2 :> 22 | [ metadata |-> "UNSET", 23 | imageId |-> "UNSET", 24 | state |-> "waiting", 25 | userId |-> "UNSET", 26 | image |-> "UNSET" ] ) 27 | ], 28 | [ 29 | _TEAction |-> [ 30 | position |-> 2, 31 | name |-> "ServerStartWrite", 32 | location |-> "line 138, col 5 to line 155, col 65 of module storagecleanernaive" 33 | ], 34 | blobStoreState |-> (i1 :> "UNSET" @@ i2 :> "UNSET"), 35 | cleanerStates |-> ( c1 :> [state |-> "waiting", blobKeys |-> {}, unusedBlobKeys |-> {}] @@ 36 | c2 :> [state |-> "waiting", blobKeys |-> {}, unusedBlobKeys |-> {}] ), 37 | databaseState |-> ( ui1 :> [metadata |-> "UNSET", imageId |-> "UNSET"] @@ 38 | ui2 :> [metadata |-> "UNSET", imageId |-> "UNSET"] @@ 39 | ui3 :> [metadata |-> "UNSET", imageId |-> "UNSET"] ), 40 | operations |-> <<[metadata |-> m1, userId |-> ui1, image |-> u1, type |-> "WRITE"]>>, 41 | serverStates |-> ( s1 :> 42 | [ metadata |-> m1, 43 | imageId |-> "UNSET", 44 | state |-> "started_write", 45 | userId |-> ui1, 46 | image |-> u1 ] @@ 47 | s2 :> 48 | [ metadata |-> "UNSET", 49 | imageId |-> "UNSET", 50 | state |-> "waiting", 51 | userId |-> "UNSET", 52 | image |-> "UNSET" ] ) 53 | ], 54 | [ 55 | _TEAction |-> [ 56 | position |-> 3, 57 | name |-> "ServerWriteBlob", 58 | location |-> "line 161, col 5 to line 176, col 28 of module storagecleanernaive" 59 | ], 60 | blobStoreState |-> (i1 :> u1 @@ i2 :> "UNSET"), 61 | cleanerStates |-> ( c1 :> [state |-> "waiting", blobKeys |-> {}, unusedBlobKeys |-> {}] @@ 62 | c2 :> [state |-> "waiting", blobKeys |-> {}, unusedBlobKeys |-> {}] ), 63 | databaseState |-> ( ui1 :> [metadata |-> "UNSET", imageId |-> "UNSET"] @@ 64 | ui2 :> [metadata |-> "UNSET", imageId |-> "UNSET"] @@ 65 | ui3 :> [metadata |-> "UNSET", imageId |-> "UNSET"] ), 66 | operations |-> <<[metadata |-> m1, userId |-> ui1, image |-> u1, type |-> "WRITE"]>>, 67 | serverStates |-> ( s1 :> 68 | [ metadata |-> m1, 69 | imageId |-> i1, 70 | state |-> "wrote_blob", 71 | userId |-> ui1, 72 | image |-> u1 ] @@ 73 | s2 :> 74 | [ metadata |-> "UNSET", 75 | imageId |-> "UNSET", 76 | state |-> "waiting", 77 | userId |-> "UNSET", 78 | image |-> "UNSET" ] ) 79 | ] 80 | >> -------------------------------------------------------------------------------- /database-blob/cost-efficent/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | parent: Coordinating a Database and Blob Store 4 | title: (Adding Requirements) A more cost efficent solution 5 | nav_order: 4 6 | has_toc: false 7 | always: 8 | 3: 9 | note: All it takes to fail is a blob to be written 10 | class: text-red-300 11 | --- 12 | 13 | # {{page.title}} 14 | 15 | ## The new requirements hit 16 | You've hit the big time. Absolute perfection. You've just built the database blob store coordinator [to beat them all](../working). Nothing is going to bring you down... until your boss walks into your office. 17 | 18 | "I just talked to Jim in DevOps," she says ominously, "and we're getting hosed on our data storage costs. Apparently this last update writes a new object every time someone updates their profile. Even if they update it again, all the old stuff sits around." 19 | 20 | "But it's way more correct this way," you respond defensively. "I modeled and verified it. That means I'm right, I win the conversation. Good day sir/madam." 21 | 22 | "Not so fast, bucko," she retorts. "You know what I always say: 'Time is money, money is money, and cloud storage costs are money.' You're going to have to figure it out." 23 | 24 | "But please, boss," you plaintively mewl, "I just got this working correctly. I proved it and everything. Running that last test took twelve hours. Who knows what will happen if I go mucking about in there?!" 25 | 26 | She looks at you with a mixture of contempt and pity. "Don't you know one of the best characteristics of formal models is that you can build on existing models, make them more robust, and use them to verify change?" 27 | 28 | A realization hits you. You underestimated her technical skills due to her alignment with company priorities and the profit motive. She was absolutely correct: modeling is very useful for evolving designs. Duly chastened, you get back to work, and we go back to using a collective pronoun. 29 | 30 | "One more thing", says our boss with an evil glint in her eye. "Team Ninja-Dragon is integrating their latest sprint. The **Server** codebase is frozen. You won't be able to add any more functionality on that component." 31 | 32 | ## Updated system components 33 | 34 | So we have to implement data cleanup without adding functionality to the **Server**. This means we need to create a new microservice, the **Storage Cleaner**. It will need to be able to read from the **Database** and **Blob Store** to find orphaned files, then delete them from the **Blob Store**. It will likely be triggered periodically, perhaps by a cloudwatch alarm; however, at large enough scale it may stay on permanently. The component diagram looks like this: 35 | 36 | ```plantuml 37 | @startuml 38 | skinparam fixCircleLabelOverlapping true 39 | left to right direction 40 | 41 | component [Blob Store] as BlobStore 42 | 43 | () "Blob Read" as BlobRead 44 | () "Blob Write" as BlobWrite 45 | 46 | [BlobStore] -up- BlobRead 47 | [BlobStore] -up- BlobWrite 48 | 49 | component [Database] 50 | () "Metadata Read" as DatabaseRead 51 | () "Metadata Write" as DatabaseWrite 52 | 53 | [Database] -up- DatabaseRead 54 | [Database] -up- DatabaseWrite 55 | 56 | 57 | component [Storage Cleaners] as StorageCleaner 58 | () "Time triggered activation" as TimeTrigger 59 | 60 | StorageCleaner -up- TimeTrigger 61 | StorageCleaner ..> DatabaseRead : uses 62 | StorageCleaner ..> BlobRead: uses 63 | StorageCleaner ..> BlobWrite: deletes 64 | 65 | component [Servers] 66 | Servers ..> DatabaseRead : uses 67 | Servers ..> DatabaseWrite : uses 68 | Servers ..> BlobRead: uses 69 | Servers ..> BlobWrite: uses 70 | 71 | () "Create or Update Profile (userId)" as WriteProfile 72 | () "Read Profile (userId)" as ReadProfile 73 | Servers -up- ReadProfile 74 | Servers -up- WriteProfile 75 | 76 | 77 | @enduml 78 | ``` 79 | 80 | We have two main design considerations at this point: 81 | - We will have to plan for more than one **Storage Cleaner** to be active simultaneously. They could be run in a replica set, or delays could cause triggered instances to overlap. 82 | - We will need to ensure that the behavior of **Storage Cleaner** doesn't break the invariants we tested previously. 83 | 84 | ## A formal definition of success 85 | 86 | Before starting work, it's good to understand the definition of success. Because of our previous modeling work, we can now state it formally. 87 | 88 | {% include code.html path="success" %} 89 | 90 | Ideally, we'd like to never have an orphan file. Let's test that really quick. 91 | 92 | {% include trace.html traceconfig=page.always constraint="AlwaysNoOrphanFiles is violated. 93 | ." trace=site.data.database-blob.cost-efficent.always %} 94 | 95 | Failing on the first write sounds like a bad success criteria. Instead we'll go with **AlwaysEventuallyNoOrphanFiles** as our definition of success. 96 | 97 |

98 | 99 | |Next: [(Implementing New Requirements) A naive update](../storage-cleaner-naive) | -------------------------------------------------------------------------------- /database-blob/cost-efficent/success.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/cost-efficent/success.dvi -------------------------------------------------------------------------------- /database-blob/cost-efficent/success.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/cost-efficent/success.pdf -------------------------------------------------------------------------------- /database-blob/cost-efficent/success.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE success ---------------------------- 2 | 3 | NoOrphanFiles == 4 | \* There does not exist a key 5 | ~\E k \in UUIDS: 6 | \* That is in the block store 7 | /\ blobStoreState[k] # "UNSET" 8 | \* And not in database 9 | /\ \A u \in USERIDS: 10 | databaseState[u].imageId # k 11 | 12 | AlwaysNoOrphanFiles == []NoOrphanFiles 13 | 14 | \* At some point in the future there will be no orphan files 15 | \* If it's true ever, it is True 16 | EventuallyNoOrphanFiles == <>NoOrphanFiles 17 | 18 | \* Always, at some point in the future, there will be no orphan files 19 | \* This is how we test eventual consistency. It can't just happen once 20 | \* It must always happen 21 | AlwaysEventuallyNoOrphanFiles == []EventuallyNoOrphanFiles 22 | ============================================================================= -------------------------------------------------------------------------------- /database-blob/improved/improved.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2} 5 | METADATAS = {m1, m2} 6 | USERIDS = {u1} 7 | IMAGES = {i1, i2} 8 | 9 | CONSTRAINT 10 | StopAfter3Operations 11 | 12 | INVARIANT 13 | TypeOk 14 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/improved/improved.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/improved/improved.dvi -------------------------------------------------------------------------------- /database-blob/improved/improved.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/improved/improved.pdf -------------------------------------------------------------------------------- /database-blob/improved/improved_small.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1} 5 | METADATAS = {m1, m2} 6 | USERIDS = {u1} 7 | IMAGES = {i1, i2} 8 | 9 | CONSTRAINT 10 | StopAfter3Operations 11 | 12 | INVARIANT 13 | TypeOk 14 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/improved/multi.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":2,"name":"StartWrite","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"started_write","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":3,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":4,"name":"WriteMetadataAndReturn","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"waiting","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":5,"name":"StartWrite","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"started_write","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":6,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":7,"name":"StartRead","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"started_read","userId":"u1","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":8,"name":"ReadMetadata","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s2":{"state":"read_metadata","userId":"u1","metadata":"m2","image":"UNSET"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":9,"name":"ReadBlobAndReturn","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"},{"userId":"u1","metadata":"m2","image":"i2","type":"READ"}],"serverStates":{"s2":{"state":"waiting","userId":"u1","metadata":"m2","image":"i2"},"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}}] 2 | -------------------------------------------------------------------------------- /database-blob/improved/single.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[],"serverStates":{"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":2,"name":"StartWrite","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s1":{"state":"started_write","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":3,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s1":{"state":"wrote_blob","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":4,"name":"WriteMetadataAndReturn","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"}],"serverStates":{"s1":{"state":"waiting","userId":"u1","metadata":"m2","image":"i1"}}}},{"no":5,"name":"StartWrite","state":{"blobStoreState":{"u1":"i1"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"started_write","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":6,"name":"WriteBlob","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"wrote_blob","userId":"u1","metadata":"m1","image":"i2"}}}},{"no":7,"name":"FailWrite","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":8,"name":"StartRead","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"started_read","userId":"u1","metadata":"UNSET","image":"UNSET"}}}},{"no":9,"name":"ReadMetadata","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"}],"serverStates":{"s1":{"state":"read_metadata","userId":"u1","metadata":"m2","image":"UNSET"}}}},{"no":10,"name":"ReadBlobAndReturn","state":{"blobStoreState":{"u1":"i2"},"databaseState":{"u1":"m2"},"operations":[{"userId":"u1","metadata":"m2","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"i2","type":"WRITE"},{"userId":"u1","metadata":"m2","image":"i2","type":"READ"}],"serverStates":{"s1":{"state":"waiting","userId":"u1","metadata":"m2","image":"i2"}}}}] 2 | -------------------------------------------------------------------------------- /database-blob/naive/error-discription.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/naive/error-discription.png -------------------------------------------------------------------------------- /database-blob/naive/error-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/naive/error-trace.png -------------------------------------------------------------------------------- /database-blob/naive/multiserver.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"}}}},{"no":2,"name":"StartWrite","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"UNSET"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"started_write","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":3,"name":"WriteMetadata","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"waiting","userId":"UNSET","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":4,"name":"StartRead","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"started_read","userId":"u1","metadata":"UNSET","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":5,"name":"ReadMetadata","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"}],"serverStates":{"s2":{"state":"read_metadata","userId":"u1","metadata":"m1","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}},{"no":6,"name":"ReadBlobAndReturn","state":{"blobStoreState":{"u1":"UNSET"},"databaseState":{"u1":"m1"},"operations":[{"userId":"u1","metadata":"m1","image":"i1","type":"WRITE"},{"userId":"u1","metadata":"m1","image":"UNSET","type":"READ"}],"serverStates":{"s2":{"state":"waiting","userId":"u1","metadata":"m1","image":"UNSET"},"s1":{"state":"wrote_metadata","userId":"u1","metadata":"m1","image":"i1"}}}}] 2 | -------------------------------------------------------------------------------- /database-blob/naive/multiserver.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | blobStoreState |-> (u1 :> "UNSET"), 9 | databaseState |-> (u1 :> "UNSET"), 10 | operations |-> <<>>, 11 | serverStates |-> ( s1 :> 12 | [ state |-> "waiting", 13 | userId |-> "UNSET", 14 | metadata |-> "UNSET", 15 | image |-> "UNSET" ] @@ 16 | s2 :> 17 | [ state |-> "waiting", 18 | userId |-> "UNSET", 19 | metadata |-> "UNSET", 20 | image |-> "UNSET" ] ) 21 | ], 22 | [ 23 | _TEAction |-> [ 24 | position |-> 2, 25 | name |-> "StartWrite", 26 | location |-> "line 143, col 5 to line 167, col 50 of module naive" 27 | ], 28 | blobStoreState |-> (u1 :> "UNSET"), 29 | databaseState |-> (u1 :> "UNSET"), 30 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 31 | serverStates |-> ( s1 :> 32 | [ state |-> "started_write", 33 | userId |-> u1, 34 | metadata |-> m1, 35 | image |-> i1 ] @@ 36 | s2 :> 37 | [ state |-> "waiting", 38 | userId |-> "UNSET", 39 | metadata |-> "UNSET", 40 | image |-> "UNSET" ] ) 41 | ], 42 | [ 43 | _TEAction |-> [ 44 | position |-> 3, 45 | name |-> "WriteMetadata", 46 | location |-> "line 175, col 5 to line 182, col 47 of module naive" 47 | ], 48 | blobStoreState |-> (u1 :> "UNSET"), 49 | databaseState |-> (u1 :> m1), 50 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 51 | serverStates |-> ( s1 :> 52 | [ state |-> "wrote_metadata", 53 | userId |-> u1, 54 | metadata |-> m1, 55 | image |-> i1 ] @@ 56 | s2 :> 57 | [ state |-> "waiting", 58 | userId |-> "UNSET", 59 | metadata |-> "UNSET", 60 | image |-> "UNSET" ] ) 61 | ], 62 | [ 63 | _TEAction |-> [ 64 | position |-> 4, 65 | name |-> "StartRead", 66 | location |-> "line 225, col 5 to line 232, col 27 of module naive" 67 | ], 68 | blobStoreState |-> (u1 :> "UNSET"), 69 | databaseState |-> (u1 :> m1), 70 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 71 | serverStates |-> ( s1 :> 72 | [ state |-> "wrote_metadata", 73 | userId |-> u1, 74 | metadata |-> m1, 75 | image |-> i1 ] @@ 76 | s2 :> 77 | [ state |-> "started_read", 78 | userId |-> u1, 79 | metadata |-> "UNSET", 80 | image |-> "UNSET" ] ) 81 | ], 82 | [ 83 | _TEAction |-> [ 84 | position |-> 5, 85 | name |-> "ReadMetadata", 86 | location |-> "line 238, col 5 to line 249, col 27 of module naive" 87 | ], 88 | blobStoreState |-> (u1 :> "UNSET"), 89 | databaseState |-> (u1 :> m1), 90 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 91 | serverStates |-> ( s1 :> 92 | [ state |-> "wrote_metadata", 93 | userId |-> u1, 94 | metadata |-> m1, 95 | image |-> i1 ] @@ 96 | s2 :> 97 | [ state |-> "read_metadata", 98 | userId |-> u1, 99 | metadata |-> m1, 100 | image |-> "UNSET" ] ) 101 | ], 102 | [ 103 | _TEAction |-> [ 104 | position |-> 6, 105 | name |-> "ReadBlobAndReturn", 106 | location |-> "line 255, col 5 to line 276, col 50 of module naive" 107 | ], 108 | blobStoreState |-> (u1 :> "UNSET"), 109 | databaseState |-> (u1 :> m1), 110 | operations |-> << [userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"], 111 | [userId |-> u1, metadata |-> m1, image |-> "UNSET", type |-> "READ"] >>, 112 | serverStates |-> ( s1 :> 113 | [ state |-> "wrote_metadata", 114 | userId |-> u1, 115 | metadata |-> m1, 116 | image |-> i1 ] @@ 117 | s2 :> 118 | [state |-> "waiting", userId |-> u1, metadata |-> m1, image |-> "UNSET"] ) 119 | ] 120 | >> -------------------------------------------------------------------------------- /database-blob/naive/naive.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2} 5 | METADATAS = {m1, m2} 6 | USERIDS = {u1} 7 | IMAGES = {i1, i2} 8 | 9 | CONSTRAINT 10 | StopAfter3Operations 11 | 12 | INVARIANT 13 | TypeOk 14 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/naive/naive.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/naive/naive.dvi -------------------------------------------------------------------------------- /database-blob/naive/naive.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/naive/naive.pdf -------------------------------------------------------------------------------- /database-blob/naive/naive_small.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1} 5 | METADATAS = {m1, m2} 6 | USERIDS = {u1} 7 | IMAGES = {i1, i2} 8 | 9 | CONSTRAINT 10 | StopAfter3Operations 11 | 12 | INVARIANT 13 | TypeOk 14 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/naive/process.sh: -------------------------------------------------------------------------------- 1 | 2 | latex naive.toolbox/naive.tex 3 | dvisvgm naive.dvi --no-fonts 4 | -------------------------------------------------------------------------------- /database-blob/naive/small.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | blobStoreState |-> (u1 :> "UNSET"), 9 | databaseState |-> (u1 :> "UNSET"), 10 | operations |-> <<>>, 11 | serverStates |-> ( s1 :> 12 | [ state |-> "waiting", 13 | userId |-> "UNSET", 14 | metadata |-> "UNSET", 15 | image |-> "UNSET" ] ) 16 | ], 17 | [ 18 | _TEAction |-> [ 19 | position |-> 2, 20 | name |-> "StartWrite", 21 | location |-> "line 143, col 5 to line 167, col 50 of module naive" 22 | ], 23 | blobStoreState |-> (u1 :> "UNSET"), 24 | databaseState |-> (u1 :> "UNSET"), 25 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 26 | serverStates |-> ( s1 :> 27 | [ state |-> "started_write", 28 | userId |-> u1, 29 | metadata |-> m1, 30 | image |-> i1 ] ) 31 | ], 32 | [ 33 | _TEAction |-> [ 34 | position |-> 3, 35 | name |-> "WriteMetadata", 36 | location |-> "line 175, col 5 to line 182, col 47 of module naive" 37 | ], 38 | blobStoreState |-> (u1 :> "UNSET"), 39 | databaseState |-> (u1 :> m1), 40 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 41 | serverStates |-> ( s1 :> 42 | [ state |-> "wrote_metadata", 43 | userId |-> u1, 44 | metadata |-> m1, 45 | image |-> i1 ] ) 46 | ], 47 | [ 48 | _TEAction |-> [ 49 | position |-> 4, 50 | name |-> "FailWrite", 51 | location |-> "line 206, col 5 to line 216, col 62 of module naive" 52 | ], 53 | blobStoreState |-> (u1 :> "UNSET"), 54 | databaseState |-> (u1 :> m1), 55 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 56 | serverStates |-> ( s1 :> 57 | [ state |-> "waiting", 58 | userId |-> "UNSET", 59 | metadata |-> "UNSET", 60 | image |-> "UNSET" ] ) 61 | ], 62 | [ 63 | _TEAction |-> [ 64 | position |-> 5, 65 | name |-> "StartRead", 66 | location |-> "line 225, col 5 to line 232, col 27 of module naive" 67 | ], 68 | blobStoreState |-> (u1 :> "UNSET"), 69 | databaseState |-> (u1 :> m1), 70 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 71 | serverStates |-> ( s1 :> 72 | [ state |-> "started_read", 73 | userId |-> u1, 74 | metadata |-> "UNSET", 75 | image |-> "UNSET" ] ) 76 | ], 77 | [ 78 | _TEAction |-> [ 79 | position |-> 6, 80 | name |-> "ReadMetadata", 81 | location |-> "line 238, col 5 to line 249, col 27 of module naive" 82 | ], 83 | blobStoreState |-> (u1 :> "UNSET"), 84 | databaseState |-> (u1 :> m1), 85 | operations |-> <<[userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"]>>, 86 | serverStates |-> ( s1 :> 87 | [ state |-> "read_metadata", 88 | userId |-> u1, 89 | metadata |-> m1, 90 | image |-> "UNSET" ] ) 91 | ], 92 | [ 93 | _TEAction |-> [ 94 | position |-> 7, 95 | name |-> "ReadBlobAndReturn", 96 | location |-> "line 255, col 5 to line 276, col 50 of module naive" 97 | ], 98 | blobStoreState |-> (u1 :> "UNSET"), 99 | databaseState |-> (u1 :> m1), 100 | operations |-> << [userId |-> u1, metadata |-> m1, image |-> i1, type |-> "WRITE"], 101 | [userId |-> u1, metadata |-> m1, image |-> "UNSET", type |-> "READ"] >>, 102 | serverStates |-> (s1 :> [state |-> "waiting", userId |-> u1, metadata |-> m1, image |-> "UNSET"]) 103 | ] 104 | >> -------------------------------------------------------------------------------- /database-blob/storage-cleaner-improved/storagecleanerimproved-killsnippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-improved/storagecleanerimproved-killsnippet.dvi -------------------------------------------------------------------------------- /database-blob/storage-cleaner-improved/storagecleanerimproved.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2} 5 | CLEANERS = {c1, c2} 6 | METADATAS = {m1, m2} 7 | USERIDS = {u1} 8 | IMAGES = {i1, i2} 9 | UUIDS = {ui1, ui2, ui3} 10 | 11 | CONSTRAINT 12 | StopAfter3Operations 13 | 14 | INVARIANT 15 | TypeOk 16 | ConsistentReads 17 | 18 | PROPERTY 19 | AlwaysEventuallyNoOrphanFiles -------------------------------------------------------------------------------- /database-blob/storage-cleaner-improved/storagecleanerimproved.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-improved/storagecleanerimproved.dvi -------------------------------------------------------------------------------- /database-blob/storage-cleaner-improved/storagecleanerimproved.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-improved/storagecleanerimproved.pdf -------------------------------------------------------------------------------- /database-blob/storage-cleaner-improved/storagecleanerimprovedkillsnippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-improved/storagecleanerimprovedkillsnippet.dvi -------------------------------------------------------------------------------- /database-blob/storage-cleaner-improved/storagecleanerimprovedkillsnippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-improved/storagecleanerimprovedkillsnippet.pdf -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | parent: Coordinating a Database and Blob Store 4 | title: (Implementing New Requirements) A naive update 5 | nav_order: 5 6 | has_toc: false 7 | 8 | single: 9 | 3: 10 | note: Our server wrote an image 11 | 4: 12 | note: Our cleaner starts 13 | 5: 14 | note: and notices that the written image isn't in the database 15 | 6: 16 | note: The server completes the write, thinking everything's fine 17 | 7: 18 | note: A read starts for the same user 19 | 9: 20 | note: But oh no, the cleaner deleted the key 21 | 10: 22 | note: Leading to a corrupted read 23 | class: text-red-300 24 | 25 | multi: 26 | 10: 27 | note: Same corrupted read 28 | class: text-red-300 29 | --- 30 | # {{page.title}} 31 | {: .no_toc } 32 | 33 | 1. TOC 34 | {:toc} 35 | 36 | ## Updating the design 37 | 38 | The **Storage Cleaner** is going to to query the blob store and get a batch of keys. It will then query the database in one query and find all the images that are missing a database entry. Then it will delete those unused keys using a batch API call. 39 | ### Storage Cleaner Run 40 | ```plantuml 41 | @startuml 42 | participant "Storage Cleaner" as StorageCleaner 43 | participant Database 44 | participant "Blob Store" as BlobStore 45 | 46 | StorageCleaner -> BlobStore: Gets batch of keys 47 | StorageCleaner <-- BlobStore: Returns batch of keys 48 | 49 | StorageCleaner -> Database : Queries for unused keys 50 | StorageCleaner <-- Database: Returns unused keys 51 | 52 | StorageCleaner -> BlobStore: Batch deletes unused keys 53 | StorageCleaner <-- BlobStore: Returns status 54 | 55 | alt any failure 56 | StorageCleaner --> StorageCleaner: Repeats from beginning 57 | end 58 | 59 | @enduml 60 | ``` 61 | 62 | ## Modeling the design 63 | 64 | This is the first example in our modeling tasks in which the model will not match the solution 1-1. 65 | - **Relaxing a constraint**: While the design calls for batches, for simplicity's sake we will model it as if the entire blob store keyset can fit into one batch. This hides the complexity of figuring out which items have already been checked and how large of a batch size to use; we can either handle these considerations in implementation or model them separately. In this model, we will need to handle new keys being added after we query for keys, as well as the deletion process failing before all key are deleted. This should also alert us to problems that may be introduced by batching. _Note: This design decision is a judgment call that may or may not be correct, but it holds for the current examples._ 66 | - **Enhancing a constraint**: Deleting from the blob store will be modeled as a one by one operation, even though it is submitted in one API call. This is because blob stores don't provide transactions. A batch delete may happen over the course of time. 67 | 68 | The **Storage Cleaner** state diagram looks like this: 69 | ```plantuml 70 | @startuml 71 | hide empty description 72 | [*] --> Waiting 73 | 74 | Waiting --> CleanerStartGetBlobKeys 75 | CleanerStartGetBlobKeys --> CleanerGetUnusedKeys 76 | CleanerStartGetBlobKeys --> CleanerFail 77 | CleanerGetUnusedKeys --> CleanerDeletingKeys 78 | CleanerGetUnusedKeys --> CleanerFail 79 | CleanerDeletingKeys --> CleanerFinished 80 | CleanerDeletingKeys --> CleanerFail 81 | CleanerFinished --> Waiting 82 | 83 | @enduml 84 | ``` 85 | 86 | 87 | 88 | Only the core additions to the spec are shown here. Click _Download Code_ or _Download PDF_ to see the whole thing. 89 | 90 | {% include code.html path="storagecleanernaive" snippet="storagecleanernaive-snippet" %} 91 | 92 | ## Verifying the design 93 | 94 | Let's start small and see what happens: 95 | 96 | {% highlight tla %} 97 | CONSTANTS 98 | SERVERS = {s1} 99 | CLEANERS = {c1} 100 | {% endhighlight %} 101 | 102 | {% include trace.html traceconfig=page.single constraint="Invariant ConsistentReads is violated." trace=site.data.database-blob.storage-cleaner-naive.single modelcfg="storagecleanernaive_small.cfg" %} 103 | 104 | Let's try it again with two servers and two cleaners to see if we get different behavior. 105 | 106 | {% highlight tla %} 107 | CONSTANTS 108 | SERVERS = {s1, s2} 109 | CLEANERS = {c1, c2} 110 | {% endhighlight %} 111 | 112 | {% include trace.html traceconfig=page.multi constraint="Same behavior. Invariant ConsistentReads is violated." trace=site.data.database-blob.storage-cleaner-naive.multi modelcfg="storagecleanernaive.cfg" %} 113 | 114 | Adding more servers and cleaners didn't change the failure mode. We've likely hit upon the essential failure of this design. 115 | 116 | ### Summary 117 | 118 | Clearly this solution isn't going to work as is. It can delete images that were part of records being created at that moment. Normal cleanup systems don't do that; normally they wait a little while... 119 | 120 |

121 | 122 | | Next: [(Implementing New Requirements) Significant improvement](../storage-cleaner-improved) | -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/single.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}},{"no":2,"name":"ServerStartWrite","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"UNSET","state":"started_write","userId":"ui1","image":"u1"}}}},{"no":3,"name":"ServerWriteBlob","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"waiting","blobKeys":[],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}},{"no":4,"name":"CleanerStartGetBlobKeys","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_blob_keys","blobKeys":["i1"],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}},{"no":5,"name":"CleanerGetUnusedKeys","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"UNSET","imageId":"UNSET"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"wrote_blob","userId":"ui1","image":"u1"}}}},{"no":6,"name":"ServerWriteMetadataAndReturn","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}},{"no":7,"name":"ServerStartRead","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"started_read","userId":"ui1","image":"UNSET"}}}},{"no":8,"name":"ServerReadMetadata","state":{"blobStoreState":{"i2":"UNSET","i1":"u1"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":["i1"]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"read_metadata","userId":"ui1","image":"UNSET"}}}},{"no":9,"name":"CleanerDeletingKeys","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"}],"serverStates":{"s1":{"metadata":"m1","imageId":"i1","state":"read_metadata","userId":"ui1","image":"UNSET"}}}},{"no":10,"name":"ServerReadBlobAndReturn","state":{"blobStoreState":{"i2":"UNSET","i1":"UNSET"},"cleanerStates":{"c1":{"state":"got_unused_keys","blobKeys":["i1"],"unusedBlobKeys":[]}},"databaseState":{"ui3":{"metadata":"UNSET","imageId":"UNSET"},"ui2":{"metadata":"UNSET","imageId":"UNSET"},"ui1":{"metadata":"m1","imageId":"i1"}},"operations":[{"metadata":"m1","userId":"ui1","image":"u1","type":"WRITE"},{"metadata":"m1","userId":"ui1","image":"UNSET","type":"READ"}],"serverStates":{"s1":{"metadata":"UNSET","imageId":"UNSET","state":"waiting","userId":"UNSET","image":"UNSET"}}}}] 2 | -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/storagecleanernaive-snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-naive/storagecleanernaive-snippet.dvi -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/storagecleanernaive-snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-naive/storagecleanernaive-snippet.pdf -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/storagecleanernaive.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2} 5 | CLEANERS = {c1, c2} 6 | METADATAS = {m1, m2} 7 | USERIDS = {u1} 8 | IMAGES = {i1, i2} 9 | UUIDS = {ui1, ui2, ui3} 10 | 11 | CONSTRAINT 12 | StopAfter3Operations 13 | 14 | INVARIANT 15 | TypeOk 16 | ConsistentReads 17 | 18 | PROPERTY 19 | AlwaysEventuallyNoOrphanFiles -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/storagecleanernaive.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-naive/storagecleanernaive.dvi -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/storagecleanernaive.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-naive/storagecleanernaive.pdf -------------------------------------------------------------------------------- /database-blob/storage-cleaner-naive/storagecleanernaive_small.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1} 5 | CLEANERS = {c1} 6 | METADATAS = {m1, m2} 7 | USERIDS = {u1} 8 | IMAGES = {i1, i2} 9 | UUIDS = {ui1, ui2, ui3} 10 | 11 | CONSTRAINT 12 | StopAfter3Operations 13 | 14 | INVARIANT 15 | TypeOk 16 | ConsistentReads 17 | 18 | PROPERTY 19 | AlwaysEventuallyNoOrphanFiles -------------------------------------------------------------------------------- /database-blob/storage-cleaner-working/storagecleaner-snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-working/storagecleaner-snippet.dvi -------------------------------------------------------------------------------- /database-blob/storage-cleaner-working/storagecleaner-snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-working/storagecleaner-snippet.pdf -------------------------------------------------------------------------------- /database-blob/storage-cleaner-working/storagecleaner-snippet.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE storagecleaner ---------------------------- 2 | 3 | CleanerGetUnusedKeys(c) == 4 | LET current == cleanerStates[c] IN 5 | /\ current.state = "got_blob_keys" 6 | /\ cleanerStates' = [ 7 | cleanerStates EXCEPT 8 | ![c].state = "got_unused_keys", 9 | ![c].unusedBlobKeys = 10 | {k \in current.blobKeys: 11 | \A u \in USERIDS: 12 | databaseState[u].imageId # k}, 13 | \* Mark the time the unused keys were retrieved 14 | ![c].unusedKeyTime = time 15 | ] 16 | /\ UNCHANGED <> 17 | /\ UNCHANGED time 18 | 19 | CleanerDeletingKeys(c) == 20 | LET current == cleanerStates[c] IN 21 | \* Keys get deleted a minimum 1 hour after they are valid 22 | \* This gives reads time to die 23 | LET earliestDeleteTime == current.unusedKeyTime + 1 IN 24 | /\ time >= earliestDeleteTime 25 | /\ current.state \in {"got_unused_keys", "deleting_keys"} 26 | /\ Cardinality(current.unusedBlobKeys) # 0 27 | /\ \E k \in current.unusedBlobKeys: \* Pick a key to delete 28 | /\ blobStoreState' = 29 | [blobStoreState EXCEPT 30 | ![k] = [status |-> "UNSET", image |-> "UNSET"]] 31 | /\ cleanerStates' = [ 32 | cleanerStates EXCEPT 33 | ![c].unusedBlobKeys = current.unusedBlobKeys \ {k} 34 | ] 35 | /\ UNCHANGED <> 36 | /\ UNCHANGED time 37 | 38 | ============================================================================= -------------------------------------------------------------------------------- /database-blob/storage-cleaner-working/storagecleaner.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2} 5 | CLEANERS = {c1, c2} 6 | METADATAS = {m1, m2} 7 | USERIDS = {u1} 8 | IMAGES = {i1, i2} 9 | UUIDS = {ui1, ui2, ui3} 10 | 11 | CONSTRAINT 12 | StopAfter3Operations 13 | 14 | INVARIANT 15 | TypeOk 16 | ConsistentReads 17 | 18 | PROPERTY 19 | AlwaysEventuallyNoOrphanFiles -------------------------------------------------------------------------------- /database-blob/storage-cleaner-working/storagecleaner.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-working/storagecleaner.dvi -------------------------------------------------------------------------------- /database-blob/storage-cleaner-working/storagecleaner.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/storage-cleaner-working/storagecleaner.pdf -------------------------------------------------------------------------------- /database-blob/working/working.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2, s3, s4} 5 | METADATAS = {m1, m2, m3, m4, m5, m6} 6 | USERIDS = {u1, u2, u3, u4, u5} 7 | IMAGES = {i1, i2, i3, i4, i5} 8 | UUIDS = {ui1, ui2, ui3, ui4, ui5, ui6, ui7, ui8, ui9, ui10} 9 | 10 | CONSTRAINT 11 | StopAfter10Operations 12 | 13 | INVARIANT 14 | TypeOk 15 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/working/working.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/working/working.dvi -------------------------------------------------------------------------------- /database-blob/working/working.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/working/working.pdf -------------------------------------------------------------------------------- /database-blob/working/working_large.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2, s3} 5 | METADATAS = {m1, m2, m3} 6 | USERIDS = {u1, u2, u3, u4, u5} 7 | IMAGES = {i1, i2, i3} 8 | UUIDS = {ui1, ui2} 9 | 10 | CONSTRAINT 11 | StopAfter10Operations 12 | 13 | INVARIANT 14 | TypeOk 15 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/working/working_small.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1} 5 | METADATAS = {m1, m2} 6 | USERIDS = {u1} 7 | IMAGES = {i1, i2} 8 | UUIDS = {ui1, ui2, ui3, ui4, ui5} 9 | 10 | CONSTRAINT 11 | StopAfter3Operations 12 | 13 | INVARIANT 14 | TypeOk 15 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/working/working_standard.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | 3 | CONSTANTS 4 | SERVERS = {s1, s2} 5 | METADATAS = {m1, m2} 6 | USERIDS = {u1} 7 | IMAGES = {i1, i2} 8 | UUIDS = {ui1, ui2, ui3, ui4, ui5} 9 | 10 | CONSTRAINT 11 | StopAfter3Operations 12 | 13 | INVARIANT 14 | TypeOk 15 | ConsistentReads -------------------------------------------------------------------------------- /database-blob/working/workingsingleserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/database-blob/working/workingsingleserver.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/favicon.ico -------------------------------------------------------------------------------- /favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /learning-material/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Learning Material 4 | nav_order: 9 5 | --- 6 | 7 | # {{page.title}} 8 | 9 | ## Getting Started 10 | 11 | The best way to start learning TLA+ is with the [TLA+ Video Course](https://lamport.azurewebsites.net/video/videos.html), which is actually very engaging. Then read the first 80 pages of the [Specifying Systems](https://lamport.azurewebsites.net/tla/book.html?back-link=learning.html#book) book, which is somewhat less engaging, but very helpful. The official repository of [TLA+ learning material can be found here](https://lamport.azurewebsites.net/tla/learning.html). 12 | 13 | > _Note: there's also a language that compiles into TLA+ called PlusCal. It is slightly less powerful (and in fact you may need to use TLA+ expressions inline); however, it looks more like a programming language.[Practical TLA+](https://link.springer.com/book/10.1007/978-1-4842-3829-5) is a good book if you want to learn it. I'm sure it's sufficiently powerful to work the examples on this site. AWS uses both TLA+ and PlusCal for different algorithms. However, as PlusCal compiles to TLA+, understanding TLA+ is generally considered a good idea, regardless of which one you use._ 14 | 15 | The [TLA+ Language Manual for Engineers](https://apalache.informal.systems/docs/lang/index.html) provides a semi-comprehensive guide to the TLA+ language. 16 | 17 | The [Learn TLA Website](https://learntla.com/introduction/) has both [TLA+](https://learntla.com/tla/) and PlusCal information. It is less comprehensive, but somewhat more accessible, than the [TLA+ Language Manual for Engineers](https://apalache.informal.systems/docs/lang/index.html). 18 | 19 | 20 | 21 | ## Advanced Concepts 22 | 23 | ### Want structured learning? 24 | - [Specifying Systems](https://lamport.azurewebsites.net/tla/book.html?back-link=learning.html#book): **Pages 81-227**: Advanced but well-explained examples 25 | - [Weeks of Debugging Can Save You Hours of TLA+ (Video)](https://www.youtube.com/watch?v=wjsI0lTSjIo) 26 | - [Blocking Queue](https://github.com/lemmy/BlockingQueue) 27 | - [Using TLA+ in the Real World to Understand a Glibc Bug](https://probablydance.com/2020/10/31/using-tla-in-the-real-world-to-understand-a-glibc-bug/) 28 | 29 | ### Know what you're looking for? 30 | - [TLA+ Examples Repository](https://github.com/tlaplus/Examples): Highly advise you read each algorithm along with a paper or explainer, to correlate concepts to code. 31 | - [Advanced Concepts from TLA+ Website](https://lamport.azurewebsites.net/tla/advanced.html): Provide references for very specific parts of TLA+. 32 | - [Specifying Systems](https://lamport.azurewebsites.net/tla/book.html?back-link=learning.html#book): **Pages 228-End**. Basically a reference manual. 33 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/logo.png -------------------------------------------------------------------------------- /tictactoe/1everygame/owin.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","X"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","X"],["_","O","_"],["_","_","_"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","X"],["X","O","_"],["_","_","_"]],"nextTurn":"O"}},{"no":7,"name":"MoveO","state":{"board":[["X","O","X"],["X","O","_"],["_","O","_"]],"nextTurn":"X"}}] 2 | -------------------------------------------------------------------------------- /tictactoe/1everygame/owin.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | board |-> <<<<"_", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 9 | nextTurn |-> "X" 10 | ], 11 | [ 12 | _TEAction |-> [ 13 | position |-> 2, 14 | name |-> "MoveX", 15 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 16 | ], 17 | board |-> <<<<"X", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 18 | nextTurn |-> "O" 19 | ], 20 | [ 21 | _TEAction |-> [ 22 | position |-> 3, 23 | name |-> "MoveO", 24 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 25 | ], 26 | board |-> <<<<"X", "O", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 27 | nextTurn |-> "X" 28 | ], 29 | [ 30 | _TEAction |-> [ 31 | position |-> 4, 32 | name |-> "MoveX", 33 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 34 | ], 35 | board |-> <<<<"X", "O", "X">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 36 | nextTurn |-> "O" 37 | ], 38 | [ 39 | _TEAction |-> [ 40 | position |-> 5, 41 | name |-> "MoveO", 42 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 43 | ], 44 | board |-> <<<<"X", "O", "X">>, <<"_", "O", "_">>, <<"_", "_", "_">>>>, 45 | nextTurn |-> "X" 46 | ], 47 | [ 48 | _TEAction |-> [ 49 | position |-> 6, 50 | name |-> "MoveX", 51 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 52 | ], 53 | board |-> <<<<"X", "O", "X">>, <<"X", "O", "_">>, <<"_", "_", "_">>>>, 54 | nextTurn |-> "O" 55 | ], 56 | [ 57 | _TEAction |-> [ 58 | position |-> 7, 59 | name |-> "MoveO", 60 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 61 | ], 62 | board |-> <<<<"X", "O", "X">>, <<"X", "O", "_">>, <<"_", "O", "_">>>>, 63 | nextTurn |-> "X" 64 | ] 65 | >> -------------------------------------------------------------------------------- /tictactoe/1everygame/stalemate.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","X"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","X"],["O","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","_"],["_","_","_"]],"nextTurn":"O"}},{"no":7,"name":"MoveO","state":{"board":[["X","O","X"],["O","X","_"],["O","_","_"]],"nextTurn":"X"}},{"no":8,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["O","_","_"]],"nextTurn":"O"}},{"no":9,"name":"MoveO","state":{"board":[["X","O","X"],["O","X","X"],["O","_","O"]],"nextTurn":"X"}},{"no":10,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["O","X","O"]],"nextTurn":"O"}}] 2 | -------------------------------------------------------------------------------- /tictactoe/1everygame/stalemate.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | board |-> <<<<"_", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 9 | nextTurn |-> "X" 10 | ], 11 | [ 12 | _TEAction |-> [ 13 | position |-> 2, 14 | name |-> "MoveX", 15 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 16 | ], 17 | board |-> <<<<"X", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 18 | nextTurn |-> "O" 19 | ], 20 | [ 21 | _TEAction |-> [ 22 | position |-> 3, 23 | name |-> "MoveO", 24 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 25 | ], 26 | board |-> <<<<"X", "O", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 27 | nextTurn |-> "X" 28 | ], 29 | [ 30 | _TEAction |-> [ 31 | position |-> 4, 32 | name |-> "MoveX", 33 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 34 | ], 35 | board |-> <<<<"X", "O", "X">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 36 | nextTurn |-> "O" 37 | ], 38 | [ 39 | _TEAction |-> [ 40 | position |-> 5, 41 | name |-> "MoveO", 42 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 43 | ], 44 | board |-> <<<<"X", "O", "X">>, <<"O", "_", "_">>, <<"_", "_", "_">>>>, 45 | nextTurn |-> "X" 46 | ], 47 | [ 48 | _TEAction |-> [ 49 | position |-> 6, 50 | name |-> "MoveX", 51 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 52 | ], 53 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "_">>, <<"_", "_", "_">>>>, 54 | nextTurn |-> "O" 55 | ], 56 | [ 57 | _TEAction |-> [ 58 | position |-> 7, 59 | name |-> "MoveO", 60 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 61 | ], 62 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "_">>, <<"O", "_", "_">>>>, 63 | nextTurn |-> "X" 64 | ], 65 | [ 66 | _TEAction |-> [ 67 | position |-> 8, 68 | name |-> "MoveX", 69 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 70 | ], 71 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "X">>, <<"O", "_", "_">>>>, 72 | nextTurn |-> "O" 73 | ], 74 | [ 75 | _TEAction |-> [ 76 | position |-> 9, 77 | name |-> "MoveO", 78 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 79 | ], 80 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "X">>, <<"O", "_", "O">>>>, 81 | nextTurn |-> "X" 82 | ], 83 | [ 84 | _TEAction |-> [ 85 | position |-> 10, 86 | name |-> "MoveX", 87 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 88 | ], 89 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "X">>, <<"O", "X", "O">>>>, 90 | nextTurn |-> "O" 91 | ] 92 | >> -------------------------------------------------------------------------------- /tictactoe/1everygame/statespace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/1everygame/statespace.png -------------------------------------------------------------------------------- /tictactoe/1everygame/tictactoe-full.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/1everygame/tictactoe-owin.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT 2 | OHasNotWon 3 | 4 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/1everygame/tictactoe-stalemate.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT 2 | NotStalemate 3 | 4 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/1everygame/tictactoe-xwin.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT 2 | XHasNotWon 3 | 4 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/1everygame/tictactoe.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/1everygame/tictactoe.dvi -------------------------------------------------------------------------------- /tictactoe/1everygame/tictactoe.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/1everygame/tictactoe.pdf -------------------------------------------------------------------------------- /tictactoe/1everygame/tictactoe.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE tictactoe ---------------------------- 2 | 3 | EXTENDS Naturals 4 | 5 | VARIABLES 6 | board, \* board[1..3][1..3] A 3x3 tic-tac-toe board 7 | nextTurn \* who goes next 8 | 9 | Pieces == {"X", "O", "_"} \* "_" represents a blank square 10 | 11 | Init == 12 | /\ nextTurn = "X" \* X always goes first 13 | \* Every space in the board states blank 14 | /\ board = [i \in 1..3 |-> [j \in 1..3 |-> "_"]] 15 | 16 | Move(player) == 17 | \E i \in 1..3: \E j \in 1..3: \* There exists a position on the board 18 | /\ board[i][j] = "_" \* Where the board is currently empty 19 | (********************************************************************) 20 | (* The future state of board is the same, except a piece is in that *) 21 | (* spot *) 22 | (********************************************************************) 23 | /\ board' = [board EXCEPT 24 | ![i][j] = player] 25 | 26 | MoveX == 27 | /\ nextTurn = "X" \* Only enabled on X's turn 28 | /\ Move("X") 29 | /\ nextTurn' = "O" \* The future state of next turn is O 30 | 31 | MoveO == 32 | /\ nextTurn = "O" \* Only enabled on O's turn 33 | /\ Move("O") 34 | /\ nextTurn' = "X" \* The future state of next turn is X 35 | 36 | \* Every state, X will move if X's turn, O will move on O's turn 37 | Next == MoveX \/ MoveO 38 | 39 | \* A description of every possible game of tic-tac-toe 40 | \* will play until the board fills up, even if someone won 41 | Spec == Init /\ [][Next]_<> 42 | 43 | (***************************************************************************) 44 | (* Invariants: The things we are checking for. *) 45 | (***************************************************************************) 46 | 47 | WinningPositions == { 48 | \* Horizonal wins 49 | {<<1,1>>, <<1,2>>, <<1,3>>}, 50 | {<<2,1>>, <<2,2>>, <<2,3>>}, 51 | {<<3,1>>, <<3,2>>, <<3,3>>}, 52 | \* Vertical wins 53 | {<<1,1>>, <<2,1>>, <<3,1>>}, 54 | {<<1,2>>, <<2,2>>, <<3,2>>}, 55 | {<<1,3>>, <<2,3>>, <<3,3>>}, 56 | \* Diagonal wins 57 | {<<1,1>>, <<2,2>>, <<3,3>>}, 58 | {<<3,1>>, <<2,2>>, <<1,3>>} 59 | } 60 | 61 | Won(player) == 62 | \* A player has won if there exists a winning position 63 | \E winningPosition \in WinningPositions: 64 | \* Where all the needed spaces 65 | \A neededSpace \in winningPosition: 66 | \* are occupied by one player 67 | board[neededSpace[1]][neededSpace[2]] = player 68 | 69 | XHasNotWon == ~Won("X") 70 | OHasNotWon == ~Won("O") 71 | 72 | BoardFilled == 73 | \* There does not exist 74 | ~\E i \in 1..3, j \in 1..3: 75 | \* an empty space 76 | LET space == board[i][j] IN 77 | space = "_" 78 | 79 | \* It's not a stalemate if one player has won or the board is not filled 80 | NotStalemate == 81 | \/ Won("X") 82 | \/ Won("O") 83 | \/ ~BoardFilled 84 | 85 | ============================================================================= 86 | -------------------------------------------------------------------------------- /tictactoe/1everygame/xwin.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","_"],["X","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","O"],["X","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","O"],["X","_","_"],["X","_","_"]],"nextTurn":"O"}}] 2 | -------------------------------------------------------------------------------- /tictactoe/1everygame/xwin.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | board |-> <<<<"_", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 9 | nextTurn |-> "X" 10 | ], 11 | [ 12 | _TEAction |-> [ 13 | position |-> 2, 14 | name |-> "MoveX", 15 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 16 | ], 17 | board |-> <<<<"X", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 18 | nextTurn |-> "O" 19 | ], 20 | [ 21 | _TEAction |-> [ 22 | position |-> 3, 23 | name |-> "MoveO", 24 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 25 | ], 26 | board |-> <<<<"X", "O", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 27 | nextTurn |-> "X" 28 | ], 29 | [ 30 | _TEAction |-> [ 31 | position |-> 4, 32 | name |-> "MoveX", 33 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 34 | ], 35 | board |-> <<<<"X", "O", "_">>, <<"X", "_", "_">>, <<"_", "_", "_">>>>, 36 | nextTurn |-> "O" 37 | ], 38 | [ 39 | _TEAction |-> [ 40 | position |-> 5, 41 | name |-> "MoveO", 42 | location |-> "line 32, col 5 to line 34, col 22 of module tictactoe" 43 | ], 44 | board |-> <<<<"X", "O", "O">>, <<"X", "_", "_">>, <<"_", "_", "_">>>>, 45 | nextTurn |-> "X" 46 | ], 47 | [ 48 | _TEAction |-> [ 49 | position |-> 6, 50 | name |-> "MoveX", 51 | location |-> "line 27, col 5 to line 29, col 22 of module tictactoe" 52 | ], 53 | board |-> <<<<"X", "O", "O">>, <<"X", "_", "_">>, <<"X", "_", "_">>>>, 54 | nextTurn |-> "O" 55 | ] 56 | >> -------------------------------------------------------------------------------- /tictactoe/2xstrategy/safeoptions.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Combining Proven Safety With AI in Tic-Tac-Toe 4 | 5 | 6 | 7 | 8 | Legal Space 9 | 10 | 11 | Legal Space 12 | 13 | 14 | Legal Space 15 | 16 | 17 | Legal Space 18 | 19 | 20 | Not Legal Space 21 | 22 | 23 | Not Legal Space 24 | 25 | 26 | Legal Space 27 | 28 | 29 | Legal Space 30 | 31 | 32 | Legal Space 33 | 34 | 35 | 36 | O 37 | 38 | 39 | X 40 | 41 | 42 | 43 | 44 | Strategy Space 45 | 46 | 47 | Strategy Space 48 | 49 | 50 | AI Chosen Space 51 | 52 | 53 | All Moves 54 | 55 | All Moves 56 | 57 | Legal Moves 58 | 59 | 60 | Legal Moves 61 | 62 | Strategy Moves 63 | 64 | Strategy Moves 65 | AI Chosen Move: 66 | 67 | AI Chosen Space 68 | 69 | 70 | AI Chosen Space 71 | 72 | 73 | AI Chosen Space 74 | 75 | 76 | AI Chosen Space 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /tictactoe/2xstrategy/stalemate.json: -------------------------------------------------------------------------------- 1 | [{"no":1,"name":"Initial predicate","state":{"board":[["_","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":2,"name":"MoveX","state":{"board":[["X","_","_"],["_","_","_"],["_","_","_"]],"nextTurn":"O"}},{"no":3,"name":"MoveO","state":{"board":[["X","O","_"],["_","_","_"],["_","_","_"]],"nextTurn":"X"}},{"no":4,"name":"MoveX","state":{"board":[["X","O","_"],["_","X","_"],["_","_","_"]],"nextTurn":"O"}},{"no":5,"name":"MoveO","state":{"board":[["X","O","_"],["_","X","_"],["_","_","O"]],"nextTurn":"X"}},{"no":6,"name":"MoveX","state":{"board":[["X","O","_"],["_","X","X"],["_","_","O"]],"nextTurn":"O"}},{"no":7,"name":"MoveO","state":{"board":[["X","O","_"],["O","X","X"],["_","_","O"]],"nextTurn":"X"}},{"no":8,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["_","_","O"]],"nextTurn":"O"}},{"no":9,"name":"MoveO","state":{"board":[["X","O","X"],["O","X","X"],["O","_","O"]],"nextTurn":"X"}},{"no":10,"name":"MoveX","state":{"board":[["X","O","X"],["O","X","X"],["O","X","O"]],"nextTurn":"O"}}] 2 | -------------------------------------------------------------------------------- /tictactoe/2xstrategy/stalemate.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | board |-> <<<<"_", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 9 | nextTurn |-> "X" 10 | ], 11 | [ 12 | _TEAction |-> [ 13 | position |-> 2, 14 | name |-> "MoveX", 15 | location |-> "line 130, col 5 to line 149, col 22 of module tictactoexstrat" 16 | ], 17 | board |-> <<<<"X", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 18 | nextTurn |-> "O" 19 | ], 20 | [ 21 | _TEAction |-> [ 22 | position |-> 3, 23 | name |-> "MoveO", 24 | location |-> "line 51, col 5 to line 54, col 22 of module tictactoexstrat" 25 | ], 26 | board |-> <<<<"X", "O", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 27 | nextTurn |-> "X" 28 | ], 29 | [ 30 | _TEAction |-> [ 31 | position |-> 4, 32 | name |-> "MoveX", 33 | location |-> "line 130, col 5 to line 149, col 22 of module tictactoexstrat" 34 | ], 35 | board |-> <<<<"X", "O", "_">>, <<"_", "X", "_">>, <<"_", "_", "_">>>>, 36 | nextTurn |-> "O" 37 | ], 38 | [ 39 | _TEAction |-> [ 40 | position |-> 5, 41 | name |-> "MoveO", 42 | location |-> "line 51, col 5 to line 54, col 22 of module tictactoexstrat" 43 | ], 44 | board |-> <<<<"X", "O", "_">>, <<"_", "X", "_">>, <<"_", "_", "O">>>>, 45 | nextTurn |-> "X" 46 | ], 47 | [ 48 | _TEAction |-> [ 49 | position |-> 6, 50 | name |-> "MoveX", 51 | location |-> "line 130, col 5 to line 149, col 22 of module tictactoexstrat" 52 | ], 53 | board |-> <<<<"X", "O", "_">>, <<"_", "X", "X">>, <<"_", "_", "O">>>>, 54 | nextTurn |-> "O" 55 | ], 56 | [ 57 | _TEAction |-> [ 58 | position |-> 7, 59 | name |-> "MoveO", 60 | location |-> "line 51, col 5 to line 54, col 22 of module tictactoexstrat" 61 | ], 62 | board |-> <<<<"X", "O", "_">>, <<"O", "X", "X">>, <<"_", "_", "O">>>>, 63 | nextTurn |-> "X" 64 | ], 65 | [ 66 | _TEAction |-> [ 67 | position |-> 8, 68 | name |-> "MoveX", 69 | location |-> "line 130, col 5 to line 149, col 22 of module tictactoexstrat" 70 | ], 71 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "X">>, <<"_", "_", "O">>>>, 72 | nextTurn |-> "O" 73 | ], 74 | [ 75 | _TEAction |-> [ 76 | position |-> 9, 77 | name |-> "MoveO", 78 | location |-> "line 51, col 5 to line 54, col 22 of module tictactoexstrat" 79 | ], 80 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "X">>, <<"O", "_", "O">>>>, 81 | nextTurn |-> "X" 82 | ], 83 | [ 84 | _TEAction |-> [ 85 | position |-> 10, 86 | name |-> "MoveX", 87 | location |-> "line 130, col 5 to line 149, col 22 of module tictactoexstrat" 88 | ], 89 | board |-> <<<<"X", "O", "X">>, <<"O", "X", "X">>, <<"O", "X", "O">>>>, 90 | nextTurn |-> "O" 91 | ] 92 | >> -------------------------------------------------------------------------------- /tictactoe/2xstrategy/statespace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/2xstrategy/statespace.png -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat-owin.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT 2 | OHasNotWon 3 | 4 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat-snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/2xstrategy/tictactoexstrat-snippet.dvi -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat-snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/2xstrategy/tictactoexstrat-snippet.pdf -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat-snippet.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE tictactoexstrat ---------------------------- 2 | 3 | MoveX == 4 | /\ nextTurn = "X" \* Only enabled on X's turn 5 | /\ ~Won("O") \* And X has not won 6 | \* This specifies the spots X will move on X's turn 7 | /\ \/ /\ BoardEmpty 8 | /\ StartInCorner 9 | \/ /\ ~BoardEmpty \* If it's not the start 10 | /\ \/ /\ CanWin 11 | /\ Win 12 | \/ /\ ~CanWin 13 | /\ \/ /\ CanBlockWin 14 | /\ BlockWin 15 | \/ /\ ~CanBlockWin 16 | /\ \/ /\ CanTakeCenter 17 | /\ TakeCenter 18 | \/ /\ ~CanTakeCenter 19 | /\ \/ /\ CanSetupWin 20 | /\ SetupWin 21 | \/ /\ ~CanSetupWin 22 | /\ MoveToEmpty("X") \* No more strategies. Pick spot 23 | /\ nextTurn' = "O" \* The future state of next turn is O 24 | 25 | ============================================================================= -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat-stalemate.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT 2 | NotStalemate 3 | 4 | CHECK_DEADLOCK FALSE 5 | 6 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat.cfg: -------------------------------------------------------------------------------- 1 | INVARIANT 2 | OHasNotWon 3 | 4 | CHECK_DEADLOCK FALSE 5 | 6 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/2xstrategy/tictactoexstrat.dvi -------------------------------------------------------------------------------- /tictactoe/2xstrategy/tictactoexstrat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/2xstrategy/tictactoexstrat.pdf -------------------------------------------------------------------------------- /tictactoe/3xwin/stutteringstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/3xwin/stutteringstep.png -------------------------------------------------------------------------------- /tictactoe/3xwin/tictactoexwin-snippet.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/3xwin/tictactoexwin-snippet.dvi -------------------------------------------------------------------------------- /tictactoe/3xwin/tictactoexwin-snippet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/3xwin/tictactoexwin-snippet.pdf -------------------------------------------------------------------------------- /tictactoe/3xwin/tictactoexwin-snippet.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE tictactoexwin ---------------------------- 2 | 3 | 4 | XMustEventuallyWin == <>Won("X") 5 | 6 | Spec == Init /\ [][Next]_vars /\ WF_vars(Next) 7 | 8 | 9 | ============================================================================= 10 | -------------------------------------------------------------------------------- /tictactoe/3xwin/tictactoexwin.cfg: -------------------------------------------------------------------------------- 1 | PROPERTY 2 | XMustEventuallyWin 3 | 4 | CHECK_DEADLOCK FALSE 5 | 6 | SPECIFICATION Spec -------------------------------------------------------------------------------- /tictactoe/3xwin/tictactoexwin.dvi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/3xwin/tictactoexwin.dvi -------------------------------------------------------------------------------- /tictactoe/3xwin/tictactoexwin.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElliotSwart/pragmaticformalmodeling/ea86b1b5852d49d60d82d944e4807774392e1414/tictactoe/3xwin/tictactoexwin.pdf -------------------------------------------------------------------------------- /tictactoe/3xwin/xmustwin.trace: -------------------------------------------------------------------------------- 1 | << 2 | [ 3 | _TEAction |-> [ 4 | position |-> 1, 5 | name |-> "Initial predicate", 6 | location |-> "Unknown location" 7 | ], 8 | board |-> <<<<"_", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "_">>>>, 9 | nextTurn |-> "X" 10 | ], 11 | [ 12 | _TEAction |-> [ 13 | position |-> 2, 14 | name |-> "MoveX", 15 | location |-> "line 132, col 5 to line 151, col 22 of module tictactoexstrat" 16 | ], 17 | board |-> <<<<"_", "_", "_">>, <<"_", "_", "_">>, <<"_", "_", "X">>>>, 18 | nextTurn |-> "O" 19 | ], 20 | [ 21 | _TEAction |-> [ 22 | position |-> 3, 23 | name |-> "MoveO", 24 | location |-> "line 53, col 5 to line 56, col 22 of module tictactoexstrat" 25 | ], 26 | board |-> <<<<"_", "_", "_">>, <<"_", "O", "_">>, <<"_", "_", "X">>>>, 27 | nextTurn |-> "X" 28 | ], 29 | [ 30 | _TEAction |-> [ 31 | position |-> 4, 32 | name |-> "MoveX", 33 | location |-> "line 132, col 5 to line 151, col 22 of module tictactoexstrat" 34 | ], 35 | board |-> <<<<"_", "_", "_">>, <<"_", "O", "X">>, <<"_", "_", "X">>>>, 36 | nextTurn |-> "O" 37 | ], 38 | [ 39 | _TEAction |-> [ 40 | position |-> 5, 41 | name |-> "MoveO", 42 | location |-> "line 53, col 5 to line 56, col 22 of module tictactoexstrat" 43 | ], 44 | board |-> <<<<"_", "_", "O">>, <<"_", "O", "X">>, <<"_", "_", "X">>>>, 45 | nextTurn |-> "X" 46 | ], 47 | [ 48 | _TEAction |-> [ 49 | position |-> 6, 50 | name |-> "MoveX", 51 | location |-> "line 132, col 5 to line 151, col 22 of module tictactoexstrat" 52 | ], 53 | board |-> <<<<"_", "_", "O">>, <<"_", "O", "X">>, <<"X", "_", "X">>>>, 54 | nextTurn |-> "O" 55 | ], 56 | [ 57 | _TEAction |-> [ 58 | position |-> 7, 59 | name |-> "MoveO", 60 | location |-> "line 53, col 5 to line 56, col 22 of module tictactoexstrat" 61 | ], 62 | board |-> <<<<"_", "_", "O">>, <<"_", "O", "X">>, <<"X", "O", "X">>>>, 63 | nextTurn |-> "X" 64 | ], 65 | [ 66 | _TEAction |-> [ 67 | position |-> 8, 68 | name |-> "MoveX", 69 | location |-> "line 132, col 5 to line 151, col 22 of module tictactoexstrat" 70 | ], 71 | board |-> <<<<"_", "X", "O">>, <<"_", "O", "X">>, <<"X", "O", "X">>>>, 72 | nextTurn |-> "O" 73 | ], 74 | [ 75 | _TEAction |-> [ 76 | position |-> 9, 77 | name |-> "MoveO", 78 | location |-> "line 53, col 5 to line 56, col 22 of module tictactoexstrat" 79 | ], 80 | board |-> <<<<"O", "X", "O">>, <<"_", "O", "X">>, <<"X", "O", "X">>>>, 81 | nextTurn |-> "X" 82 | ], 83 | [ 84 | _TEAction |-> [ 85 | position |-> 10, 86 | name |-> "MoveX", 87 | location |-> "line 132, col 5 to line 151, col 22 of module tictactoexstrat" 88 | ], 89 | board |-> <<<<"O", "X", "O">>, <<"X", "O", "X">>, <<"X", "O", "X">>>>, 90 | nextTurn |-> "O" 91 | ] 92 | >> -------------------------------------------------------------------------------- /tools/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Tools and Additional Citations 4 | nav_order: 10 5 | --- 6 | 7 | # {{page.title}} 8 | 9 | ## Tools for TLA+ 10 | - [TLA+ Toolbox](https://lamport.azurewebsites.net/tla/toolbox.html): The fully featured (if somewhat outdated) IDE for TLA+. Includes modeling, document generation, and live syntax checking. 11 | - [VSCode TLA+](https://marketplace.visualstudio.com/items?itemName=alygin.vscode-tlaplus): Lighter weight IDE with textfile based model configuration. Generally more responsive. The latest alpha version, [found here](https://github.com/tlaplus/vscode-tlaplus/releases), has a debugger that might be useful to you while getting a handle on the language. 12 | 13 | ## Tools for TLA+ display 14 | - [tla2json](https://github.com/japgolly/tla2json): Used to turn trace output from toolbox into json for automatic trace widget generation. 15 | - [LaTex](https://www.latex-project.org/): Used to render the LaTex output of TLA+ to dvi format. 16 | - [dvisvgm](https://dvisvgm.de/): Used to convert the dvi formatted TLA+ specifications into SVGs that could be displayed inline in the website. 17 | - [Code highlighting](https://github.com/ElliotSwart/practicalformalmodeling/blob/initial/_plugins/tla.rb): Improved / repackaged for Jekyll, but the majority of the code came from [this pull request](https://github.com/rouge-ruby/rouge/pull/1740) by Tom Lee. 18 | 19 | ## Tools for the website 20 | - [Jekyll](https://jekyllrb.com/): A static site generator. This website heavily relied on the templating functionality Jekyll provides. 21 | - [PlantUML](https://plantuml.com/): Used to generate UML diagrams from text. 22 | - [Kramdown::PlantUml](https://github.com/SwedbankPay/kramdown-plantuml): Allows rendering it in Jekyll. 23 | 24 | - [Just the Docs](https://just-the-docs.github.io/just-the-docs/): The theme that was used and modified for this website. 25 | 26 | 27 | ## Assets 28 | - [Favicon](https://www.flaticon.com/free-icons/diamond): Diamond icons created by Vaadin - Flaticon. 29 | 30 | 31 | ## Acknowledgments 32 | - To [Liza Knipscher](https://github.com/knipscher) for editing and proofreading tons of text and code. 33 | - To all the people who wrote the [learning material](../learning-material) and the tools above, your work was invaluable to the completion of this project. 34 | --------------------------------------------------------------------------------