its-magic 0.1.3-0 → 0.1.3-2

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 (61) hide show
  1. package/installer.ps1 +8 -0
  2. package/installer.py +8 -0
  3. package/installer.sh +1 -0
  4. package/package.json +1 -1
  5. package/scripts/check_intake_template_parity.py +62 -0
  6. package/template/.cursor/agents/curator.mdc +1 -0
  7. package/template/.cursor/agents/po.mdc +1 -0
  8. package/template/.cursor/agents/release.mdc +1 -0
  9. package/template/.cursor/commands/auto.md +90 -0
  10. package/template/.cursor/commands/execute.md +26 -0
  11. package/template/.cursor/commands/refresh-context.md +32 -0
  12. package/template/.cursor/commands/sovereign-critic.md +104 -0
  13. package/template/.cursor/model-catalog.local.example.cursor-only.json +9 -0
  14. package/template/.cursor/model-catalog.local.example.json +9 -0
  15. package/template/.cursor/model-catalog.local.example.level-1-easy.json +9 -0
  16. package/template/.cursor/model-catalog.local.example.level-2-complex.json +9 -0
  17. package/template/.cursor/model-catalog.local.example.level-3-mega.json +9 -0
  18. package/template/.cursor/model-catalog.local.example.level-4-super.json +9 -0
  19. package/template/.cursor/model-catalog.local.example.role-based-balanced.json +18 -0
  20. package/template/.cursor/model-catalog.local.example.role-based-highend.json +18 -0
  21. package/template/.cursor/rules/sovereign-role-manifest.mdc.example +27 -0
  22. package/template/.cursor/scratchpad.local.example.md +55 -0
  23. package/template/.cursor/scratchpad.md +203 -0
  24. package/template/.cursor/sovereign-role-manifest.yaml.example +53 -0
  25. package/template/decisions/DEC-0104.md +279 -0
  26. package/template/decisions/DEC-0105.md +231 -0
  27. package/template/decisions/DEC-0107.md +246 -0
  28. package/template/docs/engineering/architecture.md +78 -0
  29. package/template/docs/engineering/auto-orchestration-reference.md +7 -0
  30. package/template/docs/engineering/context/installer-owned-paths.manifest +8 -0
  31. package/template/docs/engineering/reason_codes.md +411 -0
  32. package/template/docs/engineering/runbook.md +1119 -0
  33. package/template/docs/engineering/sovereign-memory/.gitkeep +0 -0
  34. package/template/docs/engineering/sovereign-memory/retrospectives/.gitkeep +0 -0
  35. package/template/handoffs/sovereign_decisions/.gitkeep +0 -0
  36. package/template/handoffs/sovereign_deferrals/.gitkeep +0 -0
  37. package/template/handoffs/sovereign_role_reviews.jsonl +0 -0
  38. package/template/scripts/__pycache__/decision_ledger_lib.cpython-312.pyc +0 -0
  39. package/template/scripts/check_intake_template_parity.py +181 -0
  40. package/template/scripts/decision_ledger_lib.py +732 -0
  41. package/template/scripts/ledger_validate.py +153 -0
  42. package/template/scripts/model_tier_lib.py +680 -0
  43. package/template/scripts/model_tier_validate.py +368 -0
  44. package/template/scripts/parallel_dev_arbiter.py +923 -0
  45. package/template/scripts/release_trigger_adapters.py +843 -0
  46. package/template/scripts/self_healing_deploy_lib.py +463 -0
  47. package/template/scripts/self_healing_deploy_validate.py +78 -0
  48. package/template/scripts/sovereign_convergence_lib.py +994 -0
  49. package/template/scripts/sovereign_convergence_validate.py +206 -0
  50. package/template/scripts/sovereign_critic_lib.py +629 -0
  51. package/template/scripts/sovereign_critic_validate.py +131 -0
  52. package/template/scripts/sovereign_loop_lib.py +828 -0
  53. package/template/scripts/sovereign_loop_validate.py +122 -0
  54. package/template/scripts/sovereign_memory_lib.py +869 -0
  55. package/template/scripts/sovereign_memory_validate.py +153 -0
  56. package/template/scripts/sovereign_role_manifest_lib.py +547 -0
  57. package/template/scripts/sovereign_role_manifest_validate.py +105 -0
  58. package/template/tests/us0108_contract_test.py +207 -0
  59. package/template/tests/us0109_contract_test.py +209 -0
  60. package/template/tests/us0109_us0110_compose_test.py +34 -0
  61. package/template/tests/us0111_contract_test.py +345 -0
