status.py python
917 lines 29.0 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 10 hours ago
1 """Honesty-status co-requirement check (§K9.8 / §PE.6 / §PD.5)."""
2
3 from __future__ import annotations
4
5 from dataclasses import dataclass
6 from pathlib import Path
7 from typing import Any
8
9 from adapters.config import OverseerConfig
10 from cli.paths import confine_path, repo_relative
11 from tools.honesty.artifact import sha256_file_bytes
12 from tools.honesty.gate import check_roles_file, honesty_module_disabled, hook_enabled
13 from tools.honesty.ledger_io import read_ledger_entries
14 from tools.honesty.provenance import (
15 provenance_has_signature,
16 signature_required_for_kind,
17 verify_entry_provenance,
18 )
19 from tools.honesty.types import HOOK_NAMES, HonestyStatusJson, HonestyStatusResult
20 from tools.honesty.validate import (
21 find_matching_deploy_health,
22 find_matching_independent_second_review,
23 find_matching_verification_evidence,
24 )
25
26 EXIT_MISSING_INDEPENDENT_SECOND_REVIEW = 38
27
28
29 @dataclass(frozen=True)
30 class HonestyStatusOptions:
31 """CLI options for honesty-status."""
32
33 hook: str | None
34 artifact: str | None
35 producer_session: str | None = None
36 verification_evidence: str | None = None
37 frozen_spec: str | None = None
38 deploy_health: str | None = None
39 independent_second_review: str | None = None
40 emit_json: bool = False
41
42
43 def _usage_result(
44 *,
45 hook: str | None,
46 artifact: str | None,
47 producer_session: str | None,
48 verification_evidence: dict[str, Any] | None = None,
49 deploy_health: dict[str, Any] | None = None,
50 independent_second_review: dict[str, Any] | None = None,
51 ) -> HonestyStatusResult:
52 payload = HonestyStatusJson(
53 ok=False,
54 exit_code=1,
55 hook=hook,
56 artifact=artifact,
57 producer_session=producer_session,
58 error="usage",
59 verification_evidence=verification_evidence,
60 deploy_health=deploy_health,
61 independent_second_review=independent_second_review,
62 )
63 return HonestyStatusResult(exit_code=1, json_payload=payload)
64
65
66 def _mode_b_block(
67 *,
68 phase_id: str,
69 frozen_spec: str | None,
70 require: str,
71 matched_entry_hash: str | None,
72 ) -> dict[str, Any]:
73 return {
74 "phase_id": phase_id,
75 "frozen_spec": frozen_spec,
76 "require": require,
77 "matched_entry_hash": matched_entry_hash,
78 }
79
80
81 def _mode_c_block(
82 *,
83 phase_id: str,
84 frozen_spec: str | None,
85 require: str,
86 matched_entry_hash: str | None,
87 ) -> dict[str, Any]:
88 return {
89 "phase_id": phase_id,
90 "frozen_spec": frozen_spec,
91 "require": require,
92 "matched_entry_hash": matched_entry_hash,
93 }
94
95
96 def _mode_d_block(
97 *,
98 phase_id: str,
99 frozen_spec: str | None,
100 producer_session: str | None,
101 require: str,
102 matched_entry_hash: str | None,
103 ) -> dict[str, Any]:
104 return {
105 "phase_id": phase_id,
106 "frozen_spec": frozen_spec,
107 "producer_session": producer_session,
108 "require": require,
109 "matched_entry_hash": matched_entry_hash,
110 }
111
112
113 def _has_l1_evidence(entry: dict[str, Any]) -> bool:
114 evidence = entry.get("evidence")
115 if not isinstance(evidence, dict):
116 return False
117 reexecuted = evidence.get("reexecuted")
118 if not isinstance(reexecuted, list):
119 return False
120 prefix = "verify-step:"
121 return any(isinstance(item, str) and item.startswith(prefix) for item in reexecuted)
122
123
124 def _match_verdicts(
125 entries: list[dict[str, Any]],
126 *,
127 artifact_sha256: str,
128 producer_session: str | None,
129 ) -> list[dict[str, Any]]:
130 matches: list[dict[str, Any]] = []
131 for entry in entries:
132 if entry.get("kind") != "verdict":
133 continue
134 if entry.get("passed") is not True:
135 continue
136 if entry.get("actor_role") != "verifier":
137 continue
138 if entry.get("artifact_sha256") != artifact_sha256:
139 continue
140 if producer_session is not None and entry.get("actor_session_id") == producer_session:
141 continue
142 matches.append(entry)
143 return matches
144
145
146 def _resolve_mode(options: HonestyStatusOptions) -> str | None:
147 """Return ``mode_a``–``mode_d``, or ``None`` when usage is invalid (§ISR.5.2)."""
148 hook = options.hook
149 artifact = options.artifact
150 producer = options.producer_session is not None
151 mode_a_core = bool(hook or artifact)
152 mode_a_full = bool(hook and artifact)
153 mode_b_full = options.verification_evidence is not None
154 mode_c_full = options.deploy_health is not None
155 mode_d_full = options.independent_second_review is not None
156 frozen = options.frozen_spec is not None
157
158 # --producer-session is shared metadata for Mode A (optional) and Mode D (optional).
159 mode_a_partial = mode_a_core or (producer and not mode_d_full)
160
161 if (int(mode_b_full) + int(mode_c_full) + int(mode_d_full)) > 1:
162 return None
163 if mode_a_partial and (mode_b_full or mode_c_full or mode_d_full or frozen):
164 return None
165 if frozen and not mode_b_full and not mode_c_full and not mode_d_full:
166 return None
167 if not mode_a_full and not mode_b_full and not mode_c_full and not mode_d_full:
168 return None
169 if mode_d_full:
170 return "mode_d"
171 if mode_c_full:
172 return "mode_c"
173 if mode_b_full:
174 return "mode_b"
175 return "mode_a"
176
177
178 def _run_mode_c(
179 *,
180 config: OverseerConfig,
181 repo_root: Path,
182 options: HonestyStatusOptions,
183 ) -> HonestyStatusResult:
184 phase_id = options.deploy_health
185 frozen_spec = options.frozen_spec
186 require = config.honesty.require_deploy_health
187 assert phase_id is not None
188
189 block = _mode_c_block(
190 phase_id=phase_id,
191 frozen_spec=frozen_spec,
192 require=require,
193 matched_entry_hash=None,
194 )
195
196 if honesty_module_disabled(config):
197 payload = HonestyStatusJson(
198 ok=False,
199 exit_code=4,
200 error="refused",
201 deploy_health=block,
202 )
203 return HonestyStatusResult(
204 exit_code=4,
205 json_payload=payload,
206 stderr_extra="refused: honesty.enabled is false",
207 )
208
209 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
210 if roles_exit is not None:
211 payload = HonestyStatusJson(
212 ok=False,
213 exit_code=4,
214 error="refused",
215 deploy_health=block,
216 )
217 return HonestyStatusResult(exit_code=4, json_payload=payload)
218
219 try:
220 ledger_rel = config.honesty.ledger
221 if ledger_rel is None or not ledger_rel.strip():
222 raise ValueError("missing ledger")
223 ledger_path = confine_path(repo_root, ledger_rel)
224 except Exception:
225 payload = HonestyStatusJson(
226 ok=False,
227 exit_code=4,
228 error="refused",
229 deploy_health=block,
230 )
231 return HonestyStatusResult(exit_code=4, json_payload=payload)
232
233 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
234 if require == "require":
235 block["matched_entry_hash"] = None
236 payload = HonestyStatusJson(
237 ok=False,
238 exit_code=34,
239 error="missing_deploy_health",
240 deploy_health=block,
241 )
242 return HonestyStatusResult(exit_code=34, json_payload=payload, stderr_extra=roles_warn or "")
243 warn_msg = ""
244 if require == "warn":
245 warn_msg = "warning: no matching deploy_health evidence entry"
246 block["matched_entry_hash"] = None
247 payload = HonestyStatusJson(
248 ok=True,
249 exit_code=0,
250 error=None,
251 deploy_health=block,
252 )
253 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
254 return HonestyStatusResult(
255 exit_code=0,
256 json_payload=payload,
257 stderr_extra="\n".join(stderr_parts),
258 )
259
260 try:
261 entries = read_ledger_entries(ledger_path)
262 except (ValueError, OSError):
263 payload = HonestyStatusJson(
264 ok=False,
265 exit_code=4,
266 error="refused",
267 deploy_health=block,
268 )
269 return HonestyStatusResult(exit_code=4, json_payload=payload)
270
271 winner = find_matching_deploy_health(
272 entries,
273 phase_id=phase_id,
274 frozen_spec=frozen_spec,
275 )
276
277 if winner is None:
278 if require == "require":
279 block["matched_entry_hash"] = None
280 payload = HonestyStatusJson(
281 ok=False,
282 exit_code=34,
283 error="missing_deploy_health",
284 deploy_health=block,
285 )
286 return HonestyStatusResult(exit_code=34, json_payload=payload, stderr_extra=roles_warn or "")
287 warn_msg = ""
288 if require == "warn":
289 warn_msg = "warning: no matching deploy_health evidence entry"
290 block["matched_entry_hash"] = None
291 payload = HonestyStatusJson(
292 ok=True,
293 exit_code=0,
294 error=None,
295 deploy_health=block,
296 )
297 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
298 return HonestyStatusResult(
299 exit_code=0,
300 json_payload=payload,
301 stderr_extra="\n".join(stderr_parts),
302 )
303
304 matched_hash = winner.get("entry_hash")
305 block["matched_entry_hash"] = matched_hash if isinstance(matched_hash, str) else None
306 payload = HonestyStatusJson(
307 ok=True,
308 exit_code=0,
309 error=None,
310 deploy_health=block,
311 )
312 return HonestyStatusResult(
313 exit_code=0,
314 json_payload=payload,
315 stderr_extra=roles_warn or "",
316 )
317
318
319 def _run_mode_b(
320 *,
321 config: OverseerConfig,
322 repo_root: Path,
323 options: HonestyStatusOptions,
324 ) -> HonestyStatusResult:
325 phase_id = options.verification_evidence
326 frozen_spec = options.frozen_spec
327 require = config.honesty.require_verification_evidence
328 assert phase_id is not None
329
330 block = _mode_b_block(
331 phase_id=phase_id,
332 frozen_spec=frozen_spec,
333 require=require,
334 matched_entry_hash=None,
335 )
336
337 if honesty_module_disabled(config):
338 payload = HonestyStatusJson(
339 ok=False,
340 exit_code=4,
341 error="refused",
342 verification_evidence=block,
343 )
344 return HonestyStatusResult(
345 exit_code=4,
346 json_payload=payload,
347 stderr_extra="refused: honesty.enabled is false",
348 )
349
350 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
351 if roles_exit is not None:
352 payload = HonestyStatusJson(
353 ok=False,
354 exit_code=4,
355 error="refused",
356 verification_evidence=block,
357 )
358 return HonestyStatusResult(exit_code=4, json_payload=payload)
359
360 try:
361 ledger_rel = config.honesty.ledger
362 if ledger_rel is None or not ledger_rel.strip():
363 raise ValueError("missing ledger")
364 ledger_path = confine_path(repo_root, ledger_rel)
365 except Exception:
366 payload = HonestyStatusJson(
367 ok=False,
368 exit_code=4,
369 error="refused",
370 verification_evidence=block,
371 )
372 return HonestyStatusResult(exit_code=4, json_payload=payload)
373
374 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
375 if require == "require":
376 block["matched_entry_hash"] = None
377 payload = HonestyStatusJson(
378 ok=False,
379 exit_code=33,
380 error="missing_verification_evidence",
381 verification_evidence=block,
382 )
383 return HonestyStatusResult(exit_code=33, json_payload=payload, stderr_extra=roles_warn or "")
384 warn_msg = ""
385 if require == "warn":
386 warn_msg = "warning: no matching verification_evidence entry"
387 block["matched_entry_hash"] = None
388 payload = HonestyStatusJson(
389 ok=True,
390 exit_code=0,
391 error=None,
392 verification_evidence=block,
393 )
394 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
395 return HonestyStatusResult(
396 exit_code=0,
397 json_payload=payload,
398 stderr_extra="\n".join(stderr_parts),
399 )
400
401 try:
402 entries = read_ledger_entries(ledger_path)
403 except (ValueError, OSError):
404 payload = HonestyStatusJson(
405 ok=False,
406 exit_code=4,
407 error="refused",
408 verification_evidence=block,
409 )
410 return HonestyStatusResult(exit_code=4, json_payload=payload)
411
412 winner = find_matching_verification_evidence(
413 entries,
414 phase_id=phase_id,
415 frozen_spec=frozen_spec,
416 )
417
418 if winner is None:
419 if require == "require":
420 block["matched_entry_hash"] = None
421 payload = HonestyStatusJson(
422 ok=False,
423 exit_code=33,
424 error="missing_verification_evidence",
425 verification_evidence=block,
426 )
427 return HonestyStatusResult(exit_code=33, json_payload=payload, stderr_extra=roles_warn or "")
428 warn_msg = ""
429 if require == "warn":
430 warn_msg = "warning: no matching verification_evidence entry"
431 block["matched_entry_hash"] = None
432 payload = HonestyStatusJson(
433 ok=True,
434 exit_code=0,
435 error=None,
436 verification_evidence=block,
437 )
438 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
439 return HonestyStatusResult(
440 exit_code=0,
441 json_payload=payload,
442 stderr_extra="\n".join(stderr_parts),
443 )
444
445 matched_hash = winner.get("entry_hash")
446 block["matched_entry_hash"] = matched_hash if isinstance(matched_hash, str) else None
447 payload = HonestyStatusJson(
448 ok=True,
449 exit_code=0,
450 error=None,
451 verification_evidence=block,
452 )
453 return HonestyStatusResult(
454 exit_code=0,
455 json_payload=payload,
456 stderr_extra=roles_warn or "",
457 )
458
459
460 def _run_mode_d(
461 *,
462 config: OverseerConfig,
463 repo_root: Path,
464 options: HonestyStatusOptions,
465 ) -> HonestyStatusResult:
466 phase_id = options.independent_second_review
467 frozen_spec = options.frozen_spec
468 producer_session = options.producer_session
469 require = config.honesty.require_independent_second_reviewer
470 assert phase_id is not None
471
472 block = _mode_d_block(
473 phase_id=phase_id,
474 frozen_spec=frozen_spec,
475 producer_session=producer_session,
476 require=require,
477 matched_entry_hash=None,
478 )
479
480 if honesty_module_disabled(config):
481 payload = HonestyStatusJson(
482 ok=False,
483 exit_code=4,
484 producer_session=producer_session,
485 error="refused",
486 independent_second_review=block,
487 )
488 return HonestyStatusResult(
489 exit_code=4,
490 json_payload=payload,
491 stderr_extra="refused: honesty.enabled is false",
492 )
493
494 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
495 if roles_exit is not None:
496 payload = HonestyStatusJson(
497 ok=False,
498 exit_code=4,
499 producer_session=producer_session,
500 error="refused",
501 independent_second_review=block,
502 )
503 return HonestyStatusResult(exit_code=4, json_payload=payload)
504
505 try:
506 ledger_rel = config.honesty.ledger
507 if ledger_rel is None or not ledger_rel.strip():
508 raise ValueError("missing ledger")
509 ledger_path = confine_path(repo_root, ledger_rel)
510 except Exception:
511 payload = HonestyStatusJson(
512 ok=False,
513 exit_code=4,
514 producer_session=producer_session,
515 error="refused",
516 independent_second_review=block,
517 )
518 return HonestyStatusResult(exit_code=4, json_payload=payload)
519
520 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
521 if require == "require":
522 payload = HonestyStatusJson(
523 ok=False,
524 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
525 producer_session=producer_session,
526 error="missing_independent_second_review",
527 independent_second_review=block,
528 )
529 return HonestyStatusResult(
530 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
531 json_payload=payload,
532 stderr_extra=roles_warn or "",
533 )
534 warn_msg = ""
535 if require == "warn":
536 warn_msg = "warning: no matching independent_second_review entry"
537 payload = HonestyStatusJson(
538 ok=True,
539 exit_code=0,
540 producer_session=producer_session,
541 error=None,
542 independent_second_review=block,
543 )
544 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
545 return HonestyStatusResult(
546 exit_code=0,
547 json_payload=payload,
548 stderr_extra="\n".join(stderr_parts),
549 )
550
551 try:
552 entries = read_ledger_entries(ledger_path)
553 except (ValueError, OSError):
554 payload = HonestyStatusJson(
555 ok=False,
556 exit_code=4,
557 producer_session=producer_session,
558 error="refused",
559 independent_second_review=block,
560 )
561 return HonestyStatusResult(exit_code=4, json_payload=payload)
562
563 winner = find_matching_independent_second_review(
564 entries,
565 phase_id=phase_id,
566 frozen_spec=frozen_spec,
567 producer_session=producer_session,
568 )
569
570 if winner is None:
571 if require == "require":
572 payload = HonestyStatusJson(
573 ok=False,
574 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
575 producer_session=producer_session,
576 error="missing_independent_second_review",
577 independent_second_review=block,
578 )
579 return HonestyStatusResult(
580 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
581 json_payload=payload,
582 stderr_extra=roles_warn or "",
583 )
584 warn_msg = ""
585 if require == "warn":
586 warn_msg = "warning: no matching independent_second_review entry"
587 payload = HonestyStatusJson(
588 ok=True,
589 exit_code=0,
590 producer_session=producer_session,
591 error=None,
592 independent_second_review=block,
593 )
594 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
595 return HonestyStatusResult(
596 exit_code=0,
597 json_payload=payload,
598 stderr_extra="\n".join(stderr_parts),
599 )
600
601 matched_hash = winner.get("entry_hash")
602 block["matched_entry_hash"] = matched_hash if isinstance(matched_hash, str) else None
603 payload = HonestyStatusJson(
604 ok=True,
605 exit_code=0,
606 producer_session=producer_session,
607 error=None,
608 independent_second_review=block,
609 )
610 return HonestyStatusResult(
611 exit_code=0,
612 json_payload=payload,
613 stderr_extra=roles_warn or "",
614 )
615
616
617 def _run_mode_a(
618 *,
619 config: OverseerConfig,
620 repo_root: Path,
621 options: HonestyStatusOptions,
622 ) -> HonestyStatusResult:
623 """Evaluate co-requirement for a hook + artifact pair (unchanged from pre-P-evidence)."""
624 hook = options.hook
625 artifact = options.artifact
626
627 if not hook or not artifact:
628 payload = HonestyStatusJson(
629 ok=False,
630 exit_code=1,
631 hook=hook,
632 artifact=artifact,
633 producer_session=options.producer_session,
634 error="usage",
635 )
636 return HonestyStatusResult(exit_code=1, json_payload=payload)
637
638 if hook not in HOOK_NAMES:
639 payload = HonestyStatusJson(
640 ok=False,
641 exit_code=1,
642 hook=hook,
643 artifact=artifact,
644 producer_session=options.producer_session,
645 error="usage",
646 )
647 return HonestyStatusResult(exit_code=1, json_payload=payload)
648
649 if honesty_module_disabled(config):
650 payload = HonestyStatusJson(
651 ok=False,
652 exit_code=4,
653 hook=hook,
654 artifact=artifact,
655 producer_session=options.producer_session,
656 error="refused",
657 )
658 return HonestyStatusResult(
659 exit_code=4,
660 json_payload=payload,
661 stderr_extra="refused: honesty.enabled is false",
662 )
663
664 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
665 if roles_exit is not None:
666 payload = HonestyStatusJson(
667 ok=False,
668 exit_code=4,
669 hook=hook,
670 artifact=artifact,
671 producer_session=options.producer_session,
672 error="refused",
673 )
674 return HonestyStatusResult(exit_code=4, json_payload=payload)
675
676 if not hook_enabled(config, hook):
677 payload = HonestyStatusJson(
678 ok=False,
679 exit_code=4,
680 hook=hook,
681 artifact=artifact,
682 producer_session=options.producer_session,
683 error="refused",
684 )
685 return HonestyStatusResult(
686 exit_code=4,
687 json_payload=payload,
688 stderr_extra=f"refused: hook {hook!r} not enabled for co-requirement",
689 )
690
691 try:
692 ledger_rel = config.honesty.ledger
693 if ledger_rel is None or not ledger_rel.strip():
694 raise ValueError("missing ledger")
695 ledger_path = confine_path(repo_root, ledger_rel)
696 except Exception:
697 payload = HonestyStatusJson(
698 ok=False,
699 exit_code=4,
700 hook=hook,
701 artifact=artifact,
702 producer_session=options.producer_session,
703 error="refused",
704 )
705 return HonestyStatusResult(exit_code=4, json_payload=payload)
706
707 try:
708 artifact_path = confine_path(repo_root, artifact)
709 except Exception:
710 payload = HonestyStatusJson(
711 ok=False,
712 exit_code=4,
713 hook=hook,
714 artifact=artifact,
715 producer_session=options.producer_session,
716 error="refused",
717 )
718 return HonestyStatusResult(exit_code=4, json_payload=payload)
719
720 if not artifact_path.is_file():
721 payload = HonestyStatusJson(
722 ok=False,
723 exit_code=4,
724 hook=hook,
725 artifact=artifact,
726 producer_session=options.producer_session,
727 error="refused",
728 )
729 return HonestyStatusResult(exit_code=4, json_payload=payload)
730
731 artifact_rel = repo_relative(repo_root, artifact_path)
732 try:
733 artifact_sha256 = sha256_file_bytes(artifact_path)
734 except OSError:
735 payload = HonestyStatusJson(
736 ok=False,
737 exit_code=4,
738 hook=hook,
739 artifact=artifact_rel,
740 producer_session=options.producer_session,
741 error="refused",
742 )
743 return HonestyStatusResult(exit_code=4, json_payload=payload)
744
745 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
746 payload = HonestyStatusJson(
747 ok=False,
748 exit_code=20,
749 hook=hook,
750 artifact=artifact_rel,
751 artifact_sha256=artifact_sha256,
752 producer_session=options.producer_session,
753 matched_verdict_hash=None,
754 error="missing_verdict",
755 )
756 stderr = roles_warn or ""
757 return HonestyStatusResult(exit_code=20, json_payload=payload, stderr_extra=stderr)
758
759 try:
760 entries = read_ledger_entries(ledger_path)
761 except (ValueError, OSError):
762 payload = HonestyStatusJson(
763 ok=False,
764 exit_code=4,
765 hook=hook,
766 artifact=artifact_rel,
767 artifact_sha256=artifact_sha256,
768 producer_session=options.producer_session,
769 error="refused",
770 )
771 return HonestyStatusResult(exit_code=4, json_payload=payload)
772
773 matches = _match_verdicts(
774 entries,
775 artifact_sha256=artifact_sha256,
776 producer_session=options.producer_session,
777 )
778
779 if not matches:
780 payload = HonestyStatusJson(
781 ok=False,
782 exit_code=20,
783 hook=hook,
784 artifact=artifact_rel,
785 artifact_sha256=artifact_sha256,
786 producer_session=options.producer_session,
787 matched_verdict_hash=None,
788 error="missing_verdict",
789 )
790 return HonestyStatusResult(exit_code=20, json_payload=payload, stderr_extra=roles_warn or "")
791
792 winner = matches[-1]
793 matched_hash = winner.get("entry_hash")
794
795 if signature_required_for_kind(
796 require_agent_signature=config.honesty.require_agent_signature,
797 kind=str(winner.get("kind", "")),
798 ) and not provenance_has_signature(winner):
799 payload = HonestyStatusJson(
800 ok=False,
801 exit_code=26,
802 hook=hook,
803 artifact=artifact_rel,
804 artifact_sha256=artifact_sha256,
805 producer_session=options.producer_session,
806 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
807 error="refused",
808 )
809 return HonestyStatusResult(
810 exit_code=26,
811 json_payload=payload,
812 stderr_extra=roles_warn or "",
813 )
814
815 sig_code = verify_entry_provenance(winner, regime=config.vcs.regime)
816 if sig_code != 0:
817 payload = HonestyStatusJson(
818 ok=False,
819 exit_code=sig_code,
820 hook=hook,
821 artifact=artifact_rel,
822 artifact_sha256=artifact_sha256,
823 producer_session=options.producer_session,
824 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
825 error="refused",
826 )
827 return HonestyStatusResult(
828 exit_code=sig_code,
829 json_payload=payload,
830 stderr_extra=roles_warn or "",
831 )
832
833 mode = config.honesty.require_l1_evidence
834 warn_msg = ""
835 if mode in {"warn", "require"} and not _has_l1_evidence(winner):
836 if mode == "require":
837 payload = HonestyStatusJson(
838 ok=False,
839 exit_code=20,
840 hook=hook,
841 artifact=artifact_rel,
842 artifact_sha256=artifact_sha256,
843 producer_session=options.producer_session,
844 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
845 error="missing_verdict",
846 )
847 return HonestyStatusResult(exit_code=20, json_payload=payload, stderr_extra=roles_warn or "")
848 warn_msg = "warning: matched verdict lacks verify-step: L1 evidence"
849
850 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
851 payload = HonestyStatusJson(
852 ok=True,
853 exit_code=0,
854 hook=hook,
855 artifact=artifact_rel,
856 artifact_sha256=artifact_sha256,
857 producer_session=options.producer_session,
858 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
859 error=None,
860 )
861 return HonestyStatusResult(
862 exit_code=0,
863 json_payload=payload,
864 stderr_extra="\n".join(stderr_parts),
865 )
866
867
868 def run_honesty_status(
869 *,
870 config: OverseerConfig,
871 repo_root: Path,
872 options: HonestyStatusOptions,
873 ) -> HonestyStatusResult:
874 """Evaluate honesty-status in Mode A, B, C, or D."""
875 mode = _resolve_mode(options)
876 if mode is None:
877 verification_block = None
878 deploy_block = None
879 isr_block = None
880 if options.verification_evidence:
881 verification_block = _mode_b_block(
882 phase_id=options.verification_evidence,
883 frozen_spec=options.frozen_spec,
884 require=config.honesty.require_verification_evidence,
885 matched_entry_hash=None,
886 )
887 if options.deploy_health:
888 deploy_block = _mode_c_block(
889 phase_id=options.deploy_health,
890 frozen_spec=options.frozen_spec,
891 require=config.honesty.require_deploy_health,
892 matched_entry_hash=None,
893 )
894 if options.independent_second_review:
895 isr_block = _mode_d_block(
896 phase_id=options.independent_second_review,
897 frozen_spec=options.frozen_spec,
898 producer_session=options.producer_session,
899 require=config.honesty.require_independent_second_reviewer,
900 matched_entry_hash=None,
901 )
902 return _usage_result(
903 hook=options.hook,
904 artifact=options.artifact,
905 producer_session=options.producer_session,
906 verification_evidence=verification_block,
907 deploy_health=deploy_block,
908 independent_second_review=isr_block,
909 )
910
911 if mode == "mode_d":
912 return _run_mode_d(config=config, repo_root=repo_root, options=options)
913 if mode == "mode_c":
914 return _run_mode_c(config=config, repo_root=repo_root, options=options)
915 if mode == "mode_b":
916 return _run_mode_b(config=config, repo_root=repo_root, options=options)
917 return _run_mode_a(config=config, repo_root=repo_root, options=options)
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 10 hours ago