The warehouse and RecruitOS have been out of step for months
Results
The ticket
This is PROBLEM.md exactly as the agent received it. Vendor documentation and the starter repository ship inside the task environment and are not reproduced here.
The warehouse and RecruitOS have been out of step for months
From: Data Platform / Integrations Vendor: RecruitOS Surface: polling, webhooks Category: harden · Tier: 3
Context
Everything downstream of recruiting at Harbour reads the warehouse. Reporting, the exec dashboard, the weekly pipeline review — none of them talk to RecruitOS. The nightly pass in this repo is the only thing that puts RecruitOS data into the warehouse, and it has run unattended since the spring.
Last week a hiring manager asked why a candidate she placed in March still shows as screening. We spot-checked forty rows against RecruitOS by hand and nine were wrong. We have no idea how long it has been like that, or how much more of the file is wrong.
The connector runs, exits 0 and writes its report every night, and has been
quietly wrong for months. That is what you are fixing. The file it reads,
input/warehouse_mirror.json, is exactly what last night’s run left behind — we
have not cleaned it up, because the pass has to cope with whatever it finds.
The Data Platform desk note for this job is at
docs/harbour-parity-desk-note.md.
The rules
These are ours, not RecruitOS’s.
- The warehouse holds one row per candidate, job and application, live records
only, keeping one value for each:
statusfor candidates and jobs,stagefor applications. That value is what “in step” means. - Every row where the warehouse and RecruitOS are not in step gets exactly one
line in the report, and the vocabulary is closed:
drop— the warehouse holds an id RecruitOS has never issued.add— RecruitOS serves the record, the warehouse does not hold it.remove— the warehouse holds it and RecruitOS no longer serves it live.update— both hold it and the value differs.
- A record that is already in step gets no line.
- The census we hand the Data Platform is the number of live records the warehouse holds for each collection once the pass is finished.
Full vendor documentation is in docs/ — start at docs/index.md.
What we need
The harness runs your code exactly as follows — both commands are the contract:
python -m harbour_parity_probe serve # the listener, long-running
python -m harbour_parity_probe sync # one nightly pass
Output artifacts
output/import_report.csv— header row plus one row per divergence, sorted byentitythenrecord_id, with columnsentity(candidate,joborapplication),record_id,divergence(one of the four names above),mirror_value(what the warehouse held; empty foradd) andvendor_value(what RecruitOS serves for it as a live record; empty forremoveanddrop).output/result.json—source,snapshot_synced_through,divergences(one integer per divergence name) andcensus(one integer per collection).output/events.json—applied: one entry per webhook event the listener accepted, each withevent_id,eventandentity_id.
Environment
| Variable | Meaning |
|---|---|
VENDOR_BASE_URL | RecruitOS sandbox base URL |
RO_CLIENT_ID, RO_CLIENT_SECRET | RecruitOS credentials from the harness |
RO_WEBHOOK_SECRET | Webhook signing secret from the harness |
SNAPSHOT_FILE | Last night’s warehouse file |
OUTPUT_DIR | Directory where output artifacts land |
STATE_DIR | Directory shared between the listener and the pass |
SERVE_HOST, SERVE_PORT | Where the listener binds |
Engineering expectations
This ships to production, so the usual bar applies:
- Well-organized, modular code — keep transport, sync logic, and output concerns separate.
- Tests, runnable with
pytest, covering the core logic you add or change. - Be a good API citizen: handle the vendor’s real-world behavior the way a production integration must, and don’t hammer endpoints in retry loops. Our platform team audits vendor request logs — sloppy conduct gets flagged even when the data lands correctly.
Done when
sync exits 0, the report accounts for every row on which the warehouse and
RecruitOS disagree and for no row on which they agree, the census is the live
count the warehouse ends up holding, and the events the listener accepted are
the ones RecruitOS signed.
Graded checks (96)
Names of every assertion the grader runs for this task. Per-attempt pass/fail and details are on each trajectory page.
- webhook_deliveries_were_answered_by_the_listener
- webhook_event_acked_by_the_listener
- webhook_forged_delivery_refused
- parity_pass_completed
- divergence_application_app_0001
- divergence_application_app_0002
- divergence_application_app_0011
- divergence_application_app_0012
- divergence_application_app_0040
- divergence_application_app_0068
- divergence_application_app_0096
- divergence_application_app_0124
- divergence_application_app_0152
- divergence_application_app_0180
- divergence_application_app_0208
- divergence_application_app_0236
- divergence_application_app_0264
- divergence_application_app_0999
- divergence_candidate_cand_0001
- divergence_candidate_cand_0014
- divergence_candidate_cand_0028
- divergence_candidate_cand_0045
- divergence_candidate_cand_0046
- divergence_candidate_cand_0047
- divergence_candidate_cand_0048
- divergence_candidate_cand_0049
- divergence_candidate_cand_0050
- divergence_candidate_cand_0051
- divergence_candidate_cand_0052
- divergence_candidate_cand_0053
- divergence_candidate_cand_0054
- divergence_candidate_cand_0056
- divergence_candidate_cand_0069
- divergence_candidate_cand_0082
- divergence_candidate_cand_0095
- divergence_candidate_cand_0108
- divergence_candidate_cand_0121
- divergence_candidate_cand_0134
- divergence_candidate_cand_0147
- divergence_candidate_cand_0160
- divergence_candidate_cand_0173
- divergence_candidate_cand_0186
- divergence_candidate_cand_0199
- divergence_candidate_cand_0212
- divergence_candidate_cand_0225
- divergence_candidate_cand_0238
- divergence_candidate_cand_0777
- divergence_candidate_cand_0888
- divergence_candidate_cand_90001
- divergence_candidate_cand_90002
- divergence_candidate_cand_90003
- divergence_candidate_cand_90004
- divergence_candidate_cand_90005
- divergence_candidate_cand_90006
- divergence_candidate_cand_90007
- divergence_candidate_cand_90008
- divergence_job_job_0001
- divergence_job_job_0002
- divergence_job_job_0004
- divergence_job_job_0005
- and 36 more