@@ -0,0 +1,629 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Cross-model adversarial critic helper library (US-0104 / DEC-0104).
4
+
5
+ Reason codes (DEC-0104 §11):
6
+ CROSS_MODEL_REVIEW_DISABLED, CROSS_MODEL_CRITIC_SPAWN_FAILED,
7
+ CROSS_MODEL_MODEL_COLLISION, CROSS_MODEL_ANTISLOP_FAIL,
8
+ CROSS_MODEL_REWORK_CAP_EXHAUSTED, CROSS_MODEL_FINDINGS_INVALID,
9
+ CROSS_MODEL_RECONCILE_FAILED, CROSS_MODEL_DEGRADED_MODE,
10
+ CROSS_MODEL_CRITIC_MODEL_UNAVAILABLE, ISOLATION_EVIDENCE_MODEL_ID_MISSING
11
+
12
+ Default-off: CROSS_MODEL_REVIEW=0 → zero overhead.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import json
19
+ import os
20
+ import re
21
+ import string
22
+ import sys
23
+ import uuid
24
+ from dataclasses import dataclass, field
25
+ from datetime import datetime, timezone
26
+ from enum import Enum
27
+ from pathlib import Path
28
+ from typing import Any, Dict, List, Optional, Tuple
29
+
30
+ _SCRIPT_DIR = Path(__file__).resolve().parent
31
+ if str(_SCRIPT_DIR) not in sys.path:
32
+ sys.path.insert(0, str(_SCRIPT_DIR))
33
+
34
+ from model_tier_lib import Tier, resolve_model_for_phase # noqa: E402
35
+
36
+
37
+ # --- Scratchpad key contracts (DEC-0104 §1) ------------------------------------
38
+
39
+ CROSS_MODEL_REVIEW_KEY = "CROSS_MODEL_REVIEW"
40
+ CROSS_MODEL_ANTISLOP_THRESHOLD_KEY = "CROSS_MODEL_ANTISLOP_THRESHOLD"
41
+ CROSS_MODEL_REWORK_MAX_KEY = "CROSS_MODEL_REWORK_MAX"
42
+
43
+ CROSS_MODEL_REVIEW_VALUES = frozenset({"0", "1"})
44
+ CROSS_MODEL_REVIEW_DEFAULT = "0"
45
+ CROSS_MODEL_ANTISLOP_THRESHOLD_DEFAULT = 6
46
+ CROSS_MODEL_REWORK_MAX_DEFAULT = 2
47
+
48
+ LENS_VALUES = frozenset({"challenger", "architect", "subtractor"})
49
+ SEVERITY_VALUES = frozenset({"low", "medium", "high", "critical"})
50
+ CONFIDENCE_VALUES = frozenset({"low", "medium", "high"})
51
+ STATUS_VALUES = frozenset({"open", "resolved", "waived"})
52
+
53
+ FINDINGS_REL = "handoffs/sovereign_critic_findings.jsonl"
54
+ FINDINGS_PATH = Path(FINDINGS_REL)
55
+ SCHEMA_VERSION = 1
56
+ ISSUE_KEY_MAX_CHARS = 80
57
+ ISSUE_KEY_HEX_LEN = 16
58
+
59
+ FINDING_REQUIRED_FIELDS = frozenset({
60
+ "ts",
61
+ "orchestrator_run_id",
62
+ "phase_id",
63
+ "role",
64
+ "producer_model_id",
65
+ "critic_model_id",
66
+ "lens",
67
+ "finding_id",
68
+ "severity",
69
+ "confidence",
70
+ "anti_slop_score",
71
+ "finding_text",
72
+ "status",
73
+ "blocking",
74
+ "degraded_mode",
75
+ })
76
+
77
+ ISOLATION_EVIDENCE_BASE_FIELDS = frozenset({
78
+ "phase_id",
79
+ "role",
80
+ "fresh_context_marker",
81
+ "timestamp",
82
+ "evidence_ref",
83
+ })
84
+
85
+ LENS_CHECKLIST_KEYS = {
86
+ "challenger": (
87
+ "edge_case_cited",
88
+ "failure_mode_named",
89
+ "concurrency_considered",
90
+ "input_boundary_tested",
91
+ ),
92
+ "architect": (
93
+ "coupling_named",
94
+ "layer_boundary_stated",
95
+ "dependency_direction_explicit",
96
+ "interface_contract_mentioned",
97
+ ),
98
+ "subtractor": (
99
+ "unnecessary_abstraction_flagged",
100
+ "yagni_applied",
101
+ "premature_generalization_challenged",
102
+ "scope_creep_identified",
103
+ ),
104
+ }
105
+
106
+ CRITIC_TIER_OPPOSITION = {
107
+ Tier.STRONG: Tier.CHEAP,
108
+ Tier.BALANCED: Tier.CHEAP,
109
+ Tier.CHEAP: Tier.STRONG,
110
+ }
111
+
112
+
113
+ class ReasonCode(str, Enum):
114
+ CROSS_MODEL_REVIEW_DISABLED = "CROSS_MODEL_REVIEW_DISABLED"
115
+ CROSS_MODEL_CRITIC_SPAWN_FAILED = "CROSS_MODEL_CRITIC_SPAWN_FAILED"
116
+ CROSS_MODEL_MODEL_COLLISION = "CROSS_MODEL_MODEL_COLLISION"
117
+ CROSS_MODEL_ANTISLOP_FAIL = "CROSS_MODEL_ANTISLOP_FAIL"
118
+ CROSS_MODEL_REWORK_CAP_EXHAUSTED = "CROSS_MODEL_REWORK_CAP_EXHAUSTED"
119
+ CROSS_MODEL_FINDINGS_INVALID = "CROSS_MODEL_FINDINGS_INVALID"
120
+ CROSS_MODEL_RECONCILE_FAILED = "CROSS_MODEL_RECONCILE_FAILED"
121
+ CROSS_MODEL_DEGRADED_MODE = "CROSS_MODEL_DEGRADED_MODE"
122
+ CROSS_MODEL_CRITIC_MODEL_UNAVAILABLE = "CROSS_MODEL_CRITIC_MODEL_UNAVAILABLE"
123
+ ISOLATION_EVIDENCE_MODEL_ID_MISSING = "ISOLATION_EVIDENCE_MODEL_ID_MISSING"
124
+
125
+
126
+ REASON_CODES = frozenset(code.value for code in ReasonCode)
127
+
128
+
129
+ @dataclass
130
+ class SelectCriticResult:
131
+ critic_model_id: str
132
+ degraded: bool
133
+ reason_code: Optional[ReasonCode] = None
134
+ producer_model_id: str = ""
135
+
136
+
137
+ @dataclass
138
+ class ReconciliationResult:
139
+ findings: List[dict] = field(default_factory=list)
140
+ agreement_groups: List[List[str]] = field(default_factory=list)
141
+ single_finder_flags: List[str] = field(default_factory=list)
142
+
143
+
144
+ def _utc_now_iso() -> str:
145
+ return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
146
+
147
+
148
+ def _fsync_file(path: Path) -> None:
149
+ """fsync a file; best-effort on Windows where some handles reject it."""
150
+ try:
151
+ fd = os.open(str(path), os.O_RDONLY)
152
+ try:
153
+ os.fsync(fd)
154
+ except OSError:
155
+ pass
156
+ finally:
157
+ os.close(fd)
158
+ except OSError:
159
+ pass
160
+
161
+
162
+ def parse_scratchpad_threshold(scratchpad: Optional[Dict[str, str]]) -> int:
163
+ pad = scratchpad or {}
164
+ raw = pad.get(CROSS_MODEL_ANTISLOP_THRESHOLD_KEY, str(CROSS_MODEL_ANTISLOP_THRESHOLD_DEFAULT))
165
+ try:
166
+ value = int(str(raw).strip())
167
+ except (TypeError, ValueError):
168
+ return CROSS_MODEL_ANTISLOP_THRESHOLD_DEFAULT
169
+ return max(0, min(10, value))
170
+
171
+
172
+ def parse_scratchpad_rework_max(scratchpad: Optional[Dict[str, str]]) -> int:
173
+ pad = scratchpad or {}
174
+ raw = pad.get(CROSS_MODEL_REWORK_MAX_KEY, str(CROSS_MODEL_REWORK_MAX_DEFAULT))
175
+ try:
176
+ value = int(str(raw).strip())
177
+ except (TypeError, ValueError):
178
+ return CROSS_MODEL_REWORK_MAX_DEFAULT
179
+ return max(0, value)
180
+
181
+
182
+ def check_isolation_model_id(
183
+ evidence: dict,
184
+ scratchpad: Optional[Dict[str, str]] = None,
185
+ ) -> Tuple[bool, Optional[ReasonCode]]:
186
+ """Fail-closed when critic enabled and model_id absent on isolation evidence."""
187
+ if not is_cross_model_review_enabled(scratchpad):
188
+ return True, None
189
+ model_id = evidence.get("model_id") if isinstance(evidence, dict) else None
190
+ if model_id is not None and str(model_id).strip():
191
+ return True, None
192
+ return False, ReasonCode.ISOLATION_EVIDENCE_MODEL_ID_MISSING
193
+
194
+
195
+ def is_cross_model_review_enabled(scratchpad: Optional[Dict[str, str]]) -> bool:
196
+ if not scratchpad:
197
+ return False
198
+ return scratchpad.get(CROSS_MODEL_REVIEW_KEY, CROSS_MODEL_REVIEW_DEFAULT).strip() == "1"
199
+
200
+
201
+ def _normalize_model_token(model_id: str) -> str:
202
+ token = (model_id or "").strip().lower()
203
+ if token in ("", "inherit", "none", "omit"):
204
+ return "inherit"
205
+ if token == "fast":
206
+ return "fast"
207
+ return token
208
+
209
+
210
+ def _infer_producer_tier(producer_model_id: str) -> Tier:
211
+ norm = _normalize_model_token(producer_model_id)
212
+ if norm == "fast":
213
+ return Tier.CHEAP
214
+ if norm == "inherit":
215
+ return Tier.BALANCED
216
+ return Tier.STRONG
217
+
218
+
219
+ def _resolve_slug_for_tier(phase_id: str, tier: Tier, scratchpad: Dict[str, str]) -> str:
220
+ tier_key = f"MODEL_TIER_{phase_id.upper().replace('-', '_')}"
221
+ pad = dict(scratchpad)
222
+ pad[tier_key] = tier.value
223
+ pad.setdefault("MODEL_TIER_DEFAULT", tier.value)
224
+ result = resolve_model_for_phase(phase_id, pad)
225
+ if result.success:
226
+ if result.slug:
227
+ return result.slug
228
+ if result.alias:
229
+ return result.alias
230
+ return "inherit"
231
+
232
+
233
+ def select_critic_model(
234
+ producer_model_id: str,
235
+ scratchpad: Optional[Dict[str, str]],
236
+ phase_id: str,
237
+ ) -> SelectCriticResult:
238
+ pad = scratchpad or {}
239
+ producer = producer_model_id.strip()
240
+ if not producer:
241
+ resolved = resolve_model_for_phase(phase_id, pad)
242
+ if resolved.success:
243
+ producer = resolved.slug or resolved.alias or "inherit"
244
+ else:
245
+ producer = "inherit"
246
+
247
+ producer_tier = _infer_producer_tier(producer)
248
+ critic_tier = CRITIC_TIER_OPPOSITION.get(producer_tier, Tier.CHEAP)
249
+ critic = _resolve_slug_for_tier("sovereign-critic", critic_tier, pad)
250
+
251
+ if _normalize_model_token(critic) == _normalize_model_token(producer):
252
+ return SelectCriticResult(
253
+ critic_model_id=producer,
254
+ degraded=True,
255
+ reason_code=ReasonCode.CROSS_MODEL_DEGRADED_MODE,
256
+ producer_model_id=producer,
257
+ )
258
+
259
+ return SelectCriticResult(
260
+ critic_model_id=critic,
261
+ degraded=False,
262
+ reason_code=None,
263
+ producer_model_id=producer,
264
+ )
265
+
266
+
267
+ def compute_issue_key(finding_text: str) -> str:
268
+ lowered = finding_text.lower()
269
+ table = str.maketrans("", "", string.punctuation)
270
+ cleaned = lowered.translate(table)
271
+ collapsed = re.sub(r"\s+", " ", cleaned).strip()
272
+ if len(collapsed) > ISSUE_KEY_MAX_CHARS:
273
+ truncated = collapsed[:ISSUE_KEY_MAX_CHARS]
274
+ if " " in truncated:
275
+ truncated = truncated.rsplit(" ", 1)[0]
276
+ collapsed = truncated
277
+ digest = hashlib.sha256(collapsed.encode("utf-8")).hexdigest()[:ISSUE_KEY_HEX_LEN]
278
+ return f"ik_{digest}"
279
+
280
+
281
+ def score_lens_antislop(lens: str, checklist_hits: Dict[str, bool]) -> int:
282
+ if lens not in LENS_CHECKLIST_KEYS:
283
+ return 0
284
+ keys = LENS_CHECKLIST_KEYS[lens]
285
+ hits = sum(1 for key in keys if checklist_hits.get(key))
286
+ score = int(round(hits * 2.5))
287
+ return max(0, min(10, score))
288
+
289
+
290
+ def compute_anti_slop_aggregate(lens_scores: List[int]) -> int:
291
+ if not lens_scores:
292
+ return 0
293
+ return min(int(s) for s in lens_scores)
294
+
295
+
296
+ def reconcile_findings(raw_findings: List[dict]) -> ReconciliationResult:
297
+ if not raw_findings:
298
+ return ReconciliationResult()
299
+
300
+ keyed: Dict[str, List[dict]] = {}
301
+ for item in raw_findings:
302
+ text = str(item.get("finding_text", ""))
303
+ key = item.get("issue_key") or compute_issue_key(text)
304
+ keyed.setdefault(key, []).append({**item, "issue_key": key})
305
+
306
+ merged: List[dict] = []
307
+ agreement_groups: List[List[str]] = []
308
+ single_finder_flags: List[str] = []
309
+
310
+ for key, group in keyed.items():
311
+ lenses = {str(g.get("lens", "")) for g in group}
312
+ finding_ids = [str(g.get("finding_id", "")) for g in group if g.get("finding_id")]
313
+ if len(lenses) >= 2:
314
+ confidence = "high"
315
+ single_finder = False
316
+ agreement_groups.append(finding_ids)
317
+ else:
318
+ confidence = "medium"
319
+ single_finder = True
320
+ single_finder_flags.extend(finding_ids)
321
+
322
+ base = dict(group[0])
323
+ base["confidence"] = confidence
324
+ base["single_finder"] = single_finder
325
+ base["issue_key"] = key
326
+ merged.append(base)
327
+
328
+ return ReconciliationResult(
329
+ findings=merged,
330
+ agreement_groups=agreement_groups,
331
+ single_finder_flags=single_finder_flags,
332
+ )
333
+
334
+
335
+ def schema_check(entry: dict) -> Tuple[bool, Optional[str]]:
336
+ if not isinstance(entry, dict):
337
+ return False, "entry must be object"
338
+ missing = FINDING_REQUIRED_FIELDS - set(entry.keys())
339
+ if missing:
340
+ return False, f"missing fields: {sorted(missing)}"
341
+ extra = set(entry.keys()) - FINDING_REQUIRED_FIELDS - {"issue_key", "single_finder", "rework_generation", "schema_version"}
342
+ if extra:
343
+ return False, f"unknown fields: {sorted(extra)}"
344
+ if entry["lens"] not in LENS_VALUES:
345
+ return False, f"invalid lens: {entry['lens']}"
346
+ if entry["severity"] not in SEVERITY_VALUES:
347
+ return False, f"invalid severity: {entry['severity']}"
348
+ if entry["confidence"] not in CONFIDENCE_VALUES:
349
+ return False, f"invalid confidence: {entry['confidence']}"
350
+ if entry["status"] not in STATUS_VALUES:
351
+ return False, f"invalid status: {entry['status']}"
352
+ score = entry["anti_slop_score"]
353
+ if not isinstance(score, int) or score < 0 or score > 10:
354
+ return False, "anti_slop_score must be int 0-10"
355
+ for bool_field in ("blocking", "degraded_mode"):
356
+ if not isinstance(entry[bool_field], bool):
357
+ return False, f"{bool_field} must be bool"
358
+ if not str(entry["finding_text"]).strip():
359
+ return False, "finding_text must be non-empty"
360
+ return True, None
361
+
362
+
363
+ def append_finding(
364
+ path: Path,
365
+ entry: dict,
366
+ *,
367
+ scratchpad: Optional[Dict[str, str]] = None,
368
+ ) -> Tuple[bool, Optional[ReasonCode]]:
369
+ if not is_cross_model_review_enabled(scratchpad):
370
+ return False, ReasonCode.CROSS_MODEL_REVIEW_DISABLED
371
+ ok, err = schema_check(entry)
372
+ if not ok:
373
+ return False, ReasonCode.CROSS_MODEL_FINDINGS_INVALID
374
+ path.parent.mkdir(parents=True, exist_ok=True)
375
+ line = json.dumps(entry, ensure_ascii=False) + "\n"
376
+ with path.open("a", encoding="utf-8", newline="\n") as handle:
377
+ handle.write(line)
378
+ handle.flush()
379
+ try:
380
+ os.fsync(handle.fileno())
381
+ except OSError:
382
+ _fsync_file(path)
383
+ return True, None
384
+
385
+
386
+ def read_open_blocking(repo: Path) -> List[dict]:
387
+ path = repo / FINDINGS_PATH
388
+ if not path.is_file():
389
+ return []
390
+ open_rows: List[dict] = []
391
+ for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
392
+ if not raw.strip():
393
+ continue
394
+ try:
395
+ obj = json.loads(raw)
396
+ except json.JSONDecodeError:
397
+ continue
398
+ if obj.get("blocking") and obj.get("status") == "open":
399
+ open_rows.append(obj)
400
+ return open_rows
401
+
402
+
403
+ def resolve_finding(path: Path, finding_id: str, status: str) -> bool:
404
+ if status not in ("resolved", "waived"):
405
+ return False
406
+ if not path.is_file():
407
+ return False
408
+ lines = path.read_text(encoding="utf-8", errors="replace").splitlines(keepends=True)
409
+ changed = False
410
+ out: List[str] = []
411
+ for raw in lines:
412
+ if not raw.strip():
413
+ out.append(raw)
414
+ continue
415
+ try:
416
+ obj = json.loads(raw)
417
+ except json.JSONDecodeError:
418
+ out.append(raw)
419
+ continue
420
+ if str(obj.get("finding_id")) == finding_id:
421
+ obj["status"] = status
422
+ changed = True
423
+ out.append(json.dumps(obj, ensure_ascii=False) + "\n")
424
+ else:
425
+ out.append(raw)
426
+ if changed:
427
+ path.write_text("".join(out), encoding="utf-8")
428
+ return changed
429
+
430
+
431
+ def build_qa_cross_reviewer_block(repo: Path) -> dict:
432
+ rows = read_open_blocking(repo)
433
+ return {
434
+ "cross_reviewer_findings": [
435
+ {
436
+ "finding_id": row.get("finding_id"),
437
+ "lens": row.get("lens"),
438
+ "severity": row.get("severity"),
439
+ "confidence": row.get("confidence"),
440
+ "finding_text": str(row.get("finding_text", ""))[:200],
441
+ "blocking": row.get("blocking", False),
442
+ "degraded_mode": row.get("degraded_mode", False),
443
+ }
444
+ for row in rows
445
+ ],
446
+ "open_blocking_count": len(rows),
447
+ "findings_path": FINDINGS_REL,
448
+ }
449
+
450
+
451
+ def build_critic_evidence_block(
452
+ *,
453
+ scratchpad: Optional[Dict[str, str]] = None,
454
+ producer_model_id: str,
455
+ critic_model_id: str,
456
+ anti_slop_aggregate: int,
457
+ rework_generation: int = 0,
458
+ degraded_mode: bool = False,
459
+ ) -> Optional[dict]:
460
+ """Additive dev_to_qa evidence tuple when CROSS_MODEL_REVIEW=1."""
461
+ if not is_cross_model_review_enabled(scratchpad):
462
+ return None
463
+ return {
464
+ "producer_model_id": producer_model_id,
465
+ "critic_model_id": critic_model_id,
466
+ "anti_slop_aggregate": int(anti_slop_aggregate),
467
+ "rework_generation": int(rework_generation),
468
+ "degraded_mode": bool(degraded_mode),
469
+ "findings_path": FINDINGS_REL,
470
+ }
471
+
472
+
473
+ def patch_ledger_cross_model_reviewed(
474
+ repo: Path,
475
+ orchestrator_run_id: str,
476
+ phase_id: str,
477
+ role: str,
478
+ *,
479
+ scratchpad: Optional[Dict[str, str]] = None,
480
+ producer_model_id: str = "",
481
+ critic_model_id: str = "",
482
+ ) -> Tuple[bool, Optional[ReasonCode]]:
483
+ """Append ledger entry with cross_model_reviewed=True when ledger enabled."""
484
+ from decision_ledger_lib import ( # noqa: WPS433
485
+ append_entry,
486
+ build_new_entry,
487
+ is_ledger_enabled,
488
+ resolve_ledger_path,
489
+ resolve_plan_fidelity,
490
+ )
491
+
492
+ if not is_ledger_enabled(scratchpad):
493
+ return True, None
494
+
495
+ ledger_path = resolve_ledger_path(orchestrator_run_id, repo)
496
+ fidelity = resolve_plan_fidelity(scratchpad).value
497
+ rationale = (
498
+ f"Cross-model critic review completed for {phase_id}/{role} "
499
+ f"(producer={producer_model_id or 'inherit'}, critic={critic_model_id or 'inherit'})."
500
+ )
501
+ entry = build_new_entry(
502
+ orchestrator_run_id=orchestrator_run_id,
503
+ phase_id=phase_id,
504
+ role=role,
505
+ decision_type="CROSS_MODEL_REVIEW",
506
+ rationale=rationale,
507
+ plan_fidelity=fidelity,
508
+ cross_model_reviewed=True,
509
+ risk_tier="medium",
510
+ )
511
+ result = append_entry(ledger_path, entry, scratchpad=scratchpad)
512
+ if not result.success:
513
+ return False, ReasonCode.CROSS_MODEL_FINDINGS_INVALID
514
+ return True, None
515
+
516
+
517
+ def build_sample_finding(**overrides: Any) -> dict:
518
+ base = {
519
+ "ts": _utc_now_iso(),
520
+ "orchestrator_run_id": "self-test-run",
521
+ "phase_id": "research",
522
+ "role": "tech-lead",
523
+ "producer_model_id": "inherit",
524
+ "critic_model_id": "fast",
525
+ "lens": "challenger",
526
+ "finding_id": str(uuid.uuid4()),
527
+ "severity": "medium",
528
+ "confidence": "medium",
529
+ "anti_slop_score": 8,
530
+ "finding_text": "Missing null guard on boundary input path.",
531
+ "status": "open",
532
+ "blocking": False,
533
+ "degraded_mode": False,
534
+ }
535
+ base.update(overrides)
536
+ return base
537
+
538
+
539
+ def self_test() -> bool:
540
+ errors: List[str] = []
541
+
542
+ if len(REASON_CODES) != 10:
543
+ errors.append(f"expected 10 reason codes, got {len(REASON_CODES)}")
544
+
545
+ if CROSS_MODEL_REVIEW_DEFAULT != "0":
546
+ errors.append("CROSS_MODEL_REVIEW_DEFAULT must be 0")
547
+
548
+ if is_cross_model_review_enabled({}) or is_cross_model_review_enabled(None):
549
+ errors.append("default scratchpad must disable critic")
550
+
551
+ if not is_cross_model_review_enabled({CROSS_MODEL_REVIEW_KEY: "1"}):
552
+ errors.append("CROSS_MODEL_REVIEW=1 must enable critic")
553
+
554
+ key_a = compute_issue_key("Race on concurrent append!")
555
+ key_b = compute_issue_key("race on concurrent append")
556
+ if key_a != key_b:
557
+ errors.append("issue_key normalization mismatch")
558
+
559
+ scores = [7, 9, 6]
560
+ if compute_anti_slop_aggregate(scores) != 6:
561
+ errors.append("aggregate must be min(lens_scores)")
562
+
563
+ rubric = score_lens_antislop(
564
+ "challenger",
565
+ {
566
+ "edge_case_cited": True,
567
+ "failure_mode_named": True,
568
+ "concurrency_considered": False,
569
+ "input_boundary_tested": False,
570
+ },
571
+ )
572
+ if rubric != 5:
573
+ errors.append(f"challenger rubric expected 5, got {rubric}")
574
+
575
+ raw = [
576
+ build_sample_finding(lens="challenger", finding_text="Shared coupling risk in module boundary"),
577
+ build_sample_finding(lens="architect", finding_text="Shared coupling risk in module boundary"),
578
+ build_sample_finding(lens="subtractor", finding_text="Unique over abstraction in helper layer"),
579
+ ]
580
+ reconciled = reconcile_findings(raw)
581
+ if len(reconciled.agreement_groups) != 1:
582
+ errors.append("expected one agreement group")
583
+ if len(reconciled.single_finder_flags) != 1:
584
+ errors.append("expected one single-finder flag")
585
+
586
+ sample = build_sample_finding()
587
+ ok, err = schema_check(sample)
588
+ if not ok:
589
+ errors.append(f"schema_check valid sample failed: {err}")
590
+
591
+ bad, err = schema_check({"lens": "challenger"})
592
+ if bad:
593
+ errors.append("schema_check should reject incomplete entry")
594
+
595
+ select = select_critic_model("inherit", {CROSS_MODEL_REVIEW_KEY: "1"}, "execute")
596
+ if not select.critic_model_id:
597
+ errors.append("select_critic_model must return critic_model_id")
598
+
599
+ ok_model, code = check_isolation_model_id({"phase_id": "execute"}, {CROSS_MODEL_REVIEW_KEY: "0"})
600
+ if not ok_model or code is not None:
601
+ errors.append("model_id check must pass when critic disabled")
602
+
603
+ bad_model, bad_code = check_isolation_model_id({}, {CROSS_MODEL_REVIEW_KEY: "1"})
604
+ if bad_model or bad_code != ReasonCode.ISOLATION_EVIDENCE_MODEL_ID_MISSING:
605
+ errors.append("model_id check must fail-closed when critic enabled and model_id missing")
606
+
607
+ if len(ISOLATION_EVIDENCE_BASE_FIELDS) != 5:
608
+ errors.append("ISOLATION_EVIDENCE_BASE_FIELDS must have 5 base fields")
609
+
610
+ if errors:
611
+ for item in errors:
612
+ print(f" {item}", file=sys.stderr)
613
+ print("[SELF_TEST_FAILED]", file=sys.stderr)
614
+ return False
615
+
616
+ print("[SOVEREIGN_CRITIC_SELF_TEST_OK]")
617
+ return True
618
+
619
+
620
+ if __name__ == "__main__":
621
+ import argparse
622
+
623
+ parser = argparse.ArgumentParser(description="Sovereign critic library (US-0104 / DEC-0104)")
624
+ parser.add_argument("--self-test", action="store_true", help="Run self-test")
625
+ args = parser.parse_args()
626
+ if args.self_test:
627
+ sys.exit(0 if self_test() else 1)
628
+ parser.print_help()
629
+ sys.exit(2)