1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import (
6 "errors"
7 "fmt"
8 "regexp"
9 "slices"
10 "strconv"
11 "strings"
12 "time"
13)
14
15// CIStatus is where a run, a job or a check is in its life (GitHub's status).
16type CIStatus string
17
18// Statuses.
19const (
20 CIQueued CIStatus = "queued"
21 CIInProgress CIStatus = "in_progress"
22 CICompleted CIStatus = "completed"
23 // CIPending is a job or a check held back by a concurrency group: another
24 // entry of the group runs, or a newer one is waiting. Never a run's status.
25 CIPending CIStatus = "pending"
26)
27
28// CIConclusion is how a run, a job, a step or a check ended, with GitHub's
29// values. It is empty until the status is completed.
30type CIConclusion string
31
32// Conclusions.
33const (
34 CINoConclusion CIConclusion = ""
35 CISuccess CIConclusion = "success"
36 CIFailure CIConclusion = "failure"
37 CICancelled CIConclusion = "cancelled"
38 CISkipped CIConclusion = "skipped"
39 CITimedOut CIConclusion = "timed_out"
40 CINeutral CIConclusion = "neutral"
41 CIActionRequired CIConclusion = "action_required"
42)
43
44// PassesMerge reports whether a check that ended this way lets a merge go
45// through under FailingChecksBlock: success, skipped and neutral do.
46func (c CIConclusion) PassesMerge() bool {
47 return c == CISuccess || c == CISkipped || c == CINeutral
48}
49
50// IsFailure reports whether the conclusion is a failure or a timeout.
51func (c CIConclusion) IsFailure() bool { return c == CIFailure || c == CITimedOut }
52
53// CIStepState is where a step is in its life.
54type CIStepState string
55
56// Step states.
57const (
58 CIStepPending CIStepState = "pending"
59 CIStepInProgress CIStepState = "in_progress"
60 CIStepCompleted CIStepState = "completed"
61)
62
63// CIJobPhase is where a job is for the server, finer than the status owners
64// see.
65type CIJobPhase string
66
67// Job phases.
68const (
69 // CIPhaseWaiting: waits for the jobs it needs or for a max-parallel slot.
70 CIPhaseWaiting CIJobPhase = "waiting"
71 // CIPhaseReady: planned, waits for a runner.
72 CIPhaseReady CIJobPhase = "ready"
73 // CIPhaseAssigned: given to a runner that has not acknowledged it.
74 CIPhaseAssigned CIJobPhase = "assigned"
75 // CIPhaseRunning: acknowledged by its runner.
76 CIPhaseRunning CIJobPhase = "running"
77 // CIPhaseEnded: over.
78 CIPhaseEnded CIJobPhase = "ended"
79)
80
81// CIIsolation is how a runner isolates jobs.
82type CIIsolation string
83
84// Isolations.
85const (
86 CIIsolationNone CIIsolation = ""
87 CIIsolationVM CIIsolation = "vm"
88 CIIsolationContainerSysbox CIIsolation = "container-sysbox"
89 CIIsolationContainerPrivileged CIIsolation = "container-privileged"
90)
91
92// CICancelReason says why a job is asked to stop.
93type CICancelReason string
94
95// Cancel reasons.
96const (
97 CICancelNone CICancelReason = ""
98 CICancelUser CICancelReason = "user"
99 CICancelFailFast CICancelReason = "fail-fast"
100 CICancelTimeout CICancelReason = "timeout"
101)
102
103// Limits and durations of the CI protocol (runner.proto).
104const (
105 // CIMaxLogBytes is the size of a job log, as stored (512 MiB).
106 CIMaxLogBytes = 512 << 20
107 // CIMaxOutputBytes bounds one output and all outputs together (1 MiB).
108 CIMaxOutputBytes = 1 << 20
109 // CIMaxLogLines and CIMaxLogChunkBytes bound one AppendLog request.
110 CIMaxLogLines = 1000
111 CIMaxLogChunkBytes = 64 << 10
112 // CIMaxSteps bounds the steps of a job.
113 CIMaxSteps = 1000
114 // CIMaxStepName bounds a step name, in characters.
115 CIMaxStepName = 1024
116 // CIMaxLabels and CIMaxLabelLen bound the labels of a runner.
117 CIMaxLabels = 64
118 CIMaxLabelLen = 64
119 // CIDefaultTimeoutMinutes is the timeout of a job without timeout-minutes.
120 CIDefaultTimeoutMinutes = 360
121 // CIMaxAssignCancels is how many times in a row an assignment may be
122 // cancelled before the job fails.
123 CIMaxAssignCancels = 3
124 // CIRetentionDays is the retention of runs, jobs, checks and logs.
125 CIRetentionDays = 90
126)
127
128// Durations of the CI protocol.
129const (
130 // CIAckTimeout is how long a runner has to send its first heartbeat.
131 CIAckTimeout = 30 * time.Second
132 // CIHeartbeatDeadline is how long a running job may go without heartbeat.
133 CIHeartbeatDeadline = 2 * time.Minute
134 // CICancelGrace is how long a runner has to conclude after a cancel request.
135 CICancelGrace = 60 * time.Second
136 // CITimeoutSlack is added to timeout-minutes by the server.
137 CITimeoutSlack = 60 * time.Second
138 // CIQueueTimeout is how long a job waits for a runner.
139 CIQueueTimeout = 24 * time.Hour
140 // CITokenGrace is how long a job token is recognised after the job ended.
141 CITokenGrace = 24 * time.Hour
142)
143
144// Messages of jobs ended by the server, GitHub's where it has them.
145const (
146 // CIQueueTimeoutMessage is GitHub's message for a job that no runner took.
147 CIQueueTimeoutMessage = "The job was not acquired by Runner of type self-hosted even after multiple attempts"
148 // CIShutdownMessage is GitHub's message for a runner that stops.
149 CIShutdownMessage = "The runner has received a shutdown signal. This can happen when the runner service is stopped, or a manually started runner is canceled."
150 // CILogLimitMessage ends a log that reached its limit.
151 CILogLimitMessage = "The log of this job reached the limit of 512 MiB; the job is stopped."
152)
153
154// CILostMessage is GitHub's message for a runner that stopped sending heartbeats.
155func CILostMessage(runner string) string {
156 return "The self-hosted runner: " + runner + " lost communication with the server. Verify the machine is running and has a healthy network connection. Anything in your workflow that terminates the runner process, starves it for CPU/Memory, or blocks its network access can cause this error."
157}
158
159// CIRun is one execution of one workflow file for one event.
160type CIRun struct {
161 Repo RepoName
162 ID int64
163 Number int64
164 Attempt int32
165 WorkflowPath string
166 WorkflowName string
167 // Name is GitHub's name of the run: the evaluated run-name when the
168 // workflow has one, else WorkflowName. WorkflowName stays the name of the
169 // workflow (github.workflow, the checks).
170 Name string
171 Title string
172 Event string
173 Ref string
174 Sha string
175 // HeadSha is the head of the merge request of a pull_request run, whose Sha
176 // is the test merge commit github.sha shows; empty for the other events. The
177 // checks of the run attach to it (CheckSha).
178 HeadSha string
179 Actor string
180 Status CIStatus
181 Conclusion CIConclusion
182 Error string
183 CreatedAt time.Time
184 StartedAt time.Time
185 CompletedAt time.Time
186 CancelRequested bool
187 CancelRequestedAt time.Time
188 // Trusted is set when the actor of the run is the owner (TrustedActor):
189 // only then do its jobs receive secrets and a write permission. A re-run
190 // keeps or lowers it, never raises it (RerunTrusted). False for a run
191 // stored before the field existed: the safe reading.
192 Trusted bool
193 // Concurrency is the workflow-level concurrency of the latest attempt.
194 Concurrency CIConcurrency
195 // Version is the version of the stored record, which grows at every
196 // change and never goes back.
197 Version uint64
198}
199
200// CheckSha is the commit the checks of the run attach to: the head of the merge
201// request for a pull_request run (as on GitHub, where the checks of the test
202// merge show on the head), the commit of the run otherwise.
203func (r CIRun) CheckSha() string {
204 if r.HeadSha != "" {
205 return r.HeadSha
206 }
207 return r.Sha
208}
209
210// CIRunSource is what a run was planned from: the workflow file at the sha of
211// the run, and the "github" context without the parts that depend on the job
212// and the attempt.
213type CIRunSource struct {
214 RunID int64
215 Repo RepoName
216 WorkflowYAML string
217 GithubJSON string
218 // Pusher is who pushed the commit: what the actions of other repositories
219 // of the server may be resolved on behalf of.
220 Pusher CIPusher
221}
222
223// CIStep is a step of a job with where its lines are in the job log.
224type CIStep struct {
225 Number int32
226 Name string
227 State CIStepState
228 Conclusion CIConclusion
229 // Outcome is how the step ended before continue-on-error was applied
230 // ("steps.<id>.outcome"). Empty for a step that was not completed by a
231 // runner that reports it: read it as Conclusion.
232 Outcome CIConclusion
233 StartedAt time.Time
234 CompletedAt time.Time
235 LogStart int64
236 // HasLines is set once a line of the step reached the log; LogStart is
237 // then the number of its first line. LogEnd is not stored: see StepRanges.
238 HasLines bool
239}
240
241// CIJob is one job instance (one matrix combination) in one attempt.
242type CIJob struct {
243 ID string
244 RunID int64
245 // Attempt is the attempt in which the job executed, RunAttempt the attempt
246 // of the run the record belongs to: they differ for a job kept by a re-run
247 // of the failed jobs.
248 Attempt int32
249 RunAttempt int32
250 Repo RepoName
251 Key string
252 Name string
253 Phase CIJobPhase
254 Conclusion CIConclusion
255 Error string
256 Labels []string
257 Needs []string
258 RunnerID string
259 RunnerName string
260 Isolation CIIsolation
261 Steps []CIStep
262
263 CreatedAt time.Time
264 StartedAt time.Time
265 CompletedAt time.Time
266
267 // Index is the position of the job in the workflow file, Combo the
268 // position of the instance in its matrix.
269 Index int
270 Combo int
271 // Planned is set once runs-on, the name and the contexts are evaluated.
272 // A job that is not planned has not been expanded yet.
273 Planned bool
274 MatrixJSON string
275 // ContextsJSON is the JSON object of contexts given to the runner, computed
276 // when the job is planned.
277 ContextsJSON string
278 // ComboTotal is the number of instances of the job's matrix.
279 ComboTotal int
280 // TimeoutMinutes is the timeout of the job, at least 1.
281 TimeoutMinutes int32
282 FailFast bool
283 // ContinueOnError is the job's continue-on-error, evaluated when the job is
284 // planned: its failure leaves the run, its siblings and the jobs that
285 // need it unaffected (needs.<job>.result is success). The conclusion of
286 // the job itself stays failure.
287 ContinueOnError bool
288 // Environment is the name of the environment of the job (its evaluated
289 // "environment:"), "" for none. Set when the job is planned; the secrets
290 // and variables of the environment are read when the job is assigned.
291 Environment string
292 // Permissions are the permissions of the job's repository token as the
293 // workflow and the job ask for them (ResolvePermissions), key to level;
294 // set when the job is planned. Empty: the default (a job planned before
295 // they were stored). The trust rule of the run is applied when the token
296 // is issued (TokenPermissions), not here.
297 Permissions map[string]string
298 // MaxParallel is the max-parallel of the strategy, 0 for no limit.
299 MaxParallel int
300 Outputs map[string]string
301 // Concurrency is the concurrency group of the job: its own, evaluated when
302 // the job is planned, else the one of its run (FromRun).
303 Concurrency CIConcurrency
304
305 // Scope is the id of the record of the job that called the reusable workflow
306 // this job belongs to, "" for a job of the run's own workflow. CallID is the
307 // id of the job in that called workflow (Key is "<caller key>/<call id>").
308 Scope string
309 CallID string
310 // Call is set on the record of a job that calls a reusable workflow: it never
311 // runs, it ends when the jobs of its scope have.
312 Call *CICall
313
314 // TokenHash is the hash of the job token of the current assignment; zero
315 // when there is none.
316 TokenHash TokenHash
317 TokenValidUntil time.Time
318 ReadyAt time.Time
319 AssignedAt time.Time
320 AckedAt time.Time
321 // AssignCancels counts the assignments cancelled in a row.
322 AssignCancels int
323
324 CancelReason CICancelReason
325 CancelMessage string
326 CancelRequestedAt time.Time
327
328 Version uint64
329}
330
331// CICall is what a job calling a reusable workflow resolved (CI-3, D7).
332type CICall struct {
333 Uses string
334 // Path is the file in its repository, Repository "owner/repo" for github.com
335 // or the name on this server, Ref what the workflow wrote ("" for a local
336 // call), Sha the commit the call resolved to.
337 Path, Repository, Ref, Sha string
338 YAML string
339 // InputsJSON is the inputs context of the called workflow.
340 InputsJSON string
341 // Secrets holds the names (upper case) of the secrets of the called workflow
342 // that the calling workflow's "secrets:" gives, with empty values: the
343 // expressions are read from the pinned calling workflow when a job is
344 // assigned. Inherit: "secrets: inherit".
345 Secrets map[string]string
346 Inherit bool
347 // Depth is 1 for a call from the run's own workflow.
348 Depth int
349 // Expanded: the jobs of the called workflow exist.
350 Expanded bool
351 // Concurrency is the workflow-level group of the called workflow. The call
352 // holds it, with the group of the calling job (CIJob.Concurrency), until it
353 // ends; its jobs wait for both.
354 Concurrency CIConcurrency
355 // Admitted: the call may start, the max-parallel of the calling job's
356 // strategy allows it.
357 Admitted bool
358 // SecretCtx is the JSON of the other contexts the Secrets expressions read.
359 SecretCtx string
360}
361
362// Status is the status owners see: a job is queued until its runner
363// acknowledged it.
364func (j CIJob) Status() CIStatus {
365 switch j.Phase {
366 case CIPhaseRunning:
367 return CIInProgress
368 case CIPhaseEnded:
369 return CICompleted
370 case CIPhaseWaiting:
371 if j.Concurrency.State == CIConcurrencyPending {
372 return CIPending
373 }
374 if j.Call != nil && j.Call.Concurrency.State == CIConcurrencyPending {
375 return CIPending
376 }
377 if j.Call != nil && j.Call.Expanded && j.Call.Admitted {
378 return CIInProgress
379 }
380 }
381 return CIQueued
382}
383
384// Active reports whether the job is given to a runner and not over.
385func (j CIJob) Active() bool { return j.Phase == CIPhaseAssigned || j.Phase == CIPhaseRunning }
386
387// Ended reports whether the job is over.
388func (j CIJob) Ended() bool { return j.Phase == CIPhaseEnded }
389
390// HasToken reports whether a job token is set.
391func (j CIJob) HasToken() bool { return j.TokenHash != TokenHash{} }
392
393// CICheck is the result of one job on one commit.
394type CICheck struct {
395 ID string
396 Repo RepoName
397 Sha string
398 Name string
399 Status CIStatus
400 Conclusion CIConclusion
401 RunID int64
402 JobID string
403 Attempt int32
404 WorkflowName string
405 CreatedAt time.Time
406 StartedAt time.Time
407 CompletedAt time.Time
408 Version uint64
409}
410
411// CIRunner is a registered CI runner. TokenHash is never sent to clients.
412type CIRunner struct {
413 ID string
414 Name string
415 Labels []string
416 Isolation CIIsolation
417 Version string
418 Arch string
419 OS string
420 RegisteredAt time.Time
421 TokenHash TokenHash
422}
423
424// RunnerTokenPrefix and JobTokenPrefix start the secrets of runners and jobs.
425const (
426 RunnerTokenPrefix = "ermiterun_"
427 JobTokenPrefix = "ermitejob_"
428 // CITokenRandomLen is the length of the base64url part of both.
429 CITokenRandomLen = 43
430)
431
432// IsRunnerToken and IsJobToken report whether s has the shape of the secret.
433func IsRunnerToken(s string) bool { return hasTokenShape(s, RunnerTokenPrefix) }
434func IsJobToken(s string) bool { return hasTokenShape(s, JobTokenPrefix) }
435
436func hasTokenShape(s, prefix string) bool {
437 rest, ok := strings.CutPrefix(s, prefix)
438 if !ok || len(rest) != CITokenRandomLen {
439 return false
440 }
441 for _, r := range rest {
442 switch {
443 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-', r == '_':
444 default:
445 return false
446 }
447 }
448 return true
449}
450
451// NewJobID names a job instance: "<run>-<index>-<combo>". It is the same in
452// every attempt of the run.
453func NewJobID(runID int64, index, combo int) string {
454 return fmt.Sprintf("%d-%d-%d", runID, index, combo)
455}
456
457// ParseJobID returns the run of a job id, or ok false when s is not one.
458func ParseJobID(s string) (runID int64, index, combo int, ok bool) {
459 parts := strings.Split(s, "-")
460 if len(parts) != 3 {
461 return 0, 0, 0, false
462 }
463 r, err1 := strconv.ParseInt(parts[0], 10, 64)
464 i, err2 := strconv.Atoi(parts[1])
465 c, err3 := strconv.Atoi(parts[2])
466 if err1 != nil || err2 != nil || err3 != nil || r < 1 || i < 0 || c < 0 || NewJobID(r, i, c) != s {
467 return 0, 0, 0, false
468 }
469 return r, i, c, true
470}
471
472var labelRE = regexp.MustCompile(`^[A-Za-z0-9._-]{1,64}$`)
473
474// ParseRunnerName validates a runner name.
475func ParseRunnerName(s string) error {
476 if !labelRE.MatchString(s) {
477 return fmt.Errorf("%w: a runner name has 1 to 64 letters, digits, '.', '_' or '-'", ErrInvalid)
478 }
479 return nil
480}
481
482// ParseRunnerLabels validates labels and returns them lowercased, without
483// duplicates, in order of first appearance.
484func ParseRunnerLabels(labels []string) ([]string, error) {
485 if len(labels) > CIMaxLabels {
486 return nil, fmt.Errorf("%w: at most %d labels", ErrInvalid, CIMaxLabels)
487 }
488 var out []string
489 for _, l := range labels {
490 if !labelRE.MatchString(l) {
491 return nil, fmt.Errorf("%w: label %q: 1 to %d letters, digits, '.', '_' or '-'", ErrInvalid, l, CIMaxLabelLen)
492 }
493 l = strings.ToLower(l)
494 if !slices.Contains(out, l) {
495 out = append(out, l)
496 }
497 }
498 return out, nil
499}
500
501// RunnerLabels returns the labels a runner matches: the ones it chose plus
502// the implicit "self-hosted", its os and its arch, lowercase and sorted.
503func RunnerLabels(chosen []string, os, arch string) []string {
504 out := slices.Clone(chosen)
505 for _, l := range []string{"self-hosted", strings.ToLower(os), strings.ToLower(arch)} {
506 if !slices.Contains(out, l) {
507 out = append(out, l)
508 }
509 }
510 slices.Sort(out)
511 return out
512}
513
514// LabelsMatch reports whether every wanted label is among have, ignoring case.
515func LabelsMatch(want, have []string) bool {
516 for _, w := range want {
517 if !slices.ContainsFunc(have, func(h string) bool { return strings.EqualFold(h, w) }) {
518 return false
519 }
520 }
521 return true
522}
523
524var outputNameRE = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_-]{0,254}$`)
525
526// CheckOutputs validates the outputs of a job: names, case-insensitive
527// uniqueness and the 1 MiB limits.
528func CheckOutputs(outputs map[string]string) error {
529 seen := make(map[string]bool, len(outputs))
530 total := 0
531 for k, v := range outputs {
532 if !outputNameRE.MatchString(k) {
533 return fmt.Errorf("%w: output name %q: 1 to 255 letters, digits, '_' or '-', starting with a letter or '_'", ErrInvalid, k)
534 }
535 lk := strings.ToLower(k)
536 if seen[lk] {
537 return fmt.Errorf("%w: output names differ only by case: %q", ErrInvalid, k)
538 }
539 seen[lk] = true
540 if len(v) > CIMaxOutputBytes {
541 return fmt.Errorf("%w: output %q is larger than 1 MiB", ErrInvalid, k)
542 }
543 total += len(k) + len(v)
544 }
545 if total > CIMaxOutputBytes {
546 return fmt.Errorf("%w: the outputs are larger than 1 MiB together", ErrInvalid)
547 }
548 return nil
549}
550
551// JobEndedError is returned for a call that needs a running job when the job
552// has ended. It matches ErrPreconditionFailed; the API adds the JobEnded
553// detail.
554type JobEndedError struct {
555 Conclusion CIConclusion
556 Message string
557}
558
559// Error implements error.
560func (e *JobEndedError) Error() string {
561 return fmt.Sprintf("%v: the job has ended (%s)", ErrPreconditionFailed, e.Conclusion)
562}
563
564// Is makes errors.Is(err, ErrPreconditionFailed) true.
565func (e *JobEndedError) Is(target error) bool { return target == ErrPreconditionFailed }
566
567// IsJobEnded returns the JobEndedError in err.
568func IsJobEnded(err error) (*JobEndedError, bool) {
569 var e *JobEndedError
570 if errors.As(err, &e) {
571 return e, true
572 }
573 return nil, false
574}
575
576// ErrOutOfRange means an offset is beyond what the server holds.
577var ErrOutOfRange = errors.New("out of range")
578
579// CILogLine is one line of a job log.
580type CILogLine struct {
581 Time time.Time
582 // Text has no line feed.
583 Text string
584 // Continued marks a piece of a line that was too long to send whole.
585 Continued bool
586}
587
588// StepRange is where the lines of a step are in the job log.
589type StepRange struct {
590 Start, End int64
591}
592
593// StepRanges returns the range of every step of the job for a log of lines
594// lines: a step with lines ends where the next step with lines begins (the
595// last ends at the end of the log); a step without lines is empty, placed
596// where the previous step ends.
597func StepRanges(steps []CIStep, lines int64) []StepRange {
598 out := make([]StepRange, len(steps))
599 var pos int64
600 for i, s := range steps {
601 if !s.HasLines {
602 out[i] = StepRange{Start: pos, End: pos}
603 continue
604 }
605 end := lines
606 for _, n := range steps[i+1:] {
607 if n.HasLines {
608 end = n.LogStart
609 break
610 }
611 }
612 out[i] = StepRange{Start: s.LogStart, End: max(end, s.LogStart)}
613 pos = out[i].End
614 }
615 return out
616}
617
618// StepOfLine returns the number of the step a log line belongs to, 0 when no
619// step has that line.
620func StepOfLine(steps []CIStep, line int64) int32 {
621 var found int32
622 for _, s := range steps {
623 if s.HasLines && s.LogStart <= line {
624 found = s.Number
625 }
626 }
627 return found
628}
629
630// RunUpdated is published when a CI run is created or changes. It carries the
631// visibility and the creation time of the repository, like PushEvent.
632type RunUpdated struct {
633 Run CIRun
634 At time.Time
635 Visibility Visibility
636 CreatedAt time.Time
637}
638
639// EventKind implements Event.
640func (RunUpdated) EventKind() string { return "ci.run_updated" }
641
642// JobUpdated is published when a CI job is created or changes (status,
643// conclusion, runner or steps; never per log line). Lines is the number of
644// lines of the job log at that time, for the end of the step in progress.
645type JobUpdated struct {
646 Job CIJob
647 Lines int64
648 At time.Time
649 Visibility Visibility
650 CreatedAt time.Time
651}
652
653// EventKind implements Event.
654func (JobUpdated) EventKind() string { return "ci.job_updated" }
655
656// CheckState is the combined state of the checks of a commit.
657type CheckState string
658
659// Combined check states.
660const (
661 // CheckStateNone: the commit has no check.
662 CheckStateNone CheckState = ""
663 // CheckStatePending: a check is queued or in progress, none has failed.
664 CheckStatePending CheckState = "pending"
665 // CheckStateFailure: a check failed (or needs an action), whatever the others.
666 CheckStateFailure CheckState = "failure"
667 // CheckStateSuccess: every check passed.
668 CheckStateSuccess CheckState = "success"
669)
670
671// CombineChecks reduces the checks of a commit to one state, the one the
672// merge rule uses too: a failed check makes the commit failing even while
673// others are pending; else a pending check makes it pending; else it is a
674// success.
675func CombineChecks(checks []CICheck) CheckState {
676 if len(checks) == 0 {
677 return CheckStateNone
678 }
679 pending := false
680 for _, c := range checks {
681 switch {
682 case c.Status != CICompleted:
683 pending = true
684 case !c.Conclusion.PassesMerge():
685 return CheckStateFailure
686 }
687 }
688 if pending {
689 return CheckStatePending
690 }
691 return CheckStateSuccess
692}