autoforge-cli 0.1.0a0__py3-none-any.whl

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.
Files changed (81) hide show
  1. autoforge/__init__.py +5 -0
  2. autoforge/agent_worktree.py +653 -0
  3. autoforge/certificate.py +495 -0
  4. autoforge/child_env.py +871 -0
  5. autoforge/claims.py +726 -0
  6. autoforge/claude_stream.py +501 -0
  7. autoforge/cli.py +2884 -0
  8. autoforge/config.py +2178 -0
  9. autoforge/config_fingerprint.py +374 -0
  10. autoforge/doctor.py +1551 -0
  11. autoforge/doctor_sandbox.py +691 -0
  12. autoforge/effect_ops.py +1272 -0
  13. autoforge/effects.py +3210 -0
  14. autoforge/engine.py +16637 -0
  15. autoforge/epic_commands.py +1486 -0
  16. autoforge/epic_graph.py +852 -0
  17. autoforge/errors.py +536 -0
  18. autoforge/evidence.py +1722 -0
  19. autoforge/executor.py +1601 -0
  20. autoforge/executor_duplex.py +985 -0
  21. autoforge/executor_supervised.py +144 -0
  22. autoforge/git_transport.py +970 -0
  23. autoforge/github.py +3402 -0
  24. autoforge/interaction.py +1027 -0
  25. autoforge/local_workspace.py +1253 -0
  26. autoforge/locking.py +1050 -0
  27. autoforge/loop_detect.py +660 -0
  28. autoforge/loop_guard.py +404 -0
  29. autoforge/materialize.py +722 -0
  30. autoforge/ownership.py +240 -0
  31. autoforge/pi_rpc.py +713 -0
  32. autoforge/premerge.py +181 -0
  33. autoforge/profiles.py +106 -0
  34. autoforge/progress.py +392 -0
  35. autoforge/prompts/__init__.py +173 -0
  36. autoforge/prompts/acceptance_actions.md +39 -0
  37. autoforge/prompts/analyze_execute.md +128 -0
  38. autoforge/prompts/common.md +132 -0
  39. autoforge/prompts/correction.md +12 -0
  40. autoforge/prompts/fix.md +252 -0
  41. autoforge/prompts/local_analyze_execute.md +69 -0
  42. autoforge/prompts/local_common.md +149 -0
  43. autoforge/prompts/local_fix.md +96 -0
  44. autoforge/prompts/local_review.md +103 -0
  45. autoforge/prompts/operator_acceptance.md +61 -0
  46. autoforge/prompts/replan_reexecute.md +991 -0
  47. autoforge/prompts/review.md +312 -0
  48. autoforge/prompts/update_epic.md +120 -0
  49. autoforge/prompts/update_epic_rerequest.md +85 -0
  50. autoforge/prompts/validation_evidence.md +26 -0
  51. autoforge/prompts/validation_failure.md +22 -0
  52. autoforge/prompts/worktree_state.md +45 -0
  53. autoforge/providers.py +2893 -0
  54. autoforge/read_context.py +389 -0
  55. autoforge/redaction.py +408 -0
  56. autoforge/replan.py +208 -0
  57. autoforge/replan_txn.py +1930 -0
  58. autoforge/result_parser.py +2234 -0
  59. autoforge/roadmap.py +151 -0
  60. autoforge/run_contract.py +408 -0
  61. autoforge/run_namespace.py +839 -0
  62. autoforge/runlog.py +764 -0
  63. autoforge/safefs.py +1717 -0
  64. autoforge/sandbox.py +1313 -0
  65. autoforge/sandbox_bwrap.py +815 -0
  66. autoforge/sandbox_pin.py +170 -0
  67. autoforge/sandbox_policy.py +1107 -0
  68. autoforge/scheduler.py +1143 -0
  69. autoforge/scheduler_state.py +2316 -0
  70. autoforge/scheduler_stop.py +421 -0
  71. autoforge/sdk_policy.py +988 -0
  72. autoforge/state.py +2417 -0
  73. autoforge/transitions.py +388 -0
  74. autoforge/validation.py +324 -0
  75. autoforge/worker.py +568 -0
  76. autoforge_cli-0.1.0a0.dist-info/METADATA +525 -0
  77. autoforge_cli-0.1.0a0.dist-info/RECORD +81 -0
  78. autoforge_cli-0.1.0a0.dist-info/WHEEL +5 -0
  79. autoforge_cli-0.1.0a0.dist-info/entry_points.txt +2 -0
  80. autoforge_cli-0.1.0a0.dist-info/licenses/LICENSE +21 -0
  81. autoforge_cli-0.1.0a0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,495 @@
