alissa-tools-github-revloop 0.31.3__tar.gz → 0.31.4__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.3/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.31.4}/PKG-INFO +1 -1
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/alissa.py +92 -19
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/loop.py +86 -18
- alissa_tools_github_revloop-0.31.4/src/main/alissa/tools/github/revloop/version +1 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
- alissa_tools_github_revloop-0.31.3/src/main/alissa/tools/github/revloop/version +0 -1
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/LICENSE +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/MANIFEST.in +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/NOTICE +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/README.md +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/requirements.txt +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/setup.cfg +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/setup.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/__main__.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/alissa_client.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/bows.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/config.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/fleet_vitals.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/loop_events.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/proc.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/prompts.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/state.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/trust.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/version.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/webui/__init__.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/webui/page.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/webui/server.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/webui/sources.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_revloop-0.31.3 → alissa_tools_github_revloop-0.31.4}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
|
@@ -239,7 +239,7 @@ _VERDICT_RE = re.compile(
|
|
|
239
239
|
# The envelope's merge-readiness judgment (issue #130). The alissa-code-review
|
|
240
240
|
# skill writes it as one list line in the verdict envelope:
|
|
241
241
|
# - **Merge-Readiness:** auto
|
|
242
|
-
# - **Merge-Readiness:** operator —
|
|
242
|
+
# - **Merge-Readiness:** operator — schema-migration: removes users.legacyId
|
|
243
243
|
# Tolerant of the markdown around the label -- an optional bullet, optional
|
|
244
244
|
# `**` bold around the label (with the colon inside or outside it), optional
|
|
245
245
|
# bold around the value -- but NOT of the value's case: `Auto` is not a
|
|
@@ -255,21 +255,83 @@ _READINESS_RE = re.compile(
|
|
|
255
255
|
re.MULTILINE,
|
|
256
256
|
)
|
|
257
257
|
|
|
258
|
+
# The operator reason's CLASS (issue #142). An `operator` judgment names WHY
|
|
259
|
+
# the merge is a human's, and until now it did so in prose the merge edge
|
|
260
|
+
# could not act on: of 14 operator verdicts on ~40 studio PRs, 3 named real
|
|
261
|
+
# merge risk and 11 were "the PR body lists an unverified item" -- validation
|
|
262
|
+
# work the human does at gate 2 anyway, not a reason to withhold the merge.
|
|
263
|
+
# So the reason now LEADS with one token from this closed enum, in the
|
|
264
|
+
# consumer's grammar:
|
|
265
|
+
#
|
|
266
|
+
# Merge-Readiness: auto
|
|
267
|
+
# Merge-Readiness: operator — <class>: <one-line reason>
|
|
268
|
+
#
|
|
269
|
+
# The order below is the SEVERITY order: when more than one row applies, the
|
|
270
|
+
# reviewer names the one nearest the top (the most severe wins), and exactly
|
|
271
|
+
# one. `auto` never carries a class. An operator line whose reason does not
|
|
272
|
+
# lead with a recognised token is still valid grammar -- it parses, value and
|
|
273
|
+
# reason intact -- and reads as UNCLASSED (`klass=None`); the consumer treats
|
|
274
|
+
# unclassed as a hard hold (fail closed), so a reviewer that forgets the
|
|
275
|
+
# class loses throughput, never safety. The token is matched exactly as
|
|
276
|
+
# written here (lowercase, hyphenated): `Schema-Migration:` is not a class
|
|
277
|
+
# this daemon reads, for the same reason `Auto` is not a value it carries.
|
|
278
|
+
READINESS_CLASSES: "tuple[str, ...]" = (
|
|
279
|
+
"schema-migration", # schema shape changes needing a migration / removal / rename on live data
|
|
280
|
+
"data-backfill", # one-off writes over existing rows (backfills, stored-shape version bumps)
|
|
281
|
+
"secrets-env", # new / changed env vars, secrets, credentials the deploy must carry
|
|
282
|
+
"infra-deploy", # Dockerfiles, workflows, Railway config, base-image pins -- what runs
|
|
283
|
+
"billing", # credit charging, pricing, tiers, quotas
|
|
284
|
+
"security", # auth, permission gates, redaction, sandboxing flagged operator-worthy
|
|
285
|
+
"unverified-ux", # the PR body's operator gate is a human look at a screen / copy / mockup
|
|
286
|
+
"unverified-runtime", # the PR body's operator gate is a live smoke / real-model run / replay
|
|
287
|
+
"release-act", # the merge itself is a release (VERSION bump -> publish, tag, npm)
|
|
288
|
+
)
|
|
289
|
+
_READINESS_CLASS_RE = re.compile(
|
|
290
|
+
r"^(" + "|".join(re.escape(klass) for klass in READINESS_CLASSES) + r")[ \t]*:"
|
|
291
|
+
)
|
|
292
|
+
|
|
258
293
|
# The reason lands in a GitHub review body (as a line-anchored trailer) and in
|
|
259
294
|
# a log line, so it is bounded and flattened at the parser: one line, no
|
|
260
295
|
# backticks (a fence would swallow the trailer and everything after it), and
|
|
261
|
-
# no more than this many characters.
|
|
296
|
+
# no more than this many characters. The class prefix is PART of the reason
|
|
297
|
+
# and survives the flattening untouched: the native trailer must stay
|
|
298
|
+
# byte-equal to the envelope's line, class included.
|
|
262
299
|
MAX_READINESS_REASON_CHARS = 200
|
|
263
300
|
|
|
264
301
|
|
|
265
302
|
def clean_readiness_reason(text: object) -> str:
|
|
266
|
-
"""Flatten a Merge-Readiness reason to one bounded, backtick-free line.
|
|
303
|
+
"""Flatten a Merge-Readiness reason to one bounded, backtick-free line.
|
|
304
|
+
|
|
305
|
+
The `<class>:` prefix, when present, is kept intact at the head of the
|
|
306
|
+
line -- classify_readiness_reason reads it off the cleaned reason, and the
|
|
307
|
+
emitter copies the cleaned reason whole.
|
|
308
|
+
"""
|
|
267
309
|
if not isinstance(text, str):
|
|
268
310
|
return ""
|
|
269
311
|
flat = " ".join(text.replace("`", "").split())
|
|
270
312
|
return flat[:MAX_READINESS_REASON_CHARS].rstrip()
|
|
271
313
|
|
|
272
314
|
|
|
315
|
+
def classify_readiness_reason(value: object, reason: object) -> "str | None":
|
|
316
|
+
"""The READINESS_CLASSES token an `operator` reason leads with, or None.
|
|
317
|
+
|
|
318
|
+
None for `auto` (never classed), for an operator reason that leads with
|
|
319
|
+
no recognised token (UNCLASSED -- the consumer's hard hold), and for a
|
|
320
|
+
token in the wrong case or spelling. The reason itself is never altered:
|
|
321
|
+
the class is read off it, not cut out of it.
|
|
322
|
+
"""
|
|
323
|
+
if value != READINESS_OPERATOR or not isinstance(reason, str):
|
|
324
|
+
return None
|
|
325
|
+
match = _READINESS_CLASS_RE.match(reason)
|
|
326
|
+
return match.group(1) if match else None
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _classed(value: "str | None", raw_reason: object) -> "tuple[str | None, str, str | None]":
|
|
330
|
+
"""The `(value, reason, klass)` triple both parsers hand back."""
|
|
331
|
+
reason = clean_readiness_reason(raw_reason)
|
|
332
|
+
return (value, reason, classify_readiness_reason(value, reason))
|
|
333
|
+
|
|
334
|
+
|
|
273
335
|
# The trailer's own grammar (issue #134) -- the bare-line sibling of
|
|
274
336
|
# _READINESS_RE. This is the line a native review ENDS with, whoever wrote
|
|
275
337
|
# it: the daemon's emitter (loop.readiness_trailer) when it posts the verdict
|
|
@@ -288,37 +350,42 @@ _TRAILER_RE = re.compile(
|
|
|
288
350
|
)
|
|
289
351
|
|
|
290
352
|
|
|
291
|
-
def parse_trailer(body: object) -> "tuple[str | None, str]":
|
|
292
|
-
"""`(value, reason)` from the first bare `Merge-Readiness:` line in
|
|
293
|
-
review body, or `(None, "")` when no line matches the trailer
|
|
353
|
+
def parse_trailer(body: object) -> "tuple[str | None, str, str | None]":
|
|
354
|
+
"""`(value, reason, klass)` from the first bare `Merge-Readiness:` line in
|
|
355
|
+
a review body, or `(None, "", None)` when no line matches the trailer
|
|
356
|
+
grammar.
|
|
294
357
|
|
|
295
358
|
Strict where parse_readiness is tolerant: the line inside backticks
|
|
296
359
|
mid-sentence (studio #1258) and the envelope's `- **Merge-Readiness:**`
|
|
297
360
|
bullet both read as MISSING here, because the consumer's regex is what
|
|
298
361
|
decides whether the merge edge sees the judgment at all. CRLF bodies (a
|
|
299
362
|
review typed into the web form) are normalised first; the grammar itself
|
|
300
|
-
is unchanged.
|
|
363
|
+
is unchanged. `klass` is the READINESS_CLASSES token an operator reason
|
|
364
|
+
leads with, or None (auto, or an unclassed operator -- issue #142).
|
|
301
365
|
"""
|
|
302
366
|
if not isinstance(body, str):
|
|
303
|
-
return (None, "")
|
|
367
|
+
return (None, "", None)
|
|
304
368
|
match = _TRAILER_RE.search(body.replace("\r\n", "\n"))
|
|
305
369
|
if match is None:
|
|
306
|
-
return (None, "")
|
|
307
|
-
return (match.group(1),
|
|
370
|
+
return (None, "", None)
|
|
371
|
+
return _classed(match.group(1), match.group(2))
|
|
308
372
|
|
|
309
373
|
|
|
310
|
-
def parse_readiness(blob: object) -> "tuple[str | None, str]":
|
|
311
|
-
"""`(value, reason)` from the first Merge-Readiness line in `blob`.
|
|
374
|
+
def parse_readiness(blob: object) -> "tuple[str | None, str, str | None]":
|
|
375
|
+
"""`(value, reason, klass)` from the first Merge-Readiness line in `blob`.
|
|
312
376
|
|
|
313
377
|
`value` is READINESS_AUTO / READINESS_OPERATOR, or None when no line
|
|
314
|
-
parses; `reason` is the cleaned trailing text (empty when there is none)
|
|
378
|
+
parses; `reason` is the cleaned trailing text (empty when there is none),
|
|
379
|
+
class prefix included; `klass` is the READINESS_CLASSES token that reason
|
|
380
|
+
leads with, or None when the value is `auto`, the reason is unclassed, or
|
|
381
|
+
nothing parsed (issue #142).
|
|
315
382
|
"""
|
|
316
383
|
if not isinstance(blob, str):
|
|
317
|
-
return (None, "")
|
|
384
|
+
return (None, "", None)
|
|
318
385
|
match = _READINESS_RE.search(blob)
|
|
319
386
|
if match is None:
|
|
320
|
-
return (None, "")
|
|
321
|
-
return (match.group(1),
|
|
387
|
+
return (None, "", None)
|
|
388
|
+
return _classed(match.group(1), match.group(2))
|
|
322
389
|
|
|
323
390
|
|
|
324
391
|
@dataclass(frozen=True)
|
|
@@ -580,12 +647,17 @@ class VerdictEnvelope:
|
|
|
580
647
|
envelope has no parseable `Merge-Readiness` line -- the case the native
|
|
581
648
|
post fails closed on. `readiness_reason` is already one bounded,
|
|
582
649
|
backtick-free line (see clean_readiness_reason); empty when the envelope
|
|
583
|
-
gave none.
|
|
650
|
+
gave none. `readiness_class` is the READINESS_CLASSES token an operator
|
|
651
|
+
reason leads with (issue #142), or None: for `auto`, for an unclassed
|
|
652
|
+
operator reason, and for a missing line alike -- the reason keeps the
|
|
653
|
+
prefix either way, so the class is never lost between here and the
|
|
654
|
+
native trailer.
|
|
584
655
|
"""
|
|
585
656
|
|
|
586
657
|
verdict: str
|
|
587
658
|
readiness: "str | None" = None
|
|
588
659
|
readiness_reason: str = ""
|
|
660
|
+
readiness_class: "str | None" = None
|
|
589
661
|
|
|
590
662
|
|
|
591
663
|
@dataclass(frozen=True)
|
|
@@ -1086,9 +1158,9 @@ class Alissa:
|
|
|
1086
1158
|
continue
|
|
1087
1159
|
match = _VERDICT_RE.search(blob)
|
|
1088
1160
|
if match:
|
|
1089
|
-
readiness, reason = parse_readiness(content)
|
|
1161
|
+
readiness, reason, klass = parse_readiness(content)
|
|
1090
1162
|
if readiness is None:
|
|
1091
|
-
readiness, reason = parse_readiness(title)
|
|
1163
|
+
readiness, reason, klass = parse_readiness(title)
|
|
1092
1164
|
found.append(
|
|
1093
1165
|
(Alissa._created_key(item.get("createdAt")),
|
|
1094
1166
|
index,
|
|
@@ -1096,6 +1168,7 @@ class Alissa:
|
|
|
1096
1168
|
verdict=match.group(1).lower(),
|
|
1097
1169
|
readiness=readiness,
|
|
1098
1170
|
readiness_reason=reason,
|
|
1171
|
+
readiness_class=klass,
|
|
1099
1172
|
))
|
|
1100
1173
|
)
|
|
1101
1174
|
break
|
|
@@ -427,13 +427,21 @@ NATIVE_VERDICT_BODY = (
|
|
|
427
427
|
# the last non-empty line before the hidden verdict marker --
|
|
428
428
|
#
|
|
429
429
|
# Merge-Readiness: auto
|
|
430
|
-
# Merge-Readiness: operator — <one-line reason>
|
|
430
|
+
# Merge-Readiness: operator — <class>: <one-line reason>
|
|
431
431
|
#
|
|
432
432
|
# The rules that make it safe to consume: it is emitted on APPROVE events
|
|
433
433
|
# ONLY (a request_changes envelope with a stray `auto` line carries nothing;
|
|
434
434
|
# an approve the checks gate downgraded carries nothing), and an envelope
|
|
435
435
|
# with no parseable line fails CLOSED to `operator`, with a reason that tells
|
|
436
|
-
# the operator why the merge waited on them
|
|
436
|
+
# the operator why the merge waited on them -- and NO class (issue #142): the
|
|
437
|
+
# fallback is unclassed by construction, the consumer's hard hold.
|
|
438
|
+
#
|
|
439
|
+
# The operator reason leads with one token from alissa.READINESS_CLASSES
|
|
440
|
+
# (issue #142) so the consumer can apply a POLICY to the hold instead of
|
|
441
|
+
# holding every operator alike. The emitter copies the envelope's cleaned
|
|
442
|
+
# reason whole, class prefix included, so the native line is byte-equal to
|
|
443
|
+
# the envelope's; it never adds, drops or rewrites a class -- an unclassed
|
|
444
|
+
# envelope posts unclassed, and the narration says so.
|
|
437
445
|
#
|
|
438
446
|
# The label and the grammar live in alissa.py (READINESS_TRAILER_LABEL,
|
|
439
447
|
# parse_trailer): the emitter below builds from the label the regex is built
|
|
@@ -441,6 +449,21 @@ NATIVE_VERDICT_BODY = (
|
|
|
441
449
|
# same regex (issue #134) -- one grammar, two writers, no drift.
|
|
442
450
|
READINESS_MISSING_REASON = "envelope carries no Merge-Readiness line"
|
|
443
451
|
|
|
452
|
+
# What the log line and the activity row say about an operator judgment whose
|
|
453
|
+
# reason leads with no recognised class (issue #142). Named because it is a
|
|
454
|
+
# state the merge policy acts on (hard hold), not an absence.
|
|
455
|
+
READINESS_UNCLASSED = "unclassed"
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def readiness_class_term(value: "str | None", klass: "str | None") -> str:
|
|
459
|
+
"""` class=<klass>` (or ` class=unclassed`) for an operator judgment, and
|
|
460
|
+
nothing at all for `auto` or a missing one -- `auto` never carries a
|
|
461
|
+
class, so naming one there would be noise the consumer must never read.
|
|
462
|
+
"""
|
|
463
|
+
if value != READINESS_OPERATOR:
|
|
464
|
+
return ""
|
|
465
|
+
return f" class={klass or READINESS_UNCLASSED}"
|
|
466
|
+
|
|
444
467
|
|
|
445
468
|
def readiness_trailer(envelope: "VerdictEnvelope | None") -> str:
|
|
446
469
|
"""The line the native APPROVE carries for `envelope`'s judgment.
|
|
@@ -449,7 +472,9 @@ def readiness_trailer(envelope: "VerdictEnvelope | None") -> str:
|
|
|
449
472
|
the envelope onto the grammar. `auto` is emitted bare -- the consumer
|
|
450
473
|
keys on the value, and an operator reads a reason only when the merge is
|
|
451
474
|
theirs to make. A missing or unparseable judgment (None envelope, or one
|
|
452
|
-
whose readiness did not parse) is `operator` with a reason saying so
|
|
475
|
+
whose readiness did not parse) is `operator` with a reason saying so and
|
|
476
|
+
no class. The reason is copied whole, so an envelope's `<class>:` prefix
|
|
477
|
+
reaches the native line byte for byte (issue #142).
|
|
453
478
|
"""
|
|
454
479
|
if envelope is not None and envelope.readiness == READINESS_AUTO:
|
|
455
480
|
return f"{READINESS_TRAILER_LABEL} {READINESS_AUTO}"
|
|
@@ -679,18 +704,53 @@ _RECORD_THE_CAP = (
|
|
|
679
704
|
# envelope without the line posts as `operator` -- so a reviewer who skips it
|
|
680
705
|
# has silently withheld auto-merge. The two grammar sentences are the
|
|
681
706
|
# README's ("The `Merge-Readiness` trailer on a native approve"), verbatim.
|
|
707
|
+
#
|
|
708
|
+
# The operator form carries a CLASS (issue #142): `operator — <class>:
|
|
709
|
+
# <reason>`, one token from alissa.READINESS_CLASSES, so the merge edge can
|
|
710
|
+
# apply a policy to the hold. The compact table below is the ONE place the
|
|
711
|
+
# directive states the enum (each token exactly once -- test_readiness pins
|
|
712
|
+
# it), in severity order with the most-severe rule and the reasoning the
|
|
713
|
+
# reviewer needs to classify honestly: an unverified-* hold is validation
|
|
714
|
+
# work, not merge risk, and saying so lets the policy merge it. The table's
|
|
715
|
+
# rows are the README's, condensed; the README is the long form.
|
|
682
716
|
_MERGE_READINESS_LINE = (
|
|
683
717
|
"Your verdict envelope MUST carry the skill's `- **Merge-Readiness:** auto | "
|
|
684
|
-
"operator — <reason>` line, AND your OWN native review (every `gh pr
|
|
685
|
-
"form and the reviews-API POST alike) MUST END with the bare line "
|
|
686
|
-
"`Merge-Readiness: auto` or `Merge-Readiness: operator — <one-line
|
|
687
|
-
"as its last non-empty line — plain text at the start of the line,
|
|
688
|
-
"backticks, not bold, not mid-sentence — byte-equal in value
|
|
689
|
-
"the envelope's line; `auto` only on an APPROVE of the
|
|
690
|
-
"
|
|
691
|
-
"
|
|
692
|
-
"
|
|
693
|
-
"`
|
|
718
|
+
"operator — <class>: <reason>` line, AND your OWN native review (every `gh pr "
|
|
719
|
+
"review` form and the reviews-API POST alike) MUST END with the bare line "
|
|
720
|
+
"`Merge-Readiness: auto` or `Merge-Readiness: operator — <class>: <one-line "
|
|
721
|
+
"reason>` as its last non-empty line — plain text at the start of the line, "
|
|
722
|
+
"not in backticks, not bold, not mid-sentence — byte-equal in value, class "
|
|
723
|
+
"and reason to the envelope's line; `auto` only on an APPROVE of the "
|
|
724
|
+
"reviewed head. `<class>` is exactly ONE token from this closed enum, "
|
|
725
|
+
"lowercase and hyphenated, followed by a colon and the reason; the rows are "
|
|
726
|
+
"in severity order, most severe first: "
|
|
727
|
+
"`schema-migration` (schema shape changes that need a migration, removal or "
|
|
728
|
+
"rename on live data); "
|
|
729
|
+
"`data-backfill` (one-off writes over existing rows — backfills, version "
|
|
730
|
+
"bumps of stored shapes); "
|
|
731
|
+
"`secrets-env` (new or changed env vars, secrets or credentials the deploy "
|
|
732
|
+
"must carry); "
|
|
733
|
+
"`infra-deploy` (Dockerfiles, workflows, Railway config, base-image pins — "
|
|
734
|
+
"anything that changes what runs); "
|
|
735
|
+
"`billing` (credit charging, pricing, tiers, quotas); "
|
|
736
|
+
"`security` (auth, permission gates, redaction, sandboxing — anything the "
|
|
737
|
+
"rubric's third dimension flagged as operator-worthy); "
|
|
738
|
+
"`unverified-ux` (the PR body's operator gate is a human look at a screen, "
|
|
739
|
+
"copy or mockup); "
|
|
740
|
+
"`unverified-runtime` (the PR body's operator gate is a live smoke, "
|
|
741
|
+
"real-model run or replay the worker could not run); "
|
|
742
|
+
"`release-act` (the merge itself is a release — VERSION bump → publish, tag, "
|
|
743
|
+
"npm). "
|
|
744
|
+
"When more than one row applies, name the MOST SEVERE one — the row listed "
|
|
745
|
+
"first — and only it. Classify honestly, not defensively: an unverified-* "
|
|
746
|
+
"hold is validation work the human does at gate 2 anyway, not merge risk, "
|
|
747
|
+
"and the merge policy may merge it while it keeps holding every row above; "
|
|
748
|
+
"an operator line whose reason leads with no recognised token still parses "
|
|
749
|
+
"but reads as UNCLASSED — a hard hold that costs throughput, never safety. "
|
|
750
|
+
"`auto` never carries a class. The merge edge reads that line off the "
|
|
751
|
+
"review and holds an approve without it. The daemon copies the envelope's "
|
|
752
|
+
"line onto a native review only when it posts the verdict itself, and an "
|
|
753
|
+
"envelope without the line posts as `operator` with no class. "
|
|
694
754
|
)
|
|
695
755
|
|
|
696
756
|
# -- the reviewer session's own CI gate (issue #84) ---------------------------
|
|
@@ -3732,9 +3792,14 @@ class ReviewWatcher:
|
|
|
3732
3792
|
pr.full_name, pr.number, judged, int(time.time()), url or ""
|
|
3733
3793
|
)
|
|
3734
3794
|
# The readiness carried on the post, in the same three records. Only
|
|
3735
|
-
# an APPROVE carries one, so only an APPROVE names it.
|
|
3795
|
+
# an APPROVE carries one, so only an APPROVE names it. The class is
|
|
3796
|
+
# read BACK off the emitted line with the shared grammar (issue
|
|
3797
|
+
# #142), so what the narration names is what the consumer will parse
|
|
3798
|
+
# -- an unclassed operator says `class=unclassed`, the hard hold.
|
|
3736
3799
|
readiness_value = trailer[len(READINESS_TRAILER_LABEL):].strip()
|
|
3737
|
-
|
|
3800
|
+
posted_value, _, posted_class = parse_trailer(trailer)
|
|
3801
|
+
class_term = readiness_class_term(posted_value, posted_class)
|
|
3802
|
+
readiness_note = f" readiness={readiness_value}{class_term}" if trailer else ""
|
|
3738
3803
|
log.info(
|
|
3739
3804
|
"%s round %d closed: native %s review submitted as %s (%s)%s%s",
|
|
3740
3805
|
pr.slug, round_, event, self.github.login, url or "no url",
|
|
@@ -3744,7 +3809,7 @@ class ReviewWatcher:
|
|
|
3744
3809
|
pr,
|
|
3745
3810
|
f"- {_now()} — round {round_} — native `{event}` review submitted "
|
|
3746
3811
|
f"as `{self.github.login}` (verdict of record){gate_note}"
|
|
3747
|
-
+ (f" — merge-readiness: `{readiness_value}`" if trailer else ""),
|
|
3812
|
+
+ (f" — merge-readiness: `{readiness_value}`{class_term}" if trailer else ""),
|
|
3748
3813
|
)
|
|
3749
3814
|
if event == EVENT_COMMENT:
|
|
3750
3815
|
# A degraded verdict takes the PR out of the loop (the review
|
|
@@ -4458,7 +4523,7 @@ class ReviewWatcher:
|
|
|
4458
4523
|
if self.state.readiness_observed(pr.full_name, pr.number, pr.head_sha) is not None:
|
|
4459
4524
|
return
|
|
4460
4525
|
|
|
4461
|
-
value, reason = parse_trailer(newest.body)
|
|
4526
|
+
value, reason, klass = parse_trailer(newest.body)
|
|
4462
4527
|
head7 = pr.head_sha[:7]
|
|
4463
4528
|
if value is None:
|
|
4464
4529
|
readiness = READINESS_MISSING
|
|
@@ -4476,7 +4541,10 @@ class ReviewWatcher:
|
|
|
4476
4541
|
)
|
|
4477
4542
|
else:
|
|
4478
4543
|
readiness = value
|
|
4479
|
-
|
|
4544
|
+
# The class rides along (issue #142): `class=<token>` when the
|
|
4545
|
+
# operator reason leads with one, `class=unclassed` when it does
|
|
4546
|
+
# not, nothing for `auto`.
|
|
4547
|
+
term = (f"{value} — {reason}" if reason else value) + readiness_class_term(value, klass)
|
|
4480
4548
|
log.info(
|
|
4481
4549
|
"%s approve at %s by %s carries readiness=%s",
|
|
4482
4550
|
pr.slug, head7, self.github.login, term,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.31.4
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.31.3
|
|
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
|
|
File without changes
|