# How Work Authorization and Visa Sponsorship Checking Works in Career-Ops Block A

> Understand how Career-Ops Block A checks work authorization and visa sponsorship. Learn about its six-step pipeline, config settings, and role classification for efficient hiring.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: how-to-guide
- Published: 2026-08-19

---

**Career-Ops Block A implements a deterministic six-step pipeline that reads candidate authorization settings from [`config/profile.example.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.example.yml), scans job descriptions for sponsorship language in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md), and classifies roles into four tiers—flagging "No sponsorship" matches as hard blockers while leaving neutral tiers unscored.**

Block A (the Role Summary) serves as the first analytical gate in every santifer/career-ops A-G evaluation. This section performs a critical safety check that determines whether a candidate can legally accept a role and whether the employer provides visa sponsorship, preventing the system from recommending positions that violate work authorization constraints.

## The Work Authorization Pipeline Architecture

The checking logic follows a deterministic pipeline defined in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md). The system consumes structured candidate data from YAML configuration and unstructured job description text to produce a final classification that drives scoring and reporting.

### Step 1: Reading Candidate Profile Data

The pipeline begins by loading authorization settings from [`config/profile.example.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.example.yml) (lines 76-85). Three fields drive the determination:

- `location.authorized_in`: Array of countries/regions where the candidate currently holds work authorization
- `location.needs_sponsorship`: Boolean indicating whether the candidate requires sponsorship for roles outside authorized regions
- `location.visa_status`: Free-text fallback field when structured keys require additional context

### Step 2: Parsing JD Sponsorship Language

During Block A processing, the system scans the job description for explicit sponsorship statements. Unlike heuristic NLP approaches, Career-Ops captures the exact phrase verbatim from the JD text as specified in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 76-84), ensuring complete auditability and transparency in the evaluation report.

### Step 3: Classification and Tier Assignment

The algorithm evaluates the intersection of profile data and JD content to assign one of four deterministic tiers.

## The Four-Tier Classification System

Career-Ops sorts every role into exactly one of four categories based on legal eligibility and employer policy:

**Sponsors**: The JD explicitly offers visa sponsorship or relocation assistance, and the role location falls outside the candidate's `authorized_in` list. This tier receives neutral scoring treatment.

**Not needed**: Either the role exists within a country present in `authorized_in`, or the candidate has explicitly set `needs_sponsorship: false`. This indicates complete authorization alignment regardless of employer policy.

**Unstated**: The role location falls outside `authorized_in`, but the JD contains no sponsorship language. Per [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 78-84), this defaults to neutral rather than assuming rejection, avoiding false negatives.

**No sponsorship**: The JD explicitly refuses sponsorship (e.g., "no visa sponsorship provided") while the role location sits outside `authorized_in`. This constitutes a legal incompatibility.

## Scoring Impact and Report Generation

### Hard Stops and Block B Flags

When classification yields **No sponsorship**, Career-Ops triggers a hard blocker mechanism. According to [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 85-95), the system lowers the location score and records a `hard_stop` in the evaluation report. A flag line immediately appears at the top of Block B quoting the JD verbatim:

```

⛔ **No sponsorship:** JD states "{verbatim JD line}" and role is outside your authorized_in

```

### Machine-Readable Output for Downstream Processing

The resulting tier is serialized as the `work_auth` field in the machine-summary JSON, as documented in [`batch/batch-prompt.md`](https://github.com/santifer/career-ops/blob/main/batch/batch-prompt.md) (lines 306-328). Automation scripts like `analyze-patterns.mjs` consume this field to identify authorization patterns across batch evaluations without re-parsing the original JD text.

## Implementation Reference

The following Node.js pattern reproduces the tier determination logic consumed by `analyze-patterns.mjs`:

```js
// Assume profile and jdText are already loaded
const profile = {
  authorized_in: ['United States'],
  needs_sponsorship: false,
  visa_status: 'No sponsorship needed'
};

function classifyWorkAuth(jdText, profile) {
  const sponsorRegex = /sponsor|relocat(e|ion)/i;
  const noSponsorRegex = /no\s+visa\s+sponsorship|must\s+have\s+existing\s+work\s+authorization/i;
  const explicitCountryRegex = new RegExp(profile.authorized_in.join('|'), 'i');

  const mentionsSponsor = sponsorRegex.test(jdText);
  const mentionsNoSponsor = noSponsorRegex.test(jdText);
  const countryMatch = explicitCountryRegex.test(jdText);

  if (mentionsSponsor && !profile.authorized_in.includes('targetCountry')) {
    return 'Sponsors';
  }
  if (profile.authorized_in.includes('targetCountry') || !profile.needs_sponsorship) {
    return 'Not needed';
  }
  if (mentionsNoSponsor && !profile.authorized_in.includes('targetCountry')) {
    return 'No sponsorship';
  }
  return 'Unstated';
}

// Example JD excerpt
const jdExcerpt = 'We do not provide visa sponsorship and require you to have work authorization in Germany.';
console.log(classifyWorkAuth(jdExcerpt, profile)); // → 'No sponsorship'

```

## Summary

- **Deterministic pipeline**: Six sequential steps defined in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) ensure consistent evaluation across all roles regardless of input source
- **Profile-driven configuration**: Authorization checks rely on `location.authorized_in` and `location.needs_sponsorship` from [`config/profile.example.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.example.yml) (lines 76-85)
- **Four-tier classification**: Roles sort into **Sponsors**, **Not needed**, **Unstated**, or **No sponsorship** based on JD content and candidate status
- **Hard stop mechanism**: The **No sponsorship** tier generates a `hard_stop` flag in Block B and lowers location scores to prevent illegal role recommendations
- **Audit trail**: Verbatim JD quotes appear in human-readable reports, while the `work_auth` JSON field supports downstream analytics in [`batch/batch-prompt.md`](https://github.com/santifer/career-ops/blob/main/batch/batch-prompt.md)

## Frequently Asked Questions

### What happens when a job description does not mention sponsorship?

When the JD remains silent on sponsorship and the role is outside the candidate's `authorized_in` list, Career-Ops assigns the **Unstated** tier. As implemented in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 78-84), this neutral classification does not affect the overall score or generate blocker flags, avoiding false negatives while maintaining transparency about the unknown status.

### How does the system handle candidates authorized in multiple countries?

The `authorized_in` field accepts an array of country strings. The classification logic checks whether the target role's country exists within this array via regex matching. If the role location matches any entry in `authorized_in`, the system returns **Not needed** regardless of `needs_sponsorship` settings, as the candidate already possesses legal work rights for that specific jurisdiction.

### What is the difference between needs_sponsorship and visa_status?

The `needs_sponsorship` boolean (lines 76-85 in [`config/profile.example.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.example.yml)) provides a structured filter for automated decision-making regarding roles outside authorized regions, while `visa_status` serves as a human-readable free-text fallback. The deterministic pipeline prioritizes the boolean flag for classification logic, reserving the text field for context in reports when structured data is incomplete.

### How does this integrate with batch processing pipelines?

The `work_auth` field in the machine-summary JSON (documented in [`batch/batch-prompt.md`](https://github.com/santifer/career-ops/blob/main/batch/batch-prompt.md), lines 306-328) exposes the classification tier to downstream automation. Scripts like `analyze-patterns.mjs` consume this field to aggregate authorization patterns across multiple evaluations, enabling cohort analysis while preserving the deterministic logic defined in Block A.