Record, change and delete offers through the API #1232

Closed
tiagoagueda wants to merge 0 commits from f-1101 into main
Owner

Closes #1101: offers can be recorded, changed and deleted through the API, not only read.

  • POST /api/v1/offers, PATCH /api/v1/offers/{id}, DELETE /api/v1/offers/{id}, under the write scope like interviews and reminders.
  • Every write goes through the page's own OfferForm, then record_offer, revise_offer or withdraw_offer, so the checks, the timeline entry (signed by the token's name) and the answer-by reminder are the page's. A form refusal is the usual validation-failed problem document, with the form's messages in the token owner's language.
  • PATCH changes only the fields sent; null clears one.
  • The isolation sweep covers the new calls with someone else's offer and someone else's application.
  • postulo-mcp is untouched and keeps offering no delete tool.

Not done: the page pre-fills a new offer from the advertised salary, the API does not (the client sends the terms); no Idempotency-Key (only captures have one).

Tested: the six new tests fail without the change; offers, API, API problems, isolation sweep and the usual guards pass. No migration, no new string.

The wiki's The capture API gains the three rows once this is merged.

🤖 Generated with Claude Code

Closes #1101: offers can be recorded, changed and deleted through the API, not only read. - `POST /api/v1/offers`, `PATCH /api/v1/offers/{id}`, `DELETE /api/v1/offers/{id}`, under the `write` scope like interviews and reminders. - Every write goes through the page's own `OfferForm`, then `record_offer`, `revise_offer` or `withdraw_offer`, so the checks, the timeline entry (signed by the token's name) and the answer-by reminder are the page's. A form refusal is the usual `validation-failed` problem document, with the form's messages in the token owner's language. - `PATCH` changes only the fields sent; `null` clears one. - The isolation sweep covers the new calls with someone else's offer and someone else's application. - postulo-mcp is untouched and keeps offering no delete tool. Not done: the page pre-fills a new offer from the advertised salary, the API does not (the client sends the terms); no `Idempotency-Key` (only captures have one). Tested: the six new tests fail without the change; offers, API, API problems, isolation sweep and the usual guards pass. No migration, no new string. The wiki's *The capture API* gains the three rows once this is merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Offers could be read through the API and not written: the router said
recording one was the interface's "for now (#237)", and #237 is closed.
An agent that moved an application to Offer could not write down what
was offered.

POST /offers, PATCH /offers/{id} and DELETE /offers/{id} now take the
write scope, as interviews and reminders do. Each hands the body to the
interface's own OfferForm, so the page and the API refuse alike, and the
form's refusal is answered as a validation-failed problem document with
the form's messages in `errors`. Recording goes through record_offer
(timeline entry signed by the token, answer-by reminder, the move to
Offer), a change through revise_offer with the form's changed fields (a
revision only when a term changed; the reminder follows a moved date),
and a delete through withdraw_offer, which ticks the reminder off. A
PATCH reads only the fields sent, and a null clears one.

The isolation sweep covers the two new addresses and the application_id
in the body; the unscoped-queries list names the application lookup.
postulo-mcp is untouched and keeps offering no delete tool.

Closes #1101

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record #1101 in the changelog
Some checks failed
CI / Unit tests and coverage (Python 3.14) (pull_request) Failing after 20m27s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Failing after 1m6s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m9s
CI / Security audit (pull_request) Successful in 24s
CI / Browser tests (Chromium) (pull_request) Failing after 44m54s
5eb0a495c6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extract the catalogues for the offers' new place in the API
Some checks failed
CI / Browser tests (Chromium) (pull_request) Failing after 4s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m25s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m22s
CI / Security audit (pull_request) Successful in 20s
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 14m18s
31fde5ac50
The API's offer calls reuse sentences the catalogues already hold, so
their source references gained the router's file; without extracting,
the catalogue checks found the files out of step with the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run CI again: data.forgejo.org answered 502 to the browser job
Some checks failed
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
c0baf6afa7
The browser job stopped before its first step, fetching actions/cache
from data.forgejo.org, which answered 502. Nothing in the change was
tested by it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tiagoagueda force-pushed f-1101 from c0baf6afa7
Some checks failed
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
to bb8e386489
All checks were successful
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 15m13s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m15s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m11s
CI / Security audit (pull_request) Successful in 21s
CI / Browser tests (Chromium) (pull_request) Successful in 36m11s
2026-11-12 01:52:33 +00:00
Compare
Author
Owner

Superseded by #1237, which stacks this branch with two other features so they share one CI run (each merge here is fast-forward-only, so landing them one by one would mean a CI run each after every rebase). The commits are the same.

🤖 Generated with Claude Code

Superseded by #1237, which stacks this branch with two other features so they share one CI run (each merge here is fast-forward-only, so landing them one by one would mean a CI run each after every rebase). The commits are the same. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tiagoagueda closed this pull request 2026-11-12 02:11:12 +00:00
All checks were successful
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 15m13s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m15s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m11s
CI / Security audit (pull_request) Successful in 21s
CI / Browser tests (Chromium) (pull_request) Successful in 36m11s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo!1232
No description provided.