alissa-tools-github-orcloop 0.18.2__tar.gz → 0.18.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {alissa_tools_github_orcloop-0.18.2/src/main/alissa_tools_github_orcloop.egg-info → alissa_tools_github_orcloop-0.18.3}/PKG-INFO +1 -1
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/alissa_client.py +13 -3
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/merge.py +122 -30
- alissa_tools_github_orcloop-0.18.3/src/main/alissa/tools/github/orcloop/version +1 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3/src/main/alissa_tools_github_orcloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_orcloop-0.18.2/src/main/alissa/tools/github/orcloop/version +0 -1
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/LICENSE +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/MANIFEST.in +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/NOTICE +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/README.md +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/requirements.txt +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/setup.cfg +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/setup.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/__init__.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/__main__.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/alissa_cli.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/config.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/console.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/fleet_vitals.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/ghclient.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/lever.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/loop.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/loop_events.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/markers.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/proc.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/seats.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/sentinel.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/state.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/version.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/watchdog.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/workplan.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/workplan_html.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa/tools/github/orcloop/workplan_state.py +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa_tools_github_orcloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa_tools_github_orcloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa_tools_github_orcloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_orcloop-0.18.2 → alissa_tools_github_orcloop-0.18.3}/src/main/alissa_tools_github_orcloop.egg-info/top_level.txt +0 -0
|
@@ -1413,13 +1413,23 @@ class AlissaClient:
|
|
|
1413
1413
|
and return the new task's opaque id -- all the create answers
|
|
1414
1414
|
(`{"taskId": …}`; the number is a `get_task` away). The merge policy's
|
|
1415
1415
|
validation floor (issue #116) is the one caller: the "Validate after
|
|
1416
|
-
merge" task on the origin task,
|
|
1416
|
+
merge" task on the origin task, OWNED BY THE CALLER -- the fleet
|
|
1417
|
+
actor, the API's own default when `assign_to_actor_id` is left empty.
|
|
1418
|
+
|
|
1419
|
+
`assign_to_actor_id` seats another actor as owner and is subject to
|
|
1420
|
+
the API's owner-assign gate: a caller that is not an admin of a team
|
|
1421
|
+
the target belongs to (and does not pass `teamSlug`) is refused with
|
|
1422
|
+
`HTTP 403 FORBIDDEN: {'error': 'FORBIDDEN', 'message': 'Only a team
|
|
1423
|
+
admin can seat another member as owner; pass teamSlug'}`. The fleet
|
|
1424
|
+
actor is a member, not an admin, so the floor never passes it (issue
|
|
1425
|
+
#120); the parameter stays for a caller that can.
|
|
1417
1426
|
|
|
1418
1427
|
`criteria` is the API's shape verbatim (`{id, type, description}`
|
|
1419
1428
|
objects -- plain strings are refused with a 400). Permission is the
|
|
1420
1429
|
parent's OWNER, which the daemon's token is on the tasks it released;
|
|
1421
|
-
a refusal comes back as
|
|
1422
|
-
(the floor skips the merge
|
|
1430
|
+
a refusal comes back as an `AlissaError` subclass and the caller
|
|
1431
|
+
decides (the floor skips the merge -- and escalates once when the
|
|
1432
|
+
refusal is permanent)."""
|
|
1423
1433
|
body: dict = {
|
|
1424
1434
|
"title": title,
|
|
1425
1435
|
"definitionOfDone": definition_of_done,
|
|
@@ -84,14 +84,24 @@ messages). The gate, in order:
|
|
|
84
84
|
10. `dry_run` -> SKIPPED "would merge …", no writes -- the floor included.
|
|
85
85
|
9b. The FLOOR (issue #116, L3), only for a merge the policy downgraded from
|
|
86
86
|
an operator hold: BEFORE the merge call, once per (pr, head) (ledger
|
|
87
|
-
`merge:floor:<pr>:<head7>`), a comment on the origin Alissa task
|
|
88
|
-
the class and
|
|
89
|
-
downstream task
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
`merge:floor:<pr>:<head7>`), a comment on the origin Alissa task saying
|
|
88
|
+
the policy IS MERGING past the gate (class and reason), and a committed
|
|
89
|
+
`Validate after merge` downstream task OWNED BY THE CALLING ACTOR -- the
|
|
90
|
+
fleet actor, the identity that owns every fed task (issue #120: the API's
|
|
91
|
+
owner-assign gate refuses the fleet actor seating the origin's reporter
|
|
92
|
+
as owner, so the reporter is reached through the origin task, which is
|
|
93
|
+
the parent of the floor task and carries the floor comment). A write
|
|
94
|
+
failing TRANSIENTLY (5xx, 429, transport) -> SKIPPED "floor not posted",
|
|
95
|
+
retried next poll. A write failing PERMANENTLY (a 4xx that is not
|
|
96
|
+
transient: 403, 404, 422) -> one escalation comment on the origin task
|
|
97
|
+
(`merge:floor-failed:<pr>:<head7>`) saying the PR needs a human merge
|
|
98
|
+
until the floor can be written, one WARNING (L4), and every later poll
|
|
99
|
+
on this head is a quiet SKIP that writes nothing; a new head is a new
|
|
100
|
+
episode. Never a merge without the page. Three rows keep the retry from
|
|
101
|
+
repeating a write that landed: the comment's, the task's
|
|
102
|
+
(`merge:floor-task:<pr>:<head7>:<taskId>`, written the moment the create
|
|
103
|
+
returns, so a failed read-back or status set never creates a second
|
|
104
|
+
task), and the floor's.
|
|
95
105
|
11. Merge: `PUT /pulls/{n}/merge` with the configured method and
|
|
96
106
|
`sha=head_sha` -- the guard that makes GitHub refuse (405/409) when the
|
|
97
107
|
head moved between our reads and the merge; a refusal is SKIPPED and
|
|
@@ -133,7 +143,7 @@ from functools import lru_cache
|
|
|
133
143
|
from typing import Callable, Sequence
|
|
134
144
|
|
|
135
145
|
from . import loop_events
|
|
136
|
-
from .alissa_client import AlissaClient, AlissaError
|
|
146
|
+
from .alissa_client import AlissaClient, AlissaError, AlissaTransient
|
|
137
147
|
from .config import MERGE_POLICY_YOLO, MERGE_READINESS_CLASSES, Config
|
|
138
148
|
from .ghclient import (
|
|
139
149
|
CheckSummary,
|
|
@@ -249,6 +259,18 @@ def policy_verdict(config: Config, klass: str | None) -> tuple[bool, str]:
|
|
|
249
259
|
return (False, f"policy yolo holds class {klass} (in neither class list)")
|
|
250
260
|
|
|
251
261
|
|
|
262
|
+
def floor_failure_is_permanent(exc: AlissaError) -> bool:
|
|
263
|
+
"""Is this floor write refused for GOOD (issue #120) -- a 4xx that is not a
|
|
264
|
+
retry-later signal? 403 (the owner-assign gate, a missing role), 404 (the
|
|
265
|
+
origin task is gone or invisible), 422 (a payload the API refuses) do not
|
|
266
|
+
heal between polls; retrying them costs a WARNING and a refused write per
|
|
267
|
+
poll for as long as the head is approved. `AlissaTransient` (408/429/5xx
|
|
268
|
+
and transport failures, status 0) is excluded by CLASS, so the client's
|
|
269
|
+
taxonomy and this predicate cannot drift; a status-0 base error (the
|
|
270
|
+
create answered no `taskId`) is a malformed answer, retried too."""
|
|
271
|
+
return not isinstance(exc, AlissaTransient) and 400 <= exc.status < 500
|
|
272
|
+
|
|
273
|
+
|
|
252
274
|
# --- hard-stop path globs ----------------------------------------------------
|
|
253
275
|
#
|
|
254
276
|
# `fnmatch` has no `**`: `*` there crosses `/`, so `**/migrations/**` and
|
|
@@ -358,11 +380,16 @@ HELD_KIND_PREFIX = MERGE_KIND_PREFIX + "held:" # merge:held:<why>:<pr
|
|
|
358
380
|
# a failed read-back or status set finishes THAT task instead of creating a
|
|
359
381
|
# second (PR #117 round 1); the floor row is the one the merge reads. A
|
|
360
382
|
# fourth, `floor-unmerged`, is the once-per-head correction the origin task
|
|
361
|
-
# gets when the merge call it was paged about is then refused.
|
|
383
|
+
# gets when the merge call it was paged about is then refused. A fifth,
|
|
384
|
+
# `floor-failed` (issue #120), is the once-per-head escalation a floor write
|
|
385
|
+
# that failed PERMANENTLY (a non-transient 4xx) leaves: its row is what keeps
|
|
386
|
+
# every later poll on that head a quiet SKIP instead of a WARNING and a
|
|
387
|
+
# refused write per poll.
|
|
362
388
|
FLOOR_KIND_PREFIX = MERGE_KIND_PREFIX + "floor:" # merge:floor:<pr>:<head7>
|
|
363
389
|
FLOOR_COMMENT_KIND_PREFIX = MERGE_KIND_PREFIX + "floor-comment:" # merge:floor-comment:<pr>:<head7>
|
|
364
390
|
FLOOR_TASK_KIND_PREFIX = MERGE_KIND_PREFIX + "floor-task:" # merge:floor-task:<pr>:<head7>:<taskId>
|
|
365
391
|
FLOOR_UNMERGED_KIND_PREFIX = MERGE_KIND_PREFIX + "floor-unmerged:" # merge:floor-unmerged:<pr>:<head7>
|
|
392
|
+
FLOOR_FAILED_KIND_PREFIX = MERGE_KIND_PREFIX + "floor-failed:" # merge:floor-failed:<pr>:<head7>
|
|
366
393
|
|
|
367
394
|
# The hold reasons, as the short slugs the ledger kind and the event key carry.
|
|
368
395
|
HOLD_OPERATOR = "operator"
|
|
@@ -422,10 +449,14 @@ MERGED_BY_POLICY_COMMENT = (
|
|
|
422
449
|
"on {task_ref} as a comment and a `Validate after merge` task before the merge."
|
|
423
450
|
)
|
|
424
451
|
# The floor (issue #116, L3): what the origin task reads BEFORE the merge call
|
|
425
|
-
# when the policy downgrades an operator hold. The wording is
|
|
452
|
+
# when the policy downgrades an operator hold. The wording is issue #120's: it
|
|
453
|
+
# is posted before the merge call, so it says the merge IS HAPPENING -- the
|
|
454
|
+
# earlier "merged" read false at posting time and stayed false whenever the
|
|
455
|
+
# floor's second write then failed.
|
|
426
456
|
FLOOR_TASK_COMMENT = (
|
|
427
|
-
"**Merge policy {policy}
|
|
428
|
-
"{klass}: {reason}. Validate
|
|
457
|
+
"**Merge policy `{policy}` is merging PR #{pr} with an open operator gate** — "
|
|
458
|
+
"{klass}: {reason}. A `Validate after merge` task follows this comment; "
|
|
459
|
+
"validate that before validating this task. ({url})"
|
|
429
460
|
)
|
|
430
461
|
FLOOR_TASK_TITLE = "Validate after merge: {reason}"
|
|
431
462
|
FLOOR_TASK_DOD = (
|
|
@@ -433,14 +464,31 @@ FLOOR_TASK_DOD = (
|
|
|
433
464
|
"has been validated by a human after the merge, and the outcome is recorded "
|
|
434
465
|
"on this task."
|
|
435
466
|
)
|
|
467
|
+
# Owner: the calling actor (issue #120). The API defaults the owner of a
|
|
468
|
+
# downstream task to the caller, and the caller is the fleet actor -- the one
|
|
469
|
+
# identity that owns every task the loop feeds. The origin's reporter is not
|
|
470
|
+
# seated (the owner-assign gate refuses it) and does not need to be: the human
|
|
471
|
+
# validates the ORIGIN task, which is this task's parent and carries the floor
|
|
472
|
+
# comment; this row exists so the item cannot vanish.
|
|
436
473
|
FLOOR_TASK_DESCRIPTION = (
|
|
437
|
-
"The orchestrator's merge edge
|
|
474
|
+
"The orchestrator's merge edge is merging {pr_url} under merge policy `{policy}` "
|
|
438
475
|
"although the reviewer's approve read `Merge-Readiness: operator — {klass}: "
|
|
439
|
-
"{reason}`. The class is in `merge_yolo_merge_classes`, so the merge
|
|
476
|
+
"{reason}`. The class is in `merge_yolo_merge_classes`, so the merge does not "
|
|
440
477
|
"wait -- but the gate is still open: validate it before validating the origin "
|
|
441
|
-
"task
|
|
478
|
+
"task. Owner: the fleet actor; the human validates the ORIGIN task — this row "
|
|
479
|
+
"exists so the item cannot vanish.\n\nAlissa-Task: {task_ref}"
|
|
442
480
|
)
|
|
443
481
|
FLOOR_TASK_CRITERION = "The open operator gate ({klass}) has been validated by a human after the merge."
|
|
482
|
+
# The escalation (issue #120): a floor write refused for good -- a 4xx that is
|
|
483
|
+
# not transient, the owner-assign gate's 403 being the one that was seen in
|
|
484
|
+
# production -- is not a next-poll retry. Once per (pr, head), on the origin
|
|
485
|
+
# task, naming the HTTP error; the merge edge then leaves this head alone.
|
|
486
|
+
FLOOR_FAILED_TASK_COMMENT = (
|
|
487
|
+
"**Merge policy `{policy}` cannot write the validation floor for PR #{pr} on "
|
|
488
|
+
"`{head7}`** — {detail}. This PR needs a human merge until the floor can be "
|
|
489
|
+
"written; the merge edge skips this head without retrying (a new head is a "
|
|
490
|
+
"new attempt). ({url})"
|
|
491
|
+
)
|
|
444
492
|
# The correction (PR #117 round 1): the floor is worded as the issue words it
|
|
445
493
|
# and posted BEFORE the merge call, so a refused merge leaves the origin task
|
|
446
494
|
# paged about a merge that did not happen. Once per (pr, head), best-effort.
|
|
@@ -941,10 +989,21 @@ class MergeEdge:
|
|
|
941
989
|
) -> "MergeDecision | None":
|
|
942
990
|
"""Page the origin task about the gate the policy is about to merge
|
|
943
991
|
past: one comment, then one committed `Validate after merge`
|
|
944
|
-
downstream task owned by the
|
|
945
|
-
call, once per (pr, head). None when the floor stands (posted
|
|
946
|
-
on an earlier poll for this head); a SKIPPED decision when any
|
|
947
|
-
failed
|
|
992
|
+
downstream task owned by the calling actor (issue #120) -- BEFORE the
|
|
993
|
+
merge call, once per (pr, head). None when the floor stands (posted
|
|
994
|
+
now or on an earlier poll for this head); a SKIPPED decision when any
|
|
995
|
+
write failed: a TRANSIENT failure (5xx, 429, transport) is retried
|
|
996
|
+
next poll; a PERMANENT one (a 4xx that is not transient -- 403, 404,
|
|
997
|
+
422) is escalated once on the origin task and every later poll on
|
|
998
|
+
this head is a quiet SKIP (`_floor_failed`).
|
|
999
|
+
|
|
1000
|
+
The task's owner is whoever the API defaults to -- the caller, i.e.
|
|
1001
|
+
the fleet actor. `assign_to_actor_id` is deliberately NOT passed: the
|
|
1002
|
+
owner-assign gate refuses the fleet actor seating another actor as
|
|
1003
|
+
owner ("Only a team admin can seat another member as owner; pass
|
|
1004
|
+
teamSlug"), which is what kept every yolo merge skipped forever
|
|
1005
|
+
(issue #120). The reporter still sees the floor through the origin
|
|
1006
|
+
task, this task's parent, which carries the floor comment.
|
|
948
1007
|
|
|
949
1008
|
Three ledger rows: the comment's lands after the comment so a failed
|
|
950
1009
|
task create does not re-post it; the task's -- carrying the created
|
|
@@ -957,6 +1016,14 @@ class MergeEdge:
|
|
|
957
1016
|
floor_kind = f"{FLOOR_KIND_PREFIX}{pr.number}:{pr.head7}"
|
|
958
1017
|
if self.state.escalated(candidate.task_number, floor_kind):
|
|
959
1018
|
return None
|
|
1019
|
+
failed_kind = f"{FLOOR_FAILED_KIND_PREFIX}{pr.number}:{pr.head7}"
|
|
1020
|
+
if self.state.escalated(candidate.task_number, failed_kind):
|
|
1021
|
+
# Escalated on an earlier poll: quiet, no write, no WARNING (L4).
|
|
1022
|
+
return self._skipped(
|
|
1023
|
+
candidate,
|
|
1024
|
+
f"{pr.pr_slug}: floor failed for good on {pr.head7} (escalated on {ref}) — "
|
|
1025
|
+
f"needs a human merge; a new head retries",
|
|
1026
|
+
)
|
|
960
1027
|
if self.alissa is None:
|
|
961
1028
|
return self._skipped(
|
|
962
1029
|
candidate,
|
|
@@ -973,15 +1040,7 @@ class MergeEdge:
|
|
|
973
1040
|
self.state.record_escalation(candidate.task_number, comment_kind, at=now)
|
|
974
1041
|
task_id = self._floor_task_id(candidate, task_prefix)
|
|
975
1042
|
if task_id is None:
|
|
976
|
-
#
|
|
977
|
-
# task. A payload without one falls back to the calling actor
|
|
978
|
-
# (the API's own default) and says so in the log.
|
|
979
|
-
origin = self.alissa.get_task(ref)
|
|
980
|
-
if not origin.reporter_id:
|
|
981
|
-
log.warning(
|
|
982
|
-
"merge: %s carries no reporterId — the validation task defaults to the "
|
|
983
|
-
"daemon's own actor", ref,
|
|
984
|
-
)
|
|
1043
|
+
# No owner override (issue #120): the API seats the caller.
|
|
985
1044
|
task_id = self.alissa.create_downstream_task(
|
|
986
1045
|
ref,
|
|
987
1046
|
title=FLOOR_TASK_TITLE.format(reason=reason or klass)[:200],
|
|
@@ -995,7 +1054,6 @@ class MergeEdge:
|
|
|
995
1054
|
"id": "vc-1", "type": "manual",
|
|
996
1055
|
"description": FLOOR_TASK_CRITERION.format(klass=klass),
|
|
997
1056
|
}],
|
|
998
|
-
assign_to_actor_id=origin.reporter_id,
|
|
999
1057
|
)
|
|
1000
1058
|
# The task exists from here on: its row lands BEFORE the
|
|
1001
1059
|
# read-back and the status set, so a failure in either
|
|
@@ -1010,6 +1068,8 @@ class MergeEdge:
|
|
|
1010
1068
|
if created.status != "committed":
|
|
1011
1069
|
self.alissa.set_status(task_id, "committed")
|
|
1012
1070
|
except AlissaError as exc:
|
|
1071
|
+
if floor_failure_is_permanent(exc):
|
|
1072
|
+
return self._floor_failed(candidate, now, exc=exc, kind=failed_kind)
|
|
1013
1073
|
log.warning(
|
|
1014
1074
|
"merge: floor on %s for %s not posted (%s) — merge skipped this poll",
|
|
1015
1075
|
ref, pr.pr_slug, exc,
|
|
@@ -1037,6 +1097,38 @@ class MergeEdge:
|
|
|
1037
1097
|
task_id = str(rows[0]["kind"])[len(task_prefix):]
|
|
1038
1098
|
return task_id or None
|
|
1039
1099
|
|
|
1100
|
+
def _floor_failed(
|
|
1101
|
+
self, candidate: MergeCandidate, now: int, *, exc: AlissaError, kind: str,
|
|
1102
|
+
) -> MergeDecision:
|
|
1103
|
+
"""A floor write refused for good (issue #120): one escalation comment
|
|
1104
|
+
on the origin task naming the HTTP error, one WARNING, the
|
|
1105
|
+
`merge:floor-failed:<pr>:<head7>` row -- and SKIPPED. The row is what
|
|
1106
|
+
makes every later poll on this head a quiet SKIP before any write
|
|
1107
|
+
(`_floor`'s second check). The comment is best-effort in the same
|
|
1108
|
+
sense as the correction: one that did not post leaves no row, so the
|
|
1109
|
+
next poll escalates again (and re-tries the floor, which fails the
|
|
1110
|
+
same way) until the page lands -- an escalation that never posted is
|
|
1111
|
+
not an escalation."""
|
|
1112
|
+
pr = candidate.pr
|
|
1113
|
+
ref = candidate.ref
|
|
1114
|
+
log.warning(
|
|
1115
|
+
"merge: floor on %s for %s failed for good (%s) — escalated once, this head "
|
|
1116
|
+
"needs a human merge; skipped quietly from here on",
|
|
1117
|
+
ref, pr.pr_slug, exc,
|
|
1118
|
+
)
|
|
1119
|
+
posted = self._task_comment(candidate, FLOOR_FAILED_TASK_COMMENT.format(
|
|
1120
|
+
policy=self.config.merge_policy, pr=pr.number, head7=pr.head7, detail=exc,
|
|
1121
|
+
url=pr.url or pr.pr_slug,
|
|
1122
|
+
))
|
|
1123
|
+
if posted:
|
|
1124
|
+
self.state.record_escalation(candidate.task_number, kind, at=now)
|
|
1125
|
+
return self._skipped(
|
|
1126
|
+
candidate,
|
|
1127
|
+
f"{pr.pr_slug}: floor failed for good on {ref} ({exc}) — escalated"
|
|
1128
|
+
+ ("" if posted else " (escalation comment pending — retries next poll)")
|
|
1129
|
+
+ ", needs a human merge; a new head retries",
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1040
1132
|
def _floor_unmerged(self, candidate: MergeCandidate, now: int, *, detail: str) -> None:
|
|
1041
1133
|
"""The correction the origin task gets when the merge call fails on a
|
|
1042
1134
|
head it was paged about: once per (pr, head), best-effort (a comment
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.18.3
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.18.2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|