ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
6 changed files with 145336 additions and 0 deletions
16
docs/spec/project-delivery/Liveness.cfg
Normal file
16
docs/spec/project-delivery/Liveness.cfg
Normal 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
|
||||
426
docs/spec/project-delivery/ProjectDelivery.tla
Normal file
426
docs/spec/project-delivery/ProjectDelivery.tla
Normal 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]
|
||||
|
||||
====
|
||||
14
docs/spec/project-delivery/QualConcurrency.cfg
Normal file
14
docs/spec/project-delivery/QualConcurrency.cfg
Normal 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
|
||||
14
docs/spec/project-delivery/Qualification.cfg
Normal file
14
docs/spec/project-delivery/Qualification.cfg
Normal 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
|
||||
205
docs/spec/project-delivery/Qualification.tla
Normal file
205
docs/spec/project-delivery/Qualification.tla
Normal 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]
|
||||
|
||||
====
|
||||
15
docs/spec/project-delivery/Safety.cfg
Normal file
15
docs/spec/project-delivery/Safety.cfg
Normal 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
|
||||
Loading…
Reference in a new issue