api.jobs.timeline.update_task — silent no-op investigation
Verdict: SDK defect, corrected. Not an api_behavior_defect, and not
environment-blocked. No issue filed against the API — the payload never matched
the documented contract, so the server had nothing to apply.
Symptom
PATCH /job/{jobDisplayId}/timeline/{timelineTaskId} returned HTTP 204 and
changed nothing observable. Every field the SDK offered was ignored:
PATCH scheduledDate = '2026-08-05T14:00:00' -> read-back None SILENT NO-OP
PATCH completedDate = '2026-08-06T10:00:00' -> read-back None SILENT NO-OP
PATCH comments = 'probe' -> read-back None SILENT NO-OP
A 204 is indistinguishable from success at the call site. The endpoint looked healthy while discarding every payload.
Preserved evidence
Captured live on 2026-07-27 against job 4000000, task 687968.
Route: PATCH /api/api/job/4000000/timeline/{timelineTaskId}
Exact serialized payload the SDK sent:
{"scheduledDate": "2026-08-05T14:00:00", "comments": "probe"}
Request headers (authorization redacted): Content-Type: application/json,
Accept: */*, Accept-Encoding: gzip, deflate, Connection: keep-alive.
Response: 204 No Content, empty body.
Before / after the PATCH (GET /job/4000000/timeline/{id}):
Field |
Before |
After |
Changed |
|---|---|---|---|
|
|
|
yes |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
modifiedDate moving proves the request reached the record and the server
committed a write — it simply had no recognized values to apply.
Root cause
Swagger UpdateTaskModel (ab/api/schemas/acportal.json):
{
"type": "object",
"properties": {
"truckId": {"$ref": "#/components/schemas/UpdateTruckModel"},
"plannedStartDate": {"$ref": "#/components/schemas/UpdateDateModel"},
"preferredStartDate": {"$ref": "#/components/schemas/UpdateDateModel"},
"plannedEndDate": {"$ref": "#/components/schemas/UpdateDateModel"},
"preferredEndDate": {"$ref": "#/components/schemas/UpdateDateModel"}
},
"additionalProperties": false
}
UpdateDateModel and UpdateTruckModel are both override wrappers —
{"value": <scalar-or-null>}.
The SDK’s TimelineTaskUpdateRequest declared status, scheduledDate,
completedDate, comments. Not one appears in the schema. With
additionalProperties: false and a 204 response, a body of entirely unknown
keys is accepted and dropped without an error to notice.
Two related metadata defects in the same routes:
_POST_TIMELINEdeclared norequest_model, so the workbench resolved the query-params model (TimelineCreateParams) as the request body and refused a validtaskCode. This blocked certification ofcreate_task._PATCH_TASKdeclaredresponse_model="TimelineTask"where Swagger says 204 No Content. The method claimed to return a task it never receives.
Fix
TimelineTaskUpdateRequest now mirrors UpdateTaskModel exactly, with a
model_validator that accepts a bare scalar and wraps it — the wrapper is the
wire shape, not something a caller should have to remember, and silently sending
an unwrapped scalar is the very failure this model exists to prevent.
tests/unit/test_timeline_update_transport.py asserts on the serialized
request body against the checked-in Swagger schema, offline and
deterministically. A test that mocked the transport and asserted “204 was
returned” would have passed throughout the entire regression.
Re-run of the controlled session
wire : {"plannedStartDate": {"value": "2026-08-05T14:00:00"}}
diff [update]:
plannedStartDate: '2026-08-01T09:00:00' -> '2026-08-05T14:00:00'
modifiedDate: None -> '2026-07-27T14:25:27.840000'
UPDATE VERIFIED — value actually persisted
wire : {"plannedStartDate": {"value": "2026-08-01T09:00:00"}}
diff [restore]:
plannedStartDate: '2026-08-05T14:00:00' -> '2026-08-01T09:00:00'
RESTORATION VERIFIED
teardown: {"status": "1 - New Job", "tasks": []} SESSION: PASS
The endpoint is correct once the payload matches the contract. It is not
certified in this change — certification of update_task is left for the
operator to authorize separately.