--------------------------------------------------------------------------------
/_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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------