kaxil opened a new pull request, #74688:
URL: https://github.com/apache/airflow/pull/74688

   Since #73235, a task that asks for a human response again in the same Dag 
run (after a retry, a clear, or an operator that pauses more than once, such as 
common.ai's per-tool approval in #74686) replaces the request's subject, body 
and options and clears the earlier answer. The response endpoint still accepts 
any answer for the task instance, though. A reviewer who loaded the first 
request, or a second reviewer with that page still open, can answer the new 
request while reading the old one: approve refund 2 while the screen says 
refund 1.
   
   The response payload now takes an optional `request_created_at`, the 
`created_at` of the request the client read. The upsert resets `created_at` on 
every repeat request, so a mismatch means the request was replaced, and the 
endpoint returns 409. The Required Actions form captures `created_at` when it 
loads and sends it back; a REST API caller that leaves the field out answers 
whatever request is waiting, as before.
   
   ## Design rationale
   
   **Why `created_at` and not a new revision column?** The execution API upsert 
already sets `created_at` to the current time on every request 
(`execution_api/routes/hitl.py`), so it changes exactly when the content does. 
No migration, and the GET endpoints already return it. It is `AwareDatetime`, 
so a naive timestamp gets a 422 instead of a 409 that reloading can never fix; 
`UtcDateTime` keeps microseconds on all three backends, so the value from the 
GET compares equal when sent back unchanged.
   
   **The form remounts per request, and keeps the value it loaded.** The 
Required Actions modal polls, and its form was keyed on the task instance, so a 
repeat request swapped the body under an open form while keeping what the user 
had typed. Both places that render the form now key it on `created_at`, and the 
form sends the `created_at` captured at mount, so an answer can only ever name 
the request the user was shown. After any refused answer the form reloads the 
HITL queries, so the next attempt shows the request that is waiting.
   
   **An answered request still reports "already updated".** That check runs 
first, so a stale form submitted after the newer request was answered is told 
the request was answered, not to answer one that is no longer waiting.
   
   ## Gotchas
   
   - #73235 is in 3.4.0b1. If this lands only on main, 3.4.0 ships the request 
replacement without this guard; it is a candidate for `v3-4-test`, which is the 
release manager's call since it adds an optional field to the public API during 
the beta.
   - Clients other than the UI form get the guard only if they send the field. 
Links built with `HITLOperator.generate_link_to_ui`, which notifiers send, open 
the Required Actions page, so they are covered; airflow-ctl has no HITL command.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to