From 83f557b40e3978a82fbbf36235f6db01be4803d2 Mon Sep 17 00:00:00 2001 From: "Aaron K. Clark" Date: Tue, 13 Oct 2026 03:09:57 -0500 Subject: [PATCH 1/2] fix(agent): grant the agent role INSERT and UPDATE on board_accounts (#446) agent_role.sql gave applytrack_agent INSERT/UPDATE on its own tables but not on board_accounts, which the agent writes when it takes a Sign in now, keeps the session it renewed, or saves an account #277 just made. RenewSessionsAsync runs before the judging pass, so once greenhouse.io's session came due every pass on pluto died with "permission denied for table board_accounts" (every tick since 2026-10-10 16:25 CDT; the last agent_events row is from 2026-10-09). The grant is re-applied on every boot like the rest of the script. Still no DELETE: an account is removed only by the person, from Settings. DbRoleTests proves the three writes and that a delete is refused. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015Xrsyx9xJYfnT2BzLMNFsB --- api/ApplyTrack.Api.Tests/DbRoleTests.cs | 27 +++++++++++++++++++ .../Migrations/Always/agent_role.sql | 4 +++ 2 files changed, 31 insertions(+) diff --git a/api/ApplyTrack.Api.Tests/DbRoleTests.cs b/api/ApplyTrack.Api.Tests/DbRoleTests.cs index 27c89c0..7119294 100644 --- a/api/ApplyTrack.Api.Tests/DbRoleTests.cs +++ b/api/ApplyTrack.Api.Tests/DbRoleTests.cs @@ -127,6 +127,33 @@ public async Task The_agent_role_updates_application_fields_but_never_tenant_or_ await AssertDeniedAsync(agent, "SELECT * FROM app_notes"); } + /// + /// The agent writes board_accounts in three places — taking a Sign in now, keeping the + /// session it renewed, saving an account it just created (#277) — and the grant missed + /// all three, so every pass on pluto died in RenewSessionsAsync for three days (#446). + /// + [Fact] + public async Task The_agent_role_writes_board_accounts_but_never_deletes_one() + { + UpgradeWith("agent-pw", "poller-pw"); + await using var owner = new NpgsqlConnection(pg.ConnectionString); + await owner.OpenAsync(); + long t = await TestAuth.EnsureUserAsync(owner, TestAuth.UniqueEmail()); + await owner.ExecuteAsync( + "INSERT INTO board_accounts (tenant_id, host, username, password_ciphertext, renew_requested_at) VALUES (@t, 'greenhouse.io', 'ada', 'sealed-pw', now())", + new { t }); + + await using var agent = await OpenAsAsync(Migrator.AgentRole, "agent-pw"); + Assert.Equal(1, await agent.ExecuteAsync( + "UPDATE board_accounts SET renew_requested_at = NULL WHERE tenant_id = @t AND host = 'greenhouse.io' AND renew_requested_at IS NOT NULL", new { t })); + Assert.Equal(1, await agent.ExecuteAsync( + "UPDATE board_accounts SET session_ciphertext = 's', session_expires_at = now(), updated_at = now() WHERE tenant_id = @t AND host = 'greenhouse.io'", new { t })); + Assert.Equal(1, await agent.ExecuteAsync( + "INSERT INTO board_accounts (tenant_id, host, username, password_ciphertext, updated_at) VALUES (@t, 'acme.wd5.myworkdayjobs.com', 'ada', 'sealed-pw', now()) " + + "ON CONFLICT (tenant_id, host) DO UPDATE SET username = EXCLUDED.username, password_ciphertext = EXCLUDED.password_ciphertext, updated_at = now()", new { t })); + await AssertDeniedAsync(agent, "DELETE FROM board_accounts WHERE tenant_id = @t", new { t }); + } + [Fact] public async Task A_changed_password_is_applied_on_the_next_boot_and_a_blank_one_is_skipped() { diff --git a/api/ApplyTrack.Api/Migrations/Always/agent_role.sql b/api/ApplyTrack.Api/Migrations/Always/agent_role.sql index 4527a51..8371c10 100644 --- a/api/ApplyTrack.Api/Migrations/Always/agent_role.sql +++ b/api/ApplyTrack.Api/Migrations/Always/agent_role.sql @@ -22,6 +22,10 @@ BEGIN REVOKE ALL ON contacts, application_contacts, app_notes FROM applytrack_agent; GRANT INSERT, UPDATE ON agent_events, agent_packets, agent_evidence, cover_letters, submit_requests, agent_workers, answer_bank TO applytrack_agent; + -- The agent takes a Sign in now (renew_requested_at), keeps the session it renews + -- and saves an account it has just created (#277) — no DELETE: an account is + -- removed only by the person, from Settings (#446). + GRANT INSERT, UPDATE ON board_accounts TO applytrack_agent; -- Status flips (ready / applied) go through ApplicationRepo's structured update, -- which writes exactly these columns — never tenant_id or name, so a compromised -- agent cannot move a row into another tenant (#345). The REVOKE drops the From 337059f5d8e5164b76a1f38a1ba6ac2f33964e90 Mon Sep 17 00:00:00 2001 From: "Aaron K. Clark" Date: Tue, 13 Oct 2026 03:10:03 -0500 Subject: [PATCH 2/2] chore: prepare v1.68.1 release Bump all five version files to 1.68.1 and add #446 to BACKLOG.md. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015Xrsyx9xJYfnT2BzLMNFsB --- .env.production.example | 2 +- BACKLOG.md | 1 + api/ApplyTrack.Api/ApplyTrack.Api.csproj | 2 +- pyproject.toml | 2 +- src/applytrack/__init__.py | 2 +- uv.lock | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.env.production.example b/.env.production.example index 9ec164c..d61431e 100644 --- a/.env.production.example +++ b/.env.production.example @@ -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.68.0 +OSAPPLYTRACK_VERSION=1.68.1 POSTGRES_USER=applytrack POSTGRES_PASSWORD=CHANGE-ME-use-a-long-random-password POSTGRES_DB=applytrack diff --git a/BACKLOG.md b/BACKLOG.md index 5751351..4ab6707 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -176,6 +176,7 @@ review items and unsupported forms still need human attention. - [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) - [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] [#446 — Agent role cannot write board_accounts: every judging pass has died since 2026-10-10](https://github.com/CryptoJones/OSApplyTrack/issues/446) (`agent_role.sql` granted the agent INSERT/UPDATE on its own tables but not on `board_accounts`, which it writes when it takes a Sign in now, keeps a renewed session, or saves an account #277 just made; `RenewSessionsAsync` runs before the judging pass, so once greenhouse.io's session came due the pass died on every tick and the Ready reconciler, the verdicts and portal renewal all stopped — the last `agent_events` row was 2026-10-09. **1.68.1:** the grant is re-applied on boot, and `DbRoleTests` proves the three writes and that a delete is still refused.) - [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) diff --git a/api/ApplyTrack.Api/ApplyTrack.Api.csproj b/api/ApplyTrack.Api/ApplyTrack.Api.csproj index 5153c05..47b783e 100644 --- a/api/ApplyTrack.Api/ApplyTrack.Api.csproj +++ b/api/ApplyTrack.Api/ApplyTrack.Api.csproj @@ -5,7 +5,7 @@ enable enable ApplyTrack.Api - 1.68.0 + 1.68.1 Aaron K. Clark Copyright 2026 Aaron K. Clark Apache-2.0 diff --git a/pyproject.toml b/pyproject.toml index b79357c..f66fb16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "applytrack-poller" -version = "1.68.0" +version = "1.68.1" description = "Discovery poller for OSApplyTrack — fetches and scores remote job leads into shared Postgres." requires-python = ">=3.10" license = { text = "Apache-2.0" } diff --git a/src/applytrack/__init__.py b/src/applytrack/__init__.py index dc38dd5..171a205 100644 --- a/src/applytrack/__init__.py +++ b/src/applytrack/__init__.py @@ -2,4 +2,4 @@ # Copyright 2026 Aaron K. Clark """applytrack — a local, markdown-backed job-application tracker.""" -__version__ = "1.68.0" +__version__ = "1.68.1" diff --git a/uv.lock b/uv.lock index d63584c..c86310c 100644 --- a/uv.lock +++ b/uv.lock @@ -43,7 +43,7 @@ wheels = [ [[package]] name = "applytrack-poller" -version = "1.68.0" +version = "1.68.1" source = { editable = "." } dependencies = [ { name = "cryptography" },