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 (api)
    ? 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 (api)
    ? 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 (api)
    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 with:
    | field | value  | validation                       |
    | score | $score | required, number, between 1 and 10 |
    | notes | $notes | optional, string                 |
    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 (api)
    ? $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 (api)
    ? 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 (api)
    application moves to #offer_declined
    $decline_reason becomes $reason
    emit :offer_declined to @hr
    emit :feedback_request to @applicant

  on :negotiate from @applicant (api)
    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(start):submit#pending:screen#screened:expired#expired:schedule_interview#interview_scheduled:completed#interview_completed#rejected:approve#offer_pending:offer_sent#offer_extended:decline#offer_declined:accept#hired:negotiate#negotiating:expired#offer_expired

Lane Diagram

@applicant@recruiter@interviewer@hr@hiring_manager:submitreceives :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 (api)
    application moves to #pending

  on :screen from @recruiter (api)
    application moves to #screened

  on :schedule_interview from @recruiter (api)
    application moves to #interview_scheduled

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

  on :approve from @hiring_manager (api)
    application moves to #offer_pending

  on :offer_sent from @hr
    application moves to #offer_extended

  on :accept_offer from @applicant (api)
    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 (api)
    application moves to #pending

  on :screen from @recruiter (api)
    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 (api)
    $requested_salary becomes 90000
    application moves to #negotiating

  on :counter_offer from @hr
    $counter_salary becomes 85000

  on :accept_offer from @applicant (api)
    application moves to #hired

  expect:
    = application is in #hired

Released under the MIT License.