Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.production.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Production deployments intentionally have no working secret defaults.
# Copy this file to .env.production, replace every CHANGE-ME value, keep the
# result out of version control, and pin a released OSApplyTrack version.
OSAPPLYTRACK_VERSION=1.67.0
OSAPPLYTRACK_VERSION=1.68.0
POSTGRES_USER=applytrack
POSTGRES_PASSWORD=CHANGE-ME-use-a-long-random-password
POSTGRES_DB=applytrack
Expand Down
2 changes: 1 addition & 1 deletion BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ review items and unsupported forms still need human attention.
- [x] [#438 — Agent: phone numbers rejected — an international widget's dial code, and a résumé parse overwriting the box (Motion Recruitment, Robert Half) — fixed in 1.67.0](https://github.com/CryptoJones/OSApplyTrack/issues/438)
- [x] [#439 — Agent: a generic placeholder ("Enter") is taken for a field's label — fixed in 1.67.0](https://github.com/CryptoJones/OSApplyTrack/issues/439)
- [ ] [#440 — Agent: Manatal (careers-page.com) Radix selects and checkbox are not driven](https://github.com/CryptoJones/OSApplyTrack/issues/440) (Yo AI Labs: after #436 the résumé attaches and the hidden questions are found, but the Nationalities / years / salary Radix selects and the terms checkbox are custom widgets neither discovered nor driven)
- [ ] [#443 — Agent: drive Workday's application wizard end to end](https://github.com/CryptoJones/OSApplyTrack/issues/443) (`BrowserCanSubmit` still refuses Workday, so #277's account recipe never gets a run to fire in; NVIDIA JR2023533 was walked by hand on 2026-10-12 and the seven steps — create account under the `click_filter` overlay with the `beecatcher` honeypot empty, résumé autofill, My Information, My Experience, Application Questions, Voluntary Disclosures, Review → Submit — are mapped on the issue, with the résumé parser's junk Work Experience rows and the Enter-committed typeaheads as the two things a driver must handle)
- [x] [#443 — Agent: drive Workday's application wizard end to end](https://github.com/CryptoJones/OSApplyTrack/issues/443) (`BrowserCanSubmit` refused Workday, so #277's account recipe never got a run to fire in; NVIDIA JR2023533 was walked by hand on 2026-10-12. **1.68.0:** Workday is driven. The application starts at the manual flow (`…/apply/applyManually`, never the résumé autofill, whose parse explodes an "earlier roles" list into rows with no company); the way in presses Apply Manually and "Sign in with email" — the password box sits behind that choice — and the sign-in types the saved account key by key (a fill Workday sometimes never hears), presses the click_filter overlay beside the aria-hidden Sign In, waits for a complaint rather than a spinner, and loads the address again afterwards, because a sign-in from inside the flow draws "Something went wrong — please refresh". A step is read only once its controls have held still (the address and phone arrive a second after the names). Discovery and the sweep now see Workday's drop-down buttons (`aria-haspopup=listbox`, named by their label, their group, or — for a named button — the text above them), its search boxes (chips beside the input are the answer, committed with Enter), and radio groups required on their wrapper; the page header's own menus are never questions. The walk reads each step's menus so a question handed back carries its choices, ticks the terms box, attaches the résumé to the file input the packet never saw, declines a demographic drop-down Workday requires an answer to (never a guess), leaves a question the draft already answers alone, waits out the Save and Continue Workday disables while it autosaves, and hands back only starred fields when Next stays disabled. My Experience's Work Experience and Education rows are filled from the structured résumé (three jobs, every school: title, company, month/year spinbuttons, "I currently work here", description; school, Degree menu, Field of Study, years) and rows a draft already holds are completed, not duplicated. A long menu (Country) is typed at so the listbox jumps to the entry. The drafter's phone rule no longer answers "Phone Device Type" with the number. Verified live on nvidia.wd5 as a dry run: three rounds — page 2's rows and page 3's authorization pair handed back and answered — then every step to Review.)
- [x] [#417 — Agent: a résumé dropzone whose wrapping label is only "Click or drag & drop PDF" is never recognised as the résumé (Ethos) — fixed in 1.67.0: dropzone text never names a file field; its row's title does](https://github.com/CryptoJones/OSApplyTrack/issues/417)
- [x] [#418 — Agent: an application form folded behind "Easy Apply" is never revealed (N-iX) — fixed in 1.67.0: "Easy/Quick/One-click Apply" open the form (N-iX itself then ends at a reCAPTCHA v2 handoff)](https://github.com/CryptoJones/OSApplyTrack/issues/418)
- [x] [#419 — Agent: a footer newsletter signup (mc4wp) is discovered as an application question (Tecbrains) — fixed in 1.67.0: newsletter forms are page widgets](https://github.com/CryptoJones/OSApplyTrack/issues/419)
Expand Down
5 changes: 3 additions & 2 deletions SPRINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,9 @@ All four shipped; **v1 is feature-complete** (every `plan.md` build step is done
`/application`) get real questions this way; the same `PacketQuestion` shape
Greenhouse's API gives, so the drafter and the submitter need no per-ATS code.
The generic adapter (fill by label, refuse to click on an unmapped required
field) is behind the per-tenant `long_tail` opt-in (0023). Workday stays
manual, permanently: detected, packet prepared, routed to copy-and-open.
field) is behind the per-tenant `long_tail` opt-in (0023). Workday was routed
to copy-and-open until #443 (v1.68.0): its wizard is now walked signed in as
the candidate, with the account made on the way under #277's opt-in.
- ✅ **Step 6 — Encryption at rest, operator allowlist & captcha code fallback** (#117, #156, #165, #166; v1.26–v1.27):
Sensitive columns sealed with AES-256-GCM at rest (résumés, cover letters, packet
answers, evidence screenshots, tenant API keys, bot tokens). Production audit closed
Expand Down
29 changes: 26 additions & 3 deletions api/ApplyTrack.Api.Tests/AtsProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ public void The_apply_url_is_the_forms_page(string link, string expected) =>
Assert.Equal(expected, AtsProvider.ApplyUrl(link, AtsProvider.Detect(link, "")));

[Fact]
public void The_browser_drives_the_discovered_boards_and_never_an_aggregator_or_workday()
public void The_browser_drives_the_discovered_boards_and_workday_and_never_an_aggregator()
{
foreach (var p in new[] { AtsProvider.Greenhouse, AtsProvider.Lever, AtsProvider.Ashby, AtsProvider.Workable, AtsProvider.Breezy, AtsProvider.SmartRecruiters, AtsProvider.Join })
Assert.True(AtsProvider.BrowserCanSubmit(p, longTailOptIn: false), p);
Assert.False(AtsProvider.BrowserCanSubmit(AtsProvider.Workday, longTailOptIn: true));
// Workday's wizard is walked signed in as the candidate (#443).
Assert.True(AtsProvider.BrowserCanSubmit(AtsProvider.Workday, longTailOptIn: false));
Assert.False(AtsProvider.BrowserCanSubmit(AtsProvider.SuccessFactors, longTailOptIn: true));
Assert.False(AtsProvider.BrowserCanSubmit(AtsProvider.Aggregator, longTailOptIn: true));
Assert.False(AtsProvider.BrowserCanSubmit(AtsProvider.Unknown, longTailOptIn: false));
Expand Down Expand Up @@ -174,7 +175,7 @@ public void Easy_apply_is_driven_only_by_its_own_switch()
Assert.True(AtsProvider.BrowserCanSubmit(AtsProvider.LinkedInEasy, longTailOptIn: false, linkedInEasyOptIn: true));
// And it switches nothing else on.
Assert.False(AtsProvider.BrowserCanSubmit(AtsProvider.Aggregator, longTailOptIn: true, linkedInEasyOptIn: true));
Assert.False(AtsProvider.BrowserCanSubmit(AtsProvider.Workday, longTailOptIn: true, linkedInEasyOptIn: true));
Assert.False(AtsProvider.BrowserCanSubmit(AtsProvider.SuccessFactors, longTailOptIn: true, linkedInEasyOptIn: true));
}

[Fact]
Expand Down Expand Up @@ -205,4 +206,26 @@ public void A_kept_linkedin_session_becomes_cookies_on_the_whole_site()
[InlineData("https://jobs.lever.co/acme/1", null)]
public void A_workday_posting_has_a_json_address_that_says_whether_it_still_exists(string link, string? expected) =>
Assert.Equal(expected, AtsProvider.WorkdayJobApi(link));

[Theory]
[InlineData("https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/US-CA-Santa-Clara/Senior-Solutions-Architect--Inference-Service-Providers_JR2023533/apply?source=Eightfold",
"https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite/job/US-CA-Santa-Clara/Senior-Solutions-Architect--Inference-Service-Providers_JR2023533/apply/applyManually")]
[InlineData("https://allstate.wd5.myworkdayjobs.com/en-US/allstate_careers/job/USA---IL-Remote/Lead-Net-Software-Engineer_R34186/?src=x",
"https://allstate.wd5.myworkdayjobs.com/en-US/allstate_careers/job/USA---IL-Remote/Lead-Net-Software-Engineer_R34186/apply/applyManually")]
[InlineData("https://strongtie.wd1.myworkdayjobs.com/en-US/External/job/Plano-TX/Senior-Software-Engineer---Cloud-Services_R1",
"https://strongtie.wd1.myworkdayjobs.com/en-US/External/job/Plano-TX/Senior-Software-Engineer---Cloud-Services_R1/apply/applyManually")]
public void A_workday_application_starts_at_the_manual_flow_with_the_query_dropped(string link, string expected)
{
// The manual flow (#443): the sign-in choices, then step one — not the résumé autofill.
Assert.Equal(expected, AtsProvider.WorkdayApplyUrl(link));
Assert.Equal(expected, AtsProvider.ApplyUrl(link, AtsProvider.Workday));
}

[Fact]
public void A_link_that_is_not_a_workday_posting_has_no_manual_flow()
{
Assert.Null(AtsProvider.WorkdayApplyUrl("https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"));
Assert.Null(AtsProvider.WorkdayApplyUrl("https://boards.greenhouse.io/acme/jobs/1"));
Assert.Equal("https://boards.greenhouse.io/acme/jobs/1", AtsProvider.ApplyUrl("https://boards.greenhouse.io/acme/jobs/1", AtsProvider.Workday));
}
}
Loading
Loading