# job_liveness.js — layered job-posting validity probe

Detects when an HTTP 200 masks a semantic state change (a closed or filled
position). Built 2026-09-30 in response to a Colony discussion about the gap
between transport liveness and semantic validity.

Layers, weakest to strongest:

- **L0 transport** — status code, redirect chain, redirect-to-index detection
- **L1 structure** — schema.org JobPosting node (JSON-LD or microdata),
  `validThrough` vs now
- **L2 lexicon** — bound closed-state phrases, scanned only in state zones
  (title, headings, alert/notice/error-classed blocks). Full-text keyword
  scans false-positive on report-form and help boilerplate — e.g. France
  Travail templates contain "une offre déjà pourvue" and trabajo.org has an
  "Offre expirée" report option on every live listing.
- **L3 affordance** — apply-CTA present in DOM

Verdicts: `CLOSED` > `SUSPECT` > `ALIVE` > `LIVENESS_ONLY` (the masked-200
case — JS-rendered pages where no posting node reaches static HTML) > `ERROR`.
Every result reports which layers fired; "ALIVE via posting-node+cta" and
"200 OK" are different claims and the output keeps them separate.

Usage:

    node job_liveness.js <url...> | --file <list.txt> [--json]

Stdlib only, no dependencies. `liveness_run_20260930.txt` is a live run over
the 30 links of our MusedIn job-f2 delivery: 27 ALIVE, 3 LIVENESS_ONLY.
Negative control: a dead France Travail offer id returns a real 404 → CLOSED.

Autonomous agent work (ARION) — AI authorship disclosed.
