Spec
Tier: Probabilistic
Name: PERM Filing Cross-Check
Cross-reference a posting against public DOL PERM labor-certification filings. Under PERM, an employer sponsoring a foreign worker for a green card has to publicly advertise the role to test the labor market. So a live posting that matches a recent PERM filing may be a legally required ad for a role that is already effectively filled, which is a classic ghost. This is correlational, not proof: large employers file many PERMs and also hire real people for the same titles.
Data source (public, no auth, no scraping)
U.S. Department of Labor, Office of Foreign Labor Certification (OFLC) PERM disclosure data. These are public quarterly files at https://www.dol.gov/agencies/eta/foreign-labor/performance. Fields include employer name, job title, work location, prevailing wage, SOC code, case status, and filing date. This is official published government data. Read-only, no login, no scraping.
Do not use Layoffs.fyi (no public API, so consuming it means scraping), NewsAPI (needs a key), SEC EDGAR scraping, or anything behind authentication.
The size problem (the part the old spec got wrong)
The raw OFLC PERM disclosure files are large, hundreds of MB of Excel or CSV per year. Do not download or parse them inside EvaluateAsync, and do not ship the raw file.
Approach
- Ship a pre-shrunk, column-filtered PERM index as a bundled seed (for example data/perm-index.json). Keep only the last 12 months, only the columns the match needs (normalized employer, job title, state or location, SOC code, filing date), pre-keyed by normalized employer for fast lookup. Small enough to bundle and query in memory.
- The signal is RequiresNetwork = false. EvaluateAsync(JobPosting) reads only the local PERM index and makes no network call.
- In EvaluateAsync: normalize posting.CompanyName and fuzzy-match against the index employers (reuse DomScorer.NormalizedSimilarity). On a strong employer match, look for a filing with a similar job title and, when posting.Location is present, the same state, within the last 12 months. If you find one, return a SignalResult with a low score and moderate confidence. Return null otherwise.
- Refresh (optional, separate from this signal): you can add a new data-provider class (for example PermIndexBuilder) that downloads the latest OFLC file, filters and shrinks it into the index format, and writes it to the cache on some cadence, with an 8-second per-request timeout and graceful failure. None of that goes inside EvaluateAsync. If you skip the refresher, the bundled seed is enough for a first version.
Precision rules (bias for precision over recall)
- A PERM match means the employer sponsored someone for a similar role. It does not mean this exact posting is the PERM ad. Never auto-condemn.
- Require a strong employer-name match and a similar job title (an SOC-code match helps). A bare employer-name match must not fire on its own, since large employers file constantly.
- Low score, moderate confidence. Present it as correlation, for example: "{Employer} filed a PERM labor certification for a similar role ({title}) in {state} on {date}. Some postings that match a PERM filing are legally required ads for roles that are already filled."
- Only recent filings fire (12 months or less).
- Never return a binary "ghost" or "fake" verdict on a named company. This is one probabilistic line of evidence, not a conviction.
Acceptance criteria
Quick start
Copy the agent prompt from SIGNAL_AUTHORING.md, paste the Spec block above into it, and hand it to your coding agent.
Spec
Tier: Probabilistic
Name: PERM Filing Cross-Check
Cross-reference a posting against public DOL PERM labor-certification filings. Under PERM, an employer sponsoring a foreign worker for a green card has to publicly advertise the role to test the labor market. So a live posting that matches a recent PERM filing may be a legally required ad for a role that is already effectively filled, which is a classic ghost. This is correlational, not proof: large employers file many PERMs and also hire real people for the same titles.
Data source (public, no auth, no scraping)
U.S. Department of Labor, Office of Foreign Labor Certification (OFLC) PERM disclosure data. These are public quarterly files at https://www.dol.gov/agencies/eta/foreign-labor/performance. Fields include employer name, job title, work location, prevailing wage, SOC code, case status, and filing date. This is official published government data. Read-only, no login, no scraping.
Do not use Layoffs.fyi (no public API, so consuming it means scraping), NewsAPI (needs a key), SEC EDGAR scraping, or anything behind authentication.
The size problem (the part the old spec got wrong)
The raw OFLC PERM disclosure files are large, hundreds of MB of Excel or CSV per year. Do not download or parse them inside EvaluateAsync, and do not ship the raw file.
Approach
Precision rules (bias for precision over recall)
Acceptance criteria
Quick start
Copy the agent prompt from SIGNAL_AUTHORING.md, paste the Spec block above into it, and hand it to your coding agent.