# How the Job Posting Legitimacy Check (Block G) Identifies Expired or Fake Listings in Career-Ops

> Discover how the job posting legitimacy check in Career-Ops (Block G) uses five signals to identify expired or fake listings, classifying them as High Confidence, Proceed with Caution, or Suspicious.

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

---

**Block G evaluates job listings using five concrete signals—posting freshness, description quality, external hiring news, repost history, and market context—to classify opportunities as High Confidence, Proceed with Caution, or Suspicious.**

The **career-ops** evaluation pipeline uses a dedicated **job posting legitimacy check** to filter out stale "ghost" jobs and fraudulent listings before they reach the applicant. This analysis runs after the initial role extraction (Blocks A–F) and leverages Playwright snapshots, natural language processing, and external web searches to verify that a position is actually open and authentic. According to the specification in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md), Block G systematically weights these signals to protect users from wasting time on inactive postings.

## Overview of Block G in the Career-Ops Pipeline

Block G operates as the final validation gate in the evaluation workflow. It receives data from the Playwright page snapshot captured in Step 0, the raw job description (JD) text parsed in earlier blocks, and the system's historical scan logs. The module outputs an **Assessment** tier that appears in the report header as `**Legitimacy:** {tier}`.

The check is implemented primarily within `gemini-eval.mjs`, which feeds the Block G prompt to the LLM, while low-level helpers for detecting posting age and Apply-button states reside in `liveness-core.mjs`. For batch operations, the same logic is defined in [`batch/batch-prompt.md`](https://github.com/santifer/career-ops/blob/main/batch/batch-prompt.md).

## The Five Signals Used to Detect Illegitimate Listings

The **job posting legitimacy check** analyzes five distinct signal categories defined in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 96–123). Each signal contributes to a weighted score that determines the final confidence tier.

### 1. Posting Freshness and Apply Button State

