status.py python
1,304 lines 40.7 KB
Raw
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4 docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit. Human 5 days ago
1 """Honesty-status co-requirement check (§K9.8 / §PE.6 / §PD.5)."""
2
3 from __future__ import annotations
4
5 import re
6 from dataclasses import dataclass
7 from pathlib import Path
8 from typing import Any
9
10 from adapters.config import OverseerConfig
11 from cli.paths import PathEscapeError, confine_path, repo_relative
12 from tools.freeze_reviewer.artifact import artifact_digest, parse_artifact
13 from tools.honesty.artifact import sha256_file_bytes
14 from tools.honesty.gate import check_roles_file, honesty_module_disabled, hook_enabled
15 from tools.honesty.ledger import verify_chain
16 from tools.honesty.ledger_io import read_ledger_entries
17 from tools.honesty.provenance import (
18 provenance_has_signature,
19 signature_required_for_kind,
20 verify_entry_provenance,
21 )
22 from tools.honesty.types import HOOK_NAMES, HonestyStatusJson, HonestyStatusResult
23 from tools.honesty.validate import (
24 find_latest_adversarial_freeze_verdict,
25 find_matching_deploy_health,
26 find_matching_independent_second_review,
27 find_matching_verification_evidence,
28 )
29
30 EXIT_MISSING_INDEPENDENT_SECOND_REVIEW = 38
31 EXIT_MISSING_ADVERSARIAL_FREEZE = 40
32
33 _DIGEST_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
34
35
36 @dataclass(frozen=True)
37 class HonestyStatusOptions:
38 """CLI options for honesty-status."""
39
40 hook: str | None
41 artifact: str | None
42 producer_session: str | None = None
43 verification_evidence: str | None = None
44 frozen_spec: str | None = None
45 deploy_health: str | None = None
46 independent_second_review: str | None = None
47 adversarial_freeze: str | None = None
48 artifact_digest: str | None = None
49 emit_json: bool = False
50
51
52 def _usage_result(
53 *,
54 hook: str | None,
55 artifact: str | None,
56 producer_session: str | None,
57 verification_evidence: dict[str, Any] | None = None,
58 deploy_health: dict[str, Any] | None = None,
59 independent_second_review: dict[str, Any] | None = None,
60 adversarial_freeze: dict[str, Any] | None = None,
61 ) -> HonestyStatusResult:
62 payload = HonestyStatusJson(
63 ok=False,
64 exit_code=1,
65 hook=hook,
66 artifact=artifact,
67 producer_session=producer_session,
68 error="usage",
69 verification_evidence=verification_evidence,
70 deploy_health=deploy_health,
71 independent_second_review=independent_second_review,
72 adversarial_freeze=adversarial_freeze,
73 )
74 return HonestyStatusResult(exit_code=1, json_payload=payload)
75
76
77 def _mode_b_block(
78 *,
79 phase_id: str,
80 frozen_spec: str | None,
81 require: str,
82 matched_entry_hash: str | None,
83 ) -> dict[str, Any]:
84 return {
85 "phase_id": phase_id,
86 "frozen_spec": frozen_spec,
87 "require": require,
88 "matched_entry_hash": matched_entry_hash,
89 }
90
91
92 def _mode_c_block(
93 *,
94 phase_id: str,
95 frozen_spec: str | None,
96 require: str,
97 matched_entry_hash: str | None,
98 ) -> dict[str, Any]:
99 return {
100 "phase_id": phase_id,
101 "frozen_spec": frozen_spec,
102 "require": require,
103 "matched_entry_hash": matched_entry_hash,
104 }
105
106
107 def _mode_d_block(
108 *,
109 phase_id: str,
110 frozen_spec: str | None,
111 producer_session: str | None,
112 require: str,
113 matched_entry_hash: str | None,
114 ) -> dict[str, Any]:
115 return {
116 "phase_id": phase_id,
117 "frozen_spec": frozen_spec,
118 "producer_session": producer_session,
119 "require": require,
120 "matched_entry_hash": matched_entry_hash,
121 }
122
123
124 def _mode_e_block(
125 *,
126 phase_id: str,
127 frozen_spec: str | None,
128 artifact_digest: str | None,
129 producer_session: str | None,
130 mode: str,
131 matched_via: str | None,
132 matched_entry_hash: str | None,
133 ) -> dict[str, Any]:
134 return {
135 "phase_id": phase_id,
136 "frozen_spec": frozen_spec,
137 "artifact_digest": artifact_digest,
138 "producer_session": producer_session,
139 "mode": mode,
140 "matched_via": matched_via,
141 "matched_entry_hash": matched_entry_hash,
142 }
143
144
145 def _has_l1_evidence(entry: dict[str, Any]) -> bool:
146 evidence = entry.get("evidence")
147 if not isinstance(evidence, dict):
148 return False
149 reexecuted = evidence.get("reexecuted")
150 if not isinstance(reexecuted, list):
151 return False
152 prefix = "verify-step:"
153 return any(isinstance(item, str) and item.startswith(prefix) for item in reexecuted)
154
155
156 def _match_verdicts(
157 entries: list[dict[str, Any]],
158 *,
159 artifact_sha256: str,
160 producer_session: str | None,
161 ) -> list[dict[str, Any]]:
162 matches: list[dict[str, Any]] = []
163 for entry in entries:
164 if entry.get("kind") != "verdict":
165 continue
166 if entry.get("passed") is not True:
167 continue
168 if entry.get("actor_role") != "verifier":
169 continue
170 if entry.get("artifact_sha256") != artifact_sha256:
171 continue
172 if producer_session is not None and entry.get("actor_session_id") == producer_session:
173 continue
174 matches.append(entry)
175 return matches
176
177
178 def _resolve_mode(options: HonestyStatusOptions) -> str | None:
179 """Return ``mode_a``–``mode_e``, or ``None`` when usage is invalid (§AFF.8.2)."""
180 hook = options.hook
181 artifact = options.artifact
182 producer = options.producer_session is not None
183 mode_a_core = bool(hook or artifact)
184 mode_a_full = bool(hook and artifact)
185 mode_b_full = options.verification_evidence is not None
186 mode_c_full = options.deploy_health is not None
187 mode_d_full = options.independent_second_review is not None
188 mode_e_full = options.adversarial_freeze is not None
189 frozen = options.frozen_spec is not None
190 digest = options.artifact_digest is not None
191
192 mode_a_partial = mode_a_core or (
193 producer and not mode_d_full and not mode_e_full
194 )
195
196 if digest and not mode_e_full:
197 return None
198 if mode_e_full and digest and not frozen:
199 return None
200 if (int(mode_b_full) + int(mode_c_full) + int(mode_d_full) + int(mode_e_full)) > 1:
201 return None
202 if mode_a_partial and (
203 mode_b_full or mode_c_full or mode_d_full or mode_e_full or frozen
204 ):
205 return None
206 if frozen and not mode_b_full and not mode_c_full and not mode_d_full and not mode_e_full:
207 return None
208 if (
209 not mode_a_full
210 and not mode_b_full
211 and not mode_c_full
212 and not mode_d_full
213 and not mode_e_full
214 ):
215 return None
216 if mode_e_full:
217 return "mode_e"
218 if mode_d_full:
219 return "mode_d"
220 if mode_c_full:
221 return "mode_c"
222 if mode_b_full:
223 return "mode_b"
224 return "mode_a"
225
226
227 def _run_mode_c(
228 *,
229 config: OverseerConfig,
230 repo_root: Path,
231 options: HonestyStatusOptions,
232 ) -> HonestyStatusResult:
233 phase_id = options.deploy_health
234 frozen_spec = options.frozen_spec
235 require = config.honesty.require_deploy_health
236 assert phase_id is not None
237
238 block = _mode_c_block(
239 phase_id=phase_id,
240 frozen_spec=frozen_spec,
241 require=require,
242 matched_entry_hash=None,
243 )
244
245 if honesty_module_disabled(config):
246 payload = HonestyStatusJson(
247 ok=False,
248 exit_code=4,
249 error="refused",
250 deploy_health=block,
251 )
252 return HonestyStatusResult(
253 exit_code=4,
254 json_payload=payload,
255 stderr_extra="refused: honesty.enabled is false",
256 )
257
258 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
259 if roles_exit is not None:
260 payload = HonestyStatusJson(
261 ok=False,
262 exit_code=4,
263 error="refused",
264 deploy_health=block,
265 )
266 return HonestyStatusResult(exit_code=4, json_payload=payload)
267
268 try:
269 ledger_rel = config.honesty.ledger
270 if ledger_rel is None or not ledger_rel.strip():
271 raise ValueError("missing ledger")
272 ledger_path = confine_path(repo_root, ledger_rel)
273 except Exception:
274 payload = HonestyStatusJson(
275 ok=False,
276 exit_code=4,
277 error="refused",
278 deploy_health=block,
279 )
280 return HonestyStatusResult(exit_code=4, json_payload=payload)
281
282 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
283 if require == "require":
284 block["matched_entry_hash"] = None
285 payload = HonestyStatusJson(
286 ok=False,
287 exit_code=34,
288 error="missing_deploy_health",
289 deploy_health=block,
290 )
291 return HonestyStatusResult(exit_code=34, json_payload=payload, stderr_extra=roles_warn or "")
292 warn_msg = ""
293 if require == "warn":
294 warn_msg = "warning: no matching deploy_health evidence entry"
295 block["matched_entry_hash"] = None
296 payload = HonestyStatusJson(
297 ok=True,
298 exit_code=0,
299 error=None,
300 deploy_health=block,
301 )
302 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
303 return HonestyStatusResult(
304 exit_code=0,
305 json_payload=payload,
306 stderr_extra="\n".join(stderr_parts),
307 )
308
309 try:
310 entries = read_ledger_entries(ledger_path)
311 except (ValueError, OSError):
312 payload = HonestyStatusJson(
313 ok=False,
314 exit_code=4,
315 error="refused",
316 deploy_health=block,
317 )
318 return HonestyStatusResult(exit_code=4, json_payload=payload)
319
320 winner = find_matching_deploy_health(
321 entries,
322 phase_id=phase_id,
323 frozen_spec=frozen_spec,
324 )
325
326 if winner is None:
327 if require == "require":
328 block["matched_entry_hash"] = None
329 payload = HonestyStatusJson(
330 ok=False,
331 exit_code=34,
332 error="missing_deploy_health",
333 deploy_health=block,
334 )
335 return HonestyStatusResult(exit_code=34, json_payload=payload, stderr_extra=roles_warn or "")
336 warn_msg = ""
337 if require == "warn":
338 warn_msg = "warning: no matching deploy_health evidence entry"
339 block["matched_entry_hash"] = None
340 payload = HonestyStatusJson(
341 ok=True,
342 exit_code=0,
343 error=None,
344 deploy_health=block,
345 )
346 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
347 return HonestyStatusResult(
348 exit_code=0,
349 json_payload=payload,
350 stderr_extra="\n".join(stderr_parts),
351 )
352
353 matched_hash = winner.get("entry_hash")
354 block["matched_entry_hash"] = matched_hash if isinstance(matched_hash, str) else None
355 payload = HonestyStatusJson(
356 ok=True,
357 exit_code=0,
358 error=None,
359 deploy_health=block,
360 )
361 return HonestyStatusResult(
362 exit_code=0,
363 json_payload=payload,
364 stderr_extra=roles_warn or "",
365 )
366
367
368 def _run_mode_b(
369 *,
370 config: OverseerConfig,
371 repo_root: Path,
372 options: HonestyStatusOptions,
373 ) -> HonestyStatusResult:
374 phase_id = options.verification_evidence
375 frozen_spec = options.frozen_spec
376 require = config.honesty.require_verification_evidence
377 assert phase_id is not None
378
379 block = _mode_b_block(
380 phase_id=phase_id,
381 frozen_spec=frozen_spec,
382 require=require,
383 matched_entry_hash=None,
384 )
385
386 if honesty_module_disabled(config):
387 payload = HonestyStatusJson(
388 ok=False,
389 exit_code=4,
390 error="refused",
391 verification_evidence=block,
392 )
393 return HonestyStatusResult(
394 exit_code=4,
395 json_payload=payload,
396 stderr_extra="refused: honesty.enabled is false",
397 )
398
399 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
400 if roles_exit is not None:
401 payload = HonestyStatusJson(
402 ok=False,
403 exit_code=4,
404 error="refused",
405 verification_evidence=block,
406 )
407 return HonestyStatusResult(exit_code=4, json_payload=payload)
408
409 try:
410 ledger_rel = config.honesty.ledger
411 if ledger_rel is None or not ledger_rel.strip():
412 raise ValueError("missing ledger")
413 ledger_path = confine_path(repo_root, ledger_rel)
414 except Exception:
415 payload = HonestyStatusJson(
416 ok=False,
417 exit_code=4,
418 error="refused",
419 verification_evidence=block,
420 )
421 return HonestyStatusResult(exit_code=4, json_payload=payload)
422
423 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
424 if require == "require":
425 block["matched_entry_hash"] = None
426 payload = HonestyStatusJson(
427 ok=False,
428 exit_code=33,
429 error="missing_verification_evidence",
430 verification_evidence=block,
431 )
432 return HonestyStatusResult(exit_code=33, json_payload=payload, stderr_extra=roles_warn or "")
433 warn_msg = ""
434 if require == "warn":
435 warn_msg = "warning: no matching verification_evidence entry"
436 block["matched_entry_hash"] = None
437 payload = HonestyStatusJson(
438 ok=True,
439 exit_code=0,
440 error=None,
441 verification_evidence=block,
442 )
443 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
444 return HonestyStatusResult(
445 exit_code=0,
446 json_payload=payload,
447 stderr_extra="\n".join(stderr_parts),
448 )
449
450 try:
451 entries = read_ledger_entries(ledger_path)
452 except (ValueError, OSError):
453 payload = HonestyStatusJson(
454 ok=False,
455 exit_code=4,
456 error="refused",
457 verification_evidence=block,
458 )
459 return HonestyStatusResult(exit_code=4, json_payload=payload)
460
461 winner = find_matching_verification_evidence(
462 entries,
463 phase_id=phase_id,
464 frozen_spec=frozen_spec,
465 )
466
467 if winner is None:
468 if require == "require":
469 block["matched_entry_hash"] = None
470 payload = HonestyStatusJson(
471 ok=False,
472 exit_code=33,
473 error="missing_verification_evidence",
474 verification_evidence=block,
475 )
476 return HonestyStatusResult(exit_code=33, json_payload=payload, stderr_extra=roles_warn or "")
477 warn_msg = ""
478 if require == "warn":
479 warn_msg = "warning: no matching verification_evidence entry"
480 block["matched_entry_hash"] = None
481 payload = HonestyStatusJson(
482 ok=True,
483 exit_code=0,
484 error=None,
485 verification_evidence=block,
486 )
487 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
488 return HonestyStatusResult(
489 exit_code=0,
490 json_payload=payload,
491 stderr_extra="\n".join(stderr_parts),
492 )
493
494 matched_hash = winner.get("entry_hash")
495 block["matched_entry_hash"] = matched_hash if isinstance(matched_hash, str) else None
496 payload = HonestyStatusJson(
497 ok=True,
498 exit_code=0,
499 error=None,
500 verification_evidence=block,
501 )
502 return HonestyStatusResult(
503 exit_code=0,
504 json_payload=payload,
505 stderr_extra=roles_warn or "",
506 )
507
508
509 def _run_mode_d(
510 *,
511 config: OverseerConfig,
512 repo_root: Path,
513 options: HonestyStatusOptions,
514 ) -> HonestyStatusResult:
515 phase_id = options.independent_second_review
516 frozen_spec = options.frozen_spec
517 producer_session = options.producer_session
518 require = config.honesty.require_independent_second_reviewer
519 assert phase_id is not None
520
521 block = _mode_d_block(
522 phase_id=phase_id,
523 frozen_spec=frozen_spec,
524 producer_session=producer_session,
525 require=require,
526 matched_entry_hash=None,
527 )
528
529 if honesty_module_disabled(config):
530 payload = HonestyStatusJson(
531 ok=False,
532 exit_code=4,
533 producer_session=producer_session,
534 error="refused",
535 independent_second_review=block,
536 )
537 return HonestyStatusResult(
538 exit_code=4,
539 json_payload=payload,
540 stderr_extra="refused: honesty.enabled is false",
541 )
542
543 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
544 if roles_exit is not None:
545 payload = HonestyStatusJson(
546 ok=False,
547 exit_code=4,
548 producer_session=producer_session,
549 error="refused",
550 independent_second_review=block,
551 )
552 return HonestyStatusResult(exit_code=4, json_payload=payload)
553
554 try:
555 ledger_rel = config.honesty.ledger
556 if ledger_rel is None or not ledger_rel.strip():
557 raise ValueError("missing ledger")
558 ledger_path = confine_path(repo_root, ledger_rel)
559 except Exception:
560 payload = HonestyStatusJson(
561 ok=False,
562 exit_code=4,
563 producer_session=producer_session,
564 error="refused",
565 independent_second_review=block,
566 )
567 return HonestyStatusResult(exit_code=4, json_payload=payload)
568
569 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
570 if require == "require":
571 payload = HonestyStatusJson(
572 ok=False,
573 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
574 producer_session=producer_session,
575 error="missing_independent_second_review",
576 independent_second_review=block,
577 )
578 return HonestyStatusResult(
579 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
580 json_payload=payload,
581 stderr_extra=roles_warn or "",
582 )
583 warn_msg = ""
584 if require == "warn":
585 warn_msg = "warning: no matching independent_second_review entry"
586 payload = HonestyStatusJson(
587 ok=True,
588 exit_code=0,
589 producer_session=producer_session,
590 error=None,
591 independent_second_review=block,
592 )
593 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
594 return HonestyStatusResult(
595 exit_code=0,
596 json_payload=payload,
597 stderr_extra="\n".join(stderr_parts),
598 )
599
600 try:
601 entries = read_ledger_entries(ledger_path)
602 except (ValueError, OSError):
603 payload = HonestyStatusJson(
604 ok=False,
605 exit_code=4,
606 producer_session=producer_session,
607 error="refused",
608 independent_second_review=block,
609 )
610 return HonestyStatusResult(exit_code=4, json_payload=payload)
611
612 winner = find_matching_independent_second_review(
613 entries,
614 phase_id=phase_id,
615 frozen_spec=frozen_spec,
616 producer_session=producer_session,
617 )
618
619 if winner is None:
620 if require == "require":
621 payload = HonestyStatusJson(
622 ok=False,
623 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
624 producer_session=producer_session,
625 error="missing_independent_second_review",
626 independent_second_review=block,
627 )
628 return HonestyStatusResult(
629 exit_code=EXIT_MISSING_INDEPENDENT_SECOND_REVIEW,
630 json_payload=payload,
631 stderr_extra=roles_warn or "",
632 )
633 warn_msg = ""
634 if require == "warn":
635 warn_msg = "warning: no matching independent_second_review entry"
636 payload = HonestyStatusJson(
637 ok=True,
638 exit_code=0,
639 producer_session=producer_session,
640 error=None,
641 independent_second_review=block,
642 )
643 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
644 return HonestyStatusResult(
645 exit_code=0,
646 json_payload=payload,
647 stderr_extra="\n".join(stderr_parts),
648 )
649
650 matched_hash = winner.get("entry_hash")
651 block["matched_entry_hash"] = matched_hash if isinstance(matched_hash, str) else None
652 payload = HonestyStatusJson(
653 ok=True,
654 exit_code=0,
655 producer_session=producer_session,
656 error=None,
657 independent_second_review=block,
658 )
659 return HonestyStatusResult(
660 exit_code=0,
661 json_payload=payload,
662 stderr_extra=roles_warn or "",
663 )
664
665
666 def _run_mode_a(
667 *,
668 config: OverseerConfig,
669 repo_root: Path,
670 options: HonestyStatusOptions,
671 ) -> HonestyStatusResult:
672 """Evaluate co-requirement for a hook + artifact pair (unchanged from pre-P-evidence)."""
673 hook = options.hook
674 artifact = options.artifact
675
676 if not hook or not artifact:
677 payload = HonestyStatusJson(
678 ok=False,
679 exit_code=1,
680 hook=hook,
681 artifact=artifact,
682 producer_session=options.producer_session,
683 error="usage",
684 )
685 return HonestyStatusResult(exit_code=1, json_payload=payload)
686
687 if hook not in HOOK_NAMES:
688 payload = HonestyStatusJson(
689 ok=False,
690 exit_code=1,
691 hook=hook,
692 artifact=artifact,
693 producer_session=options.producer_session,
694 error="usage",
695 )
696 return HonestyStatusResult(exit_code=1, json_payload=payload)
697
698 if honesty_module_disabled(config):
699 payload = HonestyStatusJson(
700 ok=False,
701 exit_code=4,
702 hook=hook,
703 artifact=artifact,
704 producer_session=options.producer_session,
705 error="refused",
706 )
707 return HonestyStatusResult(
708 exit_code=4,
709 json_payload=payload,
710 stderr_extra="refused: honesty.enabled is false",
711 )
712
713 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
714 if roles_exit is not None:
715 payload = HonestyStatusJson(
716 ok=False,
717 exit_code=4,
718 hook=hook,
719 artifact=artifact,
720 producer_session=options.producer_session,
721 error="refused",
722 )
723 return HonestyStatusResult(exit_code=4, json_payload=payload)
724
725 if not hook_enabled(config, hook):
726 payload = HonestyStatusJson(
727 ok=False,
728 exit_code=4,
729 hook=hook,
730 artifact=artifact,
731 producer_session=options.producer_session,
732 error="refused",
733 )
734 return HonestyStatusResult(
735 exit_code=4,
736 json_payload=payload,
737 stderr_extra=f"refused: hook {hook!r} not enabled for co-requirement",
738 )
739
740 try:
741 ledger_rel = config.honesty.ledger
742 if ledger_rel is None or not ledger_rel.strip():
743 raise ValueError("missing ledger")
744 ledger_path = confine_path(repo_root, ledger_rel)
745 except Exception:
746 payload = HonestyStatusJson(
747 ok=False,
748 exit_code=4,
749 hook=hook,
750 artifact=artifact,
751 producer_session=options.producer_session,
752 error="refused",
753 )
754 return HonestyStatusResult(exit_code=4, json_payload=payload)
755
756 try:
757 artifact_path = confine_path(repo_root, artifact)
758 except Exception:
759 payload = HonestyStatusJson(
760 ok=False,
761 exit_code=4,
762 hook=hook,
763 artifact=artifact,
764 producer_session=options.producer_session,
765 error="refused",
766 )
767 return HonestyStatusResult(exit_code=4, json_payload=payload)
768
769 if not artifact_path.is_file():
770 payload = HonestyStatusJson(
771 ok=False,
772 exit_code=4,
773 hook=hook,
774 artifact=artifact,
775 producer_session=options.producer_session,
776 error="refused",
777 )
778 return HonestyStatusResult(exit_code=4, json_payload=payload)
779
780 artifact_rel = repo_relative(repo_root, artifact_path)
781 try:
782 artifact_sha256 = sha256_file_bytes(artifact_path)
783 except OSError:
784 payload = HonestyStatusJson(
785 ok=False,
786 exit_code=4,
787 hook=hook,
788 artifact=artifact_rel,
789 producer_session=options.producer_session,
790 error="refused",
791 )
792 return HonestyStatusResult(exit_code=4, json_payload=payload)
793
794 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
795 payload = HonestyStatusJson(
796 ok=False,
797 exit_code=20,
798 hook=hook,
799 artifact=artifact_rel,
800 artifact_sha256=artifact_sha256,
801 producer_session=options.producer_session,
802 matched_verdict_hash=None,
803 error="missing_verdict",
804 )
805 stderr = roles_warn or ""
806 return HonestyStatusResult(exit_code=20, json_payload=payload, stderr_extra=stderr)
807
808 try:
809 entries = read_ledger_entries(ledger_path)
810 except (ValueError, OSError):
811 payload = HonestyStatusJson(
812 ok=False,
813 exit_code=4,
814 hook=hook,
815 artifact=artifact_rel,
816 artifact_sha256=artifact_sha256,
817 producer_session=options.producer_session,
818 error="refused",
819 )
820 return HonestyStatusResult(exit_code=4, json_payload=payload)
821
822 matches = _match_verdicts(
823 entries,
824 artifact_sha256=artifact_sha256,
825 producer_session=options.producer_session,
826 )
827
828 if not matches:
829 payload = HonestyStatusJson(
830 ok=False,
831 exit_code=20,
832 hook=hook,
833 artifact=artifact_rel,
834 artifact_sha256=artifact_sha256,
835 producer_session=options.producer_session,
836 matched_verdict_hash=None,
837 error="missing_verdict",
838 )
839 return HonestyStatusResult(exit_code=20, json_payload=payload, stderr_extra=roles_warn or "")
840
841 winner = matches[-1]
842 matched_hash = winner.get("entry_hash")
843
844 if signature_required_for_kind(
845 require_agent_signature=config.honesty.require_agent_signature,
846 kind=str(winner.get("kind", "")),
847 ) and not provenance_has_signature(winner):
848 payload = HonestyStatusJson(
849 ok=False,
850 exit_code=26,
851 hook=hook,
852 artifact=artifact_rel,
853 artifact_sha256=artifact_sha256,
854 producer_session=options.producer_session,
855 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
856 error="refused",
857 )
858 return HonestyStatusResult(
859 exit_code=26,
860 json_payload=payload,
861 stderr_extra=roles_warn or "",
862 )
863
864 sig_code = verify_entry_provenance(winner, regime=config.vcs.regime)
865 if sig_code != 0:
866 payload = HonestyStatusJson(
867 ok=False,
868 exit_code=sig_code,
869 hook=hook,
870 artifact=artifact_rel,
871 artifact_sha256=artifact_sha256,
872 producer_session=options.producer_session,
873 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
874 error="refused",
875 )
876 return HonestyStatusResult(
877 exit_code=sig_code,
878 json_payload=payload,
879 stderr_extra=roles_warn or "",
880 )
881
882 mode = config.honesty.require_l1_evidence
883 warn_msg = ""
884 if mode in {"warn", "require"} and not _has_l1_evidence(winner):
885 if mode == "require":
886 payload = HonestyStatusJson(
887 ok=False,
888 exit_code=20,
889 hook=hook,
890 artifact=artifact_rel,
891 artifact_sha256=artifact_sha256,
892 producer_session=options.producer_session,
893 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
894 error="missing_verdict",
895 )
896 return HonestyStatusResult(exit_code=20, json_payload=payload, stderr_extra=roles_warn or "")
897 warn_msg = "warning: matched verdict lacks verify-step: L1 evidence"
898
899 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
900 payload = HonestyStatusJson(
901 ok=True,
902 exit_code=0,
903 hook=hook,
904 artifact=artifact_rel,
905 artifact_sha256=artifact_sha256,
906 producer_session=options.producer_session,
907 matched_verdict_hash=matched_hash if isinstance(matched_hash, str) else None,
908 error=None,
909 )
910 return HonestyStatusResult(
911 exit_code=0,
912 json_payload=payload,
913 stderr_extra="\n".join(stderr_parts),
914 )
915
916
917 def run_honesty_status(
918 *,
919 config: OverseerConfig,
920 repo_root: Path,
921 options: HonestyStatusOptions,
922 ) -> HonestyStatusResult:
923 """Evaluate honesty-status in Mode A, B, C, D, or E."""
924 mode = _resolve_mode(options)
925 if mode is None:
926 verification_block = None
927 deploy_block = None
928 isr_block = None
929 aff_block = None
930 if options.verification_evidence:
931 verification_block = _mode_b_block(
932 phase_id=options.verification_evidence,
933 frozen_spec=options.frozen_spec,
934 require=config.honesty.require_verification_evidence,
935 matched_entry_hash=None,
936 )
937 if options.deploy_health:
938 deploy_block = _mode_c_block(
939 phase_id=options.deploy_health,
940 frozen_spec=options.frozen_spec,
941 require=config.honesty.require_deploy_health,
942 matched_entry_hash=None,
943 )
944 if options.independent_second_review:
945 isr_block = _mode_d_block(
946 phase_id=options.independent_second_review,
947 frozen_spec=options.frozen_spec,
948 producer_session=options.producer_session,
949 require=config.honesty.require_independent_second_reviewer,
950 matched_entry_hash=None,
951 )
952 if options.adversarial_freeze:
953 aff_block = _mode_e_block(
954 phase_id=options.adversarial_freeze,
955 frozen_spec=None,
956 artifact_digest=None,
957 producer_session=options.producer_session,
958 mode=config.honesty.adversarial_freeze,
959 matched_via=None,
960 matched_entry_hash=None,
961 )
962 return _usage_result(
963 hook=options.hook,
964 artifact=options.artifact,
965 producer_session=options.producer_session,
966 verification_evidence=verification_block,
967 deploy_health=deploy_block,
968 independent_second_review=isr_block,
969 adversarial_freeze=aff_block,
970 )
971
972 if mode == "mode_e":
973 return _run_mode_e(config=config, repo_root=repo_root, options=options)
974 if mode == "mode_d":
975 return _run_mode_d(config=config, repo_root=repo_root, options=options)
976 if mode == "mode_c":
977 return _run_mode_c(config=config, repo_root=repo_root, options=options)
978 if mode == "mode_b":
979 return _run_mode_b(config=config, repo_root=repo_root, options=options)
980 return _run_mode_a(config=config, repo_root=repo_root, options=options)
981
982
983 def _resolve_mode_e_path_digest(
984 *,
985 repo_root: Path,
986 options: HonestyStatusOptions,
987 ) -> tuple[str | None, str | None, int | None]:
988 """Return ``(frozen_spec, artifact_digest, refuse_exit)`` for Mode E (§AFF.8.1).
989
990 ``refuse_exit`` is ``4`` on path escape/I/O, ``1`` on digest usage errors,
991 or ``None`` when resolution succeeded (including fail-closed miss with nulls).
992 """
993 import stat as stat_mod
994
995 frozen_flag = options.frozen_spec
996 digest_flag = options.artifact_digest
997 effective_digest: str | None = None
998
999 if digest_flag is not None:
1000 if not isinstance(digest_flag, str) or not _DIGEST_RE.fullmatch(digest_flag):
1001 return None, None, 1
1002 effective_digest = digest_flag
1003
1004 if frozen_flag is None:
1005 return None, effective_digest, None
1006
1007 try:
1008 confined = confine_path(repo_root, frozen_flag)
1009 effective_path = repo_relative(repo_root, confined)
1010 except PathEscapeError:
1011 return None, None, 4
1012 except OSError:
1013 return None, None, 4
1014
1015 try:
1016 exists = confined.exists()
1017 except OSError:
1018 return None, None, 4
1019
1020 if not exists:
1021 return effective_path, effective_digest, None
1022
1023 try:
1024 st = confined.stat()
1025 is_regular = stat_mod.S_ISREG(st.st_mode)
1026 except OSError:
1027 return None, None, 4
1028
1029 if is_regular:
1030 try:
1031 parsed = parse_artifact(confined, rel_path=effective_path)
1032 hashed = artifact_digest(parsed)
1033 except (ValueError, OSError, UnicodeError):
1034 if effective_digest is None:
1035 return effective_path, None, None
1036 return None, None, 1
1037 if effective_digest is None:
1038 return effective_path, hashed, None
1039 if hashed != effective_digest:
1040 return None, None, 1
1041 return effective_path, effective_digest, None
1042
1043 # Existing non-regular path: explicit digest → ledger query; else miss.
1044 if effective_digest is not None:
1045 return effective_path, effective_digest, None
1046 return effective_path, None, None
1047
1048
1049 def _run_mode_e(
1050 *,
1051 config: OverseerConfig,
1052 repo_root: Path,
1053 options: HonestyStatusOptions,
1054 ) -> HonestyStatusResult:
1055 phase_id = options.adversarial_freeze
1056 producer_session = options.producer_session
1057 mode = config.honesty.adversarial_freeze
1058 assert phase_id is not None
1059
1060 # Digest shape check before path work when flag present and mode resolved.
1061 if options.artifact_digest is not None and (
1062 not isinstance(options.artifact_digest, str)
1063 or not _DIGEST_RE.fullmatch(options.artifact_digest)
1064 ):
1065 block = _mode_e_block(
1066 phase_id=phase_id,
1067 frozen_spec=None,
1068 artifact_digest=None,
1069 producer_session=producer_session,
1070 mode=mode,
1071 matched_via=None,
1072 matched_entry_hash=None,
1073 )
1074 return _usage_result(
1075 hook=None,
1076 artifact=None,
1077 producer_session=producer_session,
1078 adversarial_freeze=block,
1079 )
1080
1081 frozen_spec, artifact_digest_val, refuse = _resolve_mode_e_path_digest(
1082 repo_root=repo_root, options=options
1083 )
1084 if refuse == 1:
1085 block = _mode_e_block(
1086 phase_id=phase_id,
1087 frozen_spec=None,
1088 artifact_digest=None,
1089 producer_session=producer_session,
1090 mode=mode,
1091 matched_via=None,
1092 matched_entry_hash=None,
1093 )
1094 return _usage_result(
1095 hook=None,
1096 artifact=None,
1097 producer_session=producer_session,
1098 adversarial_freeze=block,
1099 )
1100
1101 block = _mode_e_block(
1102 phase_id=phase_id,
1103 frozen_spec=frozen_spec,
1104 artifact_digest=artifact_digest_val,
1105 producer_session=producer_session,
1106 mode=mode,
1107 matched_via=None,
1108 matched_entry_hash=None,
1109 )
1110
1111 if refuse == 4 or honesty_module_disabled(config):
1112 payload = HonestyStatusJson(
1113 ok=False,
1114 exit_code=4,
1115 producer_session=producer_session,
1116 error="refused",
1117 adversarial_freeze=block,
1118 )
1119 return HonestyStatusResult(
1120 exit_code=4,
1121 json_payload=payload,
1122 stderr_extra="refused: honesty.enabled is false"
1123 if honesty_module_disabled(config)
1124 else "refused",
1125 )
1126
1127 roles_exit, roles_warn = check_roles_file(config.honesty, repo_root)
1128 if roles_exit is not None:
1129 payload = HonestyStatusJson(
1130 ok=False,
1131 exit_code=4,
1132 producer_session=producer_session,
1133 error="refused",
1134 adversarial_freeze=block,
1135 )
1136 return HonestyStatusResult(exit_code=4, json_payload=payload)
1137
1138 # Mode off: exact block, no ledger read.
1139 if mode == "off":
1140 payload = HonestyStatusJson(
1141 ok=True,
1142 exit_code=0,
1143 producer_session=producer_session,
1144 error=None,
1145 adversarial_freeze=block,
1146 )
1147 return HonestyStatusResult(
1148 exit_code=0,
1149 json_payload=payload,
1150 stderr_extra=roles_warn or "",
1151 )
1152
1153 # Fail-closed miss when effective path or digest is null.
1154 if frozen_spec is None or artifact_digest_val is None:
1155 return _mode_e_miss(
1156 block=block,
1157 mode=mode,
1158 producer_session=producer_session,
1159 roles_warn=roles_warn,
1160 )
1161
1162 try:
1163 ledger_rel = config.honesty.ledger
1164 if ledger_rel is None or not ledger_rel.strip():
1165 raise ValueError("missing ledger")
1166 ledger_path = confine_path(repo_root, ledger_rel)
1167 except Exception:
1168 payload = HonestyStatusJson(
1169 ok=False,
1170 exit_code=4,
1171 producer_session=producer_session,
1172 error="refused",
1173 adversarial_freeze=block,
1174 )
1175 return HonestyStatusResult(exit_code=4, json_payload=payload)
1176
1177 if not ledger_path.is_file() or ledger_path.stat().st_size == 0:
1178 return _mode_e_miss(
1179 block=block,
1180 mode=mode,
1181 producer_session=producer_session,
1182 roles_warn=roles_warn,
1183 )
1184
1185 try:
1186 entries = read_ledger_entries(ledger_path)
1187 except (ValueError, OSError):
1188 payload = HonestyStatusJson(
1189 ok=False,
1190 exit_code=4,
1191 producer_session=producer_session,
1192 error="refused",
1193 adversarial_freeze=block,
1194 )
1195 return HonestyStatusResult(exit_code=4, json_payload=payload)
1196
1197 chain_code = verify_chain(
1198 entries,
1199 regime=config.vcs.regime,
1200 require_agent_signature=config.honesty.require_agent_signature,
1201 )
1202 if chain_code == 22:
1203 payload = HonestyStatusJson(
1204 ok=False,
1205 exit_code=22,
1206 producer_session=producer_session,
1207 error="ledger_broken",
1208 adversarial_freeze=block,
1209 )
1210 return HonestyStatusResult(exit_code=22, json_payload=payload, stderr_extra=roles_warn or "")
1211 if chain_code in {2, 25, 26}:
1212 payload = HonestyStatusJson(
1213 ok=False,
1214 exit_code=chain_code,
1215 producer_session=producer_session,
1216 error="refused",
1217 adversarial_freeze=block,
1218 )
1219 return HonestyStatusResult(
1220 exit_code=chain_code, json_payload=payload, stderr_extra=roles_warn or ""
1221 )
1222
1223 winner = find_latest_adversarial_freeze_verdict(
1224 entries,
1225 frozen_spec=frozen_spec,
1226 artifact_digest=artifact_digest_val,
1227 phase_id=phase_id,
1228 producer_session=producer_session,
1229 )
1230
1231 authorizing = False
1232 matched_via: str | None = None
1233 matched_hash: str | None = None
1234 if winner is not None:
1235 verdict = winner.get("aff_verdict")
1236 if verdict == "pass":
1237 authorizing = True
1238 matched_via = "pass"
1239 mh = winner.get("entry_hash")
1240 matched_hash = mh if isinstance(mh, str) else None
1241 elif verdict == "skip" and mode == "suggest":
1242 authorizing = True
1243 matched_via = "skip"
1244 mh = winner.get("entry_hash")
1245 matched_hash = mh if isinstance(mh, str) else None
1246
1247 if authorizing:
1248 block["matched_via"] = matched_via
1249 block["matched_entry_hash"] = matched_hash
1250 payload = HonestyStatusJson(
1251 ok=True,
1252 exit_code=0,
1253 producer_session=producer_session,
1254 error=None,
1255 adversarial_freeze=block,
1256 )
1257 return HonestyStatusResult(
1258 exit_code=0, json_payload=payload, stderr_extra=roles_warn or ""
1259 )
1260
1261 return _mode_e_miss(
1262 block=block,
1263 mode=mode,
1264 producer_session=producer_session,
1265 roles_warn=roles_warn,
1266 )
1267
1268
1269 def _mode_e_miss(
1270 *,
1271 block: dict[str, Any],
1272 mode: str,
1273 producer_session: str | None,
1274 roles_warn: str | None,
1275 ) -> HonestyStatusResult:
1276 if mode == "require":
1277 payload = HonestyStatusJson(
1278 ok=False,
1279 exit_code=EXIT_MISSING_ADVERSARIAL_FREEZE,
1280 producer_session=producer_session,
1281 error="missing_adversarial_freeze",
1282 adversarial_freeze=block,
1283 )
1284 return HonestyStatusResult(
1285 exit_code=EXIT_MISSING_ADVERSARIAL_FREEZE,
1286 json_payload=payload,
1287 stderr_extra=roles_warn or "",
1288 )
1289 warn_msg = ""
1290 if mode == "suggest":
1291 warn_msg = "warning: no authorizing adversarial_freeze entry"
1292 payload = HonestyStatusJson(
1293 ok=True,
1294 exit_code=0,
1295 producer_session=producer_session,
1296 error=None,
1297 adversarial_freeze=block,
1298 )
1299 stderr_parts = [part for part in (roles_warn, warn_msg) if part]
1300 return HonestyStatusResult(
1301 exit_code=0,
1302 json_payload=payload,
1303 stderr_extra="\n".join(stderr_parts),
1304 )
File History 1 commit
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4 docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit. Human 5 days ago