1
+ """The controller's review certificate: issuance and applicability (ADR 0006 D12, #174).
2
+
3
+ A ``ReviewCertificate`` means exactly (D1.2): for candidate C, in review
4
+ round N, bound to PR / HEAD / base / merge base (REMOTE) or to workspace
5
+ fingerprint F (LOCAL), the controller accepted a clean review result; it
6
+ presented exactly validation set V to that review, and every record in V is
7
+ ``PASS`` for C under the definitions and policy recorded; for REMOTE, it
8
+ verified the published review comment identity K. It is not a GitHub
9
+ approval, a CI result, permission to merge, proof that the code is correct
10
+ or an authorization token, and it stands in for no merge-gate fact
11
+ (:data:`NOT_A_PERMISSION`).
12
+
13
+ Two things live here:
14
+
15
+ - **Issuance** (D12.2). :func:`remote_certificate` and
16
+ :func:`local_certificate` build the certificate from persisted, verified
17
+ inputs only: the ``review_evidence`` binding of the completed round (its
18
+ candidate, record seqs and digests, set and policy digests are copied,
19
+ never computed from the configuration in force), the review binding, the
20
+ clean verdict and, for REMOTE, the comment identity #162 observed. An input
21
+ that is missing or inconsistent raises :class:`CertificateIssuanceError`
22
+ naming it: no certificate is built.
23
+ - **Applicability** (D12.3). :func:`certificate_problem` says why a
24
+ certificate does not apply to the state, the configuration in force and,
25
+ when given, the live PR revision, or ``""`` when it does.
26
+ :func:`configuration_drift` is its set-and-policy part alone, which the
27
+ issuing write routes on (D12.6).
28
+
29
+ The schema, its bounds and its load-time integrity check are #172's
30
+ (:func:`autoforge.evidence.check_certificate`). A certificate is never shown
31
+ to an agent, rendered into a prompt or published, and never accepted from
32
+ one (D1.3). This module does no I/O and reads no clock: the engine passes
33
+ ``issued_at``.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from collections.abc import Mapping, Sequence
39
+ from dataclasses import dataclass
40
+
41
+ from .errors import AutoForgeError, ConfigurationError, StateError
42
+ from .evidence import (
43
+ KIND_LOCAL,
44
+ KIND_REMOTE,
45
+ SCHEMA,
46
+ Candidate,
47
+ Definition,
48
+ LocalCandidate,
49
+ Outcome,
50
+ Policy,
51
+ RecordView,
52
+ RemoteCandidate,
53
+ bound_records,
54
+ certificate_digest,
55
+ check_certificate,
56
+ eligible,
57
+ parse_candidate,
58
+ set_digest,
59
+ validation_set_digest,
60
+ )
61
+ from .state import AutoForgeState
62
+ from .transitions import WorkflowMode
63
+ from .validation import parse_comment_url, parse_pr_url
64
+
65
+ # The fixed wording every display of a certificate carries (D1.2).
66
+ NOT_A_PERMISSION = (
67
+ "a review certificate records what the controller observed of one clean review; it is "
68
+ "not a GitHub approval, a CI result or permission to merge, and the merge gate re-reads "
69
+ "every fact it decides on from GitHub"
70
+ )
71
+
72
+ CLEAN = {"needs_fix_round": False, "actionable_findings": 0}
73
+
74
+
75
+ class CertificateIssuanceError(AutoForgeError):
76
+ """A certificate cannot be issued from the persisted inputs (ADR 0006 D12.2).
77
+
78
+ An invariant violation: the REVIEW entry never launches a reviewer under
79
+ another round's or candidate's binding (D5.5), a clean result over a
80
+ non-``PASS`` record is refused (D9.4) and load checks every record, so
81
+ the issuing write enters ``BLOCKED`` naming the input instead of the
82
+ transition, and writes no certificate.
83
+ """
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class Configured:
88
+ """The validation set and policy P the configuration in force yields (D3.5, D8.1)."""
89
+
90
+ definitions: tuple[Definition, ...]
91
+ policy: Policy
92
+
93
+ @property
94
+ def validation_set_digest(self) -> str:
95
+ return validation_set_digest(self.definitions)
96
+
97
+ @property
98
+ def policy_digest(self) -> str:
99
+ return self.policy.digest
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class Revision:
104
+ """A PR revision: the REMOTE review binding, or the one GitHub returned live."""
105
+
106
+ pr_url: str
107
+ head_sha: str
108
+ base_ref: str
109
+ merge_base_sha: str
110
+
111
+
112
+ # -- issuance (D12.2) ------------------------------------------------------------------
113
+
114
+
115
+ def _issuance_inputs(
116
+ binding: Mapping[str, object],
117
+ records: Sequence[RecordView],
118
+ *,
119
+ review_round: int,
120
+ needs_fix_round: bool,
121
+ actionable_findings: int,
122
+ ) -> tuple[Candidate, list[RecordView]]:
123
+ """The binding's candidate and named records, once every issuance requirement holds."""
124
+ if needs_fix_round is not False or actionable_findings != 0:
125
+ raise CertificateIssuanceError(
126
+ f"review round {review_round} is not clean (needs_fix_round {needs_fix_round}, "
127
+ f"{actionable_findings} actionable finding(s)); only a clean result is certified"
128
+ )
129
+ if not binding:
130
+ raise CertificateIssuanceError(
131
+ f"no review_evidence binding is persisted for review round {review_round}: the "
132
+ "controller cannot say which validation the review was presented"
133
+ )
134
+ bound_round = binding.get("review_round")
135
+ if bound_round != review_round:
136
+ raise CertificateIssuanceError(
137
+ f"the review_evidence binding is of review round {bound_round}, not of the "
138
+ f"completed round {review_round}"
139
+ )
140
+ try:
141
+ candidate = parse_candidate(binding.get("candidate"), "the review_evidence candidate")
142
+ named = bound_records(binding, records)
143
+ except StateError as exc:
144
+ raise CertificateIssuanceError(f"the review_evidence binding is unusable: {exc}") from None
145
+ for view in named:
146
+ if view.outcome is not Outcome.PASS:
147
+ raise CertificateIssuanceError(
148
+ f"the review_evidence binding names validation record #{view.seq} (definition "
149
+ f"[{view.index}]), which is {view.outcome.value}, not PASS"
150
+ )
151
+ if view.candidate.identity != candidate.identity:
152
+ raise CertificateIssuanceError(
153
+ f"the review_evidence binding names validation record #{view.seq}, which is "
154
+ "for another candidate"
155
+ )
156
+ if view.policy_digest != binding.get("policy_digest"):
157
+ raise CertificateIssuanceError(
158
+ f"the review_evidence binding names validation record #{view.seq}, which ran "
159
+ "under another policy"
160
+ )
161
+ if set_digest([v.definition_digest for v in named]) != binding.get("validation_set_digest"):
162
+ raise CertificateIssuanceError(
163
+ f"the {len(named)} validation record(s) the review_evidence binding names do not "
164
+ "encode to its validation_set_digest: the review was not presented a whole set"
165
+ )
166
+ return candidate, named
167
+
168
+
169
+ def _common(
170
+ kind: str,
171
+ candidate: Candidate,
172
+ review_round: int,
173
+ binding: Mapping[str, object],
174
+ issued_at: str,
175
+ ) -> dict:
176
+ pairs = binding["records"]
177
+ assert isinstance(pairs, list) # bound_records read it
178
+ return {
179
+ "schema": SCHEMA,
180
+ "kind": kind,
181
+ "candidate": candidate.to_dict(),
182
+ "review_round": review_round,
183
+ "records": [{"seq": pair["seq"], "digest": pair["digest"]} for pair in pairs],
184
+ "validation_set_digest": binding["validation_set_digest"],
185
+ "policy_digest": binding["policy_digest"],
186
+ "clean": dict(CLEAN),
187
+ "issued_at": issued_at,
188
+ }
189
+
190
+
191
+ def _sealed(
192
+ certificate: dict,
193
+ records: Sequence[RecordView],
194
+ *,
195
+ local: bool,
196
+ repository: str,
197
+ run_id: str,
198
+ ) -> dict:
199
+ """Digest ``certificate`` and check it as a load will: a writer never builds one it refuses."""
200
+ certificate["digest"] = certificate_digest(certificate)
201
+ try:
202
+ check_certificate(certificate, records, local=local, repository=repository, run_id=run_id)
203
+ except StateError as exc:
204
+ raise CertificateIssuanceError(f"the certificate would not load: {exc}") from None
205
+ return certificate
206
+
207
+
208
+ def remote_certificate(
209
+ binding: Mapping[str, object],
210
+ records: Sequence[RecordView],
211
+ *,
212
+ repository: str,
213
+ run_id: str,
214
+ review_round: int,
215
+ needs_fix_round: bool,
216
+ actionable_findings: int,
217
+ reviewed: Revision,
218
+ comment_url: str,
219
+ marker_key: Mapping[str, object],
220
+ issued_at: str,
221
+ ) -> dict:
222
+ """The REMOTE certificate of a clean round (D12.1, D12.2).
223
+
224
+ ``reviewed`` is the review binding the round's comment marker names
225
+ (PR, HEAD, base, merge base), ``comment_url`` the comment #162 read back
226
+ (or its journal observed) and ``marker_key`` that marker's key. The
227
+ binding's candidate must be the reviewed HEAD's commit: its tree was
228
+ derived from that commit at the REVIEW entry (#173).
229
+ """
230
+ candidate, _ = _issuance_inputs(
231
+ binding,
232
+ records,
233
+ review_round=review_round,
234
+ needs_fix_round=needs_fix_round,
235
+ actionable_findings=actionable_findings,
236
+ )
237
+ if not isinstance(candidate, RemoteCandidate):
238
+ raise CertificateIssuanceError("the review_evidence binding names a LOCAL candidate")
239
+ if candidate.commit != reviewed.head_sha.lower():
240
+ raise CertificateIssuanceError(
241
+ f"the review_evidence binding names commit {candidate.commit[:12]}, not the "
242
+ f"reviewed HEAD {reviewed.head_sha[:12]}"
243
+ )
244
+ try:
245
+ comment = parse_comment_url(comment_url)
246
+ pr_url = parse_pr_url(reviewed.pr_url).canonical
247
+ except ConfigurationError as exc:
248
+ raise CertificateIssuanceError(f"the review comment identity is unusable: {exc}") from None
249
+ certificate = _common(KIND_REMOTE, candidate, review_round, binding, issued_at)
250
+ certificate.update(
251
+ {
252
+ "reviewed_pr_url": pr_url,
253
+ "reviewed_head_sha": reviewed.head_sha.lower(),
254
+ "reviewed_base_ref": reviewed.base_ref,
255
+ "reviewed_merge_base_sha": reviewed.merge_base_sha,
256
+ "comment": {
257
+ "url": comment.canonical,
258
+ "id": comment.comment_id,
259
+ "marker_key": {
260
+ "review_round": marker_key.get("review_round"),
261
+ "reviewed_head_sha": marker_key.get("reviewed_head_sha"),
262
+ "reviewed_base_ref": marker_key.get("reviewed_base_ref"),
263
+ "reviewed_merge_base_sha": marker_key.get("reviewed_merge_base_sha"),
264
+ },
265
+ },
266
+ }
267
+ )
268
+ return _sealed(certificate, records, local=False, repository=repository, run_id=run_id)
269
+
270
+
271
+ def local_certificate(
272
+ binding: Mapping[str, object],
273
+ records: Sequence[RecordView],
274
+ *,
275
+ run_id: str,
276
+ review_round: int,
277
+ needs_fix_round: bool,
278
+ actionable_findings: int,
279
+ reviewed_workspace_fingerprint: str,
280
+ issued_at: str,
281
+ ) -> dict:
282
+ """The LOCAL certificate of a clean round (D12.1, D12.2, D12.5): GitHub is never consulted.
283
+
284
+ ``reviewed_workspace_fingerprint`` is the fingerprint the round was
285
+ bound to and re-checked against after the reviewer exited; the
286
+ binding's candidate must be that workspace.
287
+ """
288
+ candidate, _ = _issuance_inputs(
289
+ binding,
290
+ records,
291
+ review_round=review_round,
292
+ needs_fix_round=needs_fix_round,
293
+ actionable_findings=actionable_findings,
294
+ )
295
+ if not isinstance(candidate, LocalCandidate):
296
+ raise CertificateIssuanceError("the review_evidence binding names a REMOTE candidate")
297
+ if candidate.fingerprint != reviewed_workspace_fingerprint:
298
+ raise CertificateIssuanceError(
299
+ f"the review_evidence binding names workspace {candidate.fingerprint[:16]}..., not "
300
+ f"the reviewed workspace {reviewed_workspace_fingerprint[:16]}..."
301
+ )
302
+ certificate = _common(KIND_LOCAL, candidate, review_round, binding, issued_at)
303
+ certificate["reviewed_workspace_fingerprint"] = reviewed_workspace_fingerprint
304
+ return _sealed(certificate, records, local=True, repository="", run_id=run_id)
305
+
306
+
307
+ # -- applicability (D12.3, D12.6, D13.2) -------------------------------------------------
308
+
309
+
310
+ def configuration_drift(certificate: Mapping[str, object], configured: Configured) -> str:
311
+ """Which of the certificate's set and policy digests the configuration no longer yields, or "".
312
+
313
+ D12.6: the issuing write routes on this, and so does
314
+ :func:`certificate_problem`, so that a drift is named as the drift.
315
+ """
316
+ drifted = []
317
+ if certificate.get("validation_set_digest") != configured.validation_set_digest:
318
+ drifted.append(
319
+ f"the validation set changed (the review was presented set "
320
+ f"{str(certificate.get('validation_set_digest'))[:16]}..., the configuration now "
321
+ f"yields {configured.validation_set_digest[:16]}...)"
322
+ )
323
+ if certificate.get("policy_digest") != configured.policy_digest:
324
+ drifted.append(
325
+ f"the validation policy changed (the review was presented policy "
326
+ f"{str(certificate.get('policy_digest'))[:16]}..., the configuration now yields "
327
+ f"{configured.policy_digest[:16]}...)"
328
+ )
329
+ return "; ".join(drifted)
330
+
331
+
332
+ def _binding_problem(certificate: Mapping[str, object], state: AutoForgeState) -> str:
333
+ """REMOTE: the certificate's review binding and comment against the state's."""
334
+ for name in (
335
+ "reviewed_pr_url",
336
+ "reviewed_head_sha",
337
+ "reviewed_base_ref",
338
+ "reviewed_merge_base_sha",
339
+ ):
340
+ held = getattr(state, name)
341
+ if name == "reviewed_head_sha":
342
+ held = held.lower()
343
+ if certificate.get(name) != held:
344
+ return (
345
+ f"its {name} {str(certificate.get(name))[:80]!r} is not the state's {held[:80]!r}"
346
+ )
347
+ candidate = certificate.get("candidate")
348
+ commit = candidate.get("commit") if isinstance(candidate, dict) else None
349
+ if commit != state.reviewed_head_sha.lower():
350
+ return f"its candidate commit {str(commit)[:12]} is not the reviewed HEAD"
351
+ comment = certificate.get("comment")
352
+ url = comment.get("url") if isinstance(comment, dict) else None
353
+ if url != state.last_review_comment_url:
354
+ return (
355
+ f"its review comment {str(url)[:200]} is not the last review comment "
356
+ f"{state.last_review_comment_url[:200] or '(none)'}"
357
+ )
358
+ return ""
359
+
360
+
361
+ def _local_binding_problem(certificate: Mapping[str, object], state: AutoForgeState) -> str:
362
+ """LOCAL: the certificate's workspace against the one the state's last review bound."""
363
+ bound = state.reviewed_workspace_fingerprint
364
+ held = certificate.get("reviewed_workspace_fingerprint")
365
+ if held != bound:
366
+ return (
367
+ f"its reviewed workspace {str(held)[:16]}... is not the state's reviewed workspace "
368
+ f"{bound[:16] or '(none)'}..."
369
+ )
370
+ try:
371
+ policy_digest = state.local_contract().workspace_policy.digest()
372
+ except (StateError, ConfigurationError) as exc:
373
+ return f"the run contract is unreadable: {exc}"
374
+ expected = LocalCandidate(bound, policy_digest, state.run_id).identity
375
+ candidate = parse_candidate(certificate.get("candidate"), "the certificate candidate")
376
+ if candidate.identity != expected:
377
+ return "its candidate is not the reviewed workspace under this run's contract"
378
+ return ""
379
+
380
+
381
+ def certificate_problem(
382
+ certificate: Mapping[str, object],
383
+ state: AutoForgeState,
384
+ configured: Configured,
385
+ live: Revision | None = None,
386
+ ) -> str:
387
+ """Why ``certificate`` does not apply, or ``""`` when it does (D12.3; #175 adds ``live``).
388
+
389
+ Checked, in this order, each failing with one bounded reason: it is
390
+ held, its kind is the run's mode, its digest verifies and it loads as
391
+ a certificate against the state's records (D11.1); its round is the
392
+ state's completed round, and that round's result is still ``clean`` (an
393
+ observed drift marks it ``stale``, #175); its review binding equals the
394
+ state's (REMOTE: PR, HEAD, base ref and merge base, its candidate's
395
+ commit is the reviewed HEAD and its comment is
396
+ ``last_review_comment_url``; LOCAL: the reviewed workspace fingerprint,
397
+ under this run's contract); the
398
+ configuration in force yields its set and policy digests
399
+ (:func:`configuration_drift`); and every record it names is eligible
400
+ evidence for its candidate under that set and policy
401
+ (:func:`autoforge.evidence.eligible`). With ``live`` (REMOTE only: the
402
+ PR URL, HEAD, base ref and merge base as GitHub returned them) those
403
+ must equal the certificate's too. Pure: no I/O.
404
+ """
405
+ if not certificate:
406
+ return "no review certificate is held"
407
+ local = state.mode == WorkflowMode.LOCAL
408
+ if live is not None and local:
409
+ raise ValueError("a LOCAL certificate has no live GitHub revision")
410
+ kind = certificate.get("kind")
411
+ mode_kind = KIND_LOCAL if local else KIND_REMOTE
412
+ if kind != mode_kind:
413
+ return f"it is a {str(kind)[:16]!r} certificate in a {mode_kind} run"
414
+ if certificate_digest(certificate) != certificate.get("digest"):
415
+ return "its digest does not verify: it was edited or corrupted"
416
+ try:
417
+ records = state.validation_evidence()
418
+ check_certificate(
419
+ certificate,
420
+ records,
421
+ local=local,
422
+ repository=state.repository,
423
+ run_id=state.run_id,
424
+ )
425
+ except StateError as exc:
426
+ return f"it does not load: {str(exc)[:300]}"
427
+ if certificate["review_round"] != state.review_round:
428
+ return (
429
+ f"it is of review round {certificate['review_round']}, not of the completed round "
430
+ f"{state.review_round}"
431
+ )
432
+ if state.last_review_result != "clean":
433
+ # #175: a drift the controller observed (a push, a retarget, a
434
+ # rewritten base) marks the round stale without moving its binding.
435
+ return (
436
+ f"the state's last review result is {state.last_review_result[:32] or 'unrecorded'!r}"
437
+ ", not the clean round it certifies"
438
+ )
439
+ problem = (
440
+ _local_binding_problem(certificate, state)
441
+ if local
442
+ else _binding_problem(certificate, state)
443
+ )
444
+ if problem:
445
+ return problem
446
+ drift = configuration_drift(certificate, configured)
447
+ if drift:
448
+ return drift
449
+ candidate = parse_candidate(certificate["candidate"], "the certificate candidate")
450
+ named = bound_records(certificate, records)
451
+ for definition, view in zip(configured.definitions, named, strict=True):
452
+ if not eligible(
453
+ view, candidate, definition, configured.policy, state.validation_override_seq
454
+ ):
455
+ return (
456
+ f"validation record #{view.seq} it names is not evidence for definition "
457
+ f"[{definition.index}] of its candidate under the policy in force"
458
+ )
459
+ if live is not None:
460
+ return _live_problem(certificate, live)
461
+ return ""
462
+
463
+
464
+ def _live_problem(certificate: Mapping[str, object], live: Revision) -> str:
465
+ try:
466
+ same_pr = parse_pr_url(live.pr_url).same_target(
467
+ parse_pr_url(str(certificate["reviewed_pr_url"]))
468
+ )
469
+ except ConfigurationError:
470
+ same_pr = False
471
+ if not same_pr:
472
+ return (
473
+ f"it is of PR {certificate['reviewed_pr_url']}, not of the live PR {live.pr_url[:200]}"
474
+ )
475
+ for name, value in (
476
+ ("reviewed_head_sha", live.head_sha.lower()),
477
+ ("reviewed_base_ref", live.base_ref),
478
+ ("reviewed_merge_base_sha", live.merge_base_sha),
479
+ ):
480
+ if certificate[name] != value:
481
+ return f"its {name} {str(certificate[name])[:80]!r} is not the live {value[:80]!r}"
482
+ return ""
483
+
484
+
485
+ def describe(certificate: Mapping[str, object]) -> str:
486
+ """One display line: kind, candidate, round, set and policy digests, ``issued_at``."""
487
+ candidate = parse_candidate(certificate["candidate"], "the certificate candidate")
488
+ pairs = certificate["records"]
489
+ count = len(pairs) if isinstance(pairs, list) else 0
490
+ return (
491
+ f"{certificate['kind']}, {candidate.describe()}, review round "
492
+ f"{certificate['review_round']}, {count} record(s) "
493
+ f"of set {str(certificate['validation_set_digest'])[:16]}..., policy "
494
+ f"{str(certificate['policy_digest'])[:16]}..., issued {certificate['issued_at']}"
495
+ )