diff --git a/modes/auto-pipeline.md b/modes/auto-pipeline.md index a2cd28774..68d25c593 100644 --- a/modes/auto-pipeline.md +++ b/modes/auto-pipeline.md @@ -6,6 +6,8 @@ When the user pastes a JD (text or URL) without an explicit sub-command, execute If the input is a **URL** (not pasted JD text), follow this strategy to extract the content: +**Liveness gate (first):** Confirm the URL is live before any evaluation (#835). While navigating to read the JD, check for dead-posting evidence — 404/410, "position closed / filled / no longer accepting applications", a hard redirect to a generic careers or search page, or a page with no JD. If the posting is dead, **stop here**: do not run Step 1 evaluation, write a report, or generate a PDF. Mark the entry in `data/pipeline.md` as `- [x] ~~Company | Role~~ — posting expired` and tell the user the link is dead. The headless `check-liveness.mjs ` script is the fallback when Playwright MCP is unavailable. + **Priority order:** 1. **Playwright (preferred):** Most job portals (Lever, Ashby, Greenhouse, Workday) are SPAs. Use `browser_navigate` + `browser_snapshot` to render and read the JD. diff --git a/modes/oferta.md b/modes/oferta.md index 0de43c4dc..3abe15cde 100644 --- a/modes/oferta.md +++ b/modes/oferta.md @@ -2,7 +2,21 @@ When the candidate pastes a job (text or URL), ALWAYS deliver the 7 blocks (A-F evaluation + G legitimacy): -## Step 0 — Archetype Detection +## Step 0 — Liveness gate + +When the candidate provides a **URL** (not pasted JD text), confirm the posting is still live before spending any evaluation tokens. Dead links must be caught here — not after a full A-G report and a tailored CV have already been generated for a 404 (#835). + +1. `browser_navigate` to the URL and take a `browser_snapshot` (this snapshot is reused by Block G — Posting Freshness). +2. Read the URL, page title, JD body, and any closed/expired signals: + - **active evidence:** role title + a real job description + an apply/submit path + - **dead evidence:** 404/410, "position closed / filled / no longer accepting applications", a hard redirect to a generic careers or search page, or a page with only nav/footer and no JD +3. If the posting is dead, **stop before Block A** — do not evaluate, write a report, or generate a CV. Mark the entry in `data/pipeline.md` as `- [x] ~~Company | Role~~ — posting expired` and tell the candidate the link is dead. +4. If liveness cannot be verified (no Playwright, or navigation blocked by a challenge/403), say so and ask the candidate to confirm the posting is active before continuing. The headless `check-liveness.mjs ` script is the fallback when Playwright MCP is unavailable. +5. If the input is pasted JD text (no URL), skip this gate. + +Do not continue to Step 1 until liveness is resolved. + +## Step 1 — Archetype Detection Classify the job into one of the 6 archetypes (see `_shared.md`). If it is a hybrid, indicate the 2 closest ones. This determines: - Which proof points to prioritize in block B diff --git a/test-all.mjs b/test-all.mjs index 436686dc3..be1c25c1b 100644 --- a/test-all.mjs +++ b/test-all.mjs @@ -512,6 +512,29 @@ if ( fail('apply mode missing liveness/role-match preflight gate'); } +// #835 — the eval side (oferta / auto-pipeline) must gate on URL liveness as +// step 0, so a dead link is caught before a full A-G report + tailored CV. +const ofertaMode = readFile('modes/oferta.md'); +if ( + ofertaMode.includes('## Step 0 — Liveness gate') && + ofertaMode.includes('stop before Block A') && + ofertaMode.includes('Do not continue to Step 1 until liveness is resolved') +) { + pass('oferta mode gates evaluation on a step-0 liveness check'); +} else { + fail('oferta mode missing the step-0 liveness gate'); +} + +const autoPipelineMode = readFile('modes/auto-pipeline.md'); +if ( + autoPipelineMode.includes('Liveness gate (first)') && + autoPipelineMode.includes('do not run Step 1 evaluation') +) { + pass('auto-pipeline mode gates evaluation on URL liveness'); +} else { + fail('auto-pipeline mode missing the liveness gate before evaluation'); +} + // ── 9. LOCAL PARSER CONTRACT ──────────────────────────────────── console.log('\n9. Local parser contract');