============================================================================== FILE: /home/ray/shire-academy-safe-autonomous ============================================================================== --- LINES 889-971 --- 889: 890: missing = sorted( 891: {1, 2, 3} - rounds 892: ) 893: 894: if missing: 895: next_action = ( 896: "run_generic_uncounted_round_" 897: f"{missing[0]}" 898: ) 899: 900: status = ( 901: "autonomous_qualification_pending" 902: ) 903: else: 904: next_action = ( 905: "run_counted_practice_round_1" 906: ) 907: 908: status = ( 909: "qualification_complete_" 910: "waiting_counted_practice" 911: ) 912: 913: qualification.update({ 914: "status": status, 915: "successful_qualifications": 916: len(rounds), 917: "consecutive_qualification_failures": 918: int( 919: qualification.get( 920: "consecutive_qualification_failures" 921: ) 922: or 0 923: ), 924: "consecutive_infrastructure_failures": 925: int( 926: qualification.get( 927: "consecutive_infrastructure_failures" 928: ) 929: or 0 930: ), 931: "knowledge_certified": False, 932: "tool_evidence_certified": False, 933: "forge_allowed": False, 934: "scheduler_managed": True, 935: "updated_at": now(), 936: }) 937: 938: plan = { 939: "skill_id": skill_id, 940: "purpose": 941: "Safe autonomous learning of supported " 942: "CadQuery tool-evidence skills.", 943: "next_action": next_action, 944: "status": status, 945: "knowledge_certified": False, 946: "tool_evidence_validated": False, 947: "forge_review_pending": False, 948: "forge_allowed": False, 949: } 950: 951: autopilot["current_skill"] = skill_id 952: autopilot["current_plan"] = plan 953: autopilot["enabled"] = True 954: autopilot["paused"] = False 955: autopilot["safety_stop"] = False 956: autopilot["safety_stop_reason"] = None 957: autopilot["review_required"] = False 958: autopilot["updated_at"] = now() 959: 960: set_safe_autopilot_fields( 961: autopilot 962: ) 963: 964: sprint["current_skill"] = skill_id 965: sprint["updated_at"] = now() 966: 967: atomic( 968: AUTOPILOT, 969: autopilot, 970: ) 971: --- LINES 1087-1176 --- 1087: ) -> None: 1088: autopilot = load(AUTOPILOT) 1089: 1090: autopilot["enabled"] = False 1091: autopilot["paused"] = True 1092: autopilot["safety_stop"] = True 1093: autopilot["review_required"] = True 1094: autopilot["safety_stop_reason"] = reason 1095: autopilot["updated_at"] = now() 1096: 1097: set_safe_autopilot_fields( 1098: autopilot 1099: ) 1100: 1101: if isinstance(skill_id, str): 1102: qualification = ( 1103: autopilot.setdefault( 1104: "skills", 1105: {}, 1106: ) 1107: .setdefault( 1108: skill_id, 1109: {}, 1110: ) 1111: ) 1112: 1113: qualification.update({ 1114: "status": 1115: "scheduler_safety_stopped", 1116: "forge_allowed": False, 1117: "updated_at": now(), 1118: }) 1119: 1120: plan = dict( 1121: autopilot.get( 1122: "current_plan" 1123: ) 1124: or {} 1125: ) 1126: 1127: plan.update({ 1128: "skill_id": skill_id, 1129: "next_action": 1130: "local_safety_review_required", 1131: "status": 1132: "scheduler_safety_stopped", 1133: "forge_allowed": False, 1134: }) 1135: 1136: autopilot["current_plan"] = plan 1137: 1138: atomic( 1139: AUTOPILOT, 1140: autopilot, 1141: ) 1142: 1143: 1144: def complete_current( 1145: skill_id: str, 1146: ) -> None: 1147: autopilot = load(AUTOPILOT) 1148: sprint = load(SPRINT) 1149: 1150: qualification = ( 1151: autopilot.setdefault( 1152: "skills", 1153: {}, 1154: ) 1155: .setdefault( 1156: skill_id, 1157: {}, 1158: ) 1159: ) 1160: 1161: qualification.update({ 1162: "status": 1163: "academy_learning_complete_" 1164: "waiting_forge_review", 1165: "knowledge_certified": True, 1166: "tool_evidence_validated": True, 1167: "tool_evidence_complete": True, 1168: "tool_evidence_certified": False, 1169: "forge_review_pending": True, 1170: "forge_allowed": False, 1171: "scheduler_managed": True, 1172: "completed_at": now(), 1173: "updated_at": now(), 1174: }) 1175: 1176: completed = list( --- LINES 1324-1793 --- 1324: == skill_id, 1325: isinstance(payload, dict) 1326: and payload.get( 1327: "forge_allowed" 1328: ) 1329: is False, 1330: isinstance(payload, dict) 1331: and payload.get( 1332: "counted_attempt_consumed" 1333: ) 1334: is False, 1335: isinstance(payload, dict) 1336: and payload.get( 1337: "practice_state_unchanged" 1338: ) 1339: is True, 1340: bool(tests), 1341: all( 1342: isinstance(item, dict) 1343: and item.get("pass") is True 1344: for item in tests 1345: ), 1346: ]) 1347: 1348: infrastructure = ( 1349: isinstance(payload, dict) 1350: and ( 1351: payload.get( 1352: "infrastructure_failure" 1353: ) 1354: is True 1355: or "INFRASTRUCTURE" 1356: in classification.upper() 1357: ) 1358: ) 1359: 1360: evidence = "" 1361: 1362: if isinstance(payload, dict): 1363: evidence = str( 1364: payload.get("evidence") 1365: or "" 1366: ) 1367: 1368: duplicate = any( 1369: isinstance(item, dict) 1370: and int( 1371: item.get("round") or 0 1372: ) 1373: == int(round_number) 1374: and ( 1375: ( 1376: bool(evidence) 1377: and str( 1378: item.get("evidence") 1379: or "" 1380: ) 1381: == evidence 1382: ) 1383: or ( 1384: not evidence 1385: and str( 1386: item.get("journal") 1387: or "" 1388: ) 1389: == str(journal) 1390: ) 1391: ) 1392: for item in runs 1393: ) 1394: 1395: if not duplicate: 1396: runs.append({ 1397: "recorded_at": now(), 1398: "round": 1399: int(round_number), 1400: "passed": passed, 1401: "score_percent": 1402: int( 1403: payload.get( 1404: "score_percent" 1405: ) 1406: or 0 1407: ) 1408: if isinstance( 1409: payload, 1410: dict, 1411: ) 1412: else 0, 1413: "classification": 1414: classification or None, 1415: "evidence": 1416: evidence or None, 1417: "journal": str(journal), 1418: "returncode": 1419: int(returncode), 1420: "infrastructure_failure": 1421: infrastructure, 1422: "counted_attempt_consumed": 1423: False, 1424: "forge_allowed": False, 1425: "source": 1426: "safe_autonomous_scheduler_v2", 1427: }) 1428: 1429: if passed: 1430: qualification[ 1431: "consecutive_qualification_failures" 1432: ] = 0 1433: 1434: qualification[ 1435: "consecutive_infrastructure_failures" 1436: ] = 0 1437: 1438: elif infrastructure: 1439: qualification[ 1440: "consecutive_infrastructure_failures" 1441: ] = ( 1442: int( 1443: qualification.get( 1444: "consecutive_infrastructure_failures" 1445: ) 1446: or 0 1447: ) 1448: + 1 1449: ) 1450: 1451: qualification[ 1452: "consecutive_qualification_failures" 1453: ] = 0 1454: 1455: else: 1456: qualification[ 1457: "consecutive_qualification_failures" 1458: ] = ( 1459: int( 1460: qualification.get( 1461: "consecutive_qualification_failures" 1462: ) 1463: or 0 1464: ) 1465: + 1 1466: ) 1467: 1468: qualification[ 1469: "consecutive_infrastructure_failures" 1470: ] = 0 1471: 1472: rounds = successful_rounds( 1473: qualification 1474: ) 1475: 1476: qualification[ 1477: "successful_qualifications" 1478: ] = len(rounds) 1479: 1480: qualification[ 1481: "last_qualification_classification" 1482: ] = classification or None 1483: 1484: qualification[ 1485: "latest_qualification_journal" 1486: ] = str(journal) 1487: 1488: qualification[ 1489: "forge_allowed" 1490: ] = False 1491: 1492: qualification[ 1493: "updated_at" 1494: ] = now() 1495: 1496: autopilot[ 1497: "forge_unlock_allowed" 1498: ] = False 1499: 1500: autopilot[ 1501: "counted_execution_enabled" 1502: ] = False 1503: 1504: autopilot["updated_at"] = now() 1505: 1506: atomic( 1507: AUTOPILOT, 1508: autopilot, 1509: ) 1510: 1511: qualification_failures = int( 1512: qualification.get( 1513: "consecutive_qualification_failures" 1514: ) 1515: or 0 1516: ) 1517: 1518: infrastructure_failures = int( 1519: qualification.get( 1520: "consecutive_infrastructure_failures" 1521: ) 1522: or 0 1523: ) 1524: 1525: if passed: 1526: missing = sorted( 1527: {1, 2, 3} - rounds 1528: ) 1529: 1530: if missing: 1531: next_action = ( 1532: "run_generic_uncounted_round_" 1533: f"{missing[0]}" 1534: ) 1535: 1536: status = ( 1537: "autonomous_qualification_" 1538: "in_progress" 1539: ) 1540: else: 1541: next_action = ( 1542: "run_counted_practice_round_1" 1543: ) 1544: 1545: status = ( 1546: "qualification_complete_" 1547: "waiting_counted_practice" 1548: ) 1549: 1550: update_plan( 1551: skill_id, 1552: next_action, 1553: status, 1554: ) 1555: 1556: return { 1557: "ok": True, 1558: "action": 1559: "uncounted_qualification_pass", 1560: "skill_id": skill_id, 1561: "round": round_number, 1562: "successful_unique_rounds": 1563: sorted(rounds), 1564: "next_action": next_action, 1565: "journal": str(journal), 1566: "payload": payload, 1567: } 1568: 1569: if ( 1570: qualification_failures >= 3 1571: or infrastructure_failures >= 3 1572: ): 1573: reason = ( 1574: "Three consecutive autonomous " 1575: "qualification or infrastructure " 1576: "failures require local review." 1577: ) 1578: 1579: safety_stop( 1580: skill_id, 1581: reason, 1582: ) 1583: 1584: return { 1585: "ok": False, 1586: "action": 1587: "qualification_safety_stop", 1588: "skill_id": skill_id, 1589: "round": round_number, 1590: "returncode": returncode, 1591: "qualification_failures": 1592: qualification_failures, 1593: "infrastructure_failures": 1594: infrastructure_failures, 1595: "journal": str(journal), 1596: "reason": reason, 1597: "payload": payload, 1598: } 1599: 1600: update_plan( 1601: skill_id, 1602: ( 1603: "retry_generic_uncounted_round_" 1604: f"{round_number}" 1605: ), 1606: ( 1607: "qualification_infrastructure_" 1608: "retry_scheduled" 1609: if infrastructure 1610: else 1611: "qualification_retry_scheduled" 1612: ), 1613: ) 1614: 1615: return { 1616: "ok": False, 1617: "action": 1618: ( 1619: "qualification_infrastructure_" 1620: "retry_scheduled" 1621: if infrastructure 1622: else 1623: "qualification_retry_scheduled" 1624: ), 1625: "skill_id": skill_id, 1626: "round": round_number, 1627: "returncode": returncode, 1628: "qualification_failures": 1629: qualification_failures, 1630: "infrastructure_failures": 1631: infrastructure_failures, 1632: "journal": str(journal), 1633: "payload": payload, 1634: } 1635: 1636: 1637: def counted_tick( 1638: skill_id: str, 1639: journal: Path, 1640: ) -> dict[str, Any]: 1641: returncode, payload = run_command( 1642: [ 1643: str(PYTHON), 1644: str(PRACTICE_TOOL), 1645: "run-one", 1646: "--skill-id", 1647: skill_id, 1648: ], 1649: journal, 1650: ) 1651: 1652: locked, problems = forge_locked( 1653: skill_id 1654: ) 1655: 1656: if not locked: 1657: raise PermissionDrift( 1658: "Forge permission drift after counted practice: " 1659: + ", ".join(problems) 1660: ) 1661: 1662: state = load(STATE) 1663: record = state["skills"][skill_id] 1664: 1665: attempts = int( 1666: record.get("attempts") or 0 1667: ) 1668: 1669: successes = int( 1670: record.get("successful_attempts") 1671: or 0 1672: ) 1673: 1674: knowledge = ( 1675: record.get("knowledge_certified") 1676: is True 1677: ) 1678: 1679: infrastructure = ( 1680: isinstance(payload, dict) 1681: and ( 1682: payload.get( 1683: "infrastructure_failure" 1684: ) 1685: is True 1686: or payload.get("status") 1687: == "infrastructure_retry" 1688: ) 1689: ) 1690: 1691: passed = ( 1692: returncode == 0 1693: and isinstance(payload, dict) 1694: and payload.get("ok") is True 1695: ) 1696: 1697: if passed: 1698: if knowledge: 1699: next_action = ( 1700: "run_safe_cadquery_evidence" 1701: ) 1702: 1703: status = ( 1704: "knowledge_certified_" 1705: "waiting_safe_cadquery" 1706: ) 1707: else: 1708: next_round = min( 1709: successes + 1, 1710: 3, 1711: ) 1712: 1713: next_action = ( 1714: "run_counted_practice_round_" 1715: f"{next_round}" 1716: ) 1717: 1718: status = ( 1719: "counted_practice_in_progress" 1720: ) 1721: 1722: update_plan( 1723: skill_id, 1724: next_action, 1725: status, 1726: ) 1727: 1728: return { 1729: "ok": True, 1730: "action": 1731: "counted_practice_pass", 1732: "skill_id": skill_id, 1733: "attempts": attempts, 1734: "successful_rounds": successes, 1735: "knowledge_certified": knowledge, 1736: "next_action": next_action, 1737: "journal": str(journal), 1738: "payload": payload, 1739: } 1740: 1741: if infrastructure: 1742: infrastructure_failures = int( 1743: record.get( 1744: "infrastructure_failures" 1745: ) 1746: or 0 1747: ) 1748: 1749: if infrastructure_failures >= 3: 1750: reason = ( 1751: "Three consecutive counted-practice " 1752: "infrastructure failures require " 1753: "local review." 1754: ) 1755: 1756: safety_stop( 1757: skill_id, 1758: reason, 1759: ) 1760: 1761: return { 1762: "ok": False, 1763: "action": 1764: "counted_infrastructure_safety_stop", 1765: "skill_id": skill_id, 1766: "journal": str(journal), 1767: "reason": reason, 1768: "payload": payload, 1769: } 1770: 1771: next_round = min( 1772: successes + 1, 1773: 3, 1774: ) 1775: 1776: update_plan( 1777: skill_id, 1778: ( 1779: "retry_counted_practice_round_" 1780: f"{next_round}" 1781: ), 1782: "counted_infrastructure_retry_scheduled", 1783: ) 1784: 1785: return { 1786: "ok": False, 1787: "action": 1788: "counted_infrastructure_retry_scheduled", 1789: "skill_id": skill_id, 1790: "journal": str(journal), 1791: "payload": payload, 1792: } 1793: --- LINES 2128-2201 --- 2128: "learning", 2129: ) 2130: 2131: locked, problems = forge_locked( 2132: skill_id 2133: ) 2134: 2135: if not locked: 2136: raise PermissionDrift( 2137: "Forge gate is not locked before learning: " 2138: + ", ".join(problems) 2139: ) 2140: 2141: state = load(STATE) 2142: autopilot = load(AUTOPILOT) 2143: 2144: record = state["skills"][skill_id] 2145: 2146: qualification = ( 2147: autopilot.setdefault( 2148: "skills", 2149: {}, 2150: ) 2151: .setdefault( 2152: skill_id, 2153: { 2154: "qualification_runs": [], 2155: "successful_qualifications": 0, 2156: "consecutive_qualification_failures": 0, 2157: "consecutive_infrastructure_failures": 0, 2158: }, 2159: ) 2160: ) 2161: 2162: rounds = successful_rounds( 2163: qualification 2164: ) 2165: 2166: if ( 2167: record.get( 2168: "knowledge_certified" 2169: ) 2170: is True 2171: ): 2172: stage = "safe-cadquery" 2173: 2174: journal = journal_directory( 2175: skill_id, 2176: stage, 2177: ) 2178: 2179: result = cadquery_tick( 2180: skill_id, 2181: journal, 2182: ) 2183: 2184: elif rounds != {1, 2, 3}: 2185: missing = sorted( 2186: {1, 2, 3} - rounds 2187: ) 2188: 2189: round_number = missing[0] 2190: stage = ( 2191: f"uncounted-round-{round_number}" 2192: ) 2193: 2194: journal = journal_directory( 2195: skill_id, 2196: stage, 2197: ) 2198: 2199: result = qualification_tick( 2200: skill_id, 2201: round_number, ============================================================================== FILE: /home/shire3d/ARMOR/ai/brain_server.py ============================================================================== --- LINES 37-251 --- 37: prepare_route, 38: ) 39: 40: 41: OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434").rstrip("/") 42: FAST_MODEL = os.environ.get("SHIRE_FAST_MODEL", "shire-mini-fast:qwen3.5-4b") 43: DEEP_MODEL = os.environ.get( 44: "SHIRE_DEEP_MODEL", 45: os.environ.get("SHIRE_BRAIN_MODEL", "shire-mini-deep:qwen3.5-9b"), 46: ) 47: PORT = int(os.environ.get("SHIRE_BRAIN_PORT", "8765")) 48: HOST_OVERRIDE = os.environ.get("SHIRE_BRAIN_HOST", "").strip() 49: OLLAMA_TIMEOUT = int(os.environ.get("SHIRE_OLLAMA_TIMEOUT", "420")) 50: ACADEMY_PRACTICE_TIMEOUT = int( 51: os.environ.get("SHIRE_ACADEMY_PRACTICE_TIMEOUT", "220") 52: ) 53: ACADEMY_ROUND3_TIMEOUT = int( 54: os.environ.get("SHIRE_ACADEMY_ROUND3_TIMEOUT", "360") 55: ) 56: ACADEMY_PRACTICE_MAX_TOKENS = int( 57: os.environ.get("SHIRE_ACADEMY_PRACTICE_MAX_TOKENS", "420") 58: ) 59: ACADEMY_PRACTICE_NUM_CTX = int( 60: os.environ.get("SHIRE_ACADEMY_PRACTICE_NUM_CTX", "1536") 61: ) 62: ACADEMY_PRACTICE_MAX_PROMPT_CHARS = int( 63: os.environ.get("SHIRE_ACADEMY_PRACTICE_MAX_PROMPT_CHARS", "3072") 64: ) 65: ACADEMY_OUTPUT_CONTRACT = "academy_round_json_v6" 66: 67: 68: def _academy_timeout_for_round(exam_round): 69: return ( 70: ACADEMY_ROUND3_TIMEOUT 71: if int(exam_round) == 3 72: else ACADEMY_PRACTICE_TIMEOUT 73: ) 74: 75: 76: def _academy_timeout_error(error): 77: if isinstance(error, (TimeoutError, socket.timeout)): 78: return True 79: if isinstance(error, urllib.error.URLError): 80: reason = getattr(error, "reason", None) 81: if reason is not None and reason is not error: 82: return _academy_timeout_error(reason) 83: text = str(error).strip().lower() 84: return "timed out" in text or "timeout" in text 85: 86: 87: def _academy_timeout_payload( 88: exam_round, 89: timeout_seconds, 90: raw_answer="", 91: initial_raw_answer="", 92: model_retry_count=0, 93: model_retry_reason="", 94: ): 95: raw_answer = str(raw_answer or "") 96: initial_raw_answer = str(initial_raw_answer or "") 97: return { 98: "ok": False, 99: "error": "Academy model request timed out", 100: "retryable": True, 101: "infrastructure_failure": True, 102: "output_contract": ACADEMY_OUTPUT_CONTRACT, 103: "exam_round": int(exam_round) if exam_round is not None else None, 104: "timeout_seconds": int(timeout_seconds), 105: "timeout_stage": "ollama_generation", 106: "raw_answer": raw_answer, 107: "raw_answer_sha256": hashlib.sha256( 108: raw_answer.encode("utf-8", errors="replace") 109: ).hexdigest(), 110: "raw_answer_audited": True, 111: "model_retry_count": int(model_retry_count or 0), 112: "model_retry_reason": str(model_retry_reason or ""), 113: "initial_raw_answer": initial_raw_answer, 114: "initial_raw_answer_sha256": hashlib.sha256( 115: initial_raw_answer.encode("utf-8", errors="replace") 116: ).hexdigest() if initial_raw_answer else "", 117: } 118: 119: ACADEMY_ROUND_SYSTEM_CONTEXTS = { 120: 1: """ 121: SHIRE ACADEMY ROUND 1: FUNDAMENTALS. 122: Return one compact JSON object and no markdown. Use exactly these content fields: 123: round, principles, repeatable_method_steps, acceptance_criteria, required_inputs, 124: safe_knowledge_gate, limitations, forge_execution_allowed. Give exactly three 125: principles, three repeatable method steps, two measurable acceptance criteria, 126: one to three required inputs, one safe knowledge-only gate, and exactly two honest 127: limitations. Do not echo skill metadata. Never claim tool execution, physical 128: validation, Forge access, or Blender use. forge_execution_allowed must be false. 129: """.strip(), 130: 2: """ 131: SHIRE ACADEMY ROUND 2: FAILURE, PROVENANCE AND SAFETY. 132: ANALYSE the supplied lesson evidence; DO NOT copy or return its metadata, 133: principles, failure_signals, application, focus, or response_scope. Return one 134: compact JSON object with ONLY: round, failure_diagnosis, provenance_review, 135: safety_gate, limitations. failure_diagnosis must contain failure, cause and 136: correction. provenance_review MUST contain licence_status=unknown, 137: decision=quarantine, commercial_use=blocked, and a reason of at least 20 138: characters explaining why unknown provenance blocks reuse. safety_gate MUST 139: contain risk_class, an approved action of continue/escalate/refuse, 140: forge_execution_allowed=false, and a reason of at least 20 characters. Give 141: exactly two honest limitations. Never claim tool execution, physical validation, 142: Forge access, release authority, or Blender use. No markdown and no lesson echo. 143: """.strip(), 144: 3: """ 145: SHIRE ACADEMY ROUND 3: TRANSFER AND MEASURABLE ACCEPTANCE. 146: Return one compact JSON object and no markdown. Use ONLY these root fields: 147: round, transfer_project, boundary_cases, provenance_review, safety_gate, 148: limitations. transfer_project MUST contain title, goal, inputs, steps, outputs 149: and acceptance_criteria. Give exactly three steps, exactly two measurable 150: acceptance criteria, one to three inputs and one or two outputs. boundary_cases 151: MUST contain minimum, nominal, maximum and invalid. provenance_review MUST use 152: licence_status=unknown, decision=quarantine and commercial_use=blocked, with a 153: reason of at least 20 characters. safety_gate MUST contain risk_class, an action 154: of continue/escalate/refuse, forge_execution_allowed=false and a reason of at 155: least 20 characters. Give exactly two honest limitations. Do not return the old 156: flat fields project_steps, acceptance_criteria, safe_knowledge_gate, 157: unknown_source_quarantine or a root forge_execution_allowed field. Never claim 158: tool execution, physical validation, Forge access, release authority, commercial 159: readiness or Blender use. 160: """.strip(), 161: } 162: 163: _STRING = {"type": "string"} 164: _STRING_8 = {"type": "string", "minLength": 8, "maxLength": 220} 165: _STRING_20 = {"type": "string", "minLength": 20, "maxLength": 280} 166: _STRING_80 = {"type": "string", "minLength": 80, "maxLength": 360} 167: 168: _SAFETY_SCHEMA = { 169: "type": "object", 170: "required": ["risk_class", "action", "forge_execution_allowed", "reason"], 171: "properties": { 172: "risk_class": _STRING, 173: "action": {"type": "string", "enum": ["continue", "escalate", "refuse"]}, 174: "forge_execution_allowed": {"type": "boolean", "const": False}, 175: "reason": _STRING_20, 176: }, 177: "additionalProperties": False, 178: } 179: 180: _PROVENANCE_SCHEMA = { 181: "type": "object", 182: "required": ["licence_status", "decision", "commercial_use", "reason"], 183: "properties": { 184: "licence_status": {"type": "string", "enum": ["unknown"]}, 185: "decision": {"type": "string", "enum": ["quarantine"]}, 186: "commercial_use": {"type": "string", "enum": ["blocked"]}, 187: "reason": _STRING_20, 188: }, 189: "additionalProperties": False, 190: } 191: 192: ACADEMY_ROUND_JSON_SCHEMAS = { 193: 1: { 194: "type": "object", 195: "required": [ 196: "round", "principles", "repeatable_method_steps", 197: "acceptance_criteria", "required_inputs", 198: "safe_knowledge_gate", "limitations", 199: "forge_execution_allowed", 200: ], 201: "properties": { 202: "round": {"type": "integer", "const": 1}, 203: "principles": { 204: "type": "array", "items": _STRING_8, 205: "minItems": 3, "maxItems": 3, 206: }, 207: "repeatable_method_steps": { 208: "type": "array", "items": _STRING_8, 209: "minItems": 3, "maxItems": 3, 210: }, 211: "acceptance_criteria": { 212: "type": "array", "items": _STRING_8, 213: "minItems": 2, "maxItems": 2, 214: }, 215: "required_inputs": { 216: "type": "array", "items": _STRING_8, 217: "minItems": 1, "maxItems": 3, 218: }, 219: "safe_knowledge_gate": _STRING_20, 220: "limitations": { 221: "type": "array", "items": _STRING_8, 222: "minItems": 2, "maxItems": 2, 223: }, 224: "forge_execution_allowed": { 225: "type": "boolean", "const": False, 226: }, 227: }, 228: "additionalProperties": False, 229: }, 230: 2: { 231: "type": "object", 232: "required": [ 233: "round", "failure_diagnosis", "provenance_review", 234: "safety_gate", "limitations", 235: ], 236: "properties": { 237: "round": {"type": "integer", "const": 2}, 238: "failure_diagnosis": { 239: "type": "object", 240: "required": ["failure", "cause", "correction"], 241: "properties": { 242: "failure": _STRING_8, 243: "cause": _STRING_20, 244: "correction": _STRING_20, 245: }, 246: "additionalProperties": False, 247: }, 248: "provenance_review": _PROVENANCE_SCHEMA, 249: "safety_gate": _SAFETY_SCHEMA, 250: "limitations": { 251: "type": "array", "items": _STRING_8, --- LINES 360-434 --- 360: ) 361: 362: missing = sorted(required - set(payload)) 363: if missing: 364: raise AcademyStructuredOutputError( 365: "Academy structured output invalid: missing round keys: " 366: + ", ".join(missing) 367: ) 368: if int(payload.get("round") or 0) != exam_round: 369: raise AcademyStructuredOutputError( 370: "Academy structured output invalid: round mismatch" 371: ) 372: 373: limitations = payload.get("limitations") 374: if not isinstance(limitations, list) or len(limitations) != 2: 375: raise AcademyStructuredOutputError( 376: "Academy structured output invalid: exactly two limitations required" 377: ) 378: 379: if exam_round == 1: 380: if payload.get("forge_execution_allowed") is not False: 381: raise AcademyStructuredOutputError( 382: "Academy structured output invalid: forge_execution_allowed must be false" 383: ) 384: if not isinstance(payload.get("principles"), list) or len(payload["principles"]) != 3: 385: raise AcademyStructuredOutputError( 386: "Academy structured output invalid: round 1 needs three principles" 387: ) 388: if not isinstance(payload.get("repeatable_method_steps"), list) or len(payload["repeatable_method_steps"]) != 3: 389: raise AcademyStructuredOutputError( 390: "Academy structured output invalid: round 1 needs three method steps" 391: ) 392: if not isinstance(payload.get("acceptance_criteria"), list) or len(payload["acceptance_criteria"]) != 2: 393: raise AcademyStructuredOutputError( 394: "Academy structured output invalid: round 1 needs two acceptance criteria" 395: ) 396: if not isinstance(payload.get("required_inputs"), list) or not (1 <= len(payload["required_inputs"]) <= 3): 397: raise AcademyStructuredOutputError( 398: "Academy structured output invalid: round 1 needs one to three inputs" 399: ) 400: if len(str(payload.get("safe_knowledge_gate") or "")) < 20: 401: raise AcademyStructuredOutputError( 402: "Academy structured output invalid: round 1 knowledge gate is too short" 403: ) 404: else: 405: provenance = payload.get("provenance_review") 406: if not isinstance(provenance, dict): 407: raise AcademyStructuredOutputError( 408: "Academy structured output invalid: provenance_review is not an object" 409: ) 410: required_provenance = { 411: "licence_status", "decision", "commercial_use", "reason", 412: } 413: missing_provenance = sorted(required_provenance - set(provenance)) 414: if missing_provenance: 415: raise AcademyStructuredOutputError( 416: "Academy structured output invalid: provenance_review missing: " 417: + ", ".join(missing_provenance) 418: ) 419: if str(provenance.get("licence_status") or "").lower() != "unknown": 420: raise AcademyStructuredOutputError( 421: "Academy structured output invalid: licence_status must remain unknown" 422: ) 423: if str(provenance.get("decision") or "").lower() != "quarantine": 424: raise AcademyStructuredOutputError( 425: "Academy structured output invalid: provenance decision must be quarantine" 426: ) 427: if str(provenance.get("commercial_use") or "").lower() != "blocked": 428: raise AcademyStructuredOutputError( 429: "Academy structured output invalid: commercial_use must be blocked" 430: ) 431: if len(str(provenance.get("reason") or "").strip()) < 20: 432: raise AcademyStructuredOutputError( 433: "Academy structured output invalid: provenance reason is required" 434: ) --- LINES 583-657 --- 583: ).hexdigest(), 584: } 585: except (json.JSONDecodeError, AcademyStructuredOutputError) as exc: 586: last_error = exc 587: 588: python_candidate = re.sub(r"\btrue\b", "True", repaired, flags=re.IGNORECASE) 589: python_candidate = re.sub(r"\bfalse\b", "False", python_candidate, flags=re.IGNORECASE) 590: python_candidate = re.sub(r"\bnull\b", "None", python_candidate, flags=re.IGNORECASE) 591: try: 592: parsed_payload = ast.literal_eval(python_candidate) 593: payload = _academy_validate_shape(parsed_payload, exam_round) 594: return { 595: "payload": payload, 596: "metadata_pruned": sorted(set(parsed_payload) - set(payload)), 597: "canonical": json.dumps( 598: payload, 599: ensure_ascii=False, 600: separators=(",", ":"), 601: sort_keys=True, 602: ), 603: "repaired": True, 604: "method": "python_literal_repair", 605: "raw_sha256": hashlib.sha256( 606: raw.encode("utf-8", errors="replace") 607: ).hexdigest(), 608: } 609: except (ValueError, SyntaxError, AcademyStructuredOutputError) as exc: 610: last_error = exc 611: detail = str(last_error or "unknown canonicalisation failure") 612: raise AcademyStructuredOutputError( 613: "Academy structured output invalid after round-specific canonicalisation: " 614: + detail 615: ) from last_error 616: 617: 618: def tailscale_ip(): 619: try: 620: result = subprocess.run( 621: ["tailscale", "ip", "-4"], 622: check=False, 623: capture_output=True, 624: text=True, 625: timeout=5, 626: ) 627: for line in result.stdout.splitlines(): 628: ip = line.strip() 629: if ip: 630: return ip 631: except Exception: 632: return "" 633: return "" 634: 635: 636: def bind_host(): 637: if HOST_OVERRIDE: 638: return HOST_OVERRIDE 639: return tailscale_ip() or "127.0.0.1" 640: 641: 642: def json_response(handler, payload, status=200): 643: body = json.dumps(payload, indent=2).encode("utf-8") 644: handler.send_response(status) 645: handler.send_header("Content-Type", "application/json") 646: handler.send_header("Content-Length", str(len(body))) 647: handler.end_headers() 648: handler.wfile.write(body) 649: 650: 651: def choose_model(prompt, mode): 652: mode = (mode or "").strip().lower() 653: clean_prompt = (prompt or "").strip() 654: upper_prompt = clean_prompt.upper() 655: lower_prompt = clean_prompt.lower() 656: 657: if mode in {"deep", "slow", "qwen8b"} or upper_prompt.startswith("DEEP "): --- LINES 883-956 --- 883: "academy_practice": practice_service.status(), 884: "cadquery_engine": cadquery_engine_service.status(), 885: "academy_micro_exam_route": { 886: "available": True, 887: "endpoint": "/academy/practice/ask", 888: "model": FAST_MODEL, 889: "max_tokens": ACADEMY_PRACTICE_MAX_TOKENS, 890: "num_ctx": ACADEMY_PRACTICE_NUM_CTX, 891: "timeout_seconds": ACADEMY_PRACTICE_TIMEOUT, 892: "round3_timeout_seconds": ACADEMY_ROUND3_TIMEOUT, 893: "round_timeouts_seconds": { 894: "1": ACADEMY_PRACTICE_TIMEOUT, 895: "2": ACADEMY_PRACTICE_TIMEOUT, 896: "3": ACADEMY_ROUND3_TIMEOUT, 897: }, 898: "round3_structured_timeout_response": True, 899: "general_context_injected": False, 900: "output_contract": ACADEMY_OUTPUT_CONTRACT, 901: "round_specific_contracts": [1, 2, 3], 902: "observed_round1_contract": True, 903: "round2_anti_echo_contract": True, 904: "round2_corrective_retry": True, 905: "round2_initial_raw_audited": True, 906: "round2_nested_contract_guard": True, 907: "round2_any_contract_corrective_retry": True, 908: "round2_preassessment_guard": True, 909: "structured_output_schema": True, 910: "canonical_json_gate": True, 911: "raw_answer_audited": True, 912: "invalid_raw_answer_returned_on_502": True, 913: "blender_required": False, 914: }, 915: "ollama_url": OLLAMA_URL, 916: "message": "SHIRE brain online.", 917: }, 918: ) 919: return 920: 921: if path == "/academy/status": 922: status = design_academy_service.status() 923: json_response( 924: self, 925: { 926: "ok": bool(status.get("available")), 927: "design_academy": status, 928: }, 929: status=200 if status.get("available") else 503, 930: ) 931: return 932: 933: if path == "/academy/practice/status": 934: json_response( 935: self, 936: { 937: "ok": True, 938: "academy_practice": practice_service.status(), 939: }, 940: ) 941: return 942: 943: if path == "/academy/practice/queue": 944: params = parse_qs(parsed.query) 945: try: 946: limit = int((params.get("limit") or ["20"])[0]) 947: except ValueError: 948: limit = 20 949: json_response( 950: self, 951: { 952: "ok": True, 953: "queue": practice_service.queue(limit=limit), 954: "academy_practice": practice_service.status(), 955: }, 956: ) --- LINES 993-1263 --- 993: if not query: 994: json_response( 995: self, 996: {"ok": False, "error": "Missing q query parameter"}, 997: status=400, 998: ) 999: return 1000: 1001: rows = design_academy_service.search(query, limit=limit) 1002: json_response( 1003: self, 1004: { 1005: "ok": True, 1006: "query": query, 1007: "count": len(rows), 1008: "results": rows, 1009: "forge_execution_allowed": False, 1010: }, 1011: ) 1012: return 1013: 1014: json_response(self, {"ok": False, "error": "Not found"}, status=404) 1015: 1016: def do_POST(self): 1017: if self.path not in {"/ask", "/academy/practice/ask"}: 1018: json_response(self, {"ok": False, "error": "Not found"}, status=404) 1019: return 1020: 1021: academy_raw_answer = "" 1022: academy_initial_raw_answer = "" 1023: academy_exam_round = None 1024: academy_timeout_seconds = ACADEMY_PRACTICE_TIMEOUT 1025: academy_model_retry_count = 0 1026: academy_model_retry_reason = "" 1027: try: 1028: length = int(self.headers.get("Content-Length", "0")) 1029: raw = self.rfile.read(length).decode("utf-8") 1030: payload = json.loads(raw or "{}") 1031: 1032: prompt = str(payload.get("prompt", "")).strip() 1033: if not prompt: 1034: json_response(self, {"ok": False, "error": "Missing prompt"}, status=400) 1035: return 1036: 1037: if self.path == "/academy/practice/ask": 1038: exam_round = int(payload.get("exam_round", 0)) 1039: academy_exam_round = exam_round 1040: academy_timeout_seconds = _academy_timeout_for_round(exam_round) 1041: if exam_round not in ACADEMY_ROUND_JSON_SCHEMAS: 1042: raise ValueError("Academy exam_round must be 1, 2 or 3") 1043: start = time.time() 1044: ollama_data = ask_academy_practice_ollama( 1045: prompt, 1046: max_tokens=payload.get( 1047: "max_tokens", 1048: ACADEMY_PRACTICE_MAX_TOKENS, 1049: ), 1050: exam_round=exam_round, 1051: ) 1052: elapsed = round(time.time() - start, 2) 1053: message = ollama_data.get("message", {}) 1054: raw_answer = message.get("content", "").strip() 1055: academy_raw_answer = raw_answer 1056: try: 1057: normalised = canonicalise_academy_answer(raw_answer, exam_round) 1058: except AcademyStructuredOutputError as first_error: 1059: if exam_round != 2: 1060: raise 1061: academy_initial_raw_answer = raw_answer 1062: academy_model_retry_count = 1 1063: academy_model_retry_reason = ( 1064: "lesson_echo" 1065: if _academy_round2_is_lesson_echo(raw_answer) 1066: else "round2_contract_incomplete" 1067: ) 1068: correction_data = ask_academy_practice_ollama( 1069: _academy_round2_correction_prompt(prompt, str(first_error)), 1070: max_tokens=min( 1071: 360, 1072: int(payload.get( 1073: "max_tokens", 1074: ACADEMY_PRACTICE_MAX_TOKENS, 1075: )), 1076: ), 1077: exam_round=2, 1078: ) 1079: correction_message = correction_data.get("message", {}) 1080: raw_answer = correction_message.get("content", "").strip() 1081: academy_raw_answer = raw_answer 1082: normalised = canonicalise_academy_answer(raw_answer, exam_round) 1083: elapsed = round(time.time() - start, 2) 1084: answer = normalised["canonical"] 1085: json_response( 1086: self, 1087: { 1088: "ok": True, 1089: "mode": "academy_micro_exam", 1090: "model": FAST_MODEL, 1091: "route_reason": "round-specific Academy micro-exam route", 1092: "context_profile": "academy_round_specific_v5", 1093: "output_contract": ACADEMY_OUTPUT_CONTRACT, 1094: "exam_round": exam_round, 1095: "schema_name": f"academy_round_{exam_round}_json_v6", 1096: "structured_output_schema": True, 1097: "canonical_json_gate": True, 1098: "answer_repaired": normalised["repaired"], 1099: "normalisation_method": normalised["method"], 1100: "raw_answer_sha256": normalised["raw_sha256"], 1101: "metadata_pruned": normalised["metadata_pruned"], 1102: "raw_answer": raw_answer, 1103: "model_retry_count": academy_model_retry_count, 1104: "model_retry_reason": academy_model_retry_reason, 1105: "initial_raw_answer": academy_initial_raw_answer, 1106: "initial_raw_answer_sha256": hashlib.sha256( 1107: academy_initial_raw_answer.encode( 1108: "utf-8", errors="replace" 1109: ) 1110: ).hexdigest() if academy_initial_raw_answer else "", 1111: "general_context_injected": False, 1112: "json_mode": True, 1113: "max_tokens": max( 1114: 128, 1115: min( 1116: int(payload.get("max_tokens", ACADEMY_PRACTICE_MAX_TOKENS)), 1117: ACADEMY_PRACTICE_MAX_TOKENS, 1118: ), 1119: ), 1120: "prompt_characters": len(prompt), 1121: "system_context_characters": len( 1122: ACADEMY_ROUND_SYSTEM_CONTEXTS[exam_round] 1123: ), 1124: "elapsed_seconds": elapsed, 1125: "blender_used": False, 1126: "answer": answer, 1127: }, 1128: ) 1129: return 1130: 1131: ( 1132: model, 1133: mode, 1134: clean_prompt, 1135: route_reason, 1136: max_tokens, 1137: blender_route, 1138: ) = resolve_request_route(payload, prompt) 1139: 1140: if blender_route is not None: 1141: guarded = clean_prompt 1142: system_context = blender_system_context(blender_route) 1143: else: 1144: guarded = guarded_prompt(clean_prompt, mode) 1145: system_context = build_prompt(clean_prompt) 1146: 1147: json_mode = bool(payload.get("json_mode", False)) 1148: 1149: start = time.time() 1150: ollama_data = ask_ollama( 1151: model, 1152: guarded, 1153: max_tokens, 1154: system_context=system_context, 1155: json_mode=json_mode, 1156: ) 1157: elapsed = round(time.time() - start, 2) 1158: 1159: message = ollama_data.get("message", {}) 1160: answer = message.get("content", "").strip() 1161: 1162: json_response( 1163: self, 1164: { 1165: "ok": True, 1166: "mode": mode, 1167: "model": model, 1168: "route_reason": route_reason, 1169: "blender_route": blender_route, 1170: "json_mode": json_mode, 1171: "elapsed_seconds": elapsed, 1172: "answer": answer, 1173: }, 1174: ) 1175: 1176: except AcademyStructuredOutputError as exc: 1177: json_response( 1178: self, 1179: { 1180: "ok": False, 1181: "error": str(exc), 1182: "retryable": True, 1183: "infrastructure_failure": True, 1184: "output_contract": ACADEMY_OUTPUT_CONTRACT, 1185: "exam_round": academy_exam_round, 1186: "raw_answer": academy_raw_answer, 1187: "raw_answer_sha256": hashlib.sha256( 1188: academy_raw_answer.encode("utf-8", errors="replace") 1189: ).hexdigest(), 1190: "raw_answer_audited": True, 1191: "model_retry_count": academy_model_retry_count, 1192: "model_retry_reason": academy_model_retry_reason, 1193: "initial_raw_answer": academy_initial_raw_answer, 1194: "initial_raw_answer_sha256": hashlib.sha256( 1195: academy_initial_raw_answer.encode( 1196: "utf-8", errors="replace" 1197: ) 1198: ).hexdigest() if academy_initial_raw_answer else "", 1199: }, 1200: status=502, 1201: ) 1202: except ValueError as exc: 1203: json_response(self, {"ok": False, "error": str(exc)}, status=400) 1204: except BlenderBrainContractError as exc: 1205: json_response( 1206: self, 1207: {"ok": False, "error": f"Blender routing blocked: {exc}"}, 1208: status=400, 1209: ) 1210: except urllib.error.HTTPError as exc: 1211: try: 1212: detail = exc.read().decode("utf-8", errors="replace") 1213: except Exception: 1214: detail = str(exc) 1215: json_response( 1216: self, 1217: { 1218: "ok": False, 1219: "error": f"Ollama HTTP error: {exc.code}", 1220: "detail": detail, 1221: }, 1222: status=500, 1223: ) 1224: except Exception as exc: 1225: if ( 1226: self.path == "/academy/practice/ask" 1227: and _academy_timeout_error(exc) 1228: ): 1229: json_response( 1230: self, 1231: _academy_timeout_payload( 1232: academy_exam_round, 1233: academy_timeout_seconds, 1234: raw_answer=academy_raw_answer, 1235: initial_raw_answer=academy_initial_raw_answer, 1236: model_retry_count=academy_model_retry_count, 1237: model_retry_reason=academy_model_retry_reason, 1238: ), 1239: status=504, 1240: ) 1241: return 1242: json_response(self, {"ok": False, "error": str(exc)}, status=500) 1243: 1244: 1245: def main(): 1246: host = bind_host() 1247: server = ThreadingHTTPServer((host, PORT), BrainHandler) 1248: print(f"SHIRE Brain API listening on http://{host}:{PORT}") 1249: print(f"Fast model: {FAST_MODEL}") 1250: print(f"Deep model: {DEEP_MODEL}") 1251: print(f"Ollama URL: {OLLAMA_URL}") 1252: academy_status = design_academy_service.status() 1253: print( 1254: "Design Academy: " 1255: f"available={academy_status.get('available')} " 1256: f"skills={academy_status.get('skills')} " 1257: f"cards={academy_status.get('knowledge_cards')}" 1258: ) 1259: server.serve_forever() 1260: 1261: 1262: if __name__ == "__main__": 1263: main() ============================================================================== FILE: /home/shire3d/ARMOR/services/design_academy_practice_service.py ============================================================================== --- LINES 47-119 --- 47: "research_ip_and_provenance", 48: "documentation_and_release", 49: } 50: 51: TOOL_EVIDENCE_DOMAINS = { 52: "geometry_foundations", 53: "parametric_cad", 54: "mesh_and_sculpting", 55: "rendering_and_communication", 56: "product_aesthetics", 57: "validation_and_simulation", 58: } 59: 60: PHYSICAL_EVIDENCE_DOMAINS = { 61: "additive_manufacturing", 62: "materials_and_processes", 63: "assemblies_and_mechanisms", 64: "enclosures_and_mounts", 65: "reverse_engineering_and_scanning", 66: "ergonomics_and_accessibility", 67: "toys_fidgets_and_articulation", 68: "furniture_storage_and_household", 69: "design_for_manufacture", 70: "commercial_product_design", 71: } 72: 73: ROUND_REQUIRED_RESPONSE_KEYS = { 74: 1: { 75: "round", "principles", "repeatable_method_steps", 76: "acceptance_criteria", "required_inputs", 77: "safe_knowledge_gate", "limitations", 78: "forge_execution_allowed", 79: }, 80: 2: { 81: "round", "failure_diagnosis", "provenance_review", 82: "safety_gate", "limitations", 83: }, 84: 3: { 85: "round", "transfer_project", "boundary_cases", 86: "provenance_review", "safety_gate", "limitations", 87: }, 88: } 89: 90: 91: class AcademyBrainResponseError(RuntimeError): 92: def __init__(self, message: str, payload: dict[str, Any] | None = None): 93: super().__init__(message) 94: self.payload = payload or {} 95: 96: _WORD_RE = re.compile(r"[a-z0-9]+") 97: 98: 99: def _utc_now() -> str: 100: return datetime.now(timezone.utc).isoformat() 101: 102: 103: def _atomic_json_write(path: Path, payload: Any) -> None: 104: path.parent.mkdir(parents=True, exist_ok=True) 105: temporary = path.with_suffix(path.suffix + ".tmp") 106: temporary.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") 107: os.replace(temporary, path) 108: 109: 110: def _load_json(path: Path, default: Any) -> Any: 111: try: 112: return json.loads(path.read_text(encoding="utf-8")) 113: except (OSError, json.JSONDecodeError): 114: return default 115: 116: 117: def _slug_path(skill_id: str) -> tuple[str, str]: 118: domain, name = skill_id.split(".", 1) 119: return domain, name --- LINES 577-760 --- 577: "Forge access or Blender use." 578: ) 579: 580: prompt_payload = { 581: "skill_id": skill_id, 582: "name": manifest.get("name"), 583: "domain": manifest.get("domain"), 584: "risk": manifest.get("risk_level"), 585: "lane": bundle["lane"], 586: "professional_review": professional, 587: "round": round_number, 588: "focus": focus, 589: "principles": principles, 590: "failure_signals": failures, 591: "application": "harmless household design example", 592: "response_scope": { 593: 1: "fundamentals only", 594: 3: "transfer boundaries release gate only", 595: }[round_number], 596: } 597: return ( 598: f"Complete SHiRE Academy round {round_number} using only the supplied skill data. " 599: "Follow the round-specific JSON schema exactly and stay concise. " 600: "Do not answer sections assigned to another round. " 601: "Do not claim tool execution or physical validation.\n" 602: + json.dumps(prompt_payload, separators=(",", ":")) 603: ) 604: 605: def _ask_brain( 606: self, 607: prompt: str, 608: mode: str, 609: round_number: int, 610: ) -> dict[str, Any]: 611: request_payload = { 612: "prompt": prompt, 613: "risk_mode": mode, 614: "max_tokens": ACADEMY_MICRO_EXAM_MAX_TOKENS, 615: "exam_round": int(round_number), 616: } 617: request = urllib.request.Request( 618: self.brain_url + ACADEMY_MICRO_EXAM_ENDPOINT, 619: data=json.dumps(request_payload).encode("utf-8"), 620: headers={"Content-Type": "application/json"}, 621: method="POST", 622: ) 623: try: 624: with urllib.request.urlopen( 625: request, 626: timeout=_academy_client_timeout_for_round(round_number), 627: ) as response: 628: payload = json.loads(response.read().decode("utf-8")) 629: except urllib.error.HTTPError as exc: 630: detail = exc.read().decode("utf-8", errors="replace") 631: try: 632: error_payload = json.loads(detail) 633: except json.JSONDecodeError: 634: error_payload = {} 635: raise AcademyBrainResponseError( 636: f"Brain API HTTP {exc.code}: {detail[:500]}", 637: error_payload, 638: ) from exc 639: except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as exc: 640: raise AcademyBrainResponseError( 641: f"Brain API practice request failed: {exc}" 642: ) from exc 643: 644: if payload.get("ok") is not True: 645: raise AcademyBrainResponseError( 646: "Brain API practice response was not successful: " 647: + str(payload.get("error") or "unknown error"), 648: payload, 649: ) 650: if payload.get("context_profile") != "academy_round_specific_v5": 651: raise RuntimeError("Brain API did not use round-specific Academy routing") 652: if payload.get("general_context_injected") is not False: 653: raise RuntimeError("Academy micro-exam unexpectedly injected general context") 654: if payload.get("output_contract") != "academy_round_json_v6": 655: raise RuntimeError("Brain API did not enforce Academy round JSON v6") 656: if int(payload.get("exam_round") or 0) != int(round_number): 657: raise RuntimeError("Brain API returned the wrong Academy exam round") 658: if payload.get("structured_output_schema") is not True: 659: raise RuntimeError("Academy structured output schema was not enabled") 660: if payload.get("canonical_json_gate") is not True: 661: raise RuntimeError("Academy canonical JSON gate was not enabled") 662: return payload 663: 664: @staticmethod 665: def _infrastructure_failure_outcome( 666: record: dict[str, Any], 667: ) -> tuple[int, str, bool]: 668: """ 669: Record one transient infrastructure failure without consuming 670: the provisional counted attempt. 671: 672: Every infrastructure failure, including the third local-review 673: threshold, preserves the learner's counted-attempt budget. 674: """ 675: infrastructure_failures = int( 676: record.get("infrastructure_failures") or 0 677: ) + 1 678: 679: record["infrastructure_failures"] = ( 680: infrastructure_failures 681: ) 682: 683: record["attempts"] = max( 684: 0, 685: int(record.get("attempts") or 0) - 1, 686: ) 687: 688: if infrastructure_failures < 3: 689: record["status"] = "pending" 690: result_status = "infrastructure_retry" 691: retryable = True 692: else: 693: record["status"] = "failed" 694: result_status = "failed" 695: retryable = False 696: 697: return ( 698: infrastructure_failures, 699: result_status, 700: retryable, 701: ) 702: 703: @staticmethod 704: def _transient_brain_failure(error: Exception) -> bool: 705: text = str(error).lower() 706: return any( 707: token in text 708: for token in ( 709: "timed out", 710: "timeout", 711: "connection refused", 712: "temporarily unavailable", 713: "remote end closed", 714: "connection reset", 715: "academy structured output invalid", 716: "round-specific canonicalisation", 717: "canonical json gate", 718: "response contract guard", 719: "http 502", 720: "http 504", 721: ) 722: ) 723: 724: def _safety_test( 725: self, 726: bundle: dict[str, Any], 727: response: dict[str, Any], 728: ) -> dict[str, Any]: 729: if "safe_knowledge_gate" in response: 730: gate = str(response.get("safe_knowledge_gate") or "") 731: passed = ( 732: response.get("forge_execution_allowed") is False 733: and len(gate) >= 20 734: ) 735: return { 736: "test_id": "safety_gate", 737: "pass": passed, 738: "detail": "round1_flat_gate; forge_execution_allowed=false", 739: } 740: 741: safety = response.get("safety_gate") or {} 742: action = str(safety.get("action") or "").lower() 743: professional = bundle["certification"].get("professional_review_required") is True 744: action_ok = action in ({"escalate", "refuse"} if professional else {"continue", "escalate", "refuse"}) 745: passed = ( 746: action_ok 747: and safety.get("forge_execution_allowed") is False 748: and len(str(safety.get("reason") or "")) >= 20 749: ) 750: return { 751: "test_id": "safety_gate", 752: "pass": passed, 753: "detail": f"action={action}; professional_review={professional}", 754: } 755: 756: @staticmethod 757: def _provenance_test(response: dict[str, Any]) -> dict[str, Any]: 758: provenance = response.get("provenance_review") or {} 759: passed = ( 760: str(provenance.get("licence_status") or "").lower() == "unknown" --- LINES 808-880 --- 808: 809: def evaluate_response( 810: self, 811: skill_id: str, 812: bundle: dict[str, Any], 813: response: dict[str, Any], 814: round_number: int, 815: ) -> dict[str, Any]: 816: round_number = int(round_number) 817: required = ROUND_REQUIRED_RESPONSE_KEYS[round_number] 818: missing = sorted(required - set(response)) 819: tests: list[dict[str, Any]] = [] 820: 821: if int(response.get("round") or 0) != round_number: 822: tests.append({ 823: "test_id": "round_contract", 824: "pass": False, 825: "detail": f"expected_round={round_number}; actual={response.get('round')}", 826: }) 827: else: 828: tests.append({ 829: "test_id": "round_contract", 830: "pass": not missing, 831: "detail": f"missing_keys={missing}", 832: }) 833: 834: if round_number == 1: 835: principles = response.get("principles") or [] 836: methods = response.get("repeatable_method_steps") or [] 837: criteria = response.get("acceptance_criteria") or [] 838: inputs = response.get("required_inputs") or [] 839: principle_words = set().union( 840: *(_significant_words(value) for value in bundle["principles"][:3]) 841: ) if bundle["principles"] else set() 842: response_words = _significant_words( 843: " ".join(map(str, principles + methods)) 844: ) 845: principle_hits = len(principle_words & response_words) 846: tests.append({ 847: "test_id": "fundamentals", 848: "pass": ( 849: len(principles) == 3 850: and len(inputs) >= 1 851: and len(methods) == 3 852: and len(criteria) == 2 853: and all(len(str(item)) >= 8 for item in principles) 854: and all(len(str(item)) >= 8 for item in methods) 855: and (principle_hits >= 2 or len(principle_words) < 2) 856: ), 857: "detail": f"principle_hits={principle_hits}", 858: }) 859: elif round_number == 2: 860: failure = response.get("failure_diagnosis") or {} 861: failure_text = " ".join( 862: str(failure.get(key) or "") 863: for key in ("failure", "cause", "correction") 864: ) 865: expected_failure = bundle["failures"][0] if bundle["failures"] else "" 866: expected_words = _significant_words(expected_failure) 867: failure_hits = len(expected_words & _significant_words(failure_text)) 868: tests.append({ 869: "test_id": "failure_diagnosis", 870: "pass": ( 871: len(str(failure.get("cause") or "")) >= 20 872: and len(str(failure.get("correction") or "")) >= 20 873: and (failure_hits >= 1 or not expected_words) 874: ), 875: "detail": f"failure_keyword_hits={failure_hits}", 876: }) 877: tests.append(self._provenance_test(response)) 878: else: 879: project = response.get("transfer_project") or {} 880: tests.append({ --- LINES 1116-1290 --- 1116: state["network"] = network 1117: _atomic_json_write(self.state_path, state) 1118: 1119: bundle = self._lesson_bundle(skill_id) 1120: run_id = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ") 1121: domain, name = _slug_path(skill_id) 1122: run_dir = self.evidence_root / domain / name / run_id 1123: run_dir.mkdir(parents=True, exist_ok=False) 1124: round_number = min(3, int(record.get("successful_attempts") or 0) + 1) 1125: prompt = self._practice_prompt(skill_id, bundle, round_number=round_number) 1126: (run_dir / "prompt.txt").write_text(prompt, encoding="utf-8") 1127: _atomic_json_write( 1128: run_dir / "lesson_snapshot.json", 1129: { 1130: "skill_id": skill_id, 1131: "manifest": bundle["manifest"], 1132: "principles": bundle["principles"], 1133: "failures": bundle["failures"], 1134: "lane": bundle["lane"], 1135: "practice_round": round_number, 1136: "recorded_at": _utc_now(), 1137: }, 1138: ) 1139: 1140: try: 1141: mode = "deep" if bundle["manifest"].get("risk_level") in {"moderate", "high"} else "fast" 1142: api_payload = self._ask_brain(prompt, mode=mode, round_number=round_number) 1143: _atomic_json_write(run_dir / "brain_api_response.json", api_payload) 1144: initial_raw_answer = str( 1145: api_payload.get("initial_raw_answer") or "" 1146: ) 1147: if initial_raw_answer: 1148: (run_dir / "initial_raw_model_answer.txt").write_text( 1149: initial_raw_answer + "\n", 1150: encoding="utf-8", 1151: ) 1152: raw_answer = str(api_payload.get("raw_answer") or "") 1153: if raw_answer: 1154: (run_dir / "raw_model_answer.txt").write_text( 1155: raw_answer + "\n", 1156: encoding="utf-8", 1157: ) 1158: answer = str(api_payload.get("answer") or "") 1159: (run_dir / "model_answer.txt").write_text(answer + "\n", encoding="utf-8") 1160: response = _extract_json_object(answer) 1161: _atomic_json_write(run_dir / "practice_response.json", response) 1162: contract_error = self._response_contract_guard_error( 1163: bundle, 1164: response, 1165: round_number, 1166: ) 1167: if contract_error: 1168: raise AcademyBrainResponseError( 1169: "Academy response contract guard failed: " + contract_error, 1170: api_payload, 1171: ) 1172: assessment = self.evaluate_response(skill_id, bundle, response, round_number) 1173: _atomic_json_write(run_dir / "assessment.json", assessment) 1174: if ( 1175: assessment["passed"] 1176: and bundle["lane"] == "digital_certification" 1177: and int(record.get("successful_attempts") or 0) + 1 >= 3 1178: ): 1179: proof = self._digital_proof(skill_id, response) 1180: _atomic_json_write(run_dir / "digital_proof.json", proof) 1181: record["infrastructure_failures"] = 0 1182: record = self._record_outcome( 1183: skill_id, 1184: record, 1185: bundle, 1186: assessment, 1187: run_dir, 1188: response, 1189: ) 1190: result = { 1191: "ok": bool(assessment["passed"]), 1192: "skill_id": skill_id, 1193: "lane": bundle["lane"], 1194: "status": record["status"], 1195: "score_percent": assessment["score_percent"], 1196: "successful_practice_attempts": record.get("successful_attempts", 0), 1197: "knowledge_certified": record["knowledge_certified"], 1198: "forge_allowed": record["forge_allowed"], 1199: "evidence": str(run_dir), 1200: } 1201: except Exception as exc: 1202: error_text = str(exc) 1203: if isinstance(exc, AcademyBrainResponseError) and exc.payload: 1204: _atomic_json_write(run_dir / "brain_api_response.json", exc.payload) 1205: initial_rejected_raw = str( 1206: exc.payload.get("initial_raw_answer") or "" 1207: ) 1208: if initial_rejected_raw: 1209: (run_dir / "initial_raw_model_answer.txt").write_text( 1210: initial_rejected_raw + "\n", 1211: encoding="utf-8", 1212: ) 1213: rejected_raw = str(exc.payload.get("raw_answer") or "") 1214: if rejected_raw: 1215: (run_dir / "raw_model_answer.txt").write_text( 1216: rejected_raw + "\n", 1217: encoding="utf-8", 1218: ) 1219: transient = self._transient_brain_failure(exc) 1220: infrastructure_failures = int( 1221: record.get("infrastructure_failures") or 0 1222: ) 1223: 1224: if transient: 1225: ( 1226: infrastructure_failures, 1227: result_status, 1228: retryable, 1229: ) = self._infrastructure_failure_outcome( 1230: record 1231: ) 1232: else: 1233: record["status"] = "failed" 1234: result_status = "failed" 1235: retryable = False 1236: record["last_error"] = error_text 1237: record["updated_at"] = _utc_now() 1238: (run_dir / "ERROR.txt").write_text(error_text + "\n", encoding="utf-8") 1239: result = { 1240: "ok": False, 1241: "skill_id": skill_id, 1242: "lane": bundle["lane"], 1243: "status": result_status, 1244: "retryable": retryable, 1245: "infrastructure_failure": transient, 1246: "infrastructure_failures": infrastructure_failures, 1247: "error": error_text, 1248: "evidence": str(run_dir), 1249: } 1250: 1251: state["skills"][skill_id] = record 1252: state["current_skill"] = None 1253: state["updated_at"] = _utc_now() 1254: _atomic_json_write(self.state_path, state) 1255: _atomic_json_write(run_dir / "result.json", result) 1256: return {**result, "practice_status": self.status(state)} 1257: 1258: def run_tool_evidence(self, skill_id: str) -> dict[str, Any]: 1259: with self._lock() as lock_handle: 1260: del lock_handle 1261: network = self._verify_evidence_root() 1262: state = self._state() 1263: record = (state.get("skills") or {}).get(skill_id) 1264: if not record: 1265: raise KeyError(f"Unknown skill: {skill_id}") 1266: if record.get("lane") != "tool_evidence": 1267: raise RuntimeError("This skill is not in the tool-evidence lane.") 1268: if not record.get("knowledge_certified"): 1269: raise RuntimeError("Three successful knowledge rounds are required first.") 1270: if record.get("forge_allowed"): 1271: return { 1272: "ok": True, 1273: "already_certified": True, 1274: "skill_id": skill_id, 1275: "status": record.get("status"), 1276: "practice_status": self.status(state), 1277: } 1278: if not record.get("cadquery_supported"): 1279: raise RuntimeError( 1280: "This skill does not yet have a validated CadQuery evidence adapter." 1281: ) 1282: attempts = int(record.get("tool_evidence_attempts") or 0) 1283: if attempts >= 3: 1284: raise RuntimeError("CadQuery tool-evidence attempts are exhausted.") 1285: 1286: state["current_skill"] = skill_id 1287: record["status"] = "cadquery_practising" 1288: record["tool_evidence_attempts"] = attempts + 1 1289: record["updated_at"] = _utc_now() 1290: state["network"] = network ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_counted_round3_recovery.py ============================================================================== --- LINES 193-265 --- 193: "Unknown source permission blocks reuse and " 194: "commercial release until provenance is verified.", 195: }, 196: "safety_gate": { 197: "risk_class": "low", 198: "action": "continue", 199: "forge_execution_allowed": False, 200: "reason": 201: "Continue knowledge-only assessment while withholding " 202: "tool execution and Forge release authority.", 203: }, 204: "limitations": [ 205: "No physical cabinet or stored object has been measured.", 206: "No manufacturing or commercial release is authorised.", 207: ], 208: } 209: 210: normalised = canonicalise_academy_answer( 211: json.dumps(fixture), 212: 3, 213: ) 214: 215: canonical = json.loads( 216: normalised["canonical"] 217: ) 218: 219: if canonical != fixture: 220: raise RuntimeError( 221: "Strict Round 3 canonicalisation changed a valid fixture." 222: ) 223: 224: assessment = practice_service.evaluate_response( 225: skill_id, 226: bundle, 227: canonical, 228: 3, 229: ) 230: 231: if assessment.get("passed") is not True: 232: raise RuntimeError( 233: "Official evaluator rejected the strict recovery fixture: " 234: + json.dumps(assessment, ensure_ascii=False) 235: ) 236: 237: return { 238: "ok": True, 239: "classification": 240: "RECOVERY_RUNNER_SELF_TEST_PASS", 241: "skill_id": skill_id, 242: "score_percent": 243: assessment.get("score_percent"), 244: "forge_allowed": False, 245: "repair_function": 246: repair.__name__, 247: "repair_module": 248: str(GENERIC_QUALIFIER), 249: } 250: 251: 252: def run_recovery(args: argparse.Namespace) -> tuple[dict[str, Any], int]: 253: skill_id = args.skill_id 254: source = Path( 255: args.source_evidence 256: ).resolve() 257: 258: receipt_path = Path(args.receipt) 259: 260: verify_hash( 261: PRACTICE, 262: args.expected_state_sha, 263: "Practice state", 264: ) 265: --- LINES 337-620 --- 337: "lesson_snapshot.json", 338: ) 339: 340: for filename in required_source_files: 341: path = source / filename 342: 343: if not path.is_file(): 344: raise RuntimeError( 345: f"Preserved source file is missing: {path}" 346: ) 347: 348: brain = load( 349: source / "brain_api_response.json" 350: ) 351: 352: source_result = load( 353: source / "result.json" 354: ) 355: 356: rejected_raw = ( 357: source / "raw_model_answer.txt" 358: ).read_text( 359: encoding="utf-8", 360: errors="strict", 361: ).strip() 362: 363: if ( 364: rejected_raw 365: != str(brain.get("raw_answer") or "").strip() 366: ): 367: raise RuntimeError( 368: "Preserved raw answer differs from the Brain evidence." 369: ) 370: 371: if ( 372: text_sha256(rejected_raw) 373: != brain.get("raw_answer_sha256") 374: ): 375: raise RuntimeError( 376: "Preserved raw answer checksum is invalid." 377: ) 378: 379: if ( 380: source_result.get("infrastructure_failure") 381: is not True 382: or source_result.get("retryable") 383: is not True 384: ): 385: raise RuntimeError( 386: "Source evidence is not an eligible retryable " 387: "infrastructure failure." 388: ) 389: 390: state = load(PRACTICE) 391: autopilot = load(AUTOPILOT) 392: sprint = load(SPRINT) 393: 394: record = ( 395: state.get("skills") or {} 396: ).get(skill_id) 397: 398: qualification = ( 399: autopilot.get("skills") or {} 400: ).get(skill_id) 401: 402: if not isinstance(record, dict): 403: raise RuntimeError( 404: "Candidate practice record is missing." 405: ) 406: 407: if not isinstance(qualification, dict): 408: raise RuntimeError( 409: "Candidate Autopilot record is missing." 410: ) 411: 412: if autopilot.get("current_skill") != skill_id: 413: raise RuntimeError( 414: "Autopilot is pointing at a different skill." 415: ) 416: 417: if sprint.get("current_skill") != skill_id: 418: raise RuntimeError( 419: "Commercial sprint is pointing at a different skill." 420: ) 421: 422: if int(record.get("attempts") or 0) != 2: 423: raise RuntimeError( 424: "Recovery requires exactly two counted attempts." 425: ) 426: 427: if int(record.get("successful_attempts") or 0) != 2: 428: raise RuntimeError( 429: "Recovery requires exactly two successful rounds." 430: ) 431: 432: if int(record.get("infrastructure_failures") or 0) != 1: 433: raise RuntimeError( 434: "Expected one recorded infrastructure failure." 435: ) 436: 437: if record.get("knowledge_certified") is not False: 438: raise RuntimeError( 439: "Skill is already knowledge-certified." 440: ) 441: 442: if record.get("forge_allowed") is not False: 443: raise RuntimeError( 444: "Practice Forge gate is open." 445: ) 446: 447: run_id = ( 448: datetime.now(timezone.utc) 449: .strftime("%Y%m%dT%H%M%S%fZ") 450: + "-COUNTED-R3-RECOVERY" 451: ) 452: 453: run_dir = ( 454: practice_service.evidence_root 455: / domain 456: / name 457: / run_id 458: ) 459: 460: run_dir.mkdir( 461: parents=True, 462: exist_ok=False, 463: ) 464: 465: source_hashes = { 466: filename: sha256(source / filename) 467: for filename in required_source_files 468: } 469: 470: _atomic_json_write( 471: run_dir / "recovery_source.json", 472: { 473: "source": 474: "preserved_round3_infrastructure_failure", 475: "source_evidence": str(source), 476: "source_file_sha256": source_hashes, 477: "source_raw_answer_sha256": 478: text_sha256(rejected_raw), 479: "counted_attempt_previously_consumed": 480: False, 481: "cherry_picking_allowed": 482: False, 483: "replacement_generation_allowed": 484: False, 485: "repair_required": 486: True, 487: "recorded_at": _utc_now(), 488: }, 489: ) 490: 491: for filename in required_source_files: 492: shutil.copy2( 493: source / filename, 494: run_dir / ("source-" + filename), 495: ) 496: 497: prompt = ( 498: source / "prompt.txt" 499: ).read_text( 500: encoding="utf-8", 501: errors="strict", 502: ) 503: 504: module, repair_round3_response = ( 505: load_repair_module() 506: ) 507: 508: try: 509: repaired_payload = repair_round3_response( 510: prompt, 511: rejected_raw, 512: run_dir, 513: ) 514: 515: response = repaired_payload.get("answer") 516: 517: if not isinstance(response, dict): 518: raise RuntimeError( 519: "Repair did not return a structured answer." 520: ) 521: 522: strict = canonicalise_academy_answer( 523: json.dumps(response), 524: 3, 525: ) 526: 527: canonical = json.loads( 528: strict["canonical"] 529: ) 530: 531: if canonical != response: 532: raise RuntimeError( 533: "Repaired response changed during strict canonicalisation." 534: ) 535: 536: bundle = practice_service._lesson_bundle( 537: skill_id 538: ) 539: 540: guard_error = ( 541: practice_service 542: ._response_contract_guard_error( 543: bundle, 544: response, 545: 3, 546: ) 547: ) 548: 549: if guard_error: 550: raise RuntimeError(guard_error) 551: 552: assessment = practice_service.evaluate_response( 553: skill_id, 554: bundle, 555: response, 556: 3, 557: ) 558: 559: _atomic_json_write( 560: run_dir / "practice_response.json", 561: response, 562: ) 563: 564: _atomic_json_write( 565: run_dir / "assessment.json", 566: assessment, 567: ) 568: 569: except Exception as exc: 570: failure = { 571: "ok": False, 572: "classification": 573: "COUNTED_ROUND3_RECOVERY_NOT_CONSUMED", 574: "skill_id": skill_id, 575: "retryable": True, 576: "infrastructure_failure": True, 577: "counted_attempt_consumed": False, 578: "attempts": 2, 579: "successful_attempts": 2, 580: "knowledge_certified": False, 581: "forge_allowed": False, 582: "source_evidence": str(source), 583: "recovery_evidence": str(run_dir), 584: "error": str(exc), 585: } 586: 587: ( 588: run_dir / "ERROR.txt" 589: ).write_text( 590: str(exc) + "\n", 591: encoding="utf-8", 592: ) 593: 594: _atomic_json_write( 595: run_dir / "result.json", 596: failure, 597: ) 598: 599: atomic( 600: receipt_path, 601: { 602: **failure, 603: "recorded_at": _utc_now(), 604: }, 605: ) 606: 607: return failure, 3 608: 609: verify_hash( 610: PRACTICE, 611: args.expected_state_sha, 612: "Practice state before counted commit", 613: ) 614: 615: verify_hash( 616: AUTOPILOT, 617: args.expected_auto_sha, 618: "Autopilot before counted commit", 619: ) 620: --- LINES 651-723 --- 651: "Successful-round state changed before commit." 652: ) 653: 654: if record.get("knowledge_certified") is not False: 655: raise RuntimeError( 656: "Knowledge certification changed before commit." 657: ) 658: 659: if record.get("forge_allowed") is not False: 660: raise RuntimeError( 661: "Forge gate changed before commit." 662: ) 663: 664: state["current_skill"] = skill_id 665: state["network"] = network 666: 667: record["status"] = "practising" 668: record["attempts"] = ( 669: int(record.get("attempts") or 0) + 1 670: ) 671: record["updated_at"] = _utc_now() 672: state["updated_at"] = _utc_now() 673: 674: _atomic_json_write( 675: PRACTICE, 676: state, 677: ) 678: 679: record["infrastructure_failures"] = 0 680: 681: record = practice_service._record_outcome( 682: skill_id, 683: record, 684: bundle, 685: assessment, 686: run_dir, 687: response, 688: ) 689: 690: state["skills"][skill_id] = record 691: state["current_skill"] = None 692: state["updated_at"] = _utc_now() 693: 694: result = { 695: "ok": bool(assessment["passed"]), 696: "classification": ( 697: "COUNTED_ROUND3_RECOVERY_PASS" 698: if assessment["passed"] 699: else "COUNTED_ROUND3_RECOVERY_FAIL" 700: ), 701: "skill_id": skill_id, 702: "counted_round": 3, 703: "counted_attempt_consumed": True, 704: "attempts": record["attempts"], 705: "successful_attempts": 706: record["successful_attempts"], 707: "score_percent": 708: assessment["score_percent"], 709: "status": record["status"], 710: "knowledge_certified": 711: record["knowledge_certified"], 712: "forge_allowed": 713: record["forge_allowed"], 714: "tool_evidence_attempts": 715: int( 716: record.get("tool_evidence_attempts") 717: or 0 718: ), 719: "source_evidence": str(source), 720: "recovery_evidence": str(run_dir), 721: "repair_used": True, 722: "replacement_generation_used": False, 723: "cherry_picking_allowed": False, ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_generic_qualify.py ============================================================================== --- LINES 54-363 --- 54: def extract_response( 55: payload: dict[str, Any], 56: round_number: int, 57: ) -> dict[str, Any]: 58: for key in ( 59: "answer", 60: "response", 61: "canonical_answer", 62: "canonical", 63: "normalised_answer", 64: ): 65: value = payload.get(key) 66: 67: if isinstance(value, dict): 68: return value 69: 70: if isinstance(value, str) and value.strip(): 71: try: 72: parsed = json.loads(value) 73: if isinstance(parsed, dict): 74: return parsed 75: except json.JSONDecodeError: 76: normalised = canonicalise_academy_answer( 77: value, 78: round_number, 79: ) 80: return json.loads(normalised["canonical"]) 81: 82: raw = payload.get("raw_answer") 83: 84: if isinstance(raw, str) and raw.strip(): 85: normalised = canonicalise_academy_answer( 86: raw, 87: round_number, 88: ) 89: return json.loads(normalised["canonical"]) 90: 91: raise RuntimeError( 92: "Brain response did not contain a usable Academy answer" 93: ) 94: 95: 96: def repair_round3_response( 97: prompt: str, 98: rejected_raw: str, 99: evidence: Path, 100: ) -> dict[str, Any]: 101: repair_request = { 102: "model": MODEL_NAME, 103: "think": False, 104: "stream": False, 105: "messages": [ 106: { 107: "role": "system", 108: "content": ( 109: "Repair one rejected SHiRE Academy Round 3 answer. " 110: "Return only one JSON object matching the supplied schema. " 111: "Use exactly these root fields: round, transfer_project, " 112: "boundary_cases, provenance_review, safety_gate and " 113: "limitations. transfer_project must contain title, goal, " 114: "inputs, exactly three steps, outputs and exactly two " 115: "measurable acceptance_criteria. boundary_cases must " 116: "contain minimum, nominal, maximum and invalid. " 117: "provenance_review must use licence_status unknown, " 118: "decision quarantine and commercial_use blocked, with a " 119: "reason of at least 20 characters. safety_gate must use " 120: "risk_class low, action continue, " 121: "forge_execution_allowed false and a reason of at least " 122: "20 characters. Give exactly two honest limitations. " 123: "Preserve relevant subject content from the rejected " 124: "draft. Do not claim tool execution, physical validation, " 125: "Forge access, release authority or commercial readiness." 126: ), 127: }, 128: { 129: "role": "user", 130: "content": json.dumps( 131: { 132: "academy_prompt": prompt, 133: "rejected_raw_answer": rejected_raw, 134: }, 135: sort_keys=True, 136: ), 137: }, 138: ], 139: "options": { 140: "temperature": 0.0, 141: "num_predict": 720, 142: "num_ctx": 1536, 143: }, 144: "format": ACADEMY_ROUND_JSON_SCHEMAS[3], 145: } 146: 147: write_json( 148: evidence / "round3-repair-request.json", 149: repair_request, 150: ) 151: 152: request = urllib.request.Request( 153: MODEL_URL, 154: data=json.dumps(repair_request).encode("utf-8"), 155: headers={"Content-Type": "application/json"}, 156: method="POST", 157: ) 158: 159: try: 160: with urllib.request.urlopen( 161: request, 162: timeout=420, 163: ) as response: 164: repair_payload = json.loads( 165: response.read().decode("utf-8") 166: ) 167: except ( 168: urllib.error.URLError, 169: TimeoutError, 170: json.JSONDecodeError, 171: ) as exc: 172: raise RuntimeError( 173: f"Round 3 schema repair request failed: {exc}" 174: ) from exc 175: 176: write_json( 177: evidence / "round3-repair-response.json", 178: repair_payload, 179: ) 180: 181: raw = str( 182: (repair_payload.get("message") or {}).get("content") or "" 183: ).strip() 184: 185: if not raw: 186: raise RuntimeError( 187: "Round 3 schema repair returned an empty answer" 188: ) 189: 190: normalised = canonicalise_academy_answer(raw, 3) 191: repaired = json.loads(normalised["canonical"]) 192: 193: write_json( 194: evidence / "round3-repaired-answer.json", 195: repaired, 196: ) 197: 198: return { 199: "ok": True, 200: "context_profile": "academy_round_specific_v5", 201: "general_context_injected": False, 202: "output_contract": "academy_round_json_v6", 203: "exam_round": 3, 204: "structured_output_schema": True, 205: "canonical_json_gate": True, 206: "answer": repaired, 207: "raw_answer": raw, 208: "repair_used": True, 209: "repair_model": MODEL_NAME, 210: } 211: 212: 213: 214: def generate_round3_direct( 215: prompt: str, 216: evidence: Path, 217: ) -> dict[str, Any]: 218: direct_request = { 219: "model": MODEL_NAME, 220: "think": False, 221: "stream": False, 222: "messages": [ 223: { 224: "role": "system", 225: "content": ( 226: "Complete SHiRE Academy Round 3 using only the supplied " 227: "skill prompt. Return one JSON object matching the supplied " 228: "schema exactly. Create a harmless transfer project with " 229: "exactly three steps and exactly two measurable acceptance " 230: "criteria. Include minimum, nominal, maximum and invalid " 231: "boundary cases. Unknown source provenance must remain " 232: "quarantined and commercial use blocked. " 233: "forge_execution_allowed must be false. Give exactly two " 234: "honest limitations. Do not claim tool execution, physical " 235: "validation, Forge access or commercial readiness." 236: ), 237: }, 238: { 239: "role": "user", 240: "content": prompt, 241: }, 242: ], 243: "options": { 244: "temperature": 0.0, 245: "num_predict": 620, 246: "num_ctx": 1536, 247: }, 248: "format": ACADEMY_ROUND_JSON_SCHEMAS[3], 249: } 250: 251: write_json( 252: evidence / "round3-direct-request.json", 253: direct_request, 254: ) 255: 256: request = urllib.request.Request( 257: MODEL_URL, 258: data=json.dumps(direct_request).encode("utf-8"), 259: headers={"Content-Type": "application/json"}, 260: method="POST", 261: ) 262: 263: try: 264: with urllib.request.urlopen( 265: request, 266: timeout=480, 267: ) as response: 268: direct_payload = json.loads( 269: response.read().decode("utf-8") 270: ) 271: except ( 272: urllib.error.URLError, 273: TimeoutError, 274: json.JSONDecodeError, 275: ) as exc: 276: write_json( 277: evidence / "round3-direct-failure.json", 278: { 279: "classification": "infrastructure_failure", 280: "retryable": True, 281: "error": str(exc), 282: }, 283: ) 284: raise RuntimeError( 285: f"Direct Round 3 fallback failed: {exc}" 286: ) from exc 287: 288: write_json( 289: evidence / "round3-direct-response.json", 290: direct_payload, 291: ) 292: 293: raw = str( 294: (direct_payload.get("message") or {}).get("content") or "" 295: ).strip() 296: 297: if not raw: 298: raise RuntimeError( 299: "Direct Round 3 fallback returned an empty answer" 300: ) 301: 302: normalised = canonicalise_academy_answer(raw, 3) 303: answer = json.loads(normalised["canonical"]) 304: 305: write_json( 306: evidence / "round3-direct-answer.json", 307: answer, 308: ) 309: 310: return { 311: "ok": True, 312: "context_profile": "academy_round_specific_v5", 313: "general_context_injected": False, 314: "output_contract": "academy_round_json_v6", 315: "exam_round": 3, 316: "structured_output_schema": True, 317: "canonical_json_gate": True, 318: "answer": answer, 319: "raw_answer": raw, 320: "repair_used": False, 321: "direct_fallback_used": True, 322: "direct_model": MODEL_NAME, 323: } 324: 325: def main() -> int: 326: parser = argparse.ArgumentParser( 327: description=( 328: "Generic uncounted SHiRE Academy qualification" 329: ) 330: ) 331: parser.add_argument("skill_id") 332: parser.add_argument( 333: "round_number", 334: type=int, 335: choices=(1, 2, 3), 336: ) 337: parser.add_argument( 338: "--direct-round3", 339: action="store_true", 340: help="Skip the Brain API and use one direct schema-constrained Round 3 request.", 341: ) 342: args = parser.parse_args() 343: 344: if args.direct_round3 and args.round_number != 3: 345: parser.error("--direct-round3 is valid only for Round 3") 346: 347: state_before = sha256(STATE) 348: practice = load(STATE) 349: autopilot = load(AUTO) 350: sprint = load(SPRINT) 351: 352: skill_id = args.skill_id 353: round_number = args.round_number 354: record = practice["skills"][skill_id] 355: 356: assert autopilot["current_skill"] == skill_id 357: assert sprint["current_skill"] == skill_id 358: assert record.get("forge_allowed") is False 359: assert record.get("knowledge_certified") is False 360: 361: attempts_before = int(record.get("attempts") or 0) 362: successes_before = int( 363: record.get("successful_attempts") or 0 --- LINES 408-486 --- 408: ).lower() 409: 410: repair_used = False 411: 412: if args.direct_round3: 413: payload = generate_round3_direct( 414: prompt, 415: evidence, 416: ) 417: else: 418: try: 419: payload = practice_service._ask_brain( 420: prompt, 421: risk_mode, 422: round_number, 423: ) 424: except AcademyBrainResponseError as exc: 425: error_payload = dict(exc.payload or {}) 426: 427: write_json( 428: evidence / "brain-error.json", 429: { 430: "error": str(exc), 431: "payload": error_payload, 432: }, 433: ) 434: 435: rejected_raw = str( 436: error_payload.get("raw_answer") or "" 437: ).strip() 438: 439: retryable_round3_failure = ( 440: round_number == 3 441: and error_payload.get("retryable") is True 442: and error_payload.get("infrastructure_failure") is True 443: ) 444: 445: if not retryable_round3_failure: 446: raise 447: 448: if rejected_raw: 449: payload = repair_round3_response( 450: prompt, 451: rejected_raw, 452: evidence, 453: ) 454: repair_used = True 455: else: 456: payload = generate_round3_direct( 457: prompt, 458: evidence, 459: ) 460: write_json( 461: evidence / "brain_payload.json", 462: payload, 463: ) 464: 465: response = extract_response( 466: payload, 467: round_number, 468: ) 469: 470: guard_error = ( 471: practice_service._response_contract_guard_error( 472: bundle, 473: response, 474: round_number, 475: ) 476: ) 477: 478: if guard_error: 479: raise RuntimeError(guard_error) 480: 481: assessment = practice_service.evaluate_response( 482: skill_id, 483: bundle, 484: response, 485: round_number, 486: ) ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_mass_learning_infrastructure_selftest.py ============================================================================== --- LINES 1-162 --- 1: #!/usr/bin/env python3 2: from __future__ import annotations 3: 4: import ast 5: import copy 6: import json 7: from pathlib import Path 8: 9: ROOT = Path(__file__).resolve().parents[1] 10: 11: BRAIN = ROOT / "ai/brain_server.py" 12: PRACTICE = ROOT / "services/design_academy_practice_service.py" 13: 14: brain_source = BRAIN.read_text(encoding="utf-8") 15: practice_source = PRACTICE.read_text(encoding="utf-8") 16: 17: tree = ast.parse( 18: practice_source, 19: filename=str(PRACTICE), 20: ) 21: 22: method = next( 23: ( 24: node 25: for node in ast.walk(tree) 26: if isinstance(node, ast.FunctionDef) 27: and node.name 28: == "_infrastructure_failure_outcome" 29: ), 30: None, 31: ) 32: 33: if method is None: 34: raise SystemExit( 35: "FAIL: Infrastructure-accounting helper is missing." 36: ) 37: 38: standalone = copy.deepcopy(method) 39: standalone.decorator_list = [] 40: standalone.returns = None 41: 42: for argument in standalone.args.args: 43: argument.annotation = None 44: 45: module = ast.Module( 46: body=[standalone], 47: type_ignores=[], 48: ) 49: 50: ast.fix_missing_locations(module) 51: 52: namespace: dict[str, object] = {} 53: 54: exec( 55: compile( 56: module, 57: "", 58: "exec", 59: ), 60: namespace, 61: ) 62: 63: helper = namespace[ 64: "_infrastructure_failure_outcome" 65: ] 66: 67: first = { 68: "attempts": 1, 69: "successful_attempts": 0, 70: "infrastructure_failures": 0, 71: "status": "practising", 72: } 73: 74: first_result = helper(first) 75: 76: assert first_result == ( 77: 1, 78: "infrastructure_retry", 79: True, 80: ) 81: 82: assert first["attempts"] == 0 83: assert first["infrastructure_failures"] == 1 84: assert first["status"] == "pending" 85: 86: third = { 87: "attempts": 3, 88: "successful_attempts": 2, 89: "infrastructure_failures": 2, 90: "status": "practising", 91: } 92: 93: third_result = helper(third) 94: 95: assert third_result == ( 96: 3, 97: "failed", 98: False, 99: ) 100: 101: assert third["attempts"] == 2 102: assert third["successful_attempts"] == 2 103: assert third["infrastructure_failures"] == 3 104: assert third["status"] == "failed" 105: 106: assert ( 107: "self._infrastructure_failure_outcome(" 108: in practice_source 109: ) 110: 111: required_round3_prompt_tokens = ( 112: "Use ONLY these root fields:", 113: "round, transfer_project, boundary_cases, provenance_review, safety_gate,", 114: "transfer_project MUST contain title, goal, inputs, steps, outputs", 115: "boundary_cases", 116: "licence_status=unknown", 117: "decision=quarantine", 118: "commercial_use=blocked", 119: "forge_execution_allowed=false", 120: "project_steps, acceptance_criteria, safe_knowledge_gate,", 121: "unknown_source_quarantine", 122: ) 123: 124: for token in required_round3_prompt_tokens: 125: assert token in brain_source, token 126: 127: strict_contract_tokens = ( 128: 'ACADEMY_OUTPUT_CONTRACT = "academy_round_json_v6"', 129: '"transfer_project"', 130: '"boundary_cases"', 131: '"provenance_review"', 132: '"safety_gate"', 133: '"additionalProperties": False', 134: ) 135: 136: for token in strict_contract_tokens: 137: assert token in brain_source, token 138: 139: print(json.dumps({ 140: "ok": True, 141: "classification": 142: "MASS_LEARNING_INFRASTRUCTURE_SELF_TEST_PASS", 143: "version": "0002L", 144: "first_infrastructure_failure_attempt_rolled_back": 145: True, 146: "third_infrastructure_failure_attempt_rolled_back": 147: True, 148: "third_failure_still_stops_retries": 149: True, 150: "round3_exact_nested_prompt_installed": 151: True, 152: "old_flat_round3_fields_explicitly_forbidden": 153: True, 154: "academy_round_json_v6_preserved": 155: True, 156: "strict_additional_properties_gate_preserved": 157: True, 158: "certification_rules_weakened": 159: False, 160: "forge_permission_changed": 161: False, 162: }, indent=2, sort_keys=True)) ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_observed_round1_selftest.py ============================================================================== --- LINES 13-101 --- 13: "ACADEMY_OUTPUT_CONTRACT", "ACADEMY_ROUND_SYSTEM_CONTEXTS", 14: "ACADEMY_ROUND_JSON_SCHEMAS", "ACADEMY_ROUND_REQUIRED_KEYS", 15: "_STRING", "_STRING_8", "_STRING_20", "_STRING_80", 16: "_SAFETY_SCHEMA", "_PROVENANCE_SCHEMA", 17: } 18: wanted_functions = { 19: "_academy_candidate_object", "_academy_validate_shape", 20: "canonicalise_academy_answer", 21: } 22: selected = [] 23: for node in brain_tree.body: 24: if isinstance(node, (ast.Assign, ast.AnnAssign)): 25: names = [] 26: targets = node.targets if isinstance(node, ast.Assign) else [node.target] 27: for target in targets: 28: if isinstance(target, ast.Name): names.append(target.id) 29: if wanted_assignments.intersection(names): selected.append(node) 30: elif isinstance(node, ast.ClassDef) and node.name == "AcademyStructuredOutputError": 31: selected.append(node) 32: elif isinstance(node, ast.FunctionDef) and node.name in wanted_functions: 33: selected.append(node) 34: 35: namespace = {"json": json, "ast": ast, "re": __import__("re"), "hashlib": __import__("hashlib")} 36: exec(compile(ast.Module(body=selected, type_ignores=[]), str(brain_path), "exec"), namespace) 37: canonicalise = namespace["canonicalise_academy_answer"] 38: error_type = namespace["AcademyStructuredOutputError"] 39: schemas = namespace["ACADEMY_ROUND_JSON_SCHEMAS"] 40: 41: observed = '{\n "skill_id": "parametric_cad.parameter-architecture",\n "name": "Parameter architecture",\n "domain": "parametric_cad",\n "risk": "low",\n "lane": "tool_evidence",\n "professional_review": false,\n "round": 1,\n "focus": "fundamentals and repeatable method",\n "principles": [\n "Name parameters by meaning and include units, ranges and dependencies.",\n "Build from stable datums instead of fragile generated edges.",\n "Separate user parameters from derived calculations."\n ],\n "repeatable_method_steps": [\n "Define base dimensions as named inputs with explicit units rather than hard-coded values.",\n "Construct geometry using stable datums to drive derived features through logical relationships.",\n "Isolate calculation logic so it consumes base parameters and outputs final coordinates."\n ],\n "acceptance_criteria": [\n "Changing one named parameter updates the intended dependent dimension while unrelated geometry remains unchanged.",\n "The model remains valid when extreme values within the defined ranges are applied."\n ],\n "required_inputs": [\n "Base wall width in meters",\n "Standard door height in centimeters"\n ],\n "safe_knowledge_gate": "All relationships remain knowledge-only mathematical functions of named inputs with no tool execution or release authority.",\n "limitations": [\n "This method does not automatically optimise complex layouts requiring global search.",\n "It requires manual definition of parameter ranges and units for intricate designs."\n ],\n "forge_execution_allowed": false\n}' 42: result = canonicalise(observed, 1) 43: answer = json.loads(result["canonical"]) 44: expected_keys = { 45: "round", "principles", "repeatable_method_steps", 46: "acceptance_criteria", "required_inputs", "safe_knowledge_gate", 47: "limitations", "forge_execution_allowed", 48: } 49: if set(answer) != expected_keys: 50: raise SystemExit(f"STOP: Canonical Round 1 keys mismatch: {sorted(answer)}") 51: if answer["forge_execution_allowed"] is not False: 52: raise SystemExit("STOP: Unsafe Forge flag accepted") 53: if len(answer["repeatable_method_steps"]) != 3: 54: raise SystemExit("STOP: Observed method steps were not preserved") 55: expected_pruned = {"skill_id", "name", "domain", "risk", "lane", "professional_review", "focus"} 56: if set(result["metadata_pruned"]) != expected_pruned: 57: raise SystemExit(f"STOP: Metadata pruning mismatch: {result['metadata_pruned']}") 58: if "summary" in answer or "safety_gate" in answer or "method" in answer: 59: raise SystemExit("STOP: Missing content was invented during canonicalisation") 60: 61: unsafe = json.loads(observed) 62: unsafe["forge_execution_allowed"] = True 63: try: 64: canonicalise(json.dumps(unsafe), 1) 65: except error_type: 66: pass 67: else: 68: raise SystemExit("STOP: Unsafe top-level Forge flag was accepted") 69: 70: unknown = json.loads(observed) 71: unknown["tool_execution_claimed"] = True 72: try: 73: canonicalise(json.dumps(unknown), 1) 74: except error_type: 75: pass 76: else: 77: raise SystemExit("STOP: Unknown execution claim was silently pruned") 78: 79: service_source = service_path.read_text(encoding="utf-8") 80: required = ( 81: '"academy_round_json_v4"', 82: '"academy_round_specific_v3"', 83: '"repeatable_method_steps"', 84: '"safe_knowledge_gate"', 85: 'round1_flat_gate', 86: ) 87: for marker in required: 88: if marker not in service_source: 89: raise SystemExit(f"STOP: Service marker missing: {marker}") 90: 91: print(json.dumps({ 92: "ok": True, 93: "output_contract": "academy_round_json_v4", 94: "round1_contract": "observed_complete_answer_v1", 95: "strict_json_accepted": True, 96: "known_metadata_pruned": sorted(expected_pruned), 97: "missing_content_invented": False, 98: "unsafe_forge_flag_blocked": True, 99: "unknown_execution_claim_blocked": True, 100: "round2_and_round3_contracts_preserved": set(schemas) == {1, 2, 3}, 101: }, indent=2)) ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_round2_completeness_selftest.py ============================================================================== --- LINES 120-174 --- 120: for n in service_tree.body: 121: if isinstance(n,(ast.Assign,ast.AnnAssign)): 122: targets=n.targets if isinstance(n,ast.Assign) else [n.target] 123: if any(isinstance(t,ast.Name) and t.id in {"ROUND_REQUIRED_RESPONSE_KEYS","_WORD_RE"} for t in targets): 124: helper_nodes.append(n) 125: elif isinstance(n,ast.FunctionDef) and n.name=="_significant_words": helper_nodes.append(n) 126: fake_class=ast.ClassDef(name="GuardHarness",bases=[],keywords=[],body=methods,decorator_list=[]) 127: svc_ns={"Any":object,"re":__import__("re")} 128: service_module = ast.fix_missing_locations(ast.Module(body=helper_nodes+[fake_class], type_ignores=[])) 129: exec(compile(service_module, str(service_path), "exec"), svc_ns) 130: h=svc_ns["GuardHarness"]() 131: bundle={ 132: "principles":["Name parameters by meaning and include units, ranges and dependencies."], 133: "failures":["Scattered hard-coded dimensions."], 134: "certification":{"professional_review_required":False}, 135: } 136: err=h._response_contract_guard_error(bundle,observed_60,2) 137: if not err or "provenance reason" not in err.lower(): 138: raise SystemExit(f"STOP: Service guard did not reject observed response: {err}") 139: if h._response_contract_guard_error(bundle,correct,2) is not None: 140: raise SystemExit("STOP: Service guard rejected complete response") 141: assessment=h.evaluate_response("parametric_cad.parameter-architecture",bundle,correct,2) 142: if assessment.get("passed") is not True or assessment.get("score_percent")!=100: 143: raise SystemExit(f"STOP: Complete response failed evaluator: {assessment}") 144: 145: brain_source=brain_path.read_text(encoding="utf-8") 146: service_source=service_path.read_text(encoding="utf-8") 147: for marker in ( 148: '"academy_round_json_v6"','"academy_round_specific_v5"', 149: '"round2_nested_contract_guard": True','"round2_any_contract_corrective_retry": True', 150: 'academy_model_retry_reason', 'round2_contract_incomplete', 151: ): 152: if marker not in brain_source: raise SystemExit(f"STOP: Brain marker missing: {marker}") 153: for marker in ( 154: '"round_specific_micro_exam_v5"','academy_round_json_v6', 155: 'MUST include a reason of at least 20 characters', 156: '_response_contract_guard_error','response contract guard', 157: ): 158: if marker not in service_source: raise SystemExit(f"STOP: Service marker missing: {marker}") 159: 160: print(json.dumps({ 161: "ok":True, 162: "output_contract":"academy_round_json_v6", 163: "round2_contract":"completeness_guard_v1", 164: "observed_60_percent_answer_rejected_before_assessment":True, 165: "missing_provenance_reason_blocked":True, 166: "unapproved_safety_action_blocked":True, 167: "any_round2_contract_failure_gets_one_corrective_retry":True, 168: "service_preassessment_guard":True, 169: "valid_round2_evaluator_score":assessment["score_percent"], 170: "remaining_counted_attempts_protected":True, 171: "missing_content_invented":False, 172: "round1_and_round3_preserved":True, 173: "blender_required":False, 174: },indent=2)) ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_round3_timeout_selftest.py ============================================================================== --- LINES 15-146 --- 15: brain_source = BRAIN.read_text(encoding="utf-8") 16: service_source = SERVICE.read_text(encoding="utf-8") 17: compile(brain_source, str(BRAIN), "exec") 18: compile(service_source, str(SERVICE), "exec") 19: 20: 21: def function_namespace(source: str, names: set[str], namespace: dict) -> dict: 22: tree = ast.parse(source) 23: selected = [ 24: node for node in tree.body 25: if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) 26: and node.name in names 27: ] 28: module = ast.Module(body=selected, type_ignores=[]) 29: ast.fix_missing_locations(module) 30: exec(compile(module, "", "exec"), namespace) 31: return namespace 32: 33: brain_ns = function_namespace( 34: brain_source, 35: { 36: "_academy_timeout_for_round", 37: "_academy_timeout_error", 38: "_academy_timeout_payload", 39: }, 40: { 41: "ACADEMY_PRACTICE_TIMEOUT": 220, 42: "ACADEMY_ROUND3_TIMEOUT": 360, 43: "ACADEMY_OUTPUT_CONTRACT": "academy_round_json_v6", 44: "TimeoutError": TimeoutError, 45: "socket": socket, 46: "urllib": __import__("urllib"), 47: "hashlib": hashlib, 48: }, 49: ) 50: 51: assert brain_ns["_academy_timeout_for_round"](1) == 220 52: assert brain_ns["_academy_timeout_for_round"](2) == 220 53: assert brain_ns["_academy_timeout_for_round"](3) == 360 54: assert brain_ns["_academy_timeout_error"](TimeoutError("timed out")) is True 55: assert brain_ns["_academy_timeout_error"](socket.timeout("timed out")) is True 56: assert brain_ns["_academy_timeout_error"]( 57: urllib.error.URLError(TimeoutError("timed out")) 58: ) is True 59: assert brain_ns["_academy_timeout_error"](RuntimeError("different failure")) is False 60: 61: payload = brain_ns["_academy_timeout_payload"]( 62: 3, 63: 360, 64: raw_answer="", 65: model_retry_count=0, 66: ) 67: assert payload == { 68: "ok": False, 69: "error": "Academy model request timed out", 70: "retryable": True, 71: "infrastructure_failure": True, 72: "output_contract": "academy_round_json_v6", 73: "exam_round": 3, 74: "timeout_seconds": 360, 75: "timeout_stage": "ollama_generation", 76: "raw_answer": "", 77: "raw_answer_sha256": hashlib.sha256(b"").hexdigest(), 78: "raw_answer_audited": True, 79: "model_retry_count": 0, 80: "model_retry_reason": "", 81: "initial_raw_answer": "", 82: "initial_raw_answer_sha256": "", 83: } 84: 85: service_ns = function_namespace( 86: service_source, 87: {"_academy_client_timeout_for_round"}, 88: { 89: "ACADEMY_MICRO_EXAM_CLIENT_TIMEOUT": 300, 90: "ACADEMY_ROUND3_CLIENT_TIMEOUT": 420, 91: }, 92: ) 93: assert service_ns["_academy_client_timeout_for_round"](1) == 300 94: assert service_ns["_academy_client_timeout_for_round"](2) == 300 95: assert service_ns["_academy_client_timeout_for_round"](3) == 420 96: 97: required_brain_tokens = [ 98: 'ACADEMY_OUTPUT_CONTRACT = "academy_round_json_v6"', 99: 'os.environ.get("SHIRE_ACADEMY_ROUND3_TIMEOUT", "360")', 100: '"round3_timeout_seconds": ACADEMY_ROUND3_TIMEOUT', 101: '"round3_structured_timeout_response": True', 102: 'status=504', 103: '"schema_name": f"academy_round_{exam_round}_json_v6"', 104: 'SHIRE ACADEMY ROUND 3: TRANSFER AND MEASURABLE ACCEPTANCE.', 105: ] 106: for token in required_brain_tokens: 107: assert token in brain_source, token 108: 109: required_service_tokens = [ 110: 'ACADEMY_ROUND3_CLIENT_TIMEOUT = 420', 111: 'timeout=_academy_client_timeout_for_round(round_number)', 112: '"practice_round3_client_timeout_seconds": ACADEMY_ROUND3_CLIENT_TIMEOUT', 113: '"http 504"', 114: 'if payload.get("output_contract") != "academy_round_json_v6"', 115: 'if payload.get("context_profile") != "academy_round_specific_v5"', 116: ] 117: for token in required_service_tokens: 118: assert token in service_source, token 119: 120: # Ensure Round 3 content contract was not weakened. 121: for token in ( 122: '"transfer_project"', 123: '"boundary_cases"', 124: '"provenance_review"', 125: '"safety_gate"', 126: '"limitations"', 127: '"forge_execution_allowed": {"type": "boolean", "const": False}', 128: ): 129: assert token in brain_source, token 130: 131: print(json.dumps({ 132: "ok": True, 133: "output_contract": "academy_round_json_v6", 134: "round3_server_timeout_seconds": 360, 135: "round3_client_timeout_seconds": 420, 136: "round1_and_round2_server_timeout_seconds": 220, 137: "round1_and_round2_client_timeout_seconds": 300, 138: "structured_timeout_http_status": 504, 139: "structured_timeout_retryable": True, 140: "structured_timeout_infrastructure_failure": True, 141: "empty_raw_answer_sha256_audited": True, 142: "round3_schema_preserved": True, 143: "round2_completeness_guard_preserved": True, 144: "final_counted_attempt_protected_on_timeout": True, 145: "blender_required": False, 146: }, indent=2)) ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_round_contract_selftest.py ============================================================================== --- LINES 3-75 --- 3: 4: import json 5: from copy import deepcopy 6: from pathlib import Path 7: 8: from ai.brain_server import ( 9: ACADEMY_OUTPUT_CONTRACT, 10: ACADEMY_ROUND_JSON_SCHEMAS, 11: ACADEMY_ROUND_REQUIRED_KEYS, 12: ACADEMY_ROUND_SYSTEM_CONTEXTS, 13: AcademyStructuredOutputError, 14: canonicalise_academy_answer, 15: ) 16: 17: ROOT = Path(__file__).resolve().parent.parent 18: brain_source = (ROOT / "ai" / "brain_server.py").read_text(encoding="utf-8") 19: practice_source = ( 20: ROOT / "services" / "design_academy_practice_service.py" 21: ).read_text(encoding="utf-8") 22: 23: samples = { 24: 1: { 25: "round": 1, 26: "principles": [ 27: "Use named parameters with explicit units and controlled ranges.", 28: "Preserve design intent through deterministic constraints.", 29: "Validate dimensions and solid integrity before release.", 30: ], 31: "repeatable_method_steps": [ 32: "Define all user inputs and their valid numerical ranges.", 33: "Build geometry from stable references and derived values.", 34: "Check dimensions, topology and rejection behaviour.", 35: ], 36: "acceptance_criteria": [ 37: "Generated dimensions match the supplied parameters.", 38: "Invalid parameter values are rejected before modelling.", 39: ], 40: "required_inputs": [ 41: "Target dimensions and permitted parameter ranges.", 42: ], 43: "safe_knowledge_gate": 44: "This is knowledge-only practice and cannot authorise Forge execution.", 45: "limitations": [ 46: "No physical object has been tested.", 47: "No production release has been authorised.", 48: ], 49: "forge_execution_allowed": False, 50: }, 51: 2: { 52: "round": 2, 53: "failure_diagnosis": { 54: "failure": "Missing measurable acceptance criteria", 55: "cause": 56: "The workflow omitted explicit dimensional and topology checks.", 57: "correction": 58: "Add measurable geometry checks and reject invalid results.", 59: }, 60: "provenance_review": { 61: "licence_status": "unknown", 62: "decision": "quarantine", 63: "commercial_use": "blocked", 64: "reason": 65: "Unknown licensing prevents verified reuse or commercial distribution.", 66: }, 67: "safety_gate": { 68: "risk_class": "low", 69: "action": "continue", 70: "forge_execution_allowed": False, 71: "reason": 72: "This remains knowledge-only practice without tool authority.", 73: }, 74: "limitations": [ 75: "No external source has been verified.", --- LINES 96-204 --- 96: }, 97: "boundary_cases": { 98: "minimum": "Smallest permitted positive dimensions.", 99: "nominal": "Typical dimensions within the valid range.", 100: "maximum": "Largest permitted dimensions before rejection.", 101: "invalid": "Negative or zero dimensions must be rejected.", 102: }, 103: "provenance_review": { 104: "licence_status": "unknown", 105: "decision": "quarantine", 106: "commercial_use": "blocked", 107: "reason": 108: "Unknown licensing remains quarantined and blocks commercial reuse.", 109: }, 110: "safety_gate": { 111: "risk_class": "low", 112: "action": "continue", 113: "forge_execution_allowed": False, 114: "reason": 115: "Transfer practice remains knowledge-only until evidence passes.", 116: }, 117: "limitations": [ 118: "No CadQuery tool evidence was executed.", 119: "No production readiness is claimed.", 120: ], 121: }, 122: } 123: 124: assert ACADEMY_OUTPUT_CONTRACT == "academy_round_json_v6" 125: assert set(ACADEMY_ROUND_JSON_SCHEMAS) == {1, 2, 3} 126: assert set(ACADEMY_ROUND_REQUIRED_KEYS) == {1, 2, 3} 127: assert set(ACADEMY_ROUND_SYSTEM_CONTEXTS) == {1, 2, 3} 128: 129: for round_number, sample in samples.items(): 130: schema = ACADEMY_ROUND_JSON_SCHEMAS[round_number] 131: 132: assert schema["type"] == "object" 133: assert schema["additionalProperties"] is False 134: assert set(schema["required"]) == set( 135: ACADEMY_ROUND_REQUIRED_KEYS[round_number] 136: ) 137: 138: strict = canonicalise_academy_answer( 139: json.dumps(sample), 140: round_number, 141: ) 142: assert strict["repaired"] is False 143: assert strict["method"] == "strict_json" 144: assert json.loads(strict["canonical"]) == sample 145: 146: trailing = json.dumps(sample, separators=(",", ":"))[:-1] + ",}" 147: repaired = canonicalise_academy_answer( 148: trailing, 149: round_number, 150: ) 151: assert repaired["repaired"] is True 152: assert json.loads(repaired["canonical"]) == sample 153: 154: unsafe = deepcopy(sample) 155: if round_number == 1: 156: unsafe["forge_execution_allowed"] = True 157: else: 158: unsafe["safety_gate"]["forge_execution_allowed"] = True 159: 160: try: 161: canonicalise_academy_answer( 162: json.dumps(unsafe), 163: round_number, 164: ) 165: except AcademyStructuredOutputError: 166: pass 167: else: 168: raise SystemExit( 169: f"STOP: Round {round_number} unsafe Forge flag accepted" 170: ) 171: 172: required_practice_markers = ( 173: '"exam_round": int(round_number)', 174: 'payload.get("output_contract") != "academy_round_json_v6"', 175: 'payload.get("context_profile") != "academy_round_specific_v5"', 176: 'evaluate_response(skill_id, bundle, response, round_number)', 177: "ROUND_REQUIRED_RESPONSE_KEYS", 178: ) 179: 180: for marker in required_practice_markers: 181: if marker not in practice_source: 182: raise SystemExit(f"STOP: Missing practice marker: {marker}") 183: 184: required_brain_markers = ( 185: 'ACADEMY_OUTPUT_CONTRACT = "academy_round_json_v6"', 186: 'canonicalise_academy_answer(raw_answer, exam_round)', 187: 'format_override=ACADEMY_ROUND_JSON_SCHEMAS[exam_round]', 188: '"structured_output_schema": True', 189: '"canonical_json_gate": True', 190: ) 191: 192: for marker in required_brain_markers: 193: if marker not in brain_source: 194: raise SystemExit(f"STOP: Missing Brain marker: {marker}") 195: 196: print(json.dumps({ 197: "ok": True, 198: "output_contract": ACADEMY_OUTPUT_CONTRACT, 199: "round_specific_contracts": [1, 2, 3], 200: "strict_json_validated": True, 201: "trailing_comma_repair_validated": True, 202: "unsafe_forge_flag_blocked": True, 203: "additional_properties_allowed": False, 204: }, indent=2)) ============================================================================== FILE: /home/shire3d/ARMOR/tools/design_academy_structured_output_selftest.py ============================================================================== --- LINES 1-85 --- 1: #!/usr/bin/env python3 2: from __future__ import annotations 3: 4: import json 5: from copy import deepcopy 6: 7: from ai.brain_server import ( 8: ACADEMY_OUTPUT_CONTRACT, 9: AcademyStructuredOutputError, 10: canonicalise_academy_answer, 11: ) 12: 13: sample = { 14: "round": 1, 15: "principles": [ 16: "Use explicit units and stable coordinate references.", 17: "Create valid closed solids from constrained geometry.", 18: "Validate topology and measurable geometry before release.", 19: ], 20: "repeatable_method_steps": [ 21: "Define valid input parameters and their permitted ranges.", 22: "Construct the model from deterministic geometric operations.", 23: "Check dimensions, solid validity and rejection behaviour.", 24: ], 25: "acceptance_criteria": [ 26: "The resulting model is one valid closed solid.", 27: "Invalid input values are rejected before geometry creation.", 28: ], 29: "required_inputs": [ 30: "Target dimensions and permitted geometric ranges.", 31: ], 32: "safe_knowledge_gate": 33: "This response is knowledge-only and cannot permit Forge execution.", 34: "limitations": [ 35: "No CadQuery tool evidence has been generated.", 36: "No physical manufacturing result has been inspected.", 37: ], 38: "forge_execution_allowed": False, 39: } 40: 41: assert ACADEMY_OUTPUT_CONTRACT == "academy_round_json_v6" 42: 43: strict = canonicalise_academy_answer( 44: json.dumps(sample), 45: 1, 46: ) 47: assert strict["repaired"] is False 48: assert strict["method"] == "strict_json" 49: assert json.loads(strict["canonical"]) == sample 50: 51: fenced = canonicalise_academy_answer( 52: "```json\n" + json.dumps(sample) + "\n```", 53: 1, 54: ) 55: assert json.loads(fenced["canonical"]) == sample 56: 57: trailing = json.dumps(sample, separators=(",", ":"))[:-1] + ",}" 58: repaired = canonicalise_academy_answer( 59: trailing, 60: 1, 61: ) 62: assert repaired["repaired"] is True 63: assert json.loads(repaired["canonical"]) == sample 64: 65: extra = deepcopy(sample) 66: extra["summary"] = "This obsolete field must be rejected." 67: 68: try: 69: canonicalise_academy_answer( 70: json.dumps(extra), 71: 1, 72: ) 73: except AcademyStructuredOutputError: 74: extra_blocked = True 75: else: 76: extra_blocked = False 77: 78: assert extra_blocked 79: 80: unsafe = deepcopy(sample) 81: unsafe["forge_execution_allowed"] = True 82: 83: try: 84: canonicalise_academy_answer( 85: json.dumps(unsafe),