ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c
6 changed files with 145336 additions and 0 deletions

View file

@ -0,0 +1,16 @@
SPECIFICATION LiveSpec
CONSTANTS
Deliveries = {delivery1}
Templates = {template1}
Identities = {identity1, identity2}
Revisions = {revision1, revision2}
Others = {other1}
InitialTemplateRef <- SharedTemplateRef
InitialMode <- ContinuousMode
INVARIANTS TypeOK NoConcurrentRuns TemplateAvailable PreparedAvailable CollectedSaved
PROPERTIES TemplateStable AdmissionReady PublishDrained PublishLatest RecordOnce PublishProgress DebounceProgress DrainProgress AttemptProgress ReleaseProgress RecordProgress DeliveryProgress
CHECK_DEADLOCK FALSE
CONSTRAINT LiveDomain

View file

@ -0,0 +1,426 @@
---- MODULE ProjectDelivery ----
EXTENDS FiniteSets, Naturals
CONSTANTS Deliveries, Templates, Identities, Revisions, Others, InitialTemplateRef, InitialMode
Continuous == "Continuous"
Point == "Point"
Modes == {Continuous, Point}
Absent == "Absent"
Queued == "Queued"
Running == "Running"
Terminal == "Terminal"
Collected == "Collected"
\* Retry successors use fresh identities without consuming a source identity again.
Attempts == Deliveries \X Modes \X Identities
Slots == Attempts \cup Others
Phases == {Absent, Queued, Running, Terminal, Collected}
VARIABLES mode, templateRef, eligible, persisted, recorded, phase, attemptTemplate
VARIABLES wantedRev, templateRev, settled, attemptRev, availableRev
VARIABLES prepared, publishRev
vars == <<mode, templateRef, eligible, persisted, recorded, phase, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
ASSUME /\ Deliveries /= {}
/\ Templates /= {}
/\ Identities /= {}
/\ Revisions /= {}
/\ Attempts \cap Others = {}
/\ InitialTemplateRef \in [Deliveries -> Templates]
/\ InitialMode \in [Deliveries -> Modes]
DeliveryOf(attempt) == attempt[1]
ModeOf(attempt) == attempt[2]
RetryPairs ==
{pair \in Attempts \X Attempts :
/\ pair[1] /= pair[2]
/\ DeliveryOf(pair[1]) = DeliveryOf(pair[2])
/\ ModeOf(pair[1]) = ModeOf(pair[2])}
SelectedAttempt(delivery) ==
<<delivery, mode[delivery], eligible[delivery][mode[delivery]]>>
RunningFor(template) ==
{attempt \in Slots :
/\ phase[attempt] = Running
/\ attemptTemplate[attempt] = {template}}
ActiveFor(template) ==
{attempt \in Slots :
/\ phase[attempt] \in {Queued, Running}
/\ attemptTemplate[attempt] = {template}}
TypeOK ==
/\ mode \in [Deliveries -> Modes]
/\ templateRef \in [Deliveries -> Templates]
/\ eligible \in [Deliveries -> [Modes -> Identities]]
/\ persisted \in SUBSET Attempts
/\ recorded \in [Deliveries -> [Modes -> SUBSET Attempts]]
/\ \A delivery \in Deliveries, sourceMode \in Modes :
/\ Cardinality(recorded[delivery][sourceMode]) <= 1
/\ \A attempt \in recorded[delivery][sourceMode] :
/\ DeliveryOf(attempt) = delivery
/\ ModeOf(attempt) = sourceMode
/\ attempt \in persisted
/\ phase \in [Slots -> Phases]
/\ availableRev \in [Templates -> SUBSET Revisions]
/\ wantedRev \in [Templates -> Revisions]
/\ templateRev \in [Templates -> Revisions]
/\ settled \in [Templates -> BOOLEAN]
/\ attemptRev \in [Slots -> SUBSET Revisions]
/\ attemptTemplate \in [Slots -> SUBSET Templates]
/\ prepared \in [Slots -> SUBSET (Templates \X Revisions)]
/\ publishRev \in [Templates -> SUBSET Revisions]
/\ \A template \in Templates : Cardinality(publishRev[template]) <= 1
/\ \A attempt \in Slots :
/\ Cardinality(prepared[attempt]) <= 1
/\ (phase[attempt] = Absent) = (attemptTemplate[attempt] = {})
/\ phase[attempt] /= Absent => Cardinality(attemptTemplate[attempt]) = 1
/\ (phase[attempt] = Absent) = (attemptRev[attempt] = {})
/\ phase[attempt] /= Absent => Cardinality(attemptRev[attempt]) = 1
Init ==
/\ mode = InitialMode
/\ templateRef = InitialTemplateRef
/\ eligible =
[delivery \in Deliveries |->
[sourceMode \in Modes |-> CHOOSE identity \in Identities : TRUE]]
/\ persisted = {}
/\ recorded =
[delivery \in Deliveries |-> [sourceMode \in Modes |-> {}]]
/\ phase = [attempt \in Slots |-> Absent]
/\ attemptTemplate = [attempt \in Slots |-> {}]
/\ templateRev = [template \in Templates |-> CHOOSE revision \in Revisions : TRUE]
/\ availableRev = [template \in Templates |-> {templateRev[template]}]
/\ wantedRev = templateRev
/\ settled = [template \in Templates |-> TRUE]
/\ attemptRev = [attempt \in Slots |-> {}]
/\ prepared = [attempt \in Slots |-> {}]
/\ publishRev = [template \in Templates |-> {}]
ChangeEligibility(delivery, sourceMode, identity) ==
/\ identity /= eligible[delivery][sourceMode]
/\ eligible' =
[eligible EXCEPT ![delivery][sourceMode] = identity]
/\ UNCHANGED <<mode, templateRef, persisted, recorded, phase, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
ChangeMode(delivery, sourceMode) ==
/\ sourceMode /= mode[delivery]
/\ mode' = [mode EXCEPT ![delivery] = sourceMode]
/\ UNCHANGED <<templateRef, eligible, persisted, recorded, phase, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
ChangeTemplateRef(delivery, template) ==
/\ template /= templateRef[delivery]
/\ templateRef' = [templateRef EXCEPT ![delivery] = template]
/\ UNCHANGED <<mode, eligible, persisted, recorded, phase, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
CreationEnabled(delivery) ==
LET attempt == SelectedAttempt(delivery)
IN /\ recorded[delivery][mode[delivery]] = {}
/\ attempt \notin persisted
/\ phase[attempt] \in {Absent, Collected}
/\ prepared[attempt] = {}
/\ wantedRev[templateRef[delivery]] = templateRev[templateRef[delivery]]
/\ settled[templateRef[delivery]]
Prepare(delivery) ==
LET attempt == SelectedAttempt(delivery)
template == templateRef[delivery]
IN /\ CreationEnabled(delivery)
/\ prepared' =
[prepared EXCEPT ![attempt] = {<<template, templateRev[template]>>}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, phase,
attemptTemplate, wantedRev, templateRev, settled,
attemptRev, availableRev, publishRev>>
PrepareRetry(delivery, prior, attempt) ==
LET template == templateRef[delivery]
IN /\ prior \in Attempts
/\ attempt \in Attempts
/\ DeliveryOf(prior) = delivery
/\ DeliveryOf(attempt) = delivery
/\ ModeOf(prior) = ModeOf(attempt)
/\ prior \in persisted
/\ phase[prior] \in {Terminal, Collected}
/\ attempt \notin persisted
/\ phase[attempt] \in {Absent, Collected}
/\ prepared[attempt] = {}
/\ \A sourceMode \in Modes : recorded[delivery][sourceMode] = {}
/\ wantedRev[template] = templateRev[template]
/\ settled[template]
/\ prepared' =
[prepared EXCEPT ![attempt] = {<<template, templateRev[template]>>}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, phase,
attemptTemplate, wantedRev, templateRev, settled,
attemptRev, availableRev, publishRev>>
\* An API call can finish after a later observation without rereading admission gates.
Create(attempt) ==
LET binding == CHOOSE value \in prepared[attempt] : TRUE
IN /\ attempt \in Slots
/\ prepared[attempt] /= {}
/\ IF attempt \in Attempts
THEN /\ phase[attempt] \in {Absent, Collected}
/\ attempt \notin persisted
ELSE phase[attempt] \in {Absent, Terminal, Collected}
/\ phase' = [phase EXCEPT ![attempt] = Queued]
/\ attemptTemplate' = [attemptTemplate EXCEPT ![attempt] = {binding[1]}]
/\ attemptRev' = [attemptRev EXCEPT ![attempt] = {binding[2]}]
/\ prepared' = [prepared EXCEPT ![attempt] = {}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded,
wantedRev, templateRev, settled, availableRev, publishRev>>
Start(attempt) ==
/\ attempt \in Slots
/\ phase[attempt] = Queued
/\ \A template \in attemptTemplate[attempt] : RunningFor(template) = {}
/\ phase' = [phase EXCEPT ![attempt] = Running]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
Persist(delivery, attempt) ==
/\ attempt \in Attempts
/\ DeliveryOf(attempt) = delivery
/\ attempt \notin persisted
/\ phase[attempt] /= Absent
/\ recorded[delivery][ModeOf(attempt)] = {}
/\ persisted' = persisted \cup {attempt}
/\ recorded' =
[recorded EXCEPT ![delivery][ModeOf(attempt)] = {attempt}]
/\ UNCHANGED <<mode, templateRef, eligible, phase, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
Complete(attempt) ==
/\ attempt \in Slots
/\ phase[attempt] = Running
/\ phase' = [phase EXCEPT ![attempt] = Terminal]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
Reject(attempt) ==
/\ attempt \in Slots
/\ phase[attempt] = Queued
/\ phase' = [phase EXCEPT ![attempt] = Terminal]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
Release(delivery, sourceMode) ==
/\ recorded[delivery][sourceMode] /= {}
/\ \A attempt \in recorded[delivery][sourceMode] :
phase[attempt] \in {Terminal, Collected}
/\ recorded' = [recorded EXCEPT ![delivery][sourceMode] = {}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, phase, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
Collect(attempt) ==
/\ attempt \in Attempts
/\ phase[attempt] = Terminal
/\ attempt \in persisted
/\ phase' = [phase EXCEPT ![attempt] = Collected]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, attemptTemplate,
wantedRev, templateRev, settled, attemptRev, availableRev, prepared, publishRev>>
\* Other slots represent fresh workflow executions and can be reused after completion.
PrepareOther(attempt, template) ==
/\ attempt \in Others
/\ phase[attempt] \in {Absent, Terminal, Collected}
/\ prepared[attempt] = {}
/\ wantedRev[template] = templateRev[template]
/\ settled[template]
/\ prepared' = [prepared EXCEPT ![attempt] = {<<template, templateRev[template]>>}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, phase,
attemptTemplate, wantedRev, templateRev, settled,
attemptRev, availableRev, publishRev>>
\* Requests record observed bundles; writes preceding observation leave wantedRev unchanged.
RequestTemplate(template, revision) ==
/\ revision /= wantedRev[template]
/\ wantedRev' = [wantedRev EXCEPT ![template] = revision]
/\ settled' = [settled EXCEPT ![template] = FALSE]
/\ publishRev' = [publishRev EXCEPT ![template] = {}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, phase,
attemptTemplate, templateRev, attemptRev, availableRev, prepared>>
\* Settle abstracts expiration of the configured quiet interval after the latest change.
Settle(template) ==
/\ ~settled[template]
/\ settled' = [settled EXCEPT ![template] = TRUE]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, phase,
attemptTemplate, wantedRev, templateRev, attemptRev, availableRev, prepared, publishRev>>
PreparePublish(template) ==
/\ wantedRev[template] /= templateRev[template]
/\ settled[template]
/\ ActiveFor(template) = {}
/\ publishRev[template] = {}
/\ publishRev' = [publishRev EXCEPT ![template] = {wantedRev[template]}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, phase,
attemptTemplate, wantedRev, templateRev, settled,
attemptRev, availableRev, prepared>>
Publish(template) ==
/\ wantedRev[template] /= templateRev[template]
/\ settled[template]
/\ publishRev[template] = {wantedRev[template]}
/\ templateRev' = [templateRev EXCEPT ![template] = wantedRev[template]]
/\ availableRev' =
[availableRev EXCEPT ![template] = @ \cup {wantedRev[template]}]
/\ publishRev' = [publishRev EXCEPT ![template] = {}]
/\ UNCHANGED <<mode, templateRef, eligible, persisted, recorded, phase,
attemptTemplate, wantedRev, settled, attemptRev, prepared>>
Next ==
\/ \E template \in Templates, revision \in Revisions :
RequestTemplate(template, revision)
\/ \E template \in Templates : Settle(template)
\/ \E template \in Templates : PreparePublish(template)
\/ \E template \in Templates : Publish(template)
\/ \E delivery \in Deliveries, sourceMode \in Modes, identity \in Identities :
ChangeEligibility(delivery, sourceMode, identity)
\/ \E delivery \in Deliveries, sourceMode \in Modes :
ChangeMode(delivery, sourceMode)
\/ \E delivery \in Deliveries, template \in Templates :
ChangeTemplateRef(delivery, template)
\/ \E delivery \in Deliveries : Prepare(delivery)
\/ \E pair \in RetryPairs :
PrepareRetry(DeliveryOf(pair[1]), pair[1], pair[2])
\/ \E attempt \in Others, template \in Templates : PrepareOther(attempt, template)
\/ \E attempt \in Slots : Create(attempt)
\/ \E attempt \in Slots : Start(attempt)
\/ \E delivery \in Deliveries, attempt \in Attempts : Persist(delivery, attempt)
\/ \E attempt \in Slots : Complete(attempt)
\/ \E attempt \in Slots : Reject(attempt)
\/ \E delivery \in Deliveries, sourceMode \in Modes : Release(delivery, sourceMode)
\/ \E attempt \in Attempts : Collect(attempt)
Spec == Init /\ [][Next]_vars
\* Argo Secure mode also reads the captured specification during artifact cleanup.
TemplateAvailable ==
\A attempt \in Slots : phase[attempt] \notin {Absent, Collected} =>
\A template \in attemptTemplate[attempt] :
attemptRev[attempt] \subseteq availableRev[template]
CollectedSaved ==
\A attempt \in Attempts : phase[attempt] = Collected => attempt \in persisted
NoConcurrentRuns ==
\A template \in Templates : Cardinality(RunningFor(template)) <= 1
TemplateStable ==
[][\A attempt \in Slots : phase[attempt] \in {Queued, Running} =>
UNCHANGED <<attemptTemplate[attempt], attemptRev[attempt]>>]_vars
PreparedAvailable ==
\A attempt \in Slots : \A binding \in prepared[attempt] :
binding[2] \in availableRev[binding[1]]
AdmissionReady ==
[][\A attempt \in Slots : \A binding \in prepared'[attempt] \ prepared[attempt] :
/\ settled[binding[1]]
/\ binding[2] = wantedRev[binding[1]]
/\ binding[2] = templateRev[binding[1]]]_vars
PublishDrained ==
[][\A template \in Templates : publishRev'[template] \ publishRev[template] /= {} =>
/\ settled[template]
/\ ActiveFor(template) = {}
/\ publishRev'[template] = {wantedRev[template]}]_vars
PublishLatest ==
[][\A template \in Templates : templateRev'[template] /= templateRev[template] =>
/\ settled[template]
/\ publishRev[template] = {wantedRev[template]}
/\ templateRev'[template] = wantedRev[template]]_vars
ProgressFair ==
/\ Spec
/\ \A template \in Templates :
/\ WF_vars(Settle(template))
/\ WF_vars(PreparePublish(template))
/\ WF_vars(Publish(template))
/\ \A attempt \in Slots :
/\ WF_vars(Create(attempt))
/\ WF_vars(Complete(attempt))
FairSpec ==
/\ ProgressFair
/\ \A attempt \in Slots : WF_vars(Start(attempt))
PublishProgress ==
\A template \in Templates, revision \in Revisions :
(<>[](wantedRev[template] = revision)) =>
<>[](templateRev[template] = revision)
\* Strong fairness abstracts Argo's equal-priority mutex queue, ordered by creation time.
LiveSpec ==
/\ ProgressFair
/\ \A attempt \in Slots : SF_vars(Start(attempt))
/\ \A delivery \in Deliveries : WF_vars(Prepare(delivery))
/\ \A delivery \in Deliveries, attempt \in Attempts :
WF_vars(Persist(delivery, attempt))
/\ \A delivery \in Deliveries, sourceMode \in Modes :
WF_vars(Release(delivery, sourceMode))
DebounceProgress ==
\A template \in Templates, revision \in Revisions :
(<>[](wantedRev[template] = revision)) => <>[]settled[template]
DrainProgress ==
\A template \in Templates :
(wantedRev[template] /= templateRev[template]) ~>
(ActiveFor(template) = {} \/ wantedRev[template] = templateRev[template])
AttemptProgress ==
\A attempt \in Slots :
(phase[attempt] \in {Queued, Running}) ~>
(phase[attempt] \in {Terminal, Collected})
ReleaseProgress ==
\A delivery \in Deliveries, sourceMode \in Modes :
(/\ recorded[delivery][sourceMode] /= {}
/\ \A attempt \in recorded[delivery][sourceMode] :
phase[attempt] \in {Terminal, Collected}) ~>
(recorded[delivery][sourceMode] = {})
RecordOnce ==
[][\A delivery \in Deliveries, sourceMode \in Modes :
(recorded'[delivery][sourceMode] \ recorded[delivery][sourceMode])
\subseteq (persisted' \ persisted)]_vars
RecordProgress ==
\A attempt \in Attempts :
(phase[attempt] /= Absent) ~> (attempt \in persisted)
SelectedFor(delivery, attempt, template, revision) ==
/\ SelectedAttempt(delivery) = attempt
/\ templateRef[delivery] = template
/\ wantedRev[template] = revision
DeliveryProgress ==
\A delivery \in Deliveries, attempt \in Attempts,
template \in Templates, revision \in Revisions :
(<>[]SelectedFor(delivery, attempt, template, revision)) =>
<>(attempt \in persisted /\ phase[attempt] \in {Terminal, Collected})
ContinuousOnly == \A delivery \in Deliveries : mode[delivery] = Continuous
LiveDomain ==
/\ ContinuousOnly
/\ \A delivery \in Deliveries :
eligible[delivery][Point] = CHOOSE identity \in Identities : TRUE
SharedTemplateRef ==
[delivery \in Deliveries |-> CHOOSE template \in Templates : TRUE]
ContinuousMode == [delivery \in Deliveries |-> Continuous]
====

View file

@ -0,0 +1,14 @@
SPECIFICATION FairSpec
CONSTANTS
Deliveries = {delivery1, delivery2}
MaxBuilds = 1
MaxRequests = 1
MaxTime = 0
Interval <- TwoTicks
InitialPolicy <- OnBuildPolicy
INVARIANTS TypeOK GlobalCap SuccessfulInputs AutoInterval
PROPERTIES InputsStable AutoSelection NoRestart ManualProgress AutoProgress
CHECK_DEADLOCK FALSE

View file

@ -0,0 +1,14 @@
SPECIFICATION FairSpec
CONSTANTS
Deliveries = {delivery1}
MaxBuilds = 2
MaxRequests = 2
MaxTime = 2
Interval <- TwoTicks
InitialPolicy <- OnBuildPolicy
INVARIANTS TypeOK GlobalCap SuccessfulInputs AutoInterval
PROPERTIES InputsStable AutoSelection NoRestart ManualProgress AutoProgress
CHECK_DEADLOCK FALSE

View file

@ -0,0 +1,205 @@
---- 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 == <<latest, policy, request, attempts, now, lastStart, covered>>
ASSUME /\ Deliveries /= {}
/\ MaxBuilds \in Nat \ {0}
/\ MaxRequests \in Nat \ {0}
/\ MaxTime \in Nat
/\ Interval \in [Deliveries -> Nat]
/\ InitialPolicy \in [Deliveries -> Policies]
Input(delivery, build) == <<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 <<policy, request, attempts, now, lastStart, covered>>
ChangePolicy(delivery, value) ==
/\ policy[delivery] /= value
/\ policy' = [policy EXCEPT ![delivery] = value]
/\ UNCHANGED <<latest, request, attempts, now, lastStart, covered>>
Annotate(delivery, build, nonce) ==
/\ build <= latest[delivery]
/\ request' = [request EXCEPT ![delivery] = [build |-> build, nonce |-> nonce]]
/\ UNCHANGED <<latest, policy, attempts, now, lastStart, covered>>
Accept(delivery) ==
LET value == request[delivery]
key == <<delivery, "Manual", value.nonce>>
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 <<latest, policy, request, now, lastStart, covered>>
ReserveManual(key) ==
/\ attempts[key].phase = "Pending"
/\ Active = {}
/\ attempts' = [attempts EXCEPT ![key].phase = "Reserved"]
/\ UNCHANGED <<latest, policy, request, now, lastStart, covered>>
ReserveAuto(delivery) ==
LET build == latest[delivery]
key == <<delivery, "Auto", build>>
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 <<latest, policy, request, now, lastStart, covered>>
Submit(key) ==
/\ attempts[key].phase = "Reserved"
/\ attempts' = [attempts EXCEPT ![key].phase = "Queued"]
/\ UNCHANGED <<latest, policy, request, now, lastStart, covered>>
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 <<latest, policy, request, now>>
Complete(key) ==
/\ attempts[key].phase \in {"Queued", "Running"}
/\ attempts' = [attempts EXCEPT ![key].phase = "Terminal"]
/\ UNCHANGED <<latest, policy, request, now, lastStart, covered>>
Tick ==
/\ now < MaxTime
/\ now' = now + 1
/\ UNCHANGED <<latest, policy, request, attempts, lastStart, covered>>
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[<<delivery, "Auto", build>>].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 <<attempts[key].build, attempts[key].input>>]_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]
====

View file

@ -0,0 +1,15 @@
SPECIFICATION Spec
CONSTANTS
Deliveries = {delivery1, delivery2}
Templates = {template1}
Identities = {identity1}
Revisions = {revision1, revision2}
Others = {}
InitialTemplateRef <- SharedTemplateRef
InitialMode <- ContinuousMode
INVARIANTS TypeOK NoConcurrentRuns TemplateAvailable PreparedAvailable CollectedSaved
PROPERTIES TemplateStable AdmissionReady PublishDrained PublishLatest RecordOnce
CHECK_DEADLOCK FALSE