---
title: "Applicant Processing Model"
slug: applicant-processing-model
section: Applicant Journey
status: stable
description: "How Globus models the applicant journey — phases, stages, readiness states, and operational queues"
last-updated: 2026-10-07
---

# Applicant Processing Model

How Globus models the applicant journey — phases, stages, readiness states, and operational queues

## Overview

The Globus applicant processing model is built on a canonical 8-phase journey spine that every applicant traverses from initial intake through program completion. Within each phase, programs can define configurable stages that represent operational milestones. At any point in the journey, an applicant carries a readiness state computed by the Rules Engine, and may simultaneously belong to one or more operational queues for staff action.

## The 8-Phase Journey Spine

The journey spine is the canonical ordered sequence of lifecycle phases defined in journeyPositionResolver.ts and phaseApplicability.ts. Every applicant progresses through these phases in order. Phase transitions are guarded by the lifecyclePhaseGate.ts hook, which evaluates all applicable rules before allowing advancement.

### Phase Reference

| Phase | Key Activity | Gate Condition |
| --- | --- | --- |
| 1. Intake (`application`) | Applicant record created via portal or partner sync; applicant completes portal workflow steps | Step-completion threshold met to reach Validation |
| 2. Validation | Staff review of submitted application data | Authored Validation steps complete (published workflow with full step coverage); failed rules and unverified documents remain separate blockers |
| 3. Screening (`interview`) | Sponsor interview conducted and recorded | Interview outcome = pass |
| 4. Placement | Applicant matched to a host organisation | Job order approved; placement confirmed |
| 5. Visa / SEVIS (`visa_sevis`) | Application is in the SEVIS pool or has a confirmed placement; SEVIS processing | In the SEVIS pool, or placement confirmed |
| 6. Pre-Departure | DS-2019 issued; visa obtained; travel booked | DS-2019 issued, in the SEVIS pool, and placement confirmed |
| 7. In Program (`in_program`) | Applicant is in the US; monthly check-ins active | Program start date reached; SEVIS validated |
| 8. Completion | Program end; departure confirmed; record closed | Program end date passed; no open compliance cases |

**Validation track completion is independent of its blockers.** The Validation track (one of the four parallel pre-SEVIS tracks) is derived in preSevisTrackMachine.ts. When a published workflow resolves for the program and its steps carry complete journey-position coverage, the track reaches the terminal `complete` state once every step tagged to Validation is complete (including a deliberately empty Validation phase with zero authored steps). Failed compliance rules and unverified documents do not hold the track open; they remain separate, individually listed SEVIS blockers. While the phase is incomplete or coverage is missing, the track stays `in_progress` (it never promotes on incomplete or untagged coverage). Programs with no resolvable published workflow keep the legacy evidence-only behavior: the track completes only when documents are verified and compliance is compliant. Reconciliation advances the track forward-only and is idempotent on repeat passes.

## Configurable Stages

Within each phase, programs can define stages via the stage_definitions table (each carrying a categoryKey and journeyPositionKey, and scoped through appliesToVisaTypes and appliesToProgramIds). Stages allow operational teams to track sub-milestones within a phase — for example, splitting the Validation phase into "Document Review" and "Background Check" stages. Stage transitions are managed by stages.ts and call enforcePhaseGate (lifecyclePhaseGate.ts) on each stage change. For manual operator moves, the allowed moves are rows in `stage_transitions`: each has a `fromStageId` (null for the entry point) and a `toStageId`, an `isActive` flag, `requiresReason` with a `reasonPrompt`, and an optional `requiresPermissionKey`; the (from, to) pair is unique. A manual move with no active matching row is rejected as an invalid stage transition together with the list of valid ones; a transition that `requiresReason` is rejected without a reason, and one whose `requiresPermissionKey` the caller lacks is not offered and is rejected. Milestone-verified, non-forward and admin-override moves do not traverse these rows. Offered transitions are grouped as forward, rollback (lower `sortOrder`) or exit (terminal stage).

## Readiness States

Every applicant carries a readiness state computed dynamically by the Rules Engine via resolveReadiness(). The state reflects the aggregate outcome of all rules applicable to the applicant's current phase.

| State | Meaning | Staff Action |
| --- | --- | --- |
| on_track | All rules pass; no blockers | No action required |
| blocked | A reviewer rejected a step, a compliance check is failing, or an interview ended with a non-pass outcome. Observational for staff dashboards; it no longer feeds step access (compliance enforces at the SEVIS gates) | Investigate and resolve the blocker |
| paused | A hold has been placed (e.g., awaiting external input) | Monitor; resume when condition clears |
| needs_attention | A compliance check needs review, or the applicant is waitlisted | Review the flagged checks; resolve or escalate |

## Multi-Queue Membership Model

An applicant can simultaneously belong to multiple operational queues. Queue membership is driven by rule outcomes (via the queue_review action) and is stored in compliance_review_queue_items. The queue architecture is managed by queueTransitions.ts, which handles entry, exit, and escalation transitions. Staff see queue membership surfaced on the applicant profile's Snapshot Cards Grid.

## Phase Gate Evaluation

> **Gates Are Synchronous**
>
> Phase gate evaluation is synchronous and blocking. An applicant cannot advance to the next phase until all applicable rules for the current phase return a non-failing outcome. Gates are evaluated by lifecyclePhaseGate.ts on every phase transition attempt.

The phaseGateEvaluator.ts service collects all rules applicable to the current phase (via the 6-layer cascade resolver), evaluates them in priority order, and returns a gate decision. If any rule returns a fail outcome without a permitted override, the gate is closed and the applicant's readiness state is set to blocked.

## Record-Mutation Endpoints (Staff)

These write the records the journey runs on. All require `applications.edit`; API keys need `applications.write` plus `enrollments.read` for enrollments and workflow instances, and `admin.write` plus `forms.read` for form submissions. Each write is audited (`enrollment.create` / `enrollment.update`, `workflow_instance.create` / `workflow_instance.update`, `create_form_submission` / `update_form_submission`).

| Method | Path | Behaviour |
| --- | --- | --- |
| POST | `/api/enrollments` | Creates a program enrollment (`applicantId`, which is the external portal user id and not a foreign key to local applicants, `programId`, optional `startSeasonId`; `status` defaults to `active`, `currentYearInProgram` to 1). `201`. |
| PATCH | `/api/enrollments/:id` | Partial update of the same fields; the id must be a UUID; `404` when the enrollment does not exist. |
| POST | `/api/workflow-instances` | Creates a workflow instance for an enrollment (`enrollmentId`, `workflowType`; `state` defaults to `draft`). A given `workflowDefinitionId` supplied without a `definitionVersion` is pinned at that definition's current version; without a `workflowDefinitionId`, the season's locked definition is used when a `seasonId` is given, otherwise (or when the season has none) the program's published definition for the workflow type. When no `seasonId` is given the season is then assigned from the enrollment's program. `201`. |
| PATCH | `/api/workflow-instances/:id` | Partial update; `404` when the instance does not exist. |
| POST | `/api/form-submissions` | Stores a submission against a form template version (`templateId`, `templateVersionId`, `entityType`, `entityId`, `dataJson`; `status` defaults to `in_progress`); `submittedBy` is set from the session user. `201`. |
| PATCH | `/api/form-submissions/:id` | Partial update; `404` when the submission does not exist; the audit row keeps the previous and new `dataJson`. |
