Агент v16: зазор между записями и сверка поля оценки доски (#53, I-1, I-2)

I-1: если ensure_fields записал поля через PUT /issue, перед записью оценки — зазор
ensure_fields. I-2: fieldId оценки доски сверяется с field_ids.story_points; расхождение —
failed без записи.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-25 22:28:19 +04:00
parent a3247d6f2d
commit 20747c2b65
3 changed files with 31 additions and 1 deletions

View File

@ -32,3 +32,5 @@ the appropriate screen, or unknown». Поле оценки не на экран
| 25.10.2026 | R-2: сначала остальные поля, затем оценка | новый путь не должен ломать остальные поля | частичная запись, повтор безопасен | | 25.10.2026 | R-2: сначала остальные поля, затем оценка | новый путь не должен ломать остальные поля | частичная запись, повтор безопасен |
| 25.10.2026 | R-3: без `GET /issue`, если в команде только оценка | минус вызов Jira на команду в пакете оценок | нет | | 25.10.2026 | R-3: без `GET /issue`, если в команде только оценка | минус вызов Jira на команду в пакете оценок | нет |
| 25.10.2026 | R-5: `value` — число как есть | Jira отдаёт число; живая проверка до пакета | одна правка агента | | 25.10.2026 | R-5: `value` — число как есть | Jira отдаёт число; живая проверка до пакета | одна правка агента |
| 25.10.2026 | I-1 (финальное ревью): между `PUT /issue` и `PUT …/estimation` одной команды — зазор `ensure_fields` | политика темпа: зазор между записями внутри команды | пакет «оценка + поля» идёт медленнее |
| 25.10.2026 | I-2 (финальное ревью): `fieldId` оценки доски сверяется с `field_ids.story_points` (если задан), расхождение — `failed` без записи | иначе число уйдёт в чужое поле доски другого проекта | лишний отказ, если поле настроено неверно — правится настройкой |

View File

@ -890,7 +890,14 @@ class Executor:
changed = self.ensure_issue_fields(key, p, sp_fid) changed = self.ensure_issue_fields(key, p, sp_fid)
if by_board: if by_board:
path = AGILE + "/issue/%s/estimation" % key path = AGILE + "/issue/%s/estimation" % key
if not _same_points(self.jira.get(path, boardId=p["board_id"]).get("value"), p["story_points"]): est = self.jira.get(path, boardId=p["board_id"])
own = (self.s.get("field_ids") or {}).get("story_points")
if own and est.get("fieldId") and est["fieldId"] != own: # доска оценивает другим полем — не писать туда
return self._out("failed", error="у доски %s оценка — поле %s, а не story points (%s)"
% (p["board_id"], est["fieldId"], own))
if not _same_points(est.get("value"), p["story_points"]):
if changed: # вторая запись команды — по зазору типа (политика темпа)
self.gap("ensure_fields")
self.jira.call("PUT", path, {"value": p["story_points"]}, params={"boardId": p["board_id"]}) self.jira.call("PUT", path, {"value": p["story_points"]}, params={"boardId": p["board_id"]})
changed.append("story_points") changed.append("story_points")
return self._out("done", {"changed": changed}) return self._out("done", {"changed": changed})

View File

@ -352,5 +352,26 @@ class ExecutorFieldsTest(unittest.TestCase):
self.assertEqual((out["status"], out["error"]), ("failed", "story_points — число ≥ 0 или null")) self.assertEqual((out["status"], out["error"]), ("failed", "story_points — число ≥ 0 или null"))
self.assertEqual(self.fake.requests, []) self.assertEqual(self.fake.requests, [])
def test_points_by_board_gap_after_fields(self): # I-1: зазор ensure_fields между двумя записями
gaps = []
self.ex.sleep = gaps.append
self.ex.s["pacing"] = {"ensure_fields": 120}
self.points("AURORA-1", None)
self.run_cmd("ensure_fields", key="AURORA-1", duedate="2026-10-30", story_points=3, board_id=4216)
self.assertEqual(gaps, [120])
self.run_cmd("ensure_fields", key="AURORA-1", duedate="2026-10-30", story_points=5, board_id=4216)
self.assertEqual(gaps, [120]) # PUT /issue не было — паузы нет
def test_points_by_board_other_field(self): # I-2: оценка доски — не поле story points
self.points("AURORA-1", 1.0)
self.ex.s["field_ids"] = dict(SETTINGS["field_ids"], story_points="customfield_20000")
out = self.run_cmd("ensure_fields", key="AURORA-1", story_points=2, board_id=4216)
self.assertEqual((out["status"], out["error"]),
("failed", "у доски 4216 оценка — поле customfield_10106, а не story points (customfield_20000)"))
self.assertEqual(self.fake.estimation_bodies, [])
self.ex.s["field_ids"] = dict(SETTINGS["field_ids"], story_points="customfield_10106")
out = self.run_cmd("ensure_fields", key="AURORA-1", story_points=2, board_id=4216)
self.assertEqual(out["result"], {"changed": ["story_points"]})
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()