The system extracts the posting date or "X days ago" string, checks the state of the **Apply** button, and monitors for URL redirects ([lines 96–99](https://github.com/santifer/career-ops/blob/main/modes/oferta.md#L96-L99)). 

A concerning value includes postings older than 60 days, disabled Apply buttons, or redirect chains that land on generic career portals. These indicators suggest the role may be closed but still indexed by job boards.

### 2. Description Quality and Specificity

Block G parses the JD text for concrete technical stacks, team size, clear scope, compensation ranges, and the ratio of role-specific versus boilerplate content ([lines 101–108](https://github.com/santifer/career-ops/blob/main/modes/oferta.md#L101-L108)).

Very generic text, contradictory seniority requirements (e.g., "Senior Engineer, 0–1 years experience"), or missing scope details signal a possibly fabricated posting designed to harvest resumes rather than fill a position.

### 3. External Company Hiring Signals

The system conducts targeted web searches using queries formatted as `"{company}" layoffs {year}` and `"{company}" hiring freeze {year}` ([lines 110–113](https://github.com/santifer/career-ops/blob/main/modes/oferta.md#L110-L113)).

If a company has announced recent layoffs or an active hiring freeze, the posting is likely stale or inactive, even if the job board still displays it as "new."

### 4. Reposting Detection via History Logs

Block G queries `scan-history.tsv`—the scanner's deduplication log—to identify if the same company and similar role title appeared earlier under a different URL ([lines 115–117](https://github.com/santifer/career-ops/blob/main/modes/oferta.md#L115-L117)).

Re-posting the same role multiple times over a short period is a classic "ghost job" pattern where employers collect candidate pools without immediate hiring intent.

### 5. Role Market Context Assessment

The algorithm performs a qualitative assessment of typical fill-times for the specific role, its relevance to the company's core business, and whether seniority expectations align with known hiring timelines ([lines 119–123](https://github.com/santifer/career-ops/blob/main/modes/oferta.md#L119-L123)).

A Chief Architect posting at a seed-stage startup or a role requiring obsolete technology stacks may indicate either a fake listing or an uninformed recruiter posting.

## Scoring Algorithm and Confidence Tiers

After gathering the five signals, Block G applies a weighting scheme to produce a numerical score. The system then maps this score to one of three **Assessment** tiers:

- **High Confidence** – Multiple positive signals present (fresh posting <30 days, active Apply button, detailed description with tech stack, no layoff news, unique URL). Typically requires a score of 7 or higher.
- **Proceed with Caution** – Mixed signals detected (e.g., fresh posting but vague description, or detailed posting but company recently had minor layoffs). Score range roughly 4–6.
- **Suspicious** – Several red-flag signals present (old posting >60 days, missing Apply button, repeated reposts, confirmed recent layoffs). Score below 4.

This scoring logic is implemented in the evaluation script where the LLM processes the aggregated signal data.

## Edge Case Handling

The specification in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 135–141) explicitly handles scenarios where sparse data might trigger false positives. The **job posting legitimacy check** adjusts its thresholds for:

- **Government and academic postings** – Often lack specific dates or use evergreen templates.
- **Evergreen roles** – Large corporations maintain perpetual listings for high-turnover positions.
- **Startups** – May lack detailed scope descriptions due to evolving needs rather than fraud.
- **Missing dates** – Some legitimate boards omit posting dates entirely.
- **Recruiter-sourced listings** – Third-party postings may repost the same role legitimately across multiple platforms.

These exceptions prevent the algorithm from automatically labeling a listing as "Suspicious" when the data is simply incomplete.

## Implementation in the Codebase

The legitimacy assessment relies on a distributed set of files within the `santifer/career-ops` repository:

| File | Role in Legitimacy Check |
|------|--------------------------|
| [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) | Complete specification of Block G signals, weighting, and edge-case handling |
| `liveness-core.mjs` | Provides low-level helpers for detecting posting age and Apply-button state |
| `gemini-eval.mjs` | Generates the final **Legitimacy** field by feeding the Block G prompt to the LLM |
| [`batch/batch-prompt.md`](https://github.com/santifer/career-ops/blob/main/batch/batch-prompt.md) | Contains the Block G description for batch processing workflows |
| `scan-history.tsv` | Persistent store of historic URLs used to detect reposts (Signal 4) |

The scoring logic can be conceptually represented as follows:

```javascript
// Simplified illustration mirroring the Block G weighting scheme
async function evaluateLegitimacy(page, jdText, company, title) {
  const freshness = await getPostingFreshness(page);      // → {ageDays, applyBtn, redirected}
  const descScore = assessDescriptionQuality(jdText);    // → 0-10
  const layoffs = await webSearch(`${company} layoffs ${new Date().getFullYear()}`);
  const freeze  = await webSearch(`${company} hiring freeze ${new Date().getFullYear()}`);
  const repost  = await checkRepostHistory(company, title); // → count / period

  // Weighting scheme (mirrors the spec)
  let score = 0;
  if (freshness.ageDays < 30) score += 2;
  if (freshness.applyBtn === 'active') score += 2;
  if (descScore > 7) score += 2;
  if (!layoffs && !freeze) score += 1;
  if (repost.count < 2) score += 1;

  const tier = score >= 7 ? 'High Confidence'
               : score >= 4 ? 'Proceed with Caution'
               : 'Suspicious';
  return { tier, signals: { freshness, descScore, layoffs, freeze, repost } };
}

```

## Summary

- Block G performs a **job posting legitimacy check** using five weighted signals: posting freshness, description quality, external hiring news, repost history, and market context.
- The system references [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) for signal definitions and `scan-history.tsv` for duplicate detection.
- Scores map to three tiers: **High Confidence**, **Proceed with Caution**, and **Suspicious**.
- Edge-case handling prevents false positives for government jobs, evergreen roles, and recruiter listings.
- Implementation spans `liveness-core.mjs` for data extraction and `gemini-eval.mjs` for final assessment generation.

## Frequently Asked Questions

### How accurate is the job posting legitimacy check?

The system achieves high accuracy by cross-referencing multiple independent data sources rather than relying on single indicators. By combining Playwright-rendered page states, `scan-history.tsv` deduplication logs, and real-time web searches for corporate news, Block G reduces false positives compared to simple date-based filtering. The edge-case handling for government and academic postings further refines precision.

### What triggers a "Suspicious" rating in Block G?

A **Suspicious** rating typically results from a combination of red flags: postings older than 60 days, disabled or missing Apply buttons, confirmed recent layoffs or hiring freezes at the company, detection of the same role reposted multiple times in `scan-history.tsv`, and vague job descriptions lacking specific technical requirements. A single issue may result in "Proceed with Caution," but multiple concurrent issues trigger the Suspicious classification.

### Does the system handle false positives for government jobs?

Yes. The specification in [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) (lines 135–141) explicitly accounts for government and academic postings that often use evergreen templates without specific posting dates. The algorithm adjusts its freshness thresholds and description quality expectations for these sectors, ensuring it does not flag legitimate public sector opportunities as expired simply because they lack commercial job board metadata.

### How does the repost detection work technically?

Repost detection queries the `scan-history.tsv` file, which maintains a log of previously scanned URLs along with normalized company names and role titles. When Block G evaluates a new posting, it checks for existing entries matching the current company and a similar title string within a defined time window. If the count exceeds the threshold (typically 2 or more instances within a short period), the system flags it as a potential ghost job. This check is performed via the prompt logic in `gemini-eval.mjs` using data persisted from previous pipeline runs.