Skip to content

Job Application

This example demonstrates a job application workflow with submissions, reviews, interviews, and hiring decisions.

Application Lifecycle

flow
machine: @application

scenario: submission

  given:
    @applicant is registered
    job posting is active

  on> :submit from @applicant
    ? all required fields are filled
      application moves to #pending
      $submitted_at becomes now
      emit :new_application to @recruiter

    ? missing required fields
      emit :validation_error to @applicant
        with $missing_fields

  expect:
    = application is in #pending

scenario: initial screening

  given:
    application is in #pending
    @recruiter is assigned

  on> :screen from @recruiter
    ? qualifications match requirements
      application moves to #screened
      emit :screening_passed to @applicant

    ? qualifications do not match
      application moves to #rejected
      emit :application_rejected to @applicant
        with:
          | reason | "Qualifications do not match requirements" |

  expect:
    = application has been screened

scenario: interview process

  given:
    application is in #screened

  on> :schedule_interview from @recruiter
    application moves to #interview_scheduled
    $interview_date becomes $scheduled_date
    emit :interview_invitation to @applicant
      with:
        | date     | $interview_date |
        | location | $interview_location |

  on :interview_completed from @interviewer
    application moves to #interview_completed
    $interview_score becomes $score
    $interview_notes becomes $notes

    ? $interview_score >= 8
      emit :strong_candidate_alert to @hiring_manager

  expect:
    = interview has been completed

scenario: hiring decision

  given:
    application is in #interview_completed
    $interview_score: number is 8

  on> :approve from @hiring_manager
    ? $interview_score >= 7
      application moves to #offer_pending
      emit :prepare_offer to @hr
      emit :approval_notification to @applicant

    ? $interview_score < 7
      application moves to #rejected
      emit :application_rejected to @applicant

  on :offer_sent from @hr
    application moves to #offer_extended
    $offer_expires_at becomes now + 7 days
    emit :offer_details to @applicant
      with:
        | salary   | $offered_salary |
        | benefits | $benefits_package |
        | deadline | $offer_expires_at |

  expect:
    = offer process has started

scenario: offer response

  given:
    application is in #offer_extended

  on> :accept_offer from @applicant
    ? offer has not expired
      application moves to #hired
      emit :offer_accepted to @hr
      emit :welcome_package to @applicant
      emit :onboarding_request to @onboarding

    ? offer has expired
      emit :offer_expired_error to @applicant

  on> :decline_offer from @applicant
    application moves to #offer_declined
    $decline_reason becomes $reason
    emit :offer_declined to @hr
    emit :feedback_request to @applicant

  on> :negotiate from @applicant
    application moves to #negotiating
    emit :negotiation_request to @hr
      with:
        | requested_salary | $requested_salary |
        | notes           | $negotiation_notes |

  on :counter_offer from @hr
    emit :counter_offer_details to @applicant
      with:
        | salary   | $counter_salary |
        | benefits | $counter_benefits |

  expect:
    = offer response has been processed

scenario: expiration handling

  on :check_expired_applications
    triggered: every day at 00:00

    for each application in #pending
      ? $submitted_at older than 30 days
        application moves to #expired
        emit :application_expired to @applicant

    for each application in #offer_extended
      ? $offer_expires_at has passed
        application moves to #offer_expired
        emit :offer_expired to @applicant
        emit :offer_expired to @hr

State Diagram

                          @application

┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│  ┌─────────┐   :submit     ┌─────────┐   :screen     ┌──────────┐      │
│  │ (start) │──────────────>│#pending │──────────────>│#screened │      │
│  └─────────┘               └────┬────┘               └────┬─────┘      │
│                                 │                         │            │
│                            :expired                  :schedule         │
│                                 │                    interview         │
│                                 ▼                         │            │
│                           ┌──────────┐                    ▼            │
│                           │#expired  │          ┌──────────────────┐   │
│                           └──────────┘          │#interview_scheduled│  │
│                                                 └────────┬─────────┘   │
│                                                          │             │
│                                                   :completed           │
│                                                          │             │
│  ┌──────────┐                                            ▼             │
│  │#rejected │<───────────────────────────── ┌──────────────────────┐   │
│  └──────────┘                               │#interview_completed  │   │
│       ▲                                     └──────────┬───────────┘   │
│       │                                                │               │
│       │                                           :approve             │
│       │                                                │               │
│       │                                                ▼               │
│       │                                      ┌───────────────┐         │
│       │                                      │ #offer_pending│         │
│       │                                      └───────┬───────┘         │
│       │                                              │                 │
│       │                                         :offer_sent            │
│       │                                              │                 │
│       │                                              ▼                 │
│       │                                    ┌───────────────────┐       │
│       │                                    │ #offer_extended   │       │
│       │                                    └─────────┬─────────┘       │
│       │                                              │                 │
│       │                   ┌──────────────────────────┼─────────┐       │
│       │                   │              │           │         │       │
│       │              :decline       :accept     :negotiate :expired    │
│       │                   │              │           │         │       │
│       │                   ▼              ▼           ▼         ▼       │
│       │          ┌────────────┐   ┌──────────┐ ┌───────────┐ ┌───────┐ │
│       └──────────│#offer_     │   │ #hired   │ │#negotiating│ │#offer_│ │
│                  │declined    │   └──────────┘ └───────────┘ │expired│ │
│                  └────────────┘                              └───────┘ │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Lane Diagram

┌───────────┬────────────┬────────────┬────────────┬──────────┐
│@applicant │ @recruiter │@interviewer│ @hr        │@hiring_  │
│           │            │            │            │manager   │
├───────────┼────────────┼────────────┼────────────┼──────────┤
│           │            │            │            │          │
│ :submit   │            │            │            │          │
│ ──────────┼──>         │            │            │          │
│           │            │            │            │          │
│           │ receives   │            │            │          │
│           │ :new_app   │            │            │          │
│           │            │            │            │          │
│           │ :screen    │            │            │          │
│           │ ──────────>│            │            │          │
│           │            │            │            │          │
│           │ :schedule  │            │            │          │
│           │ ──────────>│            │            │          │
│           │            │            │            │          │
│ :invite   │            │            │            │          │
│ <─────────┼────────────│            │            │          │
│           │            │            │            │          │
│           │            │:completed  │            │          │
│           │ <──────────┼────────────│            │          │
│           │            │            │            │          │
│           │            │            │            │:approve  │
│           │            │            │            │ ────────>│
│           │            │            │            │          │
│           │            │            │:prepare_   │          │
│           │            │            │offer       │          │
│           │            │            │<───────────┼──────────│
│           │            │            │            │          │
│           │            │            │:offer_sent │          │
│           │            │            │ ──────────>│          │
│           │            │            │            │          │
│ :offer    │            │            │            │          │
│ <─────────┼────────────┼────────────┼────────────│          │
│           │            │            │            │          │
│ :accept   │            │            │            │          │
│ ──────────┼────────────┼────────────┼──>         │          │
│           │            │            │            │          │
└───────────┴────────────┴────────────┴────────────┴──────────┘

Testing Scenarios

Successful Hire

flow
scenario: successful hire path

  given:
    @applicant is registered
    job posting is active

  on> :submit from @applicant
    application moves to #pending

  on> :screen from @recruiter
    application moves to #screened

  on> :schedule_interview from @recruiter
    application moves to #interview_scheduled

  on :interview_completed from @interviewer
    $interview_score becomes 9
    application moves to #interview_completed

  on> :approve from @hiring_manager
    application moves to #offer_pending

  on :offer_sent from @hr
    application moves to #offer_extended

  on> :accept_offer from @applicant
    application moves to #hired

  expect:
    = application is in #hired
    = @applicant received :welcome_package

Rejection at Screening

flow
scenario: rejected at screening

  given:
    @applicant is registered
    qualifications do not match

  on> :submit from @applicant
    application moves to #pending

  on> :screen from @recruiter
    application moves to #rejected

  expect:
    = application is in #rejected
    = @applicant received :application_rejected

Offer Negotiation

flow
scenario: salary negotiation

  given:
    application is in #offer_extended
    $offered_salary is 80000

  on> :negotiate from @applicant
    $requested_salary becomes 90000
    application moves to #negotiating

  on :counter_offer from @hr
    $counter_salary becomes 85000

  on> :accept_offer from @applicant
    application moves to #hired

  expect:
    = application is in #hired

Released under the MIT License.