---- MODULE Qualification ---- EXTENDS Integers, FiniteSets CONSTANTS Deliveries, MaxBuilds, MaxRequests, MaxTime, Interval, InitialPolicy Policies == {"OnBuild", "ManualOnly"} Keys == Deliveries \X {"Auto"} \X (1..MaxBuilds) \cup Deliveries \X {"Manual"} \X (1..MaxRequests) Phases == {"Absent", "Pending", "Reserved", "Queued", "Running", "Terminal"} ActivePhases == {"Reserved", "Queued", "Running"} VARIABLES latest, policy, request, attempts, now, lastStart, covered vars == <> ASSUME /\ Deliveries /= {} /\ MaxBuilds \in Nat \ {0} /\ MaxRequests \in Nat \ {0} /\ MaxTime \in Nat /\ Interval \in [Deliveries -> Nat] /\ InitialPolicy \in [Deliveries -> Policies] Input(delivery, build) == <> Inputs == Deliveries \X (0..MaxBuilds) EmptyRequest == [build |-> 0, nonce |-> 0] EmptyAttempt(delivery) == [phase |-> "Absent", build |-> 0, input |-> Input(delivery, 0), started |-> -1, prior |-> -1] Active == {key \in Keys : attempts[key].phase \in ActivePhases} Due(delivery) == lastStart[delivery] = -1 \/ now >= lastStart[delivery] + Interval[delivery] TypeOK == /\ latest \in [Deliveries -> 0..MaxBuilds] /\ policy \in [Deliveries -> Policies] /\ request \in [Deliveries -> [build : 0..MaxBuilds, nonce : 0..MaxRequests]] /\ attempts \in [Keys -> [phase : Phases, build : 0..MaxBuilds, input : Inputs, started : -1..MaxTime, prior : -1..MaxTime]] /\ now \in 0..MaxTime /\ lastStart \in [Deliveries -> -1..MaxTime] /\ covered \in [Deliveries -> 0..MaxBuilds] Init == /\ latest = [delivery \in Deliveries |-> 0] /\ policy = InitialPolicy /\ request = [delivery \in Deliveries |-> EmptyRequest] /\ attempts = [key \in Keys |-> EmptyAttempt(key[1])] /\ now = 0 /\ lastStart = [delivery \in Deliveries |-> -1] /\ covered = [delivery \in Deliveries |-> 0] BuildSuccess(delivery) == /\ latest[delivery] < MaxBuilds /\ latest' = [latest EXCEPT ![delivery] = @ + 1] /\ UNCHANGED <> ChangePolicy(delivery, value) == /\ policy[delivery] /= value /\ policy' = [policy EXCEPT ![delivery] = value] /\ UNCHANGED <> Annotate(delivery, build, nonce) == /\ build <= latest[delivery] /\ request' = [request EXCEPT ![delivery] = [build |-> build, nonce |-> nonce]] /\ UNCHANGED <> Accept(delivery) == LET value == request[delivery] key == <> IN /\ value.build > 0 /\ value.nonce > 0 /\ attempts[key].phase = "Absent" /\ attempts' = [attempts EXCEPT ![key] = [EmptyAttempt(delivery) EXCEPT !.phase = "Pending", !.build = value.build, !.input = Input(delivery, value.build)]] /\ UNCHANGED <> ReserveManual(key) == /\ attempts[key].phase = "Pending" /\ Active = {} /\ attempts' = [attempts EXCEPT ![key].phase = "Reserved"] /\ UNCHANGED <> ReserveAuto(delivery) == LET build == latest[delivery] key == <> IN /\ policy[delivery] = "OnBuild" /\ build > covered[delivery] /\ Due(delivery) /\ Active = {} /\ attempts[key].phase = "Absent" /\ attempts' = [attempts EXCEPT ![key] = [EmptyAttempt(delivery) EXCEPT !.phase = "Reserved", !.build = build, !.input = Input(delivery, build)]] /\ UNCHANGED <> Submit(key) == /\ attempts[key].phase = "Reserved" /\ attempts' = [attempts EXCEPT ![key].phase = "Queued"] /\ UNCHANGED <> Start(key) == LET delivery == key[1] build == attempts[key].build IN /\ attempts[key].phase = "Queued" /\ attempts' = [attempts EXCEPT ![key].phase = "Running", ![key].started = now, ![key].prior = lastStart[delivery]] /\ lastStart' = [lastStart EXCEPT ![delivery] = now] /\ covered' = [covered EXCEPT ![delivery] = IF build > @ THEN build ELSE @] /\ UNCHANGED <> Complete(key) == /\ attempts[key].phase \in {"Queued", "Running"} /\ attempts' = [attempts EXCEPT ![key].phase = "Terminal"] /\ UNCHANGED <> Tick == /\ now < MaxTime /\ now' = now + 1 /\ UNCHANGED <> Next == \/ \E delivery \in Deliveries : BuildSuccess(delivery) \/ \E delivery \in Deliveries, value \in Policies : ChangePolicy(delivery, value) \/ \E delivery \in Deliveries, build \in 1..MaxBuilds, nonce \in 1..MaxRequests : Annotate(delivery, build, nonce) \/ \E delivery \in Deliveries : Accept(delivery) \/ \E key \in Keys : ReserveManual(key) \/ \E delivery \in Deliveries : ReserveAuto(delivery) \/ \E key \in Keys : Submit(key) \/ \E key \in Keys : Start(key) \/ \E key \in Keys : Complete(key) \/ Tick Spec == Init /\ [][Next]_vars FairSpec == /\ Spec /\ \A key \in Keys : /\ SF_vars(ReserveManual(key)) /\ WF_vars(Submit(key)) /\ WF_vars(Start(key)) /\ WF_vars(Complete(key)) /\ \A delivery \in Deliveries : /\ WF_vars(Accept(delivery)) /\ SF_vars(ReserveAuto(delivery)) /\ WF_vars(Tick) ManualProgress == \A key \in Keys : (attempts[key].phase = "Pending") ~> (attempts[key].phase = "Terminal") Eligible(delivery, build) == /\ policy[delivery] = "OnBuild" /\ latest[delivery] = build /\ build > covered[delivery] /\ Due(delivery) AutoProgress == \A delivery \in Deliveries, build \in 1..MaxBuilds : Eligible(delivery, build) ~> (attempts[<>].phase /= "Absent" \/ ~Eligible(delivery, build)) GlobalCap == Cardinality(Active) <= 1 SuccessfulInputs == \A key \in Keys : attempts[key].phase /= "Absent" => /\ attempts[key].build \in 1..latest[key[1]] /\ attempts[key].input = Input(key[1], attempts[key].build) AutoInterval == \A key \in Keys : /\ key[2] = "Auto" /\ attempts[key].started >= 0 /\ attempts[key].prior >= 0 => attempts[key].started >= attempts[key].prior + Interval[key[1]] InputsStable == [][\A key \in Keys : attempts[key].phase /= "Absent" => UNCHANGED <>]_vars AutoSelection == [][\A key \in Keys : /\ key[2] = "Auto" /\ attempts[key].phase = "Absent" /\ attempts'[key].phase = "Reserved" => /\ policy[key[1]] = "OnBuild" /\ attempts'[key].build = latest[key[1]] /\ latest[key[1]] > covered[key[1]] /\ Due(key[1])]_vars NoRestart == [][\A key \in Keys : attempts[key].phase = "Terminal" => attempts'[key].phase = "Terminal"]_vars OnBuildPolicy == [delivery \in Deliveries |-> "OnBuild"] TwoTicks == [delivery \in Deliveries |-> 2] ====