alissa-tools-github-revloop 0.31.0__tar.gz → 0.31.1__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_revloop-0.31.0/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.31.1}/PKG-INFO +1 -1
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/alissa.py +37 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/loop.py +125 -8
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/state.py +57 -1
- alissa_tools_github_revloop-0.31.1/src/main/alissa/tools/github/revloop/version +1 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_revloop-0.31.0/src/main/alissa/tools/github/revloop/version +0 -1
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/LICENSE +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/MANIFEST.in +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/NOTICE +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/README.md +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/requirements.txt +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/setup.cfg +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/setup.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/__main__.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/alissa_client.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/bows.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/config.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/fleet_vitals.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/loop_events.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/proc.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/version.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/webui/__init__.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/webui/page.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/webui/server.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/webui/sources.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_revloop-0.31.0 → alissa_tools_github_revloop-0.31.1}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
|
@@ -267,6 +267,43 @@ def clean_readiness_reason(text: object) -> str:
|
|
|
267
267
|
return flat[:MAX_READINESS_REASON_CHARS].rstrip()
|
|
268
268
|
|
|
269
269
|
|
|
270
|
+
# The trailer's own grammar (issue #134) -- the bare-line sibling of
|
|
271
|
+
# _READINESS_RE. This is the line a native review ENDS with, whoever wrote
|
|
272
|
+
# it: the daemon's emitter (loop.readiness_trailer) when it posts the verdict
|
|
273
|
+
# itself, or the reviewer session's own `gh pr review` on the normal path.
|
|
274
|
+
# It is the consumer's grammar verbatim (README, "The `Merge-Readiness`
|
|
275
|
+
# trailer"): line-anchored, no bullet, no bold, no backticks, first match
|
|
276
|
+
# wins, value case-sensitive. The label is the ONE constant the emitter
|
|
277
|
+
# builds from, so the two cannot drift: a line the emitter writes is, by
|
|
278
|
+
# construction, a line this regex reads.
|
|
279
|
+
READINESS_TRAILER_LABEL = "Merge-Readiness:"
|
|
280
|
+
READINESS_MISSING = "missing"
|
|
281
|
+
_TRAILER_RE = re.compile(
|
|
282
|
+
r"^" + re.escape(READINESS_TRAILER_LABEL)
|
|
283
|
+
+ r"[ \t]*(auto|operator)(?:[ \t]*[—-][ \t]*(.+))?[ \t]*$",
|
|
284
|
+
re.MULTILINE,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def parse_trailer(body: object) -> "tuple[str | None, str]":
|
|
289
|
+
"""`(value, reason)` from the first bare `Merge-Readiness:` line in a
|
|
290
|
+
review body, or `(None, "")` when no line matches the trailer grammar.
|
|
291
|
+
|
|
292
|
+
Strict where parse_readiness is tolerant: the line inside backticks
|
|
293
|
+
mid-sentence (studio #1258) and the envelope's `- **Merge-Readiness:**`
|
|
294
|
+
bullet both read as MISSING here, because the consumer's regex is what
|
|
295
|
+
decides whether the merge edge sees the judgment at all. CRLF bodies (a
|
|
296
|
+
review typed into the web form) are normalised first; the grammar itself
|
|
297
|
+
is unchanged.
|
|
298
|
+
"""
|
|
299
|
+
if not isinstance(body, str):
|
|
300
|
+
return (None, "")
|
|
301
|
+
match = _TRAILER_RE.search(body.replace("\r\n", "\n"))
|
|
302
|
+
if match is None:
|
|
303
|
+
return (None, "")
|
|
304
|
+
return (match.group(1), clean_readiness_reason(match.group(2)))
|
|
305
|
+
|
|
306
|
+
|
|
270
307
|
def parse_readiness(blob: object) -> "tuple[str | None, str]":
|
|
271
308
|
"""`(value, reason)` from the first Merge-Readiness line in `blob`.
|
|
272
309
|
|
|
@@ -23,7 +23,9 @@ from pathlib import Path
|
|
|
23
23
|
|
|
24
24
|
from .alissa import (
|
|
25
25
|
READINESS_AUTO,
|
|
26
|
+
READINESS_MISSING,
|
|
26
27
|
READINESS_OPERATOR,
|
|
28
|
+
READINESS_TRAILER_LABEL,
|
|
27
29
|
VERDICT_APPROVE,
|
|
28
30
|
VERDICT_REQUEST_CHANGES,
|
|
29
31
|
VerdictEnvelope,
|
|
@@ -33,6 +35,7 @@ from .alissa import (
|
|
|
33
35
|
Task,
|
|
34
36
|
TaskDetail,
|
|
35
37
|
is_review_task_for,
|
|
38
|
+
parse_trailer,
|
|
36
39
|
session_repo_slug,
|
|
37
40
|
)
|
|
38
41
|
from .alissa_client import AlissaClient
|
|
@@ -413,7 +416,11 @@ NATIVE_VERDICT_BODY = (
|
|
|
413
416
|
# an approve the checks gate downgraded carries nothing), and an envelope
|
|
414
417
|
# with no parseable line fails CLOSED to `operator`, with a reason that tells
|
|
415
418
|
# the operator why the merge waited on them.
|
|
416
|
-
|
|
419
|
+
#
|
|
420
|
+
# The label and the grammar live in alissa.py (READINESS_TRAILER_LABEL,
|
|
421
|
+
# parse_trailer): the emitter below builds from the label the regex is built
|
|
422
|
+
# from, and _observe_session_readiness reads a session's own review with that
|
|
423
|
+
# same regex (issue #134) -- one grammar, two writers, no drift.
|
|
417
424
|
READINESS_MISSING_REASON = "envelope carries no Merge-Readiness line"
|
|
418
425
|
|
|
419
426
|
|
|
@@ -644,15 +651,28 @@ _RECORD_THE_CAP = (
|
|
|
644
651
|
"from a stale template default. "
|
|
645
652
|
)
|
|
646
653
|
|
|
647
|
-
# The merge-readiness line (issue #130). The verdict envelope is where
|
|
648
|
-
# reviewer's judgment lives
|
|
649
|
-
#
|
|
650
|
-
#
|
|
654
|
+
# The merge-readiness line (issue #130, #134). The verdict envelope is where
|
|
655
|
+
# the reviewer's judgment lives. On the NORMAL path the session posts its own
|
|
656
|
+
# native review, and that body passes through nothing -- so the session must
|
|
657
|
+
# write the trailer itself, as the bare last non-empty line, in the consumer's
|
|
658
|
+
# grammar (studio #1258 wrote it inside backticks mid-sentence and the merge
|
|
659
|
+
# edge held the approve). The daemon copies the envelope's line onto a native
|
|
660
|
+
# review only on the rounds where it posts the verdict itself, and an
|
|
661
|
+
# envelope without the line posts as `operator` -- so a reviewer who skips it
|
|
662
|
+
# has silently withheld auto-merge. The two grammar sentences are the
|
|
663
|
+
# README's ("The `Merge-Readiness` trailer on a native approve"), verbatim.
|
|
651
664
|
_MERGE_READINESS_LINE = (
|
|
652
665
|
"Your verdict envelope MUST carry the skill's `- **Merge-Readiness:** auto | "
|
|
653
|
-
"operator — <reason>` line
|
|
654
|
-
"the
|
|
655
|
-
"
|
|
666
|
+
"operator — <reason>` line, AND your OWN native review (every `gh pr review` "
|
|
667
|
+
"form and the reviews-API POST alike) MUST END with the bare line "
|
|
668
|
+
"`Merge-Readiness: auto` or `Merge-Readiness: operator — <one-line reason>` "
|
|
669
|
+
"as its last non-empty line — plain text at the start of the line, not in "
|
|
670
|
+
"backticks, not bold, not mid-sentence — byte-equal in value and reason to "
|
|
671
|
+
"the envelope's line; `auto` only on an APPROVE of the reviewed head. The "
|
|
672
|
+
"merge edge reads that line off the review and holds an approve without "
|
|
673
|
+
"it. The daemon copies the envelope's line onto a native review only when "
|
|
674
|
+
"it posts the verdict itself, and an envelope without the line posts as "
|
|
675
|
+
"`operator`. "
|
|
656
676
|
)
|
|
657
677
|
|
|
658
678
|
# -- the reviewer session's own CI gate (issue #84) ---------------------------
|
|
@@ -2191,6 +2211,14 @@ class ReviewWatcher:
|
|
|
2191
2211
|
# "that round has no native verdict", producing a duplicate post over a
|
|
2192
2212
|
# session that closed its own round correctly.
|
|
2193
2213
|
owed = completed - self.state.abandoned_rounds(pr.full_name, number)
|
|
2214
|
+
|
|
2215
|
+
# Observation only (issue #134): a session-posted APPROVE on the
|
|
2216
|
+
# current head is looked at once for the Merge-Readiness trailer the
|
|
2217
|
+
# merge edge reads. Above the native post and convergence because it
|
|
2218
|
+
# decides nothing -- it reports; and it must run on the pass that
|
|
2219
|
+
# converges, since a converged PR leaves the search set.
|
|
2220
|
+
self._observe_session_readiness(pr, my_reviews, completed)
|
|
2221
|
+
|
|
2194
2222
|
if task is not None and owed > native:
|
|
2195
2223
|
# Terminal for this pass either way. On a landed post the review
|
|
2196
2224
|
# request it consumed drops the PR out of the search, so
|
|
@@ -4042,6 +4070,95 @@ class ReviewWatcher:
|
|
|
4042
4070
|
deferred=True,
|
|
4043
4071
|
)
|
|
4044
4072
|
|
|
4073
|
+
def _observe_session_readiness(
|
|
4074
|
+
self, pr: PullRequest, my_reviews: list[Review], round_: int
|
|
4075
|
+
) -> None:
|
|
4076
|
+
"""Say whether the session's own APPROVE carries the trailer (issue #134).
|
|
4077
|
+
|
|
4078
|
+
The merge edge reads `Merge-Readiness:` off the reviewer identity's
|
|
4079
|
+
APPROVE on the current head. On the normal path that review is the
|
|
4080
|
+
SESSION's -- posted by its own `gh pr review`, passing through nothing
|
|
4081
|
+
here -- so a body without the bare line (studio #1258 had it inside
|
|
4082
|
+
backticks mid-sentence) is an approve the edge holds, silently. The
|
|
4083
|
+
fix is the skill's and the directive's; this is the daemon SEEING it:
|
|
4084
|
+
one WARNING and one activity row per (PR, head) when the trailer is
|
|
4085
|
+
missing, the `readiness=auto|operator` term when it parses.
|
|
4086
|
+
|
|
4087
|
+
Observation only, by contract: never a review posted, never the
|
|
4088
|
+
session's review edited, never a round re-run. Scoped to the newest
|
|
4089
|
+
reviewer-identity APPROVED review on the current head -- the review the
|
|
4090
|
+
merge edge reads, whatever the identity wrote after it (a round-k
|
|
4091
|
+
reviewer that fell back to `--comment`, a follow-up write-up) -- and
|
|
4092
|
+
NOT the daemon's own post: that one is emitter-built and already
|
|
4093
|
+
reported `readiness=` at post time (a second row for it would be
|
|
4094
|
+
noise). Once per (PR, head) via the ledger flag beside the verdicts
|
|
4095
|
+
row: a new head re-arms, a re-poll of the same head is silent.
|
|
4096
|
+
|
|
4097
|
+
Emit, then record, like every other activity note here: the ledger
|
|
4098
|
+
flag lands only after the activity row did, so a transient comment
|
|
4099
|
+
failure retries next poll instead of losing the row; and never under
|
|
4100
|
+
`--dry-run`, where the row cannot land and a durable flag would let a
|
|
4101
|
+
diagnostic pass silence the daemon it was run to diagnose (see
|
|
4102
|
+
`_warn_identity_drift`). The flag rides a verdict row that
|
|
4103
|
+
`last_verdict_at` also reads, so it is stamped only with the review's
|
|
4104
|
+
own time -- an unreadable GitHub stamp keeps the log line and skips
|
|
4105
|
+
both the row and the flag rather than inventing a verdict at "now".
|
|
4106
|
+
"""
|
|
4107
|
+
approves = [
|
|
4108
|
+
r for r in my_reviews
|
|
4109
|
+
if r.state == "APPROVED" and r.commit_id and r.commit_id == pr.head_sha
|
|
4110
|
+
]
|
|
4111
|
+
newest = approves[-1] if approves else None
|
|
4112
|
+
if newest is None or newest.verdict_round is not None:
|
|
4113
|
+
return
|
|
4114
|
+
if self.state.readiness_observed(pr.full_name, pr.number, pr.head_sha) is not None:
|
|
4115
|
+
return
|
|
4116
|
+
|
|
4117
|
+
value, reason = parse_trailer(newest.body)
|
|
4118
|
+
head7 = pr.head_sha[:7]
|
|
4119
|
+
if value is None:
|
|
4120
|
+
readiness = READINESS_MISSING
|
|
4121
|
+
log.warning(
|
|
4122
|
+
"%s approve at %s by %s carries no Merge-Readiness trailer — the "
|
|
4123
|
+
"merge edge will hold it; the session must end its review body "
|
|
4124
|
+
"with the line (see directive)",
|
|
4125
|
+
pr.slug, head7, self.github.login,
|
|
4126
|
+
)
|
|
4127
|
+
line = (
|
|
4128
|
+
f"- {_now()} — round {round_} — session-posted `APPROVE` review by "
|
|
4129
|
+
f"`{self.github.login}` at `{head7}` — readiness=missing (no "
|
|
4130
|
+
f"`{READINESS_TRAILER_LABEL}` trailer on the review body; the merge "
|
|
4131
|
+
f"edge will hold it)"
|
|
4132
|
+
)
|
|
4133
|
+
else:
|
|
4134
|
+
readiness = value
|
|
4135
|
+
term = f"{value} — {reason}" if reason else value
|
|
4136
|
+
log.info(
|
|
4137
|
+
"%s approve at %s by %s carries readiness=%s",
|
|
4138
|
+
pr.slug, head7, self.github.login, term,
|
|
4139
|
+
)
|
|
4140
|
+
line = (
|
|
4141
|
+
f"- {_now()} — round {round_} — session-posted `APPROVE` review by "
|
|
4142
|
+
f"`{self.github.login}` at `{head7}` — readiness={term}"
|
|
4143
|
+
)
|
|
4144
|
+
|
|
4145
|
+
posted_at = _epoch(newest.submitted_at)
|
|
4146
|
+
if posted_at is None:
|
|
4147
|
+
# No stamp to key a verdict row on; the log line above is the
|
|
4148
|
+
# whole report for this poll, and the next poll says it again.
|
|
4149
|
+
log.debug(
|
|
4150
|
+
"%s: approve at %s has no readable submitted_at; readiness row "
|
|
4151
|
+
"and flag skipped", pr.slug, head7,
|
|
4152
|
+
)
|
|
4153
|
+
return
|
|
4154
|
+
if not self._append_activity(pr, line):
|
|
4155
|
+
return # retried next poll, like every other activity note
|
|
4156
|
+
if self.config.dry_run:
|
|
4157
|
+
return
|
|
4158
|
+
self.state.note_readiness(
|
|
4159
|
+
pr.full_name, pr.number, pr.head_sha, int(posted_at), readiness, newest.url,
|
|
4160
|
+
)
|
|
4161
|
+
|
|
4045
4162
|
def _convergence_reason(
|
|
4046
4163
|
self, my_reviews: list[Review], resolved: "ResolvedTask", head_sha: str
|
|
4047
4164
|
) -> str | None:
|
|
@@ -106,12 +106,20 @@ CREATE TABLE IF NOT EXISTS grants (
|
|
|
106
106
|
-- never passes through the daemon). Rows are never pruned -- one per verdict
|
|
107
107
|
-- ever landed -- and a lost row costs nothing: GitHub's reviews list is read
|
|
108
108
|
-- alongside it and the gate takes the newer of the two.
|
|
109
|
+
--
|
|
110
|
+
-- `readiness` (issue #134) is the once-per-(PR, head) flag for the
|
|
111
|
+
-- Merge-Readiness observation: NULL until the loop has looked at a
|
|
112
|
+
-- session-posted APPROVE on that head, then `auto` / `operator` / `missing`
|
|
113
|
+
-- -- the last being the one the loop WARNs about, once, because the merge
|
|
114
|
+
-- edge will hold an approve with no trailer. Lives beside the verdict row
|
|
115
|
+
-- rather than in a table of its own: it is a fact about that verdict.
|
|
109
116
|
CREATE TABLE IF NOT EXISTS verdicts (
|
|
110
117
|
repo TEXT NOT NULL,
|
|
111
118
|
number INTEGER NOT NULL,
|
|
112
119
|
head_sha TEXT NOT NULL,
|
|
113
120
|
posted_at INTEGER NOT NULL,
|
|
114
121
|
review_url TEXT,
|
|
122
|
+
readiness TEXT,
|
|
115
123
|
PRIMARY KEY (repo, number, head_sha, posted_at)
|
|
116
124
|
);
|
|
117
125
|
|
|
@@ -350,6 +358,9 @@ _ADDED_COLUMNS = {
|
|
|
350
358
|
("checks_pending_at", "INTEGER"),
|
|
351
359
|
("verdict", "TEXT"),
|
|
352
360
|
),
|
|
361
|
+
"verdicts": (
|
|
362
|
+
("readiness", "TEXT"),
|
|
363
|
+
),
|
|
353
364
|
}
|
|
354
365
|
|
|
355
366
|
|
|
@@ -777,6 +788,51 @@ class State:
|
|
|
777
788
|
f"recording the verdict on {repo}#{number} at {head_sha[:8]}",
|
|
778
789
|
)
|
|
779
790
|
|
|
791
|
+
def readiness_observed(self, repo: str, number: int, head_sha: str) -> str | None:
|
|
792
|
+
"""The Merge-Readiness observation already made on this head
|
|
793
|
+
(`auto` / `operator` / `missing`), or None when the loop has not yet
|
|
794
|
+
looked at a session-posted APPROVE on it (issue #134). The once-per-
|
|
795
|
+
(PR, head) guard for the WARNING and its activity row."""
|
|
796
|
+
row = self._db.execute(
|
|
797
|
+
"SELECT readiness FROM verdicts "
|
|
798
|
+
"WHERE repo=? AND number=? AND head_sha=? AND readiness IS NOT NULL "
|
|
799
|
+
"ORDER BY posted_at DESC LIMIT 1",
|
|
800
|
+
(repo, number, head_sha),
|
|
801
|
+
).fetchone()
|
|
802
|
+
return str(row["readiness"]) if row and row["readiness"] is not None else None
|
|
803
|
+
|
|
804
|
+
def note_readiness(
|
|
805
|
+
self, repo: str, number: int, head_sha: str, posted_at: int,
|
|
806
|
+
readiness: str, review_url: str = "",
|
|
807
|
+
) -> bool:
|
|
808
|
+
"""TELEMETRY: flag the verdict row for `head_sha` at `posted_at` with
|
|
809
|
+
the readiness the loop observed on it, creating the row if GitHub's
|
|
810
|
+
list showed the review before the ledger did (issue #134). Absorbed
|
|
811
|
+
like note_observed_verdict: a flag the ledger cannot take costs one
|
|
812
|
+
repeated WARNING on the next poll, not a wrong decision -- nothing
|
|
813
|
+
downstream reads the `readiness` column but the dedupe. The ROW is
|
|
814
|
+
another matter: it is a verdict row, and `last_verdict_at` reads its
|
|
815
|
+
`posted_at` for the post-verdict cooldown, so `posted_at` must be the
|
|
816
|
+
review's own GitHub stamp -- never a synthetic "now". The caller skips
|
|
817
|
+
the write when it has no such stamp. True on success."""
|
|
818
|
+
def write() -> None:
|
|
819
|
+
self._db.execute(
|
|
820
|
+
"INSERT OR IGNORE INTO verdicts "
|
|
821
|
+
"(repo, number, head_sha, posted_at, review_url) VALUES (?,?,?,?,?)",
|
|
822
|
+
(repo, number, head_sha, int(posted_at), review_url),
|
|
823
|
+
)
|
|
824
|
+
self._db.execute(
|
|
825
|
+
"UPDATE verdicts SET readiness=? "
|
|
826
|
+
"WHERE repo=? AND number=? AND head_sha=? AND posted_at=?",
|
|
827
|
+
(readiness, repo, number, head_sha, int(posted_at)),
|
|
828
|
+
)
|
|
829
|
+
self._db.commit()
|
|
830
|
+
|
|
831
|
+
return self._write_telemetry(
|
|
832
|
+
write,
|
|
833
|
+
f"recording readiness={readiness} on {repo}#{number} at {head_sha[:8]}",
|
|
834
|
+
)
|
|
835
|
+
|
|
780
836
|
def last_verdict_at(self, repo: str, number: int, head_sha: str) -> int | None:
|
|
781
837
|
"""When the newest verdict of record on this head landed (epoch
|
|
782
838
|
seconds), or None when the ledger knows of none on it."""
|
|
@@ -794,7 +850,7 @@ class State:
|
|
|
794
850
|
with the `verdicts` table this raises `no such table`, not `[]` --
|
|
795
851
|
whoever wires it into the console first should guard for that."""
|
|
796
852
|
return self._read_rows(
|
|
797
|
-
"SELECT repo, number, head_sha, posted_at, review_url FROM verdicts "
|
|
853
|
+
"SELECT repo, number, head_sha, posted_at, review_url, readiness FROM verdicts "
|
|
798
854
|
"ORDER BY posted_at DESC, number DESC",
|
|
799
855
|
limit,
|
|
800
856
|
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.31.1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.31.0
|
|
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
|