surface.py
python
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4
docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit.
Human
6 days ago
| 1 | """Active-slice adversarial-freeze status / governance-sync surface (§AFF.9).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import re |
| 6 | from dataclasses import dataclass |
| 7 | from pathlib import Path |
| 8 | |
| 9 | from adapters.config import OverseerConfig |
| 10 | from tools.adversarial_freeze.authorize import ( |
| 11 | adversarial_authorization_state, |
| 12 | aff_hold_bypassed, |
| 13 | aggregate_adversarial_authorization_states, |
| 14 | frv_authorizing_freeze_paths, |
| 15 | ) |
| 16 | from tools.governance_gates.scan import ( |
| 17 | ROADMAP_ROW_RE, |
| 18 | _is_auto_model, |
| 19 | _normalize_phase_id, |
| 20 | scan_governance_gates, |
| 21 | ) |
| 22 | from tools.governance_hygiene.parse import compact_step_id |
| 23 | |
| 24 | # discover_freeze_candidates is imported lazily inside build_adversarial_freeze_gate |
| 25 | # to avoid a circular import: |
| 26 | # next_regen → adversarial_freeze.authorize → package __init__ → surface → next_regen |
| 27 | |
| 28 | AUTO_B_STEP_RE = re.compile(r"\bb\b$", re.IGNORECASE) |
| 29 | |
| 30 | WARN_PENDING = ( |
| 31 | "warning: no authorizing adversarial_freeze entry for active Auto slice" |
| 32 | ) |
| 33 | WARN_ABSENT = ( |
| 34 | "warning: adversarial_freeze unreadable (broken ledger or artifact) " |
| 35 | "for active Auto slice" |
| 36 | ) |
| 37 | |
| 38 | |
| 39 | @dataclass(frozen=True) |
| 40 | class AdversarialFreezeGateReport: |
| 41 | """Result of the active-slice adversarial-freeze probe (§AFF.9).""" |
| 42 | |
| 43 | skipped: bool |
| 44 | ok: bool |
| 45 | mode: str | None = None |
| 46 | state: str | None = None |
| 47 | matched: bool | None = None |
| 48 | message: str | None = None |
| 49 | token: str | None = None |
| 50 | |
| 51 | |
| 52 | def build_adversarial_freeze_gate( |
| 53 | config: OverseerConfig, |
| 54 | repo_root: Path, |
| 55 | *, |
| 56 | handover_text: str | None = None, |
| 57 | roadmap_text: str | None = None, |
| 58 | ) -> AdversarialFreezeGateReport: |
| 59 | """Run active-slice AFF probe when honesty suggest/require is enabled.""" |
| 60 | if aff_hold_bypassed(config): |
| 61 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 62 | |
| 63 | mode = config.honesty.adversarial_freeze |
| 64 | if mode not in {"suggest", "require"}: |
| 65 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 66 | |
| 67 | docs_root = config.repo.root_relative_docs |
| 68 | handover_path = repo_root / docs_root / config.docs.handover |
| 69 | roadmap_path = repo_root / docs_root / config.docs.roadmap |
| 70 | handover = handover_text if handover_text is not None else _read_text(handover_path) |
| 71 | roadmap = roadmap_text if roadmap_text is not None else _read_text(roadmap_path) |
| 72 | |
| 73 | gate_scan = scan_governance_gates( |
| 74 | config, |
| 75 | repo_root, |
| 76 | handover_text=handover, |
| 77 | roadmap_text=roadmap, |
| 78 | ) |
| 79 | if not gate_scan.active_phases: |
| 80 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 81 | |
| 82 | phase_id = _select_active_auto_phase(roadmap, gate_scan.active_phases) |
| 83 | if phase_id is None: |
| 84 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 85 | |
| 86 | row = _roadmap_row(roadmap or "", phase_id) |
| 87 | if row is None: |
| 88 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 89 | |
| 90 | step_id = compact_step_id(row.group("phase")) |
| 91 | from tools.governance_hygiene.next_regen import discover_freeze_candidates |
| 92 | |
| 93 | candidates = discover_freeze_candidates( |
| 94 | repo_root, step_id, row.group("deliverable") |
| 95 | ) |
| 96 | if not candidates: |
| 97 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 98 | |
| 99 | authorizing = frv_authorizing_freeze_paths( |
| 100 | repo_root, candidates, phase_id=step_id, config=config |
| 101 | ) |
| 102 | if not authorizing: |
| 103 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 104 | |
| 105 | states: list[str] = [] |
| 106 | for path in authorizing: |
| 107 | auth = adversarial_authorization_state(repo_root, path, config=config) |
| 108 | if auth.state == "off": |
| 109 | continue |
| 110 | states.append(auth.state) |
| 111 | |
| 112 | if not states: |
| 113 | return AdversarialFreezeGateReport(skipped=True, ok=True) |
| 114 | |
| 115 | aggregated = aggregate_adversarial_authorization_states(states) |
| 116 | matched = aggregated in {"pass", "skipped"} |
| 117 | |
| 118 | if matched: |
| 119 | return AdversarialFreezeGateReport( |
| 120 | skipped=False, |
| 121 | ok=True, |
| 122 | mode=mode, |
| 123 | state=aggregated, |
| 124 | matched=True, |
| 125 | ) |
| 126 | |
| 127 | if mode == "require": |
| 128 | return AdversarialFreezeGateReport( |
| 129 | skipped=False, |
| 130 | ok=False, |
| 131 | mode=mode, |
| 132 | state=aggregated, |
| 133 | matched=False, |
| 134 | message="missing adversarial_freeze ledger entry for active Auto slice", |
| 135 | token="missing_adversarial_freeze", |
| 136 | ) |
| 137 | |
| 138 | message = WARN_ABSENT if aggregated == "absent" else WARN_PENDING |
| 139 | return AdversarialFreezeGateReport( |
| 140 | skipped=False, |
| 141 | ok=True, |
| 142 | mode=mode, |
| 143 | state=aggregated, |
| 144 | matched=False, |
| 145 | message=message, |
| 146 | ) |
| 147 | |
| 148 | |
| 149 | def adversarial_freeze_gate_payload( |
| 150 | report: AdversarialFreezeGateReport, |
| 151 | ) -> dict | None: |
| 152 | if report.skipped: |
| 153 | return None |
| 154 | payload: dict = { |
| 155 | "ok": report.ok, |
| 156 | "mode": report.mode, |
| 157 | "state": report.state, |
| 158 | "matched": report.matched, |
| 159 | } |
| 160 | if report.token: |
| 161 | payload["token"] = report.token |
| 162 | return payload |
| 163 | |
| 164 | |
| 165 | def format_adversarial_freeze_gate_line( |
| 166 | report: AdversarialFreezeGateReport, |
| 167 | ) -> str | None: |
| 168 | if report.skipped or report.message is None: |
| 169 | return None |
| 170 | return report.message |
| 171 | |
| 172 | |
| 173 | def _read_text(path: Path) -> str | None: |
| 174 | if not path.is_file(): |
| 175 | return None |
| 176 | return path.read_text(encoding="utf-8") |
| 177 | |
| 178 | |
| 179 | def _select_active_auto_phase(roadmap: str | None, active: tuple[str, ...]) -> str | None: |
| 180 | if roadmap is None: |
| 181 | return None |
| 182 | for phase_id in active: |
| 183 | row = _roadmap_row(roadmap, phase_id) |
| 184 | if row is None: |
| 185 | continue |
| 186 | model = row.group("model") |
| 187 | if _is_active_auto_model(model, phase_id): |
| 188 | return phase_id |
| 189 | return None |
| 190 | |
| 191 | |
| 192 | def _is_active_auto_model(model: str, phase_id: str) -> bool: |
| 193 | if not _is_auto_model(model): |
| 194 | return False |
| 195 | lowered = model.lower() |
| 196 | if "thinking" in lowered and "auto" in lowered: |
| 197 | return bool(AUTO_B_STEP_RE.search(phase_id.replace(" ", ""))) |
| 198 | return True |
| 199 | |
| 200 | |
| 201 | def _roadmap_row(roadmap: str, phase_id: str): |
| 202 | normalized = _normalize_phase_id(phase_id) |
| 203 | tokens = [token for token in re.split(r"[\s/]+", normalized.lower()) if token] |
| 204 | for match in ROADMAP_ROW_RE.finditer(roadmap): |
| 205 | phase_label = _normalize_phase_id(match.group("phase")) |
| 206 | if phase_label == normalized: |
| 207 | return match |
| 208 | phase_lower = phase_label.lower() |
| 209 | if tokens and all(token in phase_lower for token in tokens): |
| 210 | return match |
| 211 | return None |
File History
1 commit
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4
docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit.
Human
6 days ago