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 @hrState 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_packageRejection 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_rejectedOffer 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