---
title: "Applicant Portal — Eligibility Surface"
slug: applicant-portal-eligibility-surface
section: Applicant Journey
status: draft
description: "A draft stub for the portal-side surface that renders Globus Rules Engine eligibility decisions."
last-updated: 2026-10-07
---

# Applicant Portal — Eligibility Surface

A draft stub for the portal-side surface that renders Globus Rules Engine eligibility decisions. This page intentionally captures only what is already settled, plus the open questions the applicant portal team still needs to answer. It is a placeholder for a future spec, not the spec itself.

> **Draft — placeholder for future scoping**
>
> Nothing on this page is final. The contract shape, copy slots, and rendering behavior are sketched only well enough to anchor the next conversation. Treat every TBD as a real open question, not a default.

> **The engine this surface reflects is LIVE (verified 2026-07-04)**
>
> The portal surface is unbuilt (PPPToo's only eligibility controller serves the SWT national-window and date-eligibility calls (POST /api/national-window/get, /api/national-window/list, /api/eligibility/calculate), not Rules Engine decisions, and its context read is GET /me/context), and the PRD confirms AP-8 is PENDING SCOPING. But the Globus Rules Engine the surface will reflect is substantial and shipping: server/services/eligibilityRulesEngine.ts plus the `rulesEngine*.ts` helpers in server/services/, shared models in shared/models/rules-eligibility.ts, rule templates / parameter schemas / catalogs / tree schema / risk classification under shared/, staff override routes in server/routes/admin/eligibilityOverrides.ts and the pool view in server/routes/eligibilityPool.ts. The draft payload contract below is the template the PRD's SWT eligibility display defers to — when refining it, align field names to the PRD's outcome / reasonCodes[] / nextStep spelling so the two drafts don't diverge. See Rules Engine .

## Scoping Status

> **Asana scoping ticket filed for the Applicant Portal team**
>
> A scoping ticket has been filed in the AP-8 project on Asana in the canonical house format: Applicant Portal: AP-8 eligibility surface — PRD scoping pass . The ticket carries the architectural rule, the draft payload contract, and the Gherkin acceptance criteria the new PRD section must satisfy. The consolidated PRD update brief for the AP team lives at docs/applicant-portal-prd-update-brief.md.

## Architectural Principle

Globus Rules Engine decides, the applicant portal reflects. The portal never computes eligibility, never re-implements predicates, never applies its own thresholds, and never overrides a Globus decision client-side. Its only job is to render what Globus returns — outcome, reason codes, human-readable copy, and any next-step CTA — in a brand-aware, applicant-appropriate way.

For the authoritative definition of rule definitions, the five-layer evaluation pipeline, decision classes, and the reason code catalog, see the Rules Engine specification . That page is the source of truth; this page only describes how the applicant portal consumes its output.

## Open Scoping Questions

These are the questions the applicant portal team needs to answer before this page becomes a real spec. Each is marked TBD on purpose — do not infer a default.

- Does the applicant see a simple pass/fail result, or something richer? — TBD
- Do we expose a per-rule breakdown (which checks passed, which failed), or only the aggregate outcome? — TBD
- When a decision blocks, do we surface the blocking reason codes plus next-step copy, and at what level of detail? — TBD
- Is the applicant-facing copy brand-aware (per Camp / Teach / Work & Travel), and who owns the copy slots? — TBD
- What next-step CTAs are allowed (upload document, contact sponsor, retry later, none), and who decides which fires? — TBD
## Draft Payload Contract

At draft level — not finalized — the portal expects Globus to return an eligibility result with at least the following surface fields. Field names mirror the Rules Engine output contract; the slots below are what the portal needs in order to render anything at all.

| Field | Shape | Purpose |
| --- | --- | --- |
| status | enum | Outcome class from the engine (e.g. pass / fail / hold / allow_with_warning). Drives the headline state the portal renders. |
| reason_codes[] | string[] | Machine-readable codes from the Rules Engine reason code catalog. Portal never invents codes — it only renders what Globus returns. |
| applicant_copy | string (slot) | Human-readable, applicant-facing copy slot. Brand-aware variant resolution TBD. |
| next_step_cta | object (slot) \| null | Optional next-step CTA descriptor (label + action hint). Allowed action set TBD. |
| decision_id | string | Engine decision identifier for support reference and deterministic replay correlation. |
| evaluated_at | timestamp | When the engine evaluated. Portal displays freshness; does not recompute. |

> **Draft only**
>
> This shape is a starting point so the team has something concrete to argue with. The final contract will be co-designed with the Rules Engine output contract owners and ratified on this page once the open questions above are closed.

## Out of Scope on This Page

- Rule authoring UI — lives in the Rules Engine spec and the Compliance Console.
- The reason code catalog itself — owned by the Rules Engine reason code catalog .
- Any decision logic, predicate evaluation, or threshold configuration — the portal never decides.
- Final UI design, copy, and brand-specific messaging treatment.
- Implementation or wiring — this page is documentation only.

**Existing PPPToo window endpoints.** `POST /api/national-window/get`, `POST /api/national-window/list` and `POST /api/eligibility/calculate` sit behind the applicant JWT guard at those literal paths (no `/v1` prefix) and report the outcome in a `code` field in the response body; missing data never produces an error status. They resolve the caller's active application to a program and season, then ask Globus for the country's Summer Work Travel window. `national-window/get` (body `countryCode`): `MISSING_COUNTRY` when `countryCode` is absent, `NOT_FOUND` when there is no active application or no window on record for the country, otherwise `OK` with the formatted window and a callout message. `national-window/list` returns `NOT_FOUND` without an active application, otherwise `OK` with a `windows` map of every country that has a window. `eligibility/calculate` (body `countryCode`, `startDate`, `endDate`, all required): `ERROR` when a field is missing, there is no active application or no window is on record; `NO_OVERLAP` when the declared vacation dates do not overlap the window (or the end precedes the start), with a `warningMessage` and an `auditSnapshot`; `ELIGIBLE_WINDOW_CALCULATED` otherwise, with `threeLineDisplay`, `eligibilityWindow` (the declared dates clipped to the window) and `auditSnapshot`.

**Flow availability reason `PROGRAM_CAPABILITY_DISABLED`.** In the Hub's available-flows surface (`GET /api/integrations/applicant-portal/applicants/:applicantId/available-flows`), a renewal, extension or J-2 flow reports `blockedReason` and `reasonCode` `PROGRAM_CAPABILITY_DISABLED` when the program flag for that flow is off (`programs.renewalsAllowed`, `programs.extensionsAllowed` or `programs.j2Allowed`). It has the highest precedence: capability, then `DUPLICATE_FLOW_IN_FLIGHT`, then `FLOW_WINDOW_CLOSED`, then `APPLICANT_INELIGIBLE`. `POST /api/portal/applications` refuses a renewal or extension for a program with that flag off with the same reason